Added 2D global max pooling and unit tests. Disabled one max pooling test, needs further investigation,tags/v0.40-tf2.4-tstring
| @@ -0,0 +1,34 @@ | |||||
| namespace Tensorflow.Keras.ArgsDefinition | |||||
| { | |||||
| public class Pooling1DArgs : LayerArgs | |||||
| { | |||||
| /// <summary> | |||||
| /// The pooling function to apply, e.g. `tf.nn.max_pool2d`. | |||||
| /// </summary> | |||||
| public IPoolFunction PoolFunction { get; set; } | |||||
| /// <summary> | |||||
| /// specifying the size of the pooling window. | |||||
| /// </summary> | |||||
| public int PoolSize { get; set; } | |||||
| /// <summary> | |||||
| /// specifying the strides of the pooling operation. | |||||
| /// </summary> | |||||
| public int Strides { | |||||
| get { return _strides.HasValue ? _strides.Value : PoolSize; } | |||||
| set { _strides = value; } | |||||
| } | |||||
| private int? _strides = null; | |||||
| /// <summary> | |||||
| /// The padding method, either 'valid' or 'same'. | |||||
| /// </summary> | |||||
| public string Padding { get; set; } = "valid"; | |||||
| /// <summary> | |||||
| /// one of `channels_last` (default) or `channels_first`. | |||||
| /// </summary> | |||||
| public string DataFormat { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -325,6 +325,16 @@ namespace Tensorflow.Keras.Layers | |||||
| return input_layer.InboundNodes[0].Outputs; | return input_layer.InboundNodes[0].Outputs; | ||||
| } | } | ||||
| public MaxPooling1D MaxPooling1D(int? pool_size = null, | |||||
| int? strides = null, | |||||
| string padding = "valid") | |||||
| => new MaxPooling1D(new Pooling1DArgs | |||||
| { | |||||
| PoolSize = pool_size ?? 2, | |||||
| Strides = strides ?? (pool_size ?? 2), | |||||
| Padding = padding | |||||
| }); | |||||
| public MaxPooling2D MaxPooling2D(TensorShape pool_size = null, | public MaxPooling2D MaxPooling2D(TensorShape pool_size = null, | ||||
| TensorShape strides = null, | TensorShape strides = null, | ||||
| string padding = "valid") | string padding = "valid") | ||||
| @@ -448,6 +458,20 @@ namespace Tensorflow.Keras.Layers | |||||
| public GlobalAveragePooling2D GlobalAveragePooling2D() | public GlobalAveragePooling2D GlobalAveragePooling2D() | ||||
| => new GlobalAveragePooling2D(new Pooling2DArgs { }); | => new GlobalAveragePooling2D(new Pooling2DArgs { }); | ||||
| public GlobalAveragePooling1D GlobalAveragePooling1D(string data_format = "channels_last") | |||||
| => new GlobalAveragePooling1D(new Pooling1DArgs { DataFormat = data_format }); | |||||
| public GlobalAveragePooling2D GlobalAveragePooling2D(string data_format = "channels_last") | |||||
| => new GlobalAveragePooling2D(new Pooling2DArgs { DataFormat = data_format }); | |||||
| public GlobalMaxPooling1D GlobalMaxPooling1D(string data_format = "channels_last") | |||||
| => new GlobalMaxPooling1D(new Pooling1DArgs { DataFormat = data_format }); | |||||
| public GlobalMaxPooling2D GlobalMaxPooling2D(string data_format = "channels_last") | |||||
| => new GlobalMaxPooling2D(new Pooling2DArgs { DataFormat = data_format }); | |||||
| Activation GetActivationByName(string name) | Activation GetActivationByName(string name) | ||||
| => name switch | => name switch | ||||
| { | { | ||||
| @@ -0,0 +1,23 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Text; | |||||
| using Tensorflow.Keras.ArgsDefinition; | |||||
| namespace Tensorflow.Keras.Layers | |||||
| { | |||||
| public class GlobalAveragePooling1D : GlobalPooling1D | |||||
| { | |||||
| public GlobalAveragePooling1D(Pooling1DArgs args) | |||||
| : base(args) | |||||
| { | |||||
| } | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | |||||
| if (data_format == "channels_last") | |||||
| return math_ops.reduce_mean(inputs, new int[] { 1 }, false); | |||||
| else | |||||
| return math_ops.reduce_mean(inputs, new int[] { 2 }, false); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,23 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Text; | |||||
| using Tensorflow.Keras.ArgsDefinition; | |||||
| namespace Tensorflow.Keras.Layers | |||||
| { | |||||
| public class GlobalMaxPooling1D : GlobalPooling1D | |||||
| { | |||||
| public GlobalMaxPooling1D(Pooling1DArgs args) | |||||
| : base(args) | |||||
| { | |||||
| } | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | |||||
| if (data_format == "channels_last") | |||||
| return math_ops.reduce_max(inputs, new int[] { 1 }, false); | |||||
| else | |||||
| return math_ops.reduce_max(inputs, new int[] { 2 }, false); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,23 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Text; | |||||
| using Tensorflow.Keras.ArgsDefinition; | |||||
| namespace Tensorflow.Keras.Layers | |||||
| { | |||||
| public class GlobalMaxPooling2D : GlobalPooling2D | |||||
| { | |||||
| public GlobalMaxPooling2D(Pooling2DArgs args) | |||||
| : base(args) | |||||
| { | |||||
| } | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | |||||
| if (data_format == "channels_last") | |||||
| return math_ops.reduce_max(inputs, new int[] { 1, 2 }, false); | |||||
| else | |||||
| return math_ops.reduce_max(inputs, new int[] { 2, 3 }, false); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,23 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Text; | |||||
| using Tensorflow.Keras.ArgsDefinition; | |||||
| using Tensorflow.Keras.Engine; | |||||
| using Tensorflow.Keras.Utils; | |||||
| namespace Tensorflow.Keras.Layers | |||||
| { | |||||
| public abstract class GlobalPooling1D : Layer | |||||
| { | |||||
| Pooling1DArgs args; | |||||
| protected string data_format => args.DataFormat; | |||||
| protected InputSpec input_spec; | |||||
| public GlobalPooling1D(Pooling1DArgs args) : base(args) | |||||
| { | |||||
| this.args = args; | |||||
| args.DataFormat = conv_utils.normalize_data_format(data_format); | |||||
| input_spec = new InputSpec(ndim: 3); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| using Tensorflow.Keras.ArgsDefinition; | |||||
| using Tensorflow.Operations; | |||||
| namespace Tensorflow.Keras.Layers | |||||
| { | |||||
| public class MaxPooling1D : Pooling1D | |||||
| { | |||||
| public MaxPooling1D(Pooling1DArgs args) | |||||
| : base(args) | |||||
| { | |||||
| args.PoolFunction = new MaxPoolFunction(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,62 @@ | |||||
| /***************************************************************************** | |||||
| Copyright 2018 The TensorFlow.NET Authors. All Rights Reserved. | |||||
| Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| you may not use this file except in compliance with the License. | |||||
| You may obtain a copy of the License at | |||||
| http://www.apache.org/licenses/LICENSE-2.0 | |||||
| Unless required by applicable law or agreed to in writing, software | |||||
| distributed under the License is distributed on an "AS IS" BASIS, | |||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| See the License for the specific language governing permissions and | |||||
| limitations under the License. | |||||
| ******************************************************************************/ | |||||
| using Tensorflow.Keras.ArgsDefinition; | |||||
| using Tensorflow.Keras.Engine; | |||||
| using Tensorflow.Keras.Utils; | |||||
| namespace Tensorflow.Keras.Layers | |||||
| { | |||||
| public class Pooling1D : Layer | |||||
| { | |||||
| Pooling1DArgs args; | |||||
| InputSpec input_spec; | |||||
| public Pooling1D(Pooling1DArgs args) | |||||
| : base(args) | |||||
| { | |||||
| this.args = args; | |||||
| args.Padding = conv_utils.normalize_padding(args.Padding); | |||||
| args.DataFormat = conv_utils.normalize_data_format(args.DataFormat); | |||||
| input_spec = new InputSpec(ndim: 3); | |||||
| } | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | |||||
| int[] pool_shape; | |||||
| int[] strides; | |||||
| if (args.DataFormat == "channels_last") | |||||
| { | |||||
| pool_shape = new int[] { 1, args.PoolSize, 1 }; | |||||
| strides = new int[] { 1, args.Strides, 1 }; | |||||
| } | |||||
| else | |||||
| { | |||||
| pool_shape = new int[] { 1, 1, args.PoolSize }; | |||||
| strides = new int[] { 1, 1, args.Strides }; | |||||
| } | |||||
| var outputs = args.PoolFunction.Apply( | |||||
| inputs, | |||||
| ksize: pool_shape, | |||||
| strides: strides, | |||||
| padding: args.Padding.ToUpper(), | |||||
| data_format: conv_utils.convert_data_format(args.DataFormat, 3)); | |||||
| return outputs; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,305 @@ | |||||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | |||||
| using NumSharp; | |||||
| using System.Linq; | |||||
| using Tensorflow; | |||||
| using static Tensorflow.Binding; | |||||
| using static Tensorflow.KerasApi; | |||||
| namespace TensorFlowNET.Keras.UnitTest | |||||
| { | |||||
| /// <summary> | |||||
| /// https://www.tensorflow.org/versions/r2.3/api_docs/python/tf/keras/layers | |||||
| /// </summary> | |||||
| [TestClass] | |||||
| public class PoolingTest : EagerModeTestBase | |||||
| { | |||||
| private NDArray input_array_1D = np.array(new float[,,] | |||||
| { | |||||
| {{1,2,3,3,3},{1,2,3,3,3},{1,2,3,3,3}}, | |||||
| {{4,5,6,3,3},{4,5,6,3,3},{4,5,6,3,3}}, | |||||
| {{7,8,9,3,3},{7,8,9,3,3},{7,8,9,3,3}}, | |||||
| {{7,8,9,3,3},{7,8,9,3,3},{7,8,9,3,3}} | |||||
| }); | |||||
| private NDArray input_array_2D = np.array(new float[,,,] | |||||
| {{ | |||||
| {{1,2,3,3,3},{1,2,3,3,3},{1,2,3,3,3}}, | |||||
| {{4,5,6,3,3},{4,5,6,3,3},{4,5,6,3,3}}, | |||||
| },{ | |||||
| {{7,8,9,3,3},{7,8,9,3,3},{7,8,9,3,3}}, | |||||
| {{7,8,9,3,3},{7,8,9,3,3},{7,8,9,3,3}} | |||||
| },{ | |||||
| {{1,2,3,3,3},{1,2,3,3,3},{1,2,3,3,3}}, | |||||
| {{4,5,6,3,3},{4,5,6,3,3},{4,5,6,3,3}}, | |||||
| },{ | |||||
| {{7,8,9,3,3},{7,8,9,3,3},{7,8,9,3,3}}, | |||||
| {{7,8,9,3,3},{7,8,9,3,3},{7,8,9,3,3}} | |||||
| }}); | |||||
| [TestMethod] | |||||
| public void GlobalAverage1DPoolingChannelsLast() | |||||
| { | |||||
| var pool = keras.layers.GlobalAveragePooling1D(); | |||||
| var y = pool.Apply(input_array_1D); | |||||
| Assert.AreEqual(4, y.shape[0]); | |||||
| Assert.AreEqual(5, y.shape[1]); | |||||
| var expected = np.array(new float[,] | |||||
| { | |||||
| {1,2,3,3,3}, | |||||
| {4,5,6,3,3}, | |||||
| {7,8,9,3,3}, | |||||
| {7,8,9,3,3} | |||||
| }); | |||||
| Assert.AreEqual(expected, y[0].numpy()); | |||||
| } | |||||
| [TestMethod] | |||||
| public void GlobalAverage1DPoolingChannelsFirst() | |||||
| { | |||||
| var pool = keras.layers.GlobalAveragePooling1D(data_format: "channels_first"); | |||||
| var y = pool.Apply(input_array_1D); | |||||
| Assert.AreEqual(4, y.shape[0]); | |||||
| Assert.AreEqual(3, y.shape[1]); | |||||
| var expected = np.array(new float[,] | |||||
| { | |||||
| {2.4f, 2.4f, 2.4f}, | |||||
| {4.2f, 4.2f, 4.2f}, | |||||
| {6.0f, 6.0f, 6.0f}, | |||||
| {6.0f, 6.0f, 6.0f} | |||||
| }); | |||||
| Assert.AreEqual(expected, y[0].numpy()); | |||||
| } | |||||
| [TestMethod] | |||||
| public void GlobalAverage2DPoolingChannelsLast() | |||||
| { | |||||
| var pool = keras.layers.GlobalAveragePooling2D(); | |||||
| var y = pool.Apply(input_array_2D); | |||||
| Assert.AreEqual(4, y.shape[0]); | |||||
| Assert.AreEqual(5, y.shape[1]); | |||||
| var expected = np.array(new float[,] | |||||
| { | |||||
| {2.5f, 3.5f, 4.5f, 3.0f, 3.0f}, | |||||
| {7.0f, 8.0f, 9.0f, 3.0f, 3.0f}, | |||||
| {2.5f, 3.5f, 4.5f, 3.0f, 3.0f}, | |||||
| {7.0f, 8.0f, 9.0f, 3.0f, 3.0f} | |||||
| }); | |||||
| Assert.AreEqual(expected, y[0].numpy()); | |||||
| } | |||||
| [TestMethod] | |||||
| public void GlobalAverage2DPoolingChannelsFirst() | |||||
| { | |||||
| var pool = keras.layers.GlobalAveragePooling2D(data_format: "channels_first"); | |||||
| var y = pool.Apply(input_array_2D); | |||||
| Assert.AreEqual(4, y.shape[0]); | |||||
| Assert.AreEqual(2, y.shape[1]); | |||||
| var expected = np.array(new float[,] | |||||
| { | |||||
| {2.4f, 4.2f}, | |||||
| {6.0f, 6.0f}, | |||||
| {2.4f, 4.2f}, | |||||
| {6.0f, 6.0f} | |||||
| }); | |||||
| Assert.AreEqual(expected, y[0].numpy()); | |||||
| } | |||||
| [TestMethod] | |||||
| public void GlobalMax1DPoolingChannelsLast() | |||||
| { | |||||
| var pool = keras.layers.GlobalMaxPooling1D(); | |||||
| var y = pool.Apply(input_array_1D); | |||||
| Assert.AreEqual(4, y.shape[0]); | |||||
| Assert.AreEqual(5, y.shape[1]); | |||||
| var expected = np.array(new float[,] | |||||
| { | |||||
| {1,2,3,3,3}, | |||||
| {4,5,6,3,3}, | |||||
| {7,8,9,3,3}, | |||||
| {7,8,9,3,3} | |||||
| }); | |||||
| Assert.AreEqual(expected, y[0].numpy()); | |||||
| } | |||||
| [TestMethod] | |||||
| public void GlobalMax1DPoolingChannelsFirst() | |||||
| { | |||||
| var pool = keras.layers.GlobalMaxPooling1D(data_format: "channels_first"); | |||||
| var y = pool.Apply(input_array_1D); | |||||
| Assert.AreEqual(4, y.shape[0]); | |||||
| Assert.AreEqual(3, y.shape[1]); | |||||
| var expected = np.array(new float[,] | |||||
| { | |||||
| {3.0f, 3.0f, 3.0f}, | |||||
| {6.0f, 6.0f, 6.0f}, | |||||
| {9.0f, 9.0f, 9.0f}, | |||||
| {9.0f, 9.0f, 9.0f} | |||||
| }); | |||||
| Assert.AreEqual(expected, y[0].numpy()); | |||||
| } | |||||
| [TestMethod] | |||||
| public void GlobalMax2DPoolingChannelsLast() | |||||
| { | |||||
| var input_array_2D = np.array(new float[,,,] | |||||
| {{ | |||||
| {{1,2,3,3,3},{1,2,3,3,3},{1,2,3,9,3}}, | |||||
| {{4,5,6,3,3},{4,5,6,3,3},{4,5,6,3,3}}, | |||||
| },{ | |||||
| {{7,8,9,3,3},{7,8,9,3,3},{7,8,9,3,3}}, | |||||
| {{7,8,9,3,3},{7,8,9,3,3},{7,8,9,3,3}} | |||||
| },{ | |||||
| {{1,2,3,3,3},{1,2,3,3,3},{1,2,3,3,9}}, | |||||
| {{4,5,6,3,3},{4,5,6,3,3},{4,5,6,3,3}}, | |||||
| },{ | |||||
| {{7,8,9,3,3},{7,8,9,3,3},{7,8,9,3,3}}, | |||||
| {{7,8,9,3,3},{7,8,9,3,3},{7,8,9,3,3}} | |||||
| }}); | |||||
| var pool = keras.layers.GlobalMaxPooling2D(); | |||||
| var y = pool.Apply(input_array_2D); | |||||
| Assert.AreEqual(4, y.shape[0]); | |||||
| Assert.AreEqual(5, y.shape[1]); | |||||
| var expected = np.array(new float[,] | |||||
| { | |||||
| {4.0f, 5.0f, 6.0f, 9.0f, 3.0f}, | |||||
| {7.0f, 8.0f, 9.0f, 3.0f, 3.0f}, | |||||
| {4.0f, 5.0f, 6.0f, 3.0f, 9.0f}, | |||||
| {7.0f, 8.0f, 9.0f, 3.0f, 3.0f} | |||||
| }); | |||||
| Assert.AreEqual(expected, y[0].numpy()); | |||||
| } | |||||
| [TestMethod] | |||||
| public void GlobalMax2DPoolingChannelsFirst() | |||||
| { | |||||
| var input_array_2D = np.array(new float[,,,] | |||||
| {{ | |||||
| {{1,2,3,3,3},{1,2,3,3,3},{1,2,3,9,3}}, | |||||
| {{4,5,6,3,3},{4,5,6,3,3},{4,5,6,3,3}}, | |||||
| },{ | |||||
| {{7,8,9,3,3},{7,8,9,3,3},{7,8,9,3,3}}, | |||||
| {{7,8,9,3,3},{7,8,9,3,3},{7,8,9,3,3}} | |||||
| },{ | |||||
| {{1,2,3,3,3},{1,2,3,3,3},{1,2,3,3,9}}, | |||||
| {{4,5,6,3,3},{4,5,6,3,3},{4,5,6,3,3}}, | |||||
| },{ | |||||
| {{7,8,9,3,3},{7,8,9,3,3},{7,8,9,3,3}}, | |||||
| {{7,8,9,3,3},{7,8,9,3,3},{7,8,9,3,3}} | |||||
| }}); | |||||
| var pool = keras.layers.GlobalMaxPooling2D(data_format: "channels_first"); | |||||
| var y = pool.Apply(input_array_2D); | |||||
| Assert.AreEqual(4, y.shape[0]); | |||||
| Assert.AreEqual(2, y.shape[1]); | |||||
| var expected = np.array(new float[,] | |||||
| { | |||||
| {9.0f, 6.0f}, | |||||
| {9.0f, 9.0f}, | |||||
| {9.0f, 6.0f}, | |||||
| {9.0f, 9.0f} | |||||
| }); | |||||
| Assert.AreEqual(expected, y[0].numpy()); | |||||
| } | |||||
| [TestMethod, Ignore("There's an error generated from TF complaining about the shape of the pool. Needs further investigation.")] | |||||
| public void Max1DPoolingChannelsLast() | |||||
| { | |||||
| var x = input_array_1D; | |||||
| var pool = keras.layers.MaxPooling1D(pool_size:2, strides:1); | |||||
| var y = pool.Apply(x); | |||||
| Assert.AreEqual(4, y.shape[0]); | |||||
| Assert.AreEqual(2, y.shape[1]); | |||||
| Assert.AreEqual(5, y.shape[2]); | |||||
| var expected = np.array(new float[,,] | |||||
| { | |||||
| {{2.0f, 2.0f, 3.0f, 3.0f, 3.0f}, | |||||
| { 1.0f, 2.0f, 3.0f, 3.0f, 3.0f}}, | |||||
| {{4.0f, 5.0f, 6.0f, 3.0f, 3.0f}, | |||||
| {4.0f, 5.0f, 6.0f, 3.0f, 3.0f}}, | |||||
| {{7.0f, 8.0f, 9.0f, 3.0f, 3.0f}, | |||||
| {7.0f, 8.0f, 9.0f, 3.0f, 3.0f}}, | |||||
| {{7.0f, 8.0f, 9.0f, 3.0f, 3.0f}, | |||||
| {7.0f, 8.0f, 9.0f, 3.0f, 3.0f}} | |||||
| }); | |||||
| Assert.AreEqual(expected, y[0].numpy()); | |||||
| } | |||||
| [TestMethod] | |||||
| public void Max2DPoolingChannelsLast() | |||||
| { | |||||
| var x = np.array(new float[,,,] | |||||
| {{ | |||||
| {{1,2,3,3,3},{1,2,3,3,3},{1,2,3,9,3}}, | |||||
| {{4,5,6,3,3},{4,5,6,3,3},{4,5,6,3,3}}, | |||||
| },{ | |||||
| {{7,8,9,3,3},{7,8,9,3,3},{7,8,9,3,3}}, | |||||
| {{7,8,9,3,3},{7,8,9,3,3},{7,8,9,3,3}} | |||||
| },{ | |||||
| {{1,2,3,3,3},{1,2,3,3,3},{1,2,3,3,9}}, | |||||
| {{4,5,6,3,3},{4,5,6,3,3},{4,5,6,3,3}}, | |||||
| },{ | |||||
| {{7,8,9,3,3},{7,8,9,3,3},{7,8,9,3,3}}, | |||||
| {{7,8,9,3,3},{7,8,9,3,3},{7,8,9,3,3}} | |||||
| }}); | |||||
| var pool = keras.layers.MaxPooling2D(pool_size: 2, strides: 1); | |||||
| var y = pool.Apply(x); | |||||
| Assert.AreEqual(4, y.shape[0]); | |||||
| Assert.AreEqual(1, y.shape[1]); | |||||
| Assert.AreEqual(2, y.shape[2]); | |||||
| Assert.AreEqual(5, y.shape[3]); | |||||
| var expected = np.array(new float[,,,] | |||||
| { | |||||
| {{{4.0f, 5.0f, 6.0f, 3.0f, 3.0f}, | |||||
| {4.0f, 5.0f, 6.0f, 9.0f, 3.0f}}}, | |||||
| {{{7.0f, 8.0f, 9.0f, 3.0f, 3.0f}, | |||||
| {7.0f, 8.0f, 9.0f, 3.0f, 3.0f}}}, | |||||
| {{{4.0f, 5.0f, 6.0f, 3.0f, 3.0f}, | |||||
| {4.0f, 5.0f, 6.0f, 3.0f, 9.0f}}}, | |||||
| {{{7.0f, 8.0f, 9.0f, 3.0f, 3.0f}, | |||||
| {7.0f, 8.0f, 9.0f, 3.0f, 3.0f}}} | |||||
| }); | |||||
| Assert.AreEqual(expected, y[0].numpy()); | |||||
| } | |||||
| } | |||||
| } | |||||