diff --git a/Backends/CNTK.CPU/CNTKBackend.cs b/Backends/CNTK.CPU/CNTKBackend.cs index 5542143..bf67f4a 100644 --- a/Backends/CNTK.CPU/CNTKBackend.cs +++ b/Backends/CNTK.CPU/CNTKBackend.cs @@ -387,8 +387,11 @@ public Tensor add(Tensor a, Tensor b) return Out(new Variable(In(a).function) + new Variable(In(b).function)); } - public Tensor bias_add(Tensor output, Tensor bias, string name = null) + public Tensor bias_add(Tensor output, Tensor bias, DataFormatType? data_format = null, string name = null) { + if (data_format != null) + throw new NotImplementedException(); + using (this.name_scope("bias_add")) { CNTKTensor _x = In(output); @@ -474,6 +477,21 @@ public Tensor transpose(Tensor tensor) return Out(C.Transpose(In(tensor))); } + /// + /// Turn a nD tensor into a 2D tensor with same 0th dimension. In other words, it flattens each data samples of a batch. + /// + /// + public Tensor batch_flatten(Tensor x) + { + // https://github.com/fchollet/keras/blob/f65a56fb65062c8d14d215c9f4b1015b97cc5bf3/keras/backend/cntk_backend.py#L1460 + // cntk's batch axis is not in shape, + // so just flatten all the dim in x.shape + int dim = Matrix.Product(x.shape.Select(s => s.Value).ToArray()); + x = Out(C.Reshape(In(x), NDShape.CreateNDShape(new[] { -1 }))); + x._keras_shape = new int?[] { null, dim }; + return x; + } + public object eval(Tensor tensor) { log(new { tensor }); @@ -504,7 +522,7 @@ public Tensor clip(Tensor norms, double minval, double maxval) throw new NotImplementedException(); } - public Tensor random_uniform(int?[] shape, double minval = 0, double maxval = 1, DataType? dtype = null, int? seed = null, string name = null) + public Tensor random_uniform(int[] shape, double minval = 0, double maxval = 1, DataType? dtype = null, int? seed = null, string name = null) { if (dtype == null) dtype = floatx(); @@ -694,7 +712,7 @@ public Tensor in_train_phase(Func x, Func alt, bool? training) return Out(In(input_tensor).function.Output.DataType); } - public Tensor constant(T value, int?[] shape = null, KerasSharp.DataType? dtype = null, string name = null) + public Tensor constant(T value, int[] shape = null, KerasSharp.DataType? dtype = null, string name = null) { log(new { value, shape, dtype, name }); @@ -705,7 +723,7 @@ public Tensor constant(T value, int?[] shape = null, KerasSharp.DataType? dty return Out(_const, shape); } - public Constant InGeneric(T value, int?[] shape = null, KerasSharp.DataType? dtype = null, string name = null) + public Constant InGeneric(T value, int[] shape = null, KerasSharp.DataType? dtype = null, string name = null) { if (dtype == null) dtype = floatx(); @@ -726,7 +744,7 @@ public Constant InGeneric(T value, int?[] shape = null, KerasSharp.DataType? } else { - _shape = shape.Select(x => x.Value).ToArray(); + _shape = shape; } Constant c = _constant(value, _shape, _dtype, name); @@ -979,6 +997,20 @@ public Tensor reshape(Tensor x, int[] shape) } + public Tensor conv1d(Tensor inputs, Tensor kernel, int strides, PaddingType padding, DataFormatType? data_format = null, int dilation_rate = 1, string name = null) + { + throw new NotImplementedException(); + } + + public Tensor conv2d(Tensor inputs, Tensor kernel, int[] strides, PaddingType padding, DataFormatType? data_format = null, int[] dilation_rate = null, string name = null) + { + throw new NotImplementedException(); + } + + public Tensor conv3d(Tensor inputs, Tensor kernel, int[] strides, PaddingType padding, DataFormatType? data_format = null, int[] dilation_rate = null, string name = null) + { + throw new NotImplementedException(); + } @@ -1084,6 +1116,11 @@ public NDShape InShape(int[] shape) return s; } + public Tensor Out(CNTK.Function function, int[] keras_shape) + { + return Out(function, keras_shape.Select(x => (int?)x).ToArray()); + } + public Tensor Out(CNTK.Function function, int?[] keras_shape = null) { var t = new CNTKTensor(this) @@ -1207,7 +1244,6 @@ public void Dispose() // TODO: uncomment the following line if the finalizer is overridden above. // GC.SuppressFinalize(this); } - #endregion } } diff --git a/Backends/TensorFlow/TensorFlowBackend.cs b/Backends/TensorFlow/TensorFlowBackend.cs index 79c8827..560d031 100644 --- a/Backends/TensorFlow/TensorFlowBackend.cs +++ b/Backends/TensorFlow/TensorFlowBackend.cs @@ -52,7 +52,7 @@ public class TensorFlowBackend : BackendBase, IBackend // This dictionary holds a mapping {graph: learning_phase}. // A learning phase is a bool tensor used to run Keras models in // either train mode (learning_phase == 1) or test mode (learning_phase == 0). - private Dictionary _GRAPH_LEARNING_PHASES = new Dictionary(); + private Dictionary _GRAPH_LEARNING_PHASES = new Dictionary(); // This dictionary holds a mapping {graph: UID_DICT}. // each UID_DICT is a dictionary mapping name prefixes to a current index, @@ -115,7 +115,7 @@ public void clear_session() // reset_uids(); TFOutput phase = tf.Placeholder(dtype: TFDataType.Bool, operName: "keras_learning_phase"); - _GRAPH_LEARNING_PHASES = new Dictionary(); + _GRAPH_LEARNING_PHASES = new Dictionary(); _GRAPH_LEARNING_PHASES[tf] = phase; } @@ -254,7 +254,7 @@ public Tensor clip_norm(Tensor g, double clipnorm, Tensor norm) throw new NotImplementedException(); } - public Tensor constant(T value, int?[] shape = null, DataType? dtype = null, string name = null) + public Tensor constant(T value, int[] shape = null, DataType? dtype = null, string name = null) { if (dtype == null) dtype = floatx(); @@ -266,11 +266,11 @@ public Tensor constant(T value, int?[] shape = null, DataType? dtype = null, if (arr != null) _shape = arr.GetLength(); else _shape = new int[0]; - shape = _shape.Select(x => (int?)x).ToArray(); + shape = _shape; } else { - _shape = shape.Select(x => x.Value).ToArray(); + _shape = shape; } TFOutput o; @@ -444,7 +444,9 @@ public Tensor in_train_phase(Func x, Func alt, bool? training) if (training == null) { - training = (bool)learning_phase(); + var t = learning_phase(); + if (t is bool) + training = (bool)t; uses_learning_phase = true; } else @@ -463,14 +465,21 @@ public Tensor in_train_phase(Func x, Func alt, bool? training) else { //else: assume learning phase is a placeholder tensor. - throw new NotImplementedException(); - } - // Tensor xx = @switch(training, x, alt); + Tensor xx = @switch((Tensor)learning_phase(), x, alt); - if (uses_learning_phase) - x()._uses_learning_phase = true; - return x(); + if (uses_learning_phase) + xx._uses_learning_phase = true; + return xx; + } + } + + /// + /// Selects `x` in test phase, and `alt` otherwise. Note that `alt` should have the* same shape* as `x`. + /// + public Tensor in_test_phase(Func x, Func alt, bool? training = null) + { + return in_train_phase(alt, x, training: training); } /// @@ -491,12 +500,10 @@ public Tensor @switch(Tensor condition, Func then_expression, Func then_expression().output, - // () => else_expression().output); - //return tensor(x); + TFOutput x = tf.Cond(In(condition), + () => In(then_expression()), + () => In(else_expression())); + return Out(x); } public bool is_sparse(Tensor tensor) @@ -530,7 +537,15 @@ public object learning_phase() _GRAPH_LEARNING_PHASES[graph] = phase; } - return Out(_GRAPH_LEARNING_PHASES[graph]); + return _GRAPH_LEARNING_PHASES[graph]; + } + + /// + /// Sets the learning phase to a fixed value. + /// + public void set_learning_phase(bool value) + { + _GRAPH_LEARNING_PHASES[tf] = value; } public Tensor max(Tensor x, int v, object p) @@ -553,6 +568,17 @@ public Tensor maximum(double v, Tensor tensor) throw new NotImplementedException(); } + /// + /// Turn a nD tensor into a 2D tensor with same 0th dimension. In other words, it flattens each data samples of a batch. + /// + /// + public Tensor batch_flatten(Tensor x) + { + var _x = In(x); + TFOutput shape = tf.Shape(_x); + TFOutput dim = tf.Prod(tf.Slice(shape, tf.Const(1), tf.Rank(shape)), reduction_indices: tf.ReduceDims(shape, null)); + return Out(tf.Reshape(In(x), tf.Stack(new TFOutput[] { tf.Const(-1), dim } ))); + } public TFOutput _normalize_axis(int[] axis, int? ndim) @@ -664,9 +690,25 @@ public Tensor add(Tensor a, Tensor b) return Out(tf.Add(In(a).output, In(b).output)); } - public Tensor bias_add(Tensor a, Tensor b, string name = null) + public Tensor bias_add(Tensor a, Tensor b, DataFormatType? data_format = null, string name = null) + { + return Out(tf.BiasAdd(In(a), In(b), data_format: In(data_format), operName: name)); + } + + private string In(DataFormatType? data_format) { - return Out(tf.BiasAdd(In(a), In(b), operName: name)); + if (data_format == null) + return null; + + switch (data_format.Value) + { + case DataFormatType.ChannelsFirst: + return "channels_first"; + case DataFormatType.ChannelsLast: + return "channels_last"; + default: + throw new Exception(); + } } public Tensor add(T a, Tensor b) @@ -766,7 +808,7 @@ public Tensor placeholder(int?[] shape = null, int? ndim = null, DataType? dtype /// /// A tensor. /// - public Tensor random_uniform(int?[] shape, double minval = 0.0, double maxval = 1.0, DataType? dtype = null, int? seed = null, string name = null) + public Tensor random_uniform(int[] shape, double minval = 0.0, double maxval = 1.0, DataType? dtype = null, int? seed = null, string name = null) { if (dtype == null) dtype = floatx(); @@ -989,6 +1031,11 @@ public Tensor transpose(Tensor tensor) return Out(tf.Transpose(In(tensor).output)); } + public Tensor transpose(Tensor tensor, int[] perm) + { + return Out(tf.Transpose(In(tensor).output, _constant(perm))); + } + public object eval(Tensor tensor) { @@ -1021,6 +1068,94 @@ public object eval(TFOutput output) + public Tensor conv1d(Tensor inputs, Tensor kernel, int strides, PaddingType padding, DataFormatType? data_format = null, int dilation_rate = 1, string name = null) + { + throw new NotImplementedException(); + } + + public Tensor conv2d(Tensor inputs, Tensor kernel, int[] strides, PaddingType padding, DataFormatType? data_format = null, int[] dilation_rate = null, string name = null) + { + // https://github.com/fchollet/keras/blob/f65a56fb65062c8d14d215c9f4b1015b97cc5bf3/keras/backend/tensorflow_backend.py#L3102 + if (data_format == null) + data_format = image_data_format(); + + if (!dilation_rate.IsEqual(new[] { 1, 1 })) + throw new NotImplementedException(); + + TFOutput x = In(inputs).output; + TFOutput _kernel = In(kernel).output; + + // With 4d inputs, tf.nn.convolution only supports + // data_format NHWC, so we transpose the inputs + // in case we are in data_format channels_first. + x = _preprocess_conv2d_input(x, data_format.Value); + string _padding = _preprocess_padding(padding); + x = tf.Conv2D( + input: x, + filter: _kernel, + //dilation_rate: dilation_rate, + strides: strides.Select(i => (long)i).ToArray(), + padding: _padding, + data_format: "NHWC"); + return Out(_postprocess_conv2d_output(x, data_format.Value)); + } + + /// + /// Transpose and cast the output from conv2d if needed. + /// + private TFOutput _postprocess_conv2d_output(TFOutput x, DataFormatType data_format) + { + if (data_format == DataFormatType.ChannelsFirst) + x = tf.Transpose(x, _constant(new[] { 0, 3, 1, 2 })); + + if (floatx() == DataType.Double) + x = tf.Cast(x, TFDataType.Double); + return x; + } + + /// + /// Convert keras' padding to tensorflow's padding. + /// + /// + public string _preprocess_padding(PaddingType padding) + { + switch (padding) + { + case PaddingType.Same: + return "SAME"; + case PaddingType.Valid: + return "VALID"; + } + + throw new ArgumentException($"Invalid padding: {padding}"); + } + + /// + /// Transpose and cast the input before the conv2d. + /// + private TFOutput _preprocess_conv2d_input(TFOutput x, DataFormatType data_format) + { + if (x.OutputType == TFDataType.Double) + x = tf.Cast(x, TFDataType.Float); + + if (data_format == DataFormatType.ChannelsFirst) + { + // TF uses the last dimension as channel dimension, + // instead of the 2nd one. + // TH input shape: (samples, input_depth, rows, cols) + // TF input shape: (samples, rows, cols, input_depth) + x = tf.Transpose(x, _constant(new[] { 0, 2, 3, 1 })); + } + + return x; + } + + public Tensor conv3d(Tensor inputs, Tensor kernel, int[] strides, PaddingType padding, DataFormatType? data_format = null, int[] dilation_rate = null, string name = null) + { + throw new NotImplementedException(); + } + + /// /// Instantiates an all-zeros variable and returns it. diff --git a/Sources/Backends/Base/IBackend.cs b/Sources/Backends/Base/IBackend.cs index b421fbb..e2fbc13 100644 --- a/Sources/Backends/Base/IBackend.cs +++ b/Sources/Backends/Base/IBackend.cs @@ -48,7 +48,7 @@ public interface IBackend : IDisposable Tensor round(Tensor x); Tensor argmax(Tensor x, int axis = -1); Tensor sum(Tensor x, int axis, bool keepdims = false, string name = null); - + Tensor batch_flatten(Tensor inputs); Tensor clip(Tensor norms, int v, int maxValue); @@ -65,6 +65,7 @@ public interface IBackend : IDisposable void clear_session(); Tensor cast(Tensor x, DataType dataType); + Tensor dropout(object p, double retain_prob, object noise_shape, object seed); @@ -137,6 +138,8 @@ public interface IBackend : IDisposable Tensor print_tensor(Tensor x, string message); + DataFormatType image_data_format(); + Tensor softsign(Tensor x); Tensor tanh(Tensor x); @@ -152,7 +155,7 @@ public interface IBackend : IDisposable - Tensor random_uniform(int?[] shape, double minval = 0.0, double maxval = 1.0, DataType? dtype = null, int? seed = null, string name = null); + Tensor random_uniform(int[] shape, double minval = 0.0, double maxval = 1.0, DataType? dtype = null, int? seed = null, string name = null); Tensor l2_normalize(Tensor expected, int axis); @@ -185,7 +188,7 @@ public interface IBackend : IDisposable DataType? dtype(Tensor input_tensor); - Tensor constant(T value, int?[] shape = null, DataType? dtype = null, string name = null); + Tensor constant(T value, int[] shape = null, DataType? dtype = null, string name = null); Tensor transpose(Tensor tensor); @@ -236,6 +239,12 @@ public interface IBackend : IDisposable Tensor not_equal(Tensor weights, T v) where T : struct; - Tensor bias_add(Tensor output, Tensor bias, string name = null); + Tensor bias_add(Tensor output, Tensor bias, DataFormatType? data_format = null, string name = null); + + Tensor conv1d(Tensor inputs, Tensor kernel, int strides, PaddingType padding, DataFormatType? data_format, int dilation_rate, string name = null); + + Tensor conv2d(Tensor inputs, Tensor kernel, int[] strides, PaddingType padding, DataFormatType? data_format, int[] dilation_rate, string name = null); + + Tensor conv3d(Tensor inputs, Tensor kernel, int[] strides, PaddingType padding, DataFormatType? data_format, int[] dilation_rate, string name = null); } } diff --git a/Sources/Engine/Topology/Layer.cs b/Sources/Engine/Topology/Layer.cs index e7f9fd2..eb75621 100644 --- a/Sources/Engine/Topology/Layer.cs +++ b/Sources/Engine/Topology/Layer.cs @@ -279,7 +279,7 @@ public virtual List non_trainable_weights /// /// The created weight variable. /// - public Tensor add_weight(string name, int?[] shape, DataType? dtype = null, + public Tensor add_weight(string name, int[] shape, DataType? dtype = null, IWeightInitializer initializer = null, IWeightRegularizer regularizer = null, bool trainable = true, IWeightConstraint constraint = null) { diff --git a/Sources/Keras Sharp.csproj b/Sources/Keras Sharp.csproj index 9dc45f5..aac0172 100644 --- a/Sources/Keras Sharp.csproj +++ b/Sources/Keras Sharp.csproj @@ -132,6 +132,9 @@ + + + diff --git a/Sources/Layers/Convolutional/Conv2D.cs b/Sources/Layers/Convolutional/Conv2D.cs index b9dcfd6..e4c3596 100644 --- a/Sources/Layers/Convolutional/Conv2D.cs +++ b/Sources/Layers/Convolutional/Conv2D.cs @@ -31,7 +31,7 @@ namespace KerasSharp using System.Linq; using System.Text; using System.Threading.Tasks; - + using System.Runtime.Serialization; using KerasSharp.Constraints; using KerasSharp.Regularizers; @@ -41,14 +41,389 @@ namespace KerasSharp using static KerasSharp.Backends.Current; + /// + /// Abstract nD convolution layer (private, used as implementation base). + /// This layer creates a convolution kernel that is convolved + /// with the layer input to produce a tensor of outputs. + /// If `use_bias` is True, a bias vector is created and added to the outputs. + /// Finally, if `activation` is not `None`, + /// it is applied to the outputs as well. + /// + /// + public class _Conv : Layer + { + private int rank; + private int filters; + private int[] kernel_size; + private int[] strides; + private PaddingType padding; + private DataFormatType? data_format; + private int[] dilation_rate; + private IActivationFunction activation; + private bool use_bias; + private IWeightInitializer kernel_initializer; + private IWeightInitializer bias_initializer; + private IWeightRegularizer kernel_regularizer; + private IWeightRegularizer bias_regularizer; + private IWeightConstraint kernel_constraint; + private IWeightConstraint bias_constraint; + private Tensor kernel; + private Tensor bias; + + /// + /// Initializes a new instance of the class. + /// + /// rank: An integer, the rank of the convolution, e.g. "2" for 2D convolution. + /// Integer, the dimensionality of the output space (i.e.the number output of filters in the convolution). + /// An integer or tuple/list of n integers, specifying the dimensions of the convolution window. + /// An integer or tuple/list of n integers, specifying the strides of the convolution. Specifying any stride value != 1 is incompatible with specifying any `dilation_rate` value != 1. + /// One of `"valid"` or `"same"` (case-insensitive). + /// A string, one of `channels_last` (default) or `channels_first`. The ordering of the dimensions in the inputs. + /// `channels_last` corresponds to inputs with shape `(batch, ..., channels)` while `channels_first` corresponds to inputs with shape + /// `(batch, channels, ...)`. It defaults to the `image_data_format` value found in your Keras config file at `~/.keras/keras.json`. + /// If you never set it, then it will be "channels_last". + /// An integer or tuple/list of n integers, specifying the dilation rate to use for dilated convolution. Currently, specifying any `dilation_rate` value != 1 is incompatible with specifying any `strides` value != 1. + /// Activation function to use (see[activations](../activations.md)). If you don't specify anything, no activation is applied (ie. "linear" activation: `a(x) = x`). + /// Boolean, whether the layer uses a bias vector. + /// Initializer for the `kernel` weights matrix (see[initializers](../initializers.md)). + /// Initializer for the bias vector (see[initializers](../initializers.md)). + /// Regularizer function applied to the `kernel` weights matrix (see[regularizer](../regularizers.md)). + /// Regularizer function applied to the bias vector (see[regularizer](../regularizers.md)). + /// Regularizer function applied to the output of the layer(its "activation"). (see[regularizer](../regularizers.md)). + /// Constraint function applied to the kernel matrix (see[constraints](../constraints.md)). + /// Constraint function applied to the bias vector (see[constraints](../constraints.md)). + public _Conv(int rank, + int filters, + int[] kernel_size, + int[] strides = null, + PaddingType padding = PaddingType.Valid, + DataFormatType? data_format = null, + int[] dilation_rate = null, + IActivationFunction activation = null, + bool use_bias = true, + IWeightInitializer kernel_initializer = null, + IWeightInitializer bias_initializer = null, + IWeightRegularizer kernel_regularizer = null, + IWeightRegularizer bias_regularizer = null, + IWeightRegularizer activity_regularizer = null, + IWeightConstraint kernel_constraint = null, + IWeightConstraint bias_constraint = null, + int?[] input_shape = null) + : base(input_shape: input_shape) + { + if (kernel_initializer == null) + kernel_initializer = new GlorotUniform(); + + if (bias_initializer == null) + bias_initializer = new Zeros(); + + if (strides == null) + strides = Vector.Create(size: rank, value: 1); + + if (dilation_rate == null) + dilation_rate = Vector.Create(size: rank, value: 1); + + if (data_format == null) + data_format = K.image_data_format(); + + if (kernel_size.Length != rank) + throw new ArgumentException("kernel_size"); + + if (strides.Length != rank) + throw new ArgumentException("strides"); + + if (dilation_rate.Length != rank) + throw new ArgumentException("dilation_rate"); + + // https://github.com/fchollet/keras/blob/f65a56fb65062c8d14d215c9f4b1015b97cc5bf3/keras/layers/convolutional.py#L101 + + this.rank = rank; + this.filters = filters; + this.kernel_size = kernel_size; + this.strides = strides; + this.padding = padding; + this.data_format = data_format; + this.dilation_rate = dilation_rate; + this.activation = activation; + this.use_bias = use_bias; + this.kernel_initializer = kernel_initializer; + this.bias_initializer = bias_initializer; + this.kernel_regularizer = kernel_regularizer; + this.bias_regularizer = bias_regularizer; + this.activity_regularizer = activity_regularizer; + this.kernel_constraint = kernel_constraint; + this.bias_constraint = bias_constraint; + this.input_spec = new List { new InputSpec(ndim: this.rank + 2) }; + } + + protected override void build(List input_shapes) + { + if (input_shapes.Count > 1) + throw new Exception(); + + var input_shape = input_shapes[0]; + + // https://github.com/fchollet/keras/blob/f65a56fb65062c8d14d215c9f4b1015b97cc5bf3/keras/layers/convolutional.py#L119 + + int channel_axis; + if (this.data_format == DataFormatType.ChannelsFirst) + channel_axis = 1; + else + channel_axis = -1; + + if (input_shape.Get(channel_axis) == null) + throw new Exception("The channel dimension of the inputs should be defined. Found `None`."); + + int input_dim = input_shape.Get(channel_axis).Value; + int[] kernel_shape = this.kernel_size.Concat(new[] { input_dim, this.filters }).ToArray(); + + this.kernel = this.add_weight(shape: kernel_shape, + initializer: this.kernel_initializer, + name: "kernel", + regularizer: this.kernel_regularizer, + constraint: this.kernel_constraint); + if (this.use_bias) + { + this.bias = this.add_weight(shape: new int[] { this.filters }, + initializer: this.bias_initializer, + name: "bias", + regularizer: this.bias_regularizer, + constraint: this.bias_constraint); + } + else + { + this.bias = null; + } + + // Set input spec. + this.input_spec = new List { new InputSpec(ndim: this.rank + 2, axes: new Dictionary { { channel_axis, input_dim } }) }; + this.built = true; + } + protected override Tensor InnerCall(Tensor inputs, Tensor mask = null, bool? training = null) + { + // https://github.com/fchollet/keras/blob/f65a56fb65062c8d14d215c9f4b1015b97cc5bf3/keras/layers/convolutional.py#L149 + + if (mask != null) + throw new Exception(); + + if (training != null) + throw new Exception(); + + Tensor outputs = null; + + if (this.rank == 1) + { + outputs = K.conv1d( + inputs, + this.kernel, + strides: this.strides[0], + padding: this.padding, + data_format: this.data_format, + dilation_rate: this.dilation_rate[0]); + } + if (this.rank == 2) + { + outputs = K.conv2d( + inputs, + this.kernel, + strides: this.strides, + padding: this.padding, + data_format: this.data_format, + dilation_rate: this.dilation_rate); + } + if (this.rank == 3) + { + outputs = K.conv3d( + inputs, + this.kernel, + strides: this.strides, + padding: this.padding, + data_format: this.data_format, + dilation_rate: this.dilation_rate); + } + if (this.use_bias) + { + outputs = K.bias_add( + outputs, + this.bias, + data_format: this.data_format); + } + + if (this.activation != null) + return this.activation.Call(outputs, null); + return outputs; + } + + public override List compute_output_shape(List input_shapes) + { + if (input_shapes.Count != 1) + throw new Exception("Expected a single input."); + int?[] input_shape = input_shapes[0]; + + // https://github.com/fchollet/keras/blob/f65a56fb65062c8d14d215c9f4b1015b97cc5bf3/keras/layers/convolutional.py#L185 + + if (this.data_format == DataFormatType.ChannelsLast) + { + var space = input_shape.Get(1, -1); + var new_space = new List(); + for (int i = 0; i < space.Length; i++) + { + int? new_dim = conv_utils.conv_output_length( + space[i], + this.kernel_size[i], + padding: this.padding, + stride: this.strides[i], + dilation: this.dilation_rate[i]); + new_space.Add(new_dim); + } + + return new[] { new[] { input_shape[0] }.Concat(new_space).Concat(new int?[] { this.filters }).ToArray() }.ToList(); + } + else if (this.data_format == DataFormatType.ChannelsFirst) + { + var space = input_shape.Get(2, 0); + var new_space = new List(); + for (int i = 0; i < space.Length; i++) + { + int? new_dim = conv_utils.conv_output_length( + space[i], + this.kernel_size[i], + padding: this.padding, + stride: this.strides[i], + dilation: this.dilation_rate[i]); + new_space.Add(new_dim); + } + + return new[] { new[] { input_shape[0] }.Concat(new int?[] { this.filters }).Concat(new_space).ToArray() }.ToList(); + } + else + { + throw new Exception(); + } + } + + //override Dictionary get_config() + //{ + // return new Dictionary + // { + // { "rank", this.rank }, + // { "filters", this.filters }, + // { "kernel_size", this.kernel_size }, + // { "strides", this.strides }, + // { "padding", this.padding }, + // { "data_format", this.data_format }, + // { "dilation_rate", this.dilation_rate }, + // { "activation", activations.serialize(this.activation) }, + // { "use_bias", this.use_bias }, + // { "kernel_initializer", initializers.serialize(this.kernel_initializer) }, + // { "bias_initializer", initializers.serialize(this.bias_initializer) }, + // { "kernel_regularizer", regularizers.serialize(this.kernel_regularizer) }, + // { "bias_regularizer", regularizers.serialize(this.bias_regularizer) }, + // { "activity_regularizer", regularizers.serialize(this.activity_regularizer) }, + // { "kernel_constraint", constraints.serialize(this.kernel_constraint) }, + // { "bias_constraint", constraints.serialize(this.bias_constraint) }, + // }; + + //base_config = super(_Conv, self).get_config() + // return dict(list(base_config.items()) + list(config.items())) + } + + + /// + /// 2D convolution layer (e.g. spatial convolution over images). + /// + /// + /// + /// This layer creates a convolution kernel that is convolved + /// with the layer input to produce a tensor of + /// outputs.If `use_bias` is True, + /// a bias vector is created and added to the outputs.Finally, if + /// `activation` is not `None`, it is applied to the outputs as well. + /// When using this layer as the first layer in a model, + /// provide the keyword argument `input_shape` + /// (tuple of integers, does not include the sample axis), + /// e.g. `input_shape=(128, 128, 3)` for 128x128 RGB pictures + /// in `data_format="channels_last"`. + /// + /// + /// + /// [DataContract] - public class Conv2D : Layer + public class Conv2D : _Conv { - public Conv2D(int v1, int[] v2, string activation, int?[] input_shape = null) + public Conv2D(int filters, + int[] kernel_size = null, + int[] strides = null, + PaddingType padding = PaddingType.Valid, + DataFormatType? data_format = null, + int[] dilation_rate = null, + IActivationFunction activation = null, + bool use_bias = true, + IWeightInitializer kernel_initializer = null, + IWeightInitializer bias_initializer = null, + IWeightRegularizer kernel_regularizer = null, + IWeightRegularizer bias_regularizer = null, + IWeightRegularizer activity_regularizer = null, + IWeightConstraint kernel_constraint = null, + IWeightConstraint bias_constraint = null, + int?[] input_shape = null) + : base(rank: 2, + filters: filters, + kernel_size: kernel_size, + strides: strides, + padding: padding, + data_format: data_format, + dilation_rate: dilation_rate, + activation: activation, + use_bias: use_bias, + kernel_initializer: kernel_initializer, + bias_initializer: bias_initializer, + kernel_regularizer: kernel_regularizer, + bias_regularizer: bias_regularizer, + activity_regularizer: activity_regularizer, + kernel_constraint: kernel_constraint, + bias_constraint: bias_constraint, + input_shape: input_shape) { - throw new NotImplementedException(); + this.input_spec = new List { new InputSpec(ndim: 4) }; } + + public Conv2D(int filters, + int[] kernel_size = null, + int[] strides = null, + PaddingType padding = PaddingType.Valid, + DataFormatType? data_format = null, + int[] dilation_rate = null, + string activation = null, + bool use_bias = true, + IWeightInitializer kernel_initializer = null, + IWeightInitializer bias_initializer = null, + IWeightRegularizer kernel_regularizer = null, + IWeightRegularizer bias_regularizer = null, + IWeightRegularizer activity_regularizer = null, + IWeightConstraint kernel_constraint = null, + IWeightConstraint bias_constraint = null, + int?[] input_shape = null) + : this(filters: filters, + kernel_size: kernel_size, + strides: strides, + padding: padding, + data_format: data_format, + dilation_rate: dilation_rate, + activation: Activation.Create(activation), + use_bias: use_bias, + kernel_initializer: kernel_initializer, + bias_initializer: bias_initializer, + kernel_regularizer: kernel_regularizer, + bias_regularizer: bias_regularizer, + activity_regularizer: activity_regularizer, + kernel_constraint: kernel_constraint, + bias_constraint: bias_constraint, + input_shape: input_shape) + { + } + } } diff --git a/Sources/Layers/Core/Dense.cs b/Sources/Layers/Core/Dense.cs index 36520b9..83387de 100644 --- a/Sources/Layers/Core/Dense.cs +++ b/Sources/Layers/Core/Dense.cs @@ -157,7 +157,7 @@ protected override void build(List input_shape) int input_dim = Matrix.Get(input_shape[0], -1).Value; - this.kernel = add_weight(shape: new int?[] { input_dim, this.units }, + this.kernel = add_weight(shape: new int[] { input_dim, this.units }, initializer: this.kernel_initializer, regularizer: this.kernel_regularizer, constraint: this.kernel_constraint, @@ -165,7 +165,7 @@ protected override void build(List input_shape) if (this.use_bias) { - this.bias = base.add_weight(shape: new int?[] { this.units }, + this.bias = base.add_weight(shape: new int[] { this.units }, name: "bias", initializer: bias_initializer, regularizer: bias_regularizer, diff --git a/Sources/Layers/Core/Dropout.cs b/Sources/Layers/Core/Dropout.cs index 8e5e741..29be283 100644 --- a/Sources/Layers/Core/Dropout.cs +++ b/Sources/Layers/Core/Dropout.cs @@ -82,8 +82,10 @@ protected override Tensor InnerCall(Tensor inputs, Tensor mask, bool? training = if (0.0 < this.rate && this.rate < 1.0) { var noise_shape = this._get_noise_shape(inputs); - Func dropped_inputs = () => K.dropout(inputs, this.rate, noise_shape, seed: this.seed); - return K.in_train_phase(dropped_inputs, () => inputs, training: training); + return K.in_train_phase( + () => K.dropout(inputs, this.rate, noise_shape, seed: this.seed), + () => inputs, + training: training); } return inputs; diff --git a/Sources/Layers/Core/Flatten.cs b/Sources/Layers/Core/Flatten.cs index f05ce54..cfe2cdc 100644 --- a/Sources/Layers/Core/Flatten.cs +++ b/Sources/Layers/Core/Flatten.cs @@ -31,7 +31,7 @@ namespace KerasSharp using System.Linq; using System.Text; using System.Threading.Tasks; - + using System.Runtime.Serialization; using KerasSharp.Constraints; using KerasSharp.Regularizers; @@ -41,10 +41,33 @@ namespace KerasSharp using static KerasSharp.Backends.Current; - + /// + /// Flattens the input. Does not affect the batch size. + /// + /// [DataContract] public class Flatten : Layer { + protected override Tensor InnerCall(Tensor inputs, Tensor mask = null, bool? training = null) + { + return K.batch_flatten(inputs); + } + + public override List compute_output_shape(List input_shapes) + { + // https://github.com/fchollet/keras/blob/f65a56fb65062c8d14d215c9f4b1015b97cc5bf3/keras/layers/core.py#L473 + if (input_shapes.Count > 0) + throw new Exception(); + + var input_shape = input_shapes[0]; + + if (!input_shape.Get(1, 0).All(x => x > 0)) + { + throw new Exception($"The shape of the input to 'Flatten' is not fully defined (got {input_shape.Get(1, 0)}). " + + $"Make sure to pass a complete {input_shape} or {batch_input_shape} argument to the first layer in your model."); + } + return new List { new int?[] { input_shape[0], Matrix.Product(input_shape.Select(x=>x.Value).ToArray().Get(1, 0)) } }; + } } } diff --git a/Sources/Utils/ConvUtils.cs b/Sources/Utils/ConvUtils.cs new file mode 100644 index 0000000..9d13870 --- /dev/null +++ b/Sources/Utils/ConvUtils.cs @@ -0,0 +1,98 @@ +// Keras-Sharp: C# port of the Keras library +// https://github.com/cesarsouza/keras-sharp +// +// Based under the Keras library for Python. See LICENSE text for more details. +// +// The MIT License(MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +namespace KerasSharp +{ + using System; + using static Python; + using Accord.Math; + + using static KerasSharp.Backends.Current; + public class conv_utils + { + /// + /// Transforms a single int or iterable of ints into an int tuple. + /// + /// The value to validate and convert. Could an int, or any iterable of ints. + /// The size of the tuple to be returned. + /// The name of the argument being validated, e.g. "strides" or "kernel_size".This is only used to format error messages. + /// System.Object. + internal int[] normalize_tuple(int value, int n, string name) + { + return Vector.Create(size: n, value: value); + } + + /// + /// Transforms a single int or iterable of ints into an int tuple. + /// + /// The value to validate and convert. Could an int, or any iterable of ints. + /// The size of the tuple to be returned. + /// The name of the argument being validated, e.g. "strides" or "kernel_size".This is only used to format error messages. + /// System.Object. + internal int[] normalize_tuple(int[] value_tuple, int n, string name) + { + // https://github.com/fchollet/keras/blob/f65a56fb65062c8d14d215c9f4b1015b97cc5bf3/keras/utils/conv_utils.py#L23 + + if (len(value_tuple) != n) + throw new Exception($"The {name} argument must be a tuple of {n} integers. Received: {value_tuple}"); + + return value_tuple; + } + + internal object normalize_data_format(DataFormatType? value) + { + // https://github.com/fchollet/keras/blob/f65a56fb65062c8d14d215c9f4b1015b97cc5bf3/keras/utils/conv_utils.py#L46 + + if (value == null) + value = K.image_data_format(); + + return value; + } + + /// + /// Determines output length of a convolution given input length. + /// + /// + public static int? conv_output_length(int? input_length, int filter_size, PaddingType padding, int stride, int dilation = 1) + { + if (input_length == null) + return null; + int dilated_filter_size = filter_size + (filter_size - 1) * (dilation - 1); + int output_length = 0; + if (padding == PaddingType.Same) + output_length = input_length.Value; + else if (padding == PaddingType.Valid) + output_length = input_length.Value - dilated_filter_size + 1; + else if (padding == PaddingType.Causal) + output_length = input_length.Value; + else if (padding == PaddingType.Full) + output_length = input_length.Value + dilated_filter_size - 1; + else + throw new Exception(); + return (output_length + stride - 1); // stride + } + } +}