@@ -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; | |||
} | |||
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, | |||
TensorShape strides = null, | |||
string padding = "valid") | |||
@@ -448,6 +458,20 @@ namespace Tensorflow.Keras.Layers | |||
public GlobalAveragePooling2D GlobalAveragePooling2D() | |||
=> 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) | |||
=> 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()); | |||
} | |||
} | |||
} |