@@ -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()); | |||||
} | |||||
} | |||||
} |