diff --git a/src/TensorFlowNET.Core/Keras/ArgsDefinition/Activation/ELUArgs.cs b/src/TensorFlowNET.Core/Keras/ArgsDefinition/Activation/ELUArgs.cs new file mode 100644 index 00000000..23552316 --- /dev/null +++ b/src/TensorFlowNET.Core/Keras/ArgsDefinition/Activation/ELUArgs.cs @@ -0,0 +1,9 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Tensorflow.Keras.ArgsDefinition { + public class ELUArgs : LayerArgs { + public float Alpha { get; set; } = 0.1f; + } +} diff --git a/src/TensorFlowNET.Core/Keras/ArgsDefinition/Cropping/Cropping2DArgs.cs b/src/TensorFlowNET.Core/Keras/ArgsDefinition/Cropping/Cropping2DArgs.cs new file mode 100644 index 00000000..16705063 --- /dev/null +++ b/src/TensorFlowNET.Core/Keras/ArgsDefinition/Cropping/Cropping2DArgs.cs @@ -0,0 +1,16 @@ +using Tensorflow.NumPy; + +namespace Tensorflow.Keras.ArgsDefinition { + public class Cropping2DArgs : LayerArgs { + /// + /// channel last: (b, h, w, c) + /// channels_first: (b, c, h, w) + /// + public enum DataFormat { channels_first = 0, channels_last = 1 } + /// + /// Accept: int[1][2], int[1][1], int[2][2] + /// + public NDArray cropping { get; set; } + public DataFormat data_format { get; set; } = DataFormat.channels_last; + } +} diff --git a/src/TensorFlowNET.Core/Keras/ArgsDefinition/Cropping/Cropping3DArgs.cs b/src/TensorFlowNET.Core/Keras/ArgsDefinition/Cropping/Cropping3DArgs.cs new file mode 100644 index 00000000..9da2adc7 --- /dev/null +++ b/src/TensorFlowNET.Core/Keras/ArgsDefinition/Cropping/Cropping3DArgs.cs @@ -0,0 +1,16 @@ +using Tensorflow.NumPy; + +namespace Tensorflow.Keras.ArgsDefinition { + public class Cropping3DArgs : LayerArgs { + /// + /// channel last: (b, h, w, c) + /// channels_first: (b, c, h, w) + /// + public enum DataFormat { channels_first = 0, channels_last = 1 } + /// + /// Accept: int[1][3], int[1][1], int[3][2] + /// + public NDArray cropping { get; set; } + public DataFormat data_format { get; set; } = DataFormat.channels_last; + } +} diff --git a/src/TensorFlowNET.Core/Keras/ArgsDefinition/Cropping/CroppingArgs.cs b/src/TensorFlowNET.Core/Keras/ArgsDefinition/Cropping/CroppingArgs.cs new file mode 100644 index 00000000..9d23acd4 --- /dev/null +++ b/src/TensorFlowNET.Core/Keras/ArgsDefinition/Cropping/CroppingArgs.cs @@ -0,0 +1,10 @@ +using Tensorflow.NumPy; + +namespace Tensorflow.Keras.ArgsDefinition { + public class CroppingArgs : LayerArgs { + /// + /// Accept length 1 or 2 + /// + public NDArray cropping { get; set; } + } +} \ No newline at end of file diff --git a/src/TensorFlowNET.Core/Keras/ArgsDefinition/Reshaping/PermuteArgs.cs b/src/TensorFlowNET.Core/Keras/ArgsDefinition/Reshaping/PermuteArgs.cs new file mode 100644 index 00000000..2686f6cd --- /dev/null +++ b/src/TensorFlowNET.Core/Keras/ArgsDefinition/Reshaping/PermuteArgs.cs @@ -0,0 +1,5 @@ +namespace Tensorflow.Keras.ArgsDefinition { + public class PermuteArgs : LayerArgs { + public int[] dims { get; set; } + } +} diff --git a/src/TensorFlowNET.Keras/Layers/Activation/ELU.cs b/src/TensorFlowNET.Keras/Layers/Activation/ELU.cs new file mode 100644 index 00000000..a38260e4 --- /dev/null +++ b/src/TensorFlowNET.Keras/Layers/Activation/ELU.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Tensorflow.Keras.ArgsDefinition; +using Tensorflow.Keras.Engine; +using static Tensorflow.Binding; + +namespace Tensorflow.Keras.Layers { + /// + /// ELU Layer: + /// x = 0 when x > 0, x = alpha( e^x-1 ) elsewhere + /// + public class ELU : Layer { + ELUArgs args; + float alpha => args.Alpha; + public ELU ( ELUArgs args ) : base(args) { + this.args = args; + } + protected override void build ( Tensors inputs ) { + if ( alpha < 0f ) { + throw new ValueError("Alpha must be a number greater than 0."); + } + built = true; + } + protected override Tensors Call ( Tensors inputs, Tensor state = null, bool? training = null ) { + Tensor output = inputs; + if ( alpha != 1f ) { + output = tf.where(output > 0f, output, alpha * (tf.exp(output) - 1f)); + } + return output; + } + + public override Shape ComputeOutputShape ( Shape input_shape ) { + return input_shape; + } + } +} diff --git a/src/TensorFlowNET.Keras/Layers/Activation/SELU.cs b/src/TensorFlowNET.Keras/Layers/Activation/SELU.cs new file mode 100644 index 00000000..8069244b --- /dev/null +++ b/src/TensorFlowNET.Keras/Layers/Activation/SELU.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Tensorflow.Keras.ArgsDefinition; +using Tensorflow.Keras.Engine; +using static Tensorflow.Binding; + +namespace Tensorflow.Keras.Layers { + /// + /// SELU Layer: + /// similar to ELU, but has pre-defined alpha and scale + /// + public class SELU : Layer { + protected const float alpha = 1.67326324f, scale = 1.05070098f; + public SELU ( LayerArgs args ) : base(args) { + // SELU has no arguments + } + protected override void build ( Tensors inputs ) { + if ( alpha < 0f ) { + throw new ValueError("Alpha must be a number greater than 0."); + } + built = true; + } + protected override Tensors Call ( Tensors inputs, Tensor state = null, bool? training = null ) { + Tensor output = inputs; + return tf.where(output > 0f, scale * output, scale * alpha * (tf.exp(output) - 1f)); + } + public override Shape ComputeOutputShape ( Shape input_shape ) { + return input_shape; + } + } +} diff --git a/src/TensorFlowNET.Keras/Layers/Cropping/Cropping1D.cs b/src/TensorFlowNET.Keras/Layers/Cropping/Cropping1D.cs new file mode 100644 index 00000000..cf71e184 --- /dev/null +++ b/src/TensorFlowNET.Keras/Layers/Cropping/Cropping1D.cs @@ -0,0 +1,50 @@ +using Tensorflow.Keras.ArgsDefinition; +using Tensorflow.Keras.Engine; + +namespace Tensorflow.Keras.Layers { + public class Cropping1D : Layer { + CroppingArgs args; + public Cropping1D ( CroppingArgs args ) : base(args) { + this.args = args; + } + + protected override void build ( Tensors inputs ) { + if ( args.cropping.rank != 1 ) { + // throw an ValueError exception + throw new ValueError(""); + } + else if ( args.cropping.shape[0] > 2 || args.cropping.shape[0] < 1 ) { + throw new ValueError("The `cropping` argument must be a tuple of 2 integers."); + } + built = true; + } + + protected override Tensors Call ( Tensors inputs, Tensor state = null, bool? training = null ) { + Tensor output = inputs; + if ( output.rank != 3 ) { + // throw an ValueError exception + throw new ValueError("Expected dim=3, found dim=" + output.rank); + } + if ( args.cropping.shape[0] == 1 ) { + int crop_start = args.cropping[0]; + output = output[new Slice(), new Slice(crop_start, ( int ) output.shape[1] - crop_start), new Slice()]; + } + else { + int crop_start = args.cropping[0], crop_end = args.cropping[1]; + output = output[new Slice(), new Slice(crop_start, ( int ) (output.shape[1]) - crop_end), new Slice()]; + } + return output; + } + + public override Shape ComputeOutputShape ( Shape input_shape ) { + if ( args.cropping.shape[0] == 1 ) { + int crop = args.cropping[0]; + return new Shape(( int ) (input_shape[0]), ( int ) (input_shape[1] - crop * 2), ( int ) (input_shape[2])); + } + else { + int crop_start = args.cropping[0], crop_end = args.cropping[1]; + return new Shape(( int ) (input_shape[0]), ( int ) (input_shape[1] - crop_start - crop_end), ( int ) (input_shape[2])); + } + } + } +} diff --git a/src/TensorFlowNET.Keras/Layers/Cropping/Cropping2D.cs b/src/TensorFlowNET.Keras/Layers/Cropping/Cropping2D.cs new file mode 100644 index 00000000..340ba42d --- /dev/null +++ b/src/TensorFlowNET.Keras/Layers/Cropping/Cropping2D.cs @@ -0,0 +1,113 @@ +using Tensorflow.Keras.ArgsDefinition; +using Tensorflow.Keras.Engine; + +namespace Tensorflow.Keras.Layers { + /// + /// Crop the input along axis 1 and 2. + /// For example: + /// shape (1, 5, 5, 5) -- crop2D ((1, 2), (1, 3)) --> shape (1, 2, 1, 5) + /// + public class Cropping2D : Layer { + Cropping2DArgs args; + public Cropping2D ( Cropping2DArgs args ) : base(args) { + this.args = args; + } + protected override void build ( Tensors inputs ) { + built = true; + } + protected override Tensors Call ( Tensors inputs, Tensor state = null, bool? training = null ) { + Tensor output = inputs; + if ( output.rank != 4 ) { + // throw an ValueError exception + throw new ValueError("Expected dim=4, found dim=" + output.rank); + } + if ( args.cropping.shape == new Shape(1) ) { + int crop = args.cropping[0]; + if ( args.data_format == Cropping2DArgs.DataFormat.channels_last ) { + output = output[new Slice(), + new Slice(crop, ( int ) output.shape[1] - crop), + new Slice(crop, ( int ) output.shape[2] - crop), + new Slice()]; + } + else { + output = output[new Slice(), + new Slice(), + new Slice(crop, ( int ) output.shape[2] - crop), + new Slice(crop, ( int ) output.shape[3] - crop)]; + } + } + // a tuple of 2 integers + else if ( args.cropping.shape == new Shape(2) ) { + int crop_1 = args.cropping[0]; + int crop_2 = args.cropping[1]; + if ( args.data_format == Cropping2DArgs.DataFormat.channels_last ) { + output = output[new Slice(), + new Slice(crop_1, ( int ) output.shape[1] - crop_1), + new Slice(crop_2, ( int ) output.shape[2] - crop_2), + new Slice()]; + } + else { + output = output[new Slice(), + new Slice(), + new Slice(crop_1, ( int ) output.shape[2] - crop_1), + new Slice(crop_2, ( int ) output.shape[3] - crop_2)]; + } + } + else if ( args.cropping.shape[0] == 2 && args.cropping.shape[1] == 2 ) { + int x_start = args.cropping[0, 0], x_end = args.cropping[0, 1]; + int y_start = args.cropping[1, 0], y_end = args.cropping[1, 1]; + if ( args.data_format == Cropping2DArgs.DataFormat.channels_last ) { + output = output[new Slice(), + new Slice(x_start, ( int ) output.shape[1] - x_end), + new Slice(y_start, ( int ) output.shape[2] - y_end), + new Slice()]; + } + else { + output = output[new Slice(), + new Slice(), + new Slice(x_start, ( int ) output.shape[2] - x_end), + new Slice(y_start, ( int ) output.shape[3] - y_end) + ]; + } + } + return output; + } + + public override Shape ComputeOutputShape ( Shape input_shape ) { + if ( args.cropping.shape == new Shape(1) ) { + int crop = args.cropping[0]; + if ( args.data_format == Cropping2DArgs.DataFormat.channels_last ) { + return new Shape(( int ) input_shape[0], ( int ) input_shape[1] - crop * 2, ( int ) input_shape[2] - crop * 2, ( int ) input_shape[3]); + } + else { + return new Shape(( int ) input_shape[0], ( int ) input_shape[1], ( int ) input_shape[2] - crop * 2, ( int ) input_shape[3] - crop * 2); + } + } + // a tuple of 2 integers + else if ( args.cropping.shape == new Shape(2) ) { + int crop_1 = args.cropping[0], crop_2 = args.cropping[1]; + if ( args.data_format == Cropping2DArgs.DataFormat.channels_last ) { + return new Shape(( int ) input_shape[0], ( int ) input_shape[1] - crop_1 * 2, ( int ) input_shape[2] - crop_2 * 2, ( int ) input_shape[3]); + } + else { + return new Shape(( int ) input_shape[0], ( int ) input_shape[1], ( int ) input_shape[2] - crop_1 * 2, ( int ) input_shape[3] - crop_2 * 2); + } + } + else if ( args.cropping.shape == new Shape(2, 2) ) { + int crop_1_start = args.cropping[0, 0], crop_1_end = args.cropping[0, 1]; + int crop_2_start = args.cropping[1, 0], crop_2_end = args.cropping[1, 1]; + if ( args.data_format == Cropping2DArgs.DataFormat.channels_last ) { + return new Shape(( int ) input_shape[0], ( int ) input_shape[1] - crop_1_start - crop_1_end, + ( int ) input_shape[2] - crop_2_start - crop_2_end, ( int ) input_shape[3]); + } + else { + return new Shape(( int ) input_shape[0], ( int ) input_shape[1], + ( int ) input_shape[2] - crop_1_start - crop_1_end, ( int ) input_shape[3] - crop_2_start - crop_2_end); + } + } + else { + throw new ValueError(); + } + } + } +} diff --git a/src/TensorFlowNET.Keras/Layers/Cropping/Cropping3D.cs b/src/TensorFlowNET.Keras/Layers/Cropping/Cropping3D.cs new file mode 100644 index 00000000..df102c1f --- /dev/null +++ b/src/TensorFlowNET.Keras/Layers/Cropping/Cropping3D.cs @@ -0,0 +1,123 @@ +using Tensorflow.Keras.ArgsDefinition; +using Tensorflow.Keras.Engine; + +namespace Tensorflow.Keras.Layers { + /// + /// Similar to copping 2D + /// + public class Cropping3D : Layer { + Cropping3DArgs args; + public Cropping3D ( Cropping3DArgs args ) : base(args) { + this.args = args; + } + + protected override void build ( Tensors inputs ) { + built = true; + } + + protected override Tensors Call ( Tensors inputs, Tensor state = null, bool? training = null ) { + Tensor output = inputs; + if ( output.rank != 5 ) { + // throw an ValueError exception + throw new ValueError("Expected dim=5, found dim=" + output.rank); + } + + if ( args.cropping.shape == new Shape(1) ) { + int crop = args.cropping[0]; + if ( args.data_format == Cropping3DArgs.DataFormat.channels_last ) { + output = output[new Slice(), + new Slice(crop, ( int ) output.shape[1] - crop), + new Slice(crop, ( int ) output.shape[2] - crop), + new Slice(crop, ( int ) output.shape[3] - crop), + new Slice()]; + } + else { + output = output[new Slice(), + new Slice(), + new Slice(crop, ( int ) output.shape[2] - crop), + new Slice(crop, ( int ) output.shape[3] - crop), + new Slice(crop, ( int ) output.shape[4] - crop)]; + } + + } + // int[1][3] equivalent to a tuple of 3 integers + else if ( args.cropping.shape == new Shape(3) ) { + var crop_1 = args.cropping[0]; + var crop_2 = args.cropping[1]; + var crop_3 = args.cropping[2]; + if ( args.data_format == Cropping3DArgs.DataFormat.channels_last ) { + output = output[new Slice(), + new Slice(crop_1, ( int ) output.shape[1] - crop_1), + new Slice(crop_2, ( int ) output.shape[2] - crop_2), + new Slice(crop_3, ( int ) output.shape[3] - crop_3), + new Slice()]; + } + else { + output = output[new Slice(), + new Slice(), + new Slice(crop_1, ( int ) output.shape[2] - crop_1), + new Slice(crop_2, ( int ) output.shape[3] - crop_2), + new Slice(crop_3, ( int ) output.shape[4] - crop_3)]; + } + } + else if ( args.cropping.shape[0] == 3 && args.cropping.shape[1] == 2 ) { + int x = args.cropping[0, 0], x_end = args.cropping[0, 1]; + int y = args.cropping[1, 0], y_end = args.cropping[1, 1]; + int z = args.cropping[2, 0], z_end = args.cropping[2, 1]; + if ( args.data_format == Cropping3DArgs.DataFormat.channels_last ) { + output = output[new Slice(), + new Slice(x, ( int ) output.shape[1] - x_end), + new Slice(y, ( int ) output.shape[2] - y_end), + new Slice(z, ( int ) output.shape[3] - z_end), + new Slice()]; + } + else { + output = output[new Slice(), + new Slice(), + new Slice(x, ( int ) output.shape[2] - x_end), + new Slice(y, ( int ) output.shape[3] - y_end), + new Slice(z, ( int ) output.shape[4] - z_end) + ]; + } + } + return output; + } + public override Shape ComputeOutputShape ( Shape input_shape ) { + if ( args.cropping.shape == new Shape(1) ) { + int crop = args.cropping[0]; + if ( args.data_format == Cropping3DArgs.DataFormat.channels_last ) { + return new Shape(( int ) input_shape[0], ( int ) input_shape[1] - crop * 2, ( int ) input_shape[2] - crop * 2, ( int ) input_shape[3] - crop * 2, ( int ) input_shape[4]); + } + else { + return new Shape(( int ) input_shape[0], ( int ) input_shape[1], ( int ) input_shape[2] - crop * 2, ( int ) input_shape[3] - crop * 2, ( int ) input_shape[4] - crop * 2); + } + } + // int[1][3] equivalent to a tuple of 3 integers + else if ( args.cropping.shape == new Shape(3) ) { + var crop_start_1 = args.cropping[0]; + var crop_start_2 = args.cropping[1]; + var crop_start_3 = args.cropping[2]; + if ( args.data_format == Cropping3DArgs.DataFormat.channels_last ) { + return new Shape(( int ) input_shape[0], ( int ) input_shape[1] - crop_start_1 * 2, ( int ) input_shape[2] - crop_start_2 * 2, ( int ) input_shape[3] - crop_start_3 * 2, ( int ) input_shape[4]); + } + else { + return new Shape(( int ) input_shape[0], ( int ) input_shape[1], ( int ) input_shape[2] - crop_start_1 * 2, ( int ) input_shape[3] - crop_start_2 * 2, ( int ) input_shape[4] - crop_start_3 * 2); + } + } + else if ( args.cropping.shape == new Shape(3, 2) ) { + int x = args.cropping[0, 0], x_end = args.cropping[0, 1]; + int y = args.cropping[1, 0], y_end = args.cropping[1, 1]; + int z = args.cropping[2, 0], z_end = args.cropping[2, 1]; + if ( args.data_format == Cropping3DArgs.DataFormat.channels_last ) { + return new Shape(( int ) input_shape[0], ( int ) input_shape[1] - x - x_end, ( int ) input_shape[2] - y - y_end, ( int ) input_shape[3] - z - z_end, ( int ) input_shape[4]); + } + else { + return new Shape(( int ) input_shape[0], ( int ) input_shape[1], ( int ) input_shape[2] - x - x_end, ( int ) input_shape[3] - y - y_end, ( int ) input_shape[4] - z - z_end); + } + } + else { + throw new ValueError(); + } + } + } +} diff --git a/src/TensorFlowNET.Keras/Layers/LayersApi.Cropping.cs b/src/TensorFlowNET.Keras/Layers/LayersApi.Cropping.cs new file mode 100644 index 00000000..f4d2230c --- /dev/null +++ b/src/TensorFlowNET.Keras/Layers/LayersApi.Cropping.cs @@ -0,0 +1,36 @@ +using Tensorflow.NumPy; +using System; +using System.Collections.Generic; +using System.Text; +using Tensorflow.Keras.ArgsDefinition; + +namespace Tensorflow.Keras.Layers { + public partial class LayersApi { + /// + /// Cropping layer for 1D input + /// + /// cropping size + public Cropping1D Cropping1D ( NDArray cropping ) + => new Cropping1D(new CroppingArgs { + cropping = cropping + }); + + /// + /// Cropping layer for 2D input
+ ///
+ public Cropping2D Cropping2D ( NDArray cropping, Cropping2DArgs.DataFormat data_format = Cropping2DArgs.DataFormat.channels_last ) + => new Cropping2D(new Cropping2DArgs { + cropping = cropping, + data_format = data_format + }); + + /// + /// Cropping layer for 3D input
+ ///
+ public Cropping3D Cropping3D ( NDArray cropping, Cropping3DArgs.DataFormat data_format = Cropping3DArgs.DataFormat.channels_last ) + => new Cropping3D(new Cropping3DArgs { + cropping = cropping, + data_format = data_format + }); + } +} diff --git a/src/TensorFlowNET.Keras/Layers/LayersApi.Reshaping.cs b/src/TensorFlowNET.Keras/Layers/LayersApi.Reshaping.cs index 71f9ef3b..5cfec89e 100644 --- a/src/TensorFlowNET.Keras/Layers/LayersApi.Reshaping.cs +++ b/src/TensorFlowNET.Keras/Layers/LayersApi.Reshaping.cs @@ -4,52 +4,54 @@ using System.Collections.Generic; using System.Text; using Tensorflow.Keras.ArgsDefinition; -namespace Tensorflow.Keras.Layers -{ - public partial class LayersApi - { - /// - /// Zero-padding layer for 2D input (e.g. picture). - /// - /// - /// - public ZeroPadding2D ZeroPadding2D(NDArray padding) - => new ZeroPadding2D(new ZeroPadding2DArgs - { - Padding = padding - }); +namespace Tensorflow.Keras.Layers { + public partial class LayersApi { + /// + /// Zero-padding layer for 2D input (e.g. picture). + /// + /// + /// + public ZeroPadding2D ZeroPadding2D ( NDArray padding ) + => new ZeroPadding2D(new ZeroPadding2DArgs { + Padding = padding + }); - /// - /// Upsampling layer for 2D inputs.
- /// Repeats the rows and columns of the data by size[0] and size[1] respectively. - ///
- /// - /// - /// - /// - public UpSampling2D UpSampling2D(Shape size = null, - string data_format = null, - string interpolation = "nearest") - => new UpSampling2D(new UpSampling2DArgs - { - Size = size ?? (2, 2) - }); + /// + /// Upsampling layer for 2D inputs.
+ /// Repeats the rows and columns of the data by size[0] and size[1] respectively. + ///
+ /// + /// + /// + /// + public UpSampling2D UpSampling2D ( Shape size = null, + string data_format = null, + string interpolation = "nearest" ) + => new UpSampling2D(new UpSampling2DArgs { + Size = size ?? (2, 2) + }); - /// - /// Layer that reshapes inputs into the given shape. - /// - /// - /// - public Reshape Reshape(Shape target_shape) - => new Reshape(new ReshapeArgs - { - TargetShape = target_shape - }); + /// + /// Permutes the dimensions of the input according to a given pattern. + /// + public Permute Permute ( int[] dims ) + => new Permute(new PermuteArgs { + dims = dims + }); - public Reshape Reshape(object[] target_shape) - => new Reshape(new ReshapeArgs - { - TargetShapeObjects = target_shape + /// + /// Layer that reshapes inputs into the given shape. + /// + /// + /// + public Reshape Reshape ( Shape target_shape ) + => new Reshape(new ReshapeArgs { + TargetShape = target_shape }); - } + + public Reshape Reshape ( object[] target_shape ) + => new Reshape(new ReshapeArgs { + TargetShapeObjects = target_shape + }); + } } diff --git a/src/TensorFlowNET.Keras/Layers/Reshaping/Permute.cs b/src/TensorFlowNET.Keras/Layers/Reshaping/Permute.cs new file mode 100644 index 00000000..08089900 --- /dev/null +++ b/src/TensorFlowNET.Keras/Layers/Reshaping/Permute.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Tensorflow.Keras.Engine; +using Tensorflow.Keras.Utils; +using static Tensorflow.Binding; +using Tensorflow.Keras.ArgsDefinition; + +namespace Tensorflow.Keras.Layers { + public class Permute : Layer { + int[] dims, permute; + public Permute ( PermuteArgs args ) : base(args) { + this.dims = args.dims; + } + protected override void build ( Tensors inputs ) { + var rank = inputs.rank; + if ( dims.Length != rank - 1 ) { + throw new ValueError("Dimensions must match."); + } + permute = new int[inputs.rank]; + dims.CopyTo(permute, 1); + built = true; + } + protected override Tensors Call ( Tensors inputs, Tensor state = null, bool? training = null ) { + Tensor outputs = inputs; + return tf.transpose(outputs, new Axis(permute)); + } + public override Shape ComputeOutputShape ( Shape input_shape ) { + Shape output_shape = new Shape(input_shape.dims); + for ( int i = 0; i < dims.Length; i += 1 ) { + var d = dims[i]; + var target_dim = input_shape[d]; + output_shape[i + 1] = target_dim; + } + return output_shape; + } + } +} diff --git a/test/TensorFlowNET.Keras.UnitTest/Layers/Layers.Cropping.Test.cs b/test/TensorFlowNET.Keras.UnitTest/Layers/Layers.Cropping.Test.cs new file mode 100644 index 00000000..b99a9abb --- /dev/null +++ b/test/TensorFlowNET.Keras.UnitTest/Layers/Layers.Cropping.Test.cs @@ -0,0 +1,39 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Tensorflow; +using Tensorflow.NumPy; +using static Tensorflow.Binding; +using static Tensorflow.KerasApi; + +namespace TensorFlowNET.Keras.UnitTest { + [TestClass] + public class LayersCroppingTest : EagerModeTestBase { + [TestMethod] + public void Cropping1D () { + Shape input_shape = (1, 5, 2); + var x = tf.zeros(input_shape); + var cropping_1d = keras.layers.Cropping1D(new[] { 1, 2 }); + var y = cropping_1d.Apply(x); + Assert.AreEqual((1, 2, 2), y.shape); + } + + [TestMethod] + public void Cropping2D () { + Shape input_shape = (1, 5, 6, 1); + NDArray cropping = new NDArray(new[,] { { 1, 2 }, { 1, 3 } }); + var x = tf.zeros(input_shape); + var cropping_2d = keras.layers.Cropping2D(cropping); + var y = cropping_2d.Apply(x); + Assert.AreEqual((1, 2, 2, 1), y.shape); + } + + [TestMethod] + public void Cropping3D () { + Shape input_shape = new Shape(1, 5, 6, 7, 1); + NDArray cropping = new NDArray(new[,] { { 1, 2 }, { 1, 3 }, { 1, 4 } }); + var x = tf.zeros(input_shape); + var cropping_3d = keras.layers.Cropping3D(cropping); + var y = cropping_3d.Apply(x); + Assert.AreEqual(new Shape(1, 2, 2, 2, 1), y.shape); + } + } +} diff --git a/test/TensorFlowNET.Keras.UnitTest/Layers/Layers.Reshaping.Test.cs b/test/TensorFlowNET.Keras.UnitTest/Layers/Layers.Reshaping.Test.cs index 70d56aa7..a79c517b 100644 --- a/test/TensorFlowNET.Keras.UnitTest/Layers/Layers.Reshaping.Test.cs +++ b/test/TensorFlowNET.Keras.UnitTest/Layers/Layers.Reshaping.Test.cs @@ -4,37 +4,40 @@ using Tensorflow.NumPy; using static Tensorflow.Binding; using static Tensorflow.KerasApi; -namespace TensorFlowNET.Keras.UnitTest -{ - [TestClass] - public class LayersReshapingTest : EagerModeTestBase - { - [TestMethod] - public void ZeroPadding2D() - { - Shape input_shape = (1, 1, 2, 2); - var x = np.arange(input_shape.size).reshape(input_shape); - var zero_padding_2d = keras.layers.ZeroPadding2D(new[,] { { 1, 0 }, { 1, 0 } }); - var y = zero_padding_2d.Apply(x); - Assert.AreEqual((1, 2, 3, 2), y.shape); - } +namespace TensorFlowNET.Keras.UnitTest { + [TestClass] + public class LayersReshapingTest : EagerModeTestBase { + [TestMethod] + public void ZeroPadding2D () { + Shape input_shape = (1, 1, 2, 2); + var x = np.arange(input_shape.size).reshape(input_shape); + var zero_padding_2d = keras.layers.ZeroPadding2D(new[,] { { 1, 0 }, { 1, 0 } }); + var y = zero_padding_2d.Apply(x); + Assert.AreEqual((1, 2, 3, 2), y.shape); + } - [TestMethod] - public void UpSampling2D() - { - Shape input_shape = (2, 2, 1, 3); - var x = np.arange(input_shape.size).reshape(input_shape); - var y = keras.layers.UpSampling2D(size: (1, 2)).Apply(x); - Assert.AreEqual((2, 2, 2, 3), y.shape); - } + [TestMethod] + public void UpSampling2D () { + Shape input_shape = (2, 2, 1, 3); + var x = np.arange(input_shape.size).reshape(input_shape); + var y = keras.layers.UpSampling2D(size: (1, 2)).Apply(x); + Assert.AreEqual((2, 2, 2, 3), y.shape); + } - [TestMethod] - public void Reshape() - { - var inputs = tf.zeros((10, 5, 20)); - var outputs = keras.layers.LeakyReLU().Apply(inputs); - outputs = keras.layers.Reshape((20, 5)).Apply(outputs); - Assert.AreEqual((10, 20, 5), outputs.shape); - } - } + [TestMethod] + public void Reshape () { + var inputs = tf.zeros((10, 5, 20)); + var outputs = keras.layers.LeakyReLU().Apply(inputs); + outputs = keras.layers.Reshape((20, 5)).Apply(outputs); + Assert.AreEqual((10, 20, 5), outputs.shape); + } + + [TestMethod] + public void Permute () { + var inputs = tf.zeros((2, 3, 4, 5)); + var outputs = keras.layers.Permute(new int[] { 3, 2, 1 }).Apply(inputs); + Assert.AreEqual((2, 5, 4, 3), outputs.shape); + } + + } }