| @@ -149,6 +149,8 @@ namespace Tensorflow | |||||
| return ndArray.ndim == 0 ? 1 : ndArray.shape[0]; | return ndArray.ndim == 0 ? 1 : ndArray.shape[0]; | ||||
| case IEnumerable enumerable: | case IEnumerable enumerable: | ||||
| return enumerable.OfType<object>().Count(); | return enumerable.OfType<object>().Count(); | ||||
| case TensorShape arr: | |||||
| return arr.ndim; | |||||
| } | } | ||||
| throw new NotImplementedException("len() not implemented for type: " + a.GetType()); | throw new NotImplementedException("len() not implemented for type: " + a.GetType()); | ||||
| } | } | ||||
| @@ -156,6 +158,9 @@ namespace Tensorflow | |||||
| public static float min(float a, float b) | public static float min(float a, float b) | ||||
| => Math.Min(a, b); | => Math.Min(a, b); | ||||
| public static int max(int a, int b) | |||||
| => Math.Max(a, b); | |||||
| public static T[] list<T>(IEnumerable<T> list) | public static T[] list<T>(IEnumerable<T> list) | ||||
| => list.ToArray(); | => list.ToArray(); | ||||
| @@ -15,6 +15,8 @@ | |||||
| ******************************************************************************/ | ******************************************************************************/ | ||||
| using System; | using System; | ||||
| using System.Linq; | |||||
| using static Tensorflow.Binding; | |||||
| namespace Tensorflow.Framework | namespace Tensorflow.Framework | ||||
| { | { | ||||
| @@ -52,7 +54,14 @@ namespace Tensorflow.Framework | |||||
| { | { | ||||
| var pred_value = tensor_util.constant_value(pred); | var pred_value = tensor_util.constant_value(pred); | ||||
| if (pred_value is null) | if (pred_value is null) | ||||
| return pred.eval(new Session(pred.graph)); | |||||
| { | |||||
| var result = range(pred.op.NumOutputs).Select(x => IntPtr.Zero).ToArray(); | |||||
| var evaluated = c_api.TF_TryEvaluateConstant(pred.graph, pred._as_tf_output(), result, tf.Status.Handle); | |||||
| if (!evaluated || c_api.TF_GetCode(tf.Status.Handle) != TF_Code.TF_OK) | |||||
| return null; | |||||
| else | |||||
| throw new NotImplementedException(""); | |||||
| } | |||||
| return pred_value; | return pred_value; | ||||
| } | } | ||||
| @@ -322,5 +322,18 @@ namespace Tensorflow | |||||
| [DllImport(TensorFlowLibName)] | [DllImport(TensorFlowLibName)] | ||||
| public static extern void TF_UpdateEdge(IntPtr graph, TF_Output new_src, TF_Input dst, SafeStatusHandle status); | public static extern void TF_UpdateEdge(IntPtr graph, TF_Output new_src, TF_Input dst, SafeStatusHandle status); | ||||
| /// <summary> | |||||
| /// Attempts to evaluate `output`. This will only be possible if `output` doesn't | |||||
| /// depend on any graph inputs (this function is safe to call if this isn't the | |||||
| /// case though). | |||||
| /// </summary> | |||||
| /// <param name="graph"></param> | |||||
| /// <param name="output"></param> | |||||
| /// <param name="result"></param> | |||||
| /// <param name="status"></param> | |||||
| /// <returns></returns> | |||||
| [DllImport(TensorFlowLibName)] | |||||
| public static extern bool TF_TryEvaluateConstant(IntPtr graph, TF_Output output, IntPtr[] result, SafeStatusHandle status); | |||||
| } | } | ||||
| } | } | ||||
| @@ -50,6 +50,6 @@ namespace Tensorflow.Keras.Engine | |||||
| } | } | ||||
| public override string ToString() | public override string ToString() | ||||
| => $"min_ndim={min_ndim}, , axes={axes.Count}"; | |||||
| => $"ndim={ndim}, min_ndim={min_ndim}, axes={axes.Count}"; | |||||
| } | } | ||||
| } | } | ||||
| @@ -21,6 +21,31 @@ namespace Tensorflow | |||||
| { | { | ||||
| public class nn_impl | public class nn_impl | ||||
| { | { | ||||
| public static Tensor conv2d_transpose(Tensor value = null, | |||||
| IVariableV1 filter = null, | |||||
| Tensor output_shape = null, | |||||
| TensorShape strides = null, | |||||
| string padding = "SAME", | |||||
| string data_format = "NHWC", | |||||
| string name = null, | |||||
| TensorShape dilations = null) | |||||
| { | |||||
| if (dilations == null) | |||||
| dilations = (1, 1, 1, 1); | |||||
| return tf_with(ops.name_scope(name, "conv2d_transpose", new { value, filter, output_shape }), scope => | |||||
| { | |||||
| return gen_nn_ops.conv2d_backprop_input( | |||||
| input_sizes: output_shape, | |||||
| filter: filter.AsTensor(), | |||||
| out_backprop: value, | |||||
| strides: strides, | |||||
| padding: padding, | |||||
| data_format: data_format, | |||||
| dilations: dilations, | |||||
| name: name); | |||||
| }); | |||||
| } | |||||
| /// <summary> | /// <summary> | ||||
| /// Normalizes along dimension `axis` using an L2 norm. | /// Normalizes along dimension `axis` using an L2 norm. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -83,6 +108,23 @@ namespace Tensorflow | |||||
| }); | }); | ||||
| } | } | ||||
| public static Tensor batch_normalization(Tensor x, | |||||
| Tensor mean, | |||||
| Tensor variance, | |||||
| Tensor offset, | |||||
| Tensor scale, | |||||
| float variance_epsilon = 0.001f, | |||||
| string name = null) | |||||
| { | |||||
| return tf_with(ops.name_scope(name, "batchnorm", new { x, mean, variance, scale, offset }), scope => | |||||
| { | |||||
| var inv = math_ops.rsqrt(variance + variance_epsilon); | |||||
| inv *= scale; | |||||
| return x * math_ops.cast(inv, x.dtype) + math_ops.cast( | |||||
| offset == null ? (-mean * inv) : (offset - mean * inv), x.dtype); | |||||
| }); | |||||
| } | |||||
| /// <summary> | /// <summary> | ||||
| /// Batch normalization. | /// Batch normalization. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -15,6 +15,10 @@ namespace Tensorflow | |||||
| else if (rank != shape1.rank) | else if (rank != shape1.rank) | ||||
| return false; | return false; | ||||
| return Enumerable.SequenceEqual(shape1.dims, dims); | return Enumerable.SequenceEqual(shape1.dims, dims); | ||||
| case int[] shape2: | |||||
| if (rank != shape2.Length) | |||||
| return false; | |||||
| return Enumerable.SequenceEqual(dims, shape2); | |||||
| default: | default: | ||||
| return false; | return false; | ||||
| } | } | ||||
| @@ -317,5 +317,30 @@ namespace Tensorflow.Keras | |||||
| return array_ops.concat(tensors, axis); | return array_ops.concat(tensors, axis); | ||||
| } | } | ||||
| public Tensor conv2d_transpose(Tensor x, | |||||
| IVariableV1 kernel, | |||||
| Tensor output_shape, | |||||
| TensorShape strides = null, | |||||
| string padding = "valid", | |||||
| string data_format = null, | |||||
| TensorShape dilation_rate = null) | |||||
| { | |||||
| var force_transpose = false; | |||||
| if (data_format == "channels_first" && !dilation_rate.Equals(new[] { 1, 1 })) | |||||
| force_transpose = true; | |||||
| // x, tf_data_format = _preprocess_conv2d_input(x, data_format, force_transpose) | |||||
| var tf_data_format = "NHWC"; | |||||
| padding = padding.ToUpper(); | |||||
| strides = new TensorShape(1, strides[0], strides[1], 1); | |||||
| if (dilation_rate.Equals(new[] { 1, 1 })) | |||||
| x = nn_impl.conv2d_transpose(x, kernel, output_shape, strides, | |||||
| padding: padding, | |||||
| data_format: tf_data_format); | |||||
| else | |||||
| throw new NotImplementedException(""); | |||||
| return x; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -301,9 +301,9 @@ namespace Tensorflow.Keras.Engine | |||||
| nodes_in_decreasing_depth.append(node); | nodes_in_decreasing_depth.append(node); | ||||
| } | } | ||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool is_training = false) | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | { | ||||
| return run_internal_graph(inputs, is_training); | |||||
| return run_internal_graph(inputs, training.Value); | |||||
| } | } | ||||
| Tensors run_internal_graph(Tensors inputs, bool training = false, Tensors mask = null) | Tensors run_internal_graph(Tensors inputs, bool training = false, Tensors mask = null) | ||||
| @@ -10,9 +10,9 @@ namespace Tensorflow.Keras.Engine | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="input"></param> | /// <param name="input"></param> | ||||
| /// <param name="state"></param> | /// <param name="state"></param> | ||||
| /// <param name="is_training"></param> | |||||
| /// <param name="training"></param> | |||||
| /// <returns></returns> | /// <returns></returns> | ||||
| public Tensors Apply(Tensors inputs, Tensor state = null, bool is_training = false) | |||||
| public Tensors Apply(Tensors inputs, Tensor state = null, bool training = false) | |||||
| { | { | ||||
| callContext = callContext ?? new ThreadLocal<CallContext>() | callContext = callContext ?? new ThreadLocal<CallContext>() | ||||
| { | { | ||||
| @@ -38,7 +38,7 @@ namespace Tensorflow.Keras.Engine | |||||
| if (!built) | if (!built) | ||||
| MaybeBuild(inputs); | MaybeBuild(inputs); | ||||
| outputs = Call(inputs, state: state, is_training: is_training); | |||||
| outputs = Call(inputs, state: state, training: training); | |||||
| // memory leak | // memory leak | ||||
| // _set_connectivity_metadata_(inputs, outputs); | // _set_connectivity_metadata_(inputs, outputs); | ||||
| @@ -155,7 +155,7 @@ namespace Tensorflow.Keras.Engine | |||||
| /// <param name="state"></param> | /// <param name="state"></param> | ||||
| /// <param name="is_training"></param> | /// <param name="is_training"></param> | ||||
| /// <returns></returns> | /// <returns></returns> | ||||
| protected virtual Tensors Call(Tensors inputs, Tensor state = null, bool is_training = false) | |||||
| protected virtual Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | { | ||||
| throw new NotImplementedException(""); | throw new NotImplementedException(""); | ||||
| } | } | ||||
| @@ -73,7 +73,7 @@ namespace Tensorflow.Keras.Engine | |||||
| List<(string, Tensor)> test_step(Tensor x, Tensor y) | List<(string, Tensor)> test_step(Tensor x, Tensor y) | ||||
| { | { | ||||
| (x, y) = data_handler.DataAdapter.Expand1d(x, y); | (x, y) = data_handler.DataAdapter.Expand1d(x, y); | ||||
| var y_pred = Apply(x, is_training: false); | |||||
| var y_pred = Apply(x, training: false); | |||||
| var loss = compiled_loss.Call(y, y_pred); | var loss = compiled_loss.Call(y, y_pred); | ||||
| compiled_metrics.update_state(y, y_pred); | compiled_metrics.update_state(y, y_pred); | ||||
| @@ -76,7 +76,7 @@ namespace Tensorflow.Keras.Engine | |||||
| Tensors predict_step(Tensor data) | Tensors predict_step(Tensor data) | ||||
| { | { | ||||
| return Apply(data, is_training: false); | |||||
| return Apply(data, training: false); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -25,7 +25,7 @@ namespace Tensorflow.Keras.Engine | |||||
| { | { | ||||
| (x, y) = data_handler.DataAdapter.Expand1d(x, y); | (x, y) = data_handler.DataAdapter.Expand1d(x, y); | ||||
| using var tape = tf.GradientTape(); | using var tape = tf.GradientTape(); | ||||
| var y_pred = Apply(x, is_training: true); | |||||
| var y_pred = Apply(x, training: true); | |||||
| var loss = compiled_loss.Call(y, y_pred); | var loss = compiled_loss.Call(y, y_pred); | ||||
| // For custom training steps, users can just write: | // For custom training steps, users can just write: | ||||
| @@ -8,9 +8,10 @@ using Tensorflow.Keras.Saving; | |||||
| namespace Tensorflow.Keras.Engine | namespace Tensorflow.Keras.Engine | ||||
| { | { | ||||
| public partial class Model | |||||
| public partial class Model | |||||
| { | { | ||||
| public List<(IVariableV1, NDArray)> load_weights(string filepath, bool by_name = false, bool skip_mismatch = false, object options = null) | |||||
| List<(IVariableV1, NDArray)> LoadedWeights; | |||||
| public void load_weights(string filepath, bool by_name = false, bool skip_mismatch = false, object options = null) | |||||
| { | { | ||||
| long fileId = Hdf5.OpenFile(filepath, true); | long fileId = Hdf5.OpenFile(filepath, true); | ||||
| @@ -25,10 +26,8 @@ namespace Tensorflow.Keras.Engine | |||||
| throw new NotImplementedException(""); | throw new NotImplementedException(""); | ||||
| else | else | ||||
| { | { | ||||
| var weights = hdf5_format.load_weights_from_hdf5_group(fileId, Layers); | |||||
| LoadedWeights = hdf5_format.load_weights_from_hdf5_group(fileId, Layers); | |||||
| Hdf5.CloseFile(fileId); | Hdf5.CloseFile(fileId); | ||||
| // return a reference to prevent GC collect Variable. | |||||
| return weights; | |||||
| } | } | ||||
| } | } | ||||
| @@ -19,7 +19,7 @@ namespace Tensorflow.Keras.Layers | |||||
| this.args = args; | this.args = args; | ||||
| } | } | ||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool is_training = false) | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | { | ||||
| return tf.nn.leaky_relu(inputs, alpha: alpha); | return tf.nn.leaky_relu(inputs, alpha: alpha); | ||||
| } | } | ||||
| @@ -0,0 +1,150 @@ | |||||
| /***************************************************************************** | |||||
| 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 System; | |||||
| using static Tensorflow.Binding; | |||||
| using Tensorflow.Keras.ArgsDefinition; | |||||
| using Tensorflow.Keras.Utils; | |||||
| using static Tensorflow.KerasApi; | |||||
| namespace Tensorflow.Keras.Layers | |||||
| { | |||||
| public class Conv2DTranspose : Conv2D | |||||
| { | |||||
| public Conv2DTranspose(Conv2DArgs args) : base(args) | |||||
| { | |||||
| } | |||||
| protected override void build(Tensors inputs) | |||||
| { | |||||
| var input_shape = inputs.shape; | |||||
| if (len(input_shape) != 4) | |||||
| throw new ValueError($"Inputs should have rank 4. Received input shape: {input_shape}"); | |||||
| var channel_axis = _get_channel_axis(); | |||||
| var input_dim = input_shape[-1]; | |||||
| var kernel_shape = new TensorShape(kernel_size[0], kernel_size[1], filters, input_dim); | |||||
| kernel = add_weight(name: "kernel", | |||||
| shape: kernel_shape, | |||||
| initializer: kernel_initializer, | |||||
| regularizer: kernel_regularizer, | |||||
| trainable: true, | |||||
| dtype: inputs.dtype); | |||||
| if (use_bias) | |||||
| bias = add_weight(name: "bias", | |||||
| shape: filters, | |||||
| initializer: bias_initializer, | |||||
| trainable: true, | |||||
| dtype: inputs.dtype); | |||||
| built = true; | |||||
| } | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | |||||
| var inputs_shape = array_ops.shape(inputs); | |||||
| var batch_size = inputs_shape[0]; | |||||
| var (h_axis, w_axis) = (1, 2); | |||||
| if (data_format == "channels_first") | |||||
| (h_axis, w_axis) = (2, 3); | |||||
| var (height, width) = (-1, -1); | |||||
| if(inputs.shape.rank > -1) | |||||
| { | |||||
| var dims = inputs.shape.dims; | |||||
| (height, width) = (dims[h_axis], dims[w_axis]); | |||||
| } | |||||
| var (kernel_h, kernel_w) = kernel_size; | |||||
| var (stride_h, stride_w) = strides; | |||||
| var (out_pad_h, out_pad_w) = (-1, -1); | |||||
| // Infer the dynamic output shape: | |||||
| var out_height = conv_utils.deconv_output_length(height, | |||||
| kernel_h, | |||||
| padding: padding, | |||||
| output_padding: out_pad_h, | |||||
| stride: stride_h, | |||||
| dilation: dilation_rate[0]); | |||||
| var out_width = conv_utils.deconv_output_length(width, | |||||
| kernel_w, | |||||
| padding: padding, | |||||
| output_padding: out_pad_w, | |||||
| stride: stride_w, | |||||
| dilation: dilation_rate[1]); | |||||
| Tensor output_shape_tensor; | |||||
| if (data_format == "channels_first") | |||||
| output_shape_tensor = array_ops.stack(new object[] { batch_size, filters, out_height, out_width }); | |||||
| else | |||||
| output_shape_tensor = array_ops.stack(new object[] { batch_size, out_height, out_width, filters }); | |||||
| var outputs = keras.backend.conv2d_transpose( | |||||
| inputs, | |||||
| kernel, | |||||
| output_shape_tensor, | |||||
| strides: strides, | |||||
| padding: padding, | |||||
| data_format: data_format, | |||||
| dilation_rate: dilation_rate); | |||||
| if (!tf.Context.executing_eagerly()) | |||||
| { | |||||
| var out_shape = ComputeOutputShape(inputs.shape); | |||||
| outputs.set_shape(out_shape); | |||||
| } | |||||
| if (use_bias) | |||||
| throw new NotImplementedException(""); | |||||
| if (activation != null) | |||||
| return activation(outputs); | |||||
| return outputs; | |||||
| } | |||||
| public override TensorShape ComputeOutputShape(TensorShape input_shape) | |||||
| { | |||||
| var output_shape = input_shape.dims; | |||||
| var (c_axis, h_axis, w_axis) = (3, 1, 2); | |||||
| if (data_format == "channels_first") | |||||
| (c_axis, h_axis, w_axis) = (1, 2, 3); | |||||
| var (kernel_h, kernel_w) = kernel_size; | |||||
| var (stride_h, stride_w) = strides; | |||||
| var (out_pad_h, out_pad_w) = (-1, -1); | |||||
| output_shape[c_axis] = filters; | |||||
| output_shape[h_axis] = conv_utils.deconv_output_length( | |||||
| output_shape[h_axis], | |||||
| kernel_h, | |||||
| padding: padding, | |||||
| output_padding: out_pad_h, | |||||
| stride: stride_h, | |||||
| dilation: dilation_rate[0]); | |||||
| output_shape[w_axis] = conv_utils.deconv_output_length( | |||||
| output_shape[w_axis], | |||||
| kernel_w, | |||||
| padding: padding, | |||||
| output_padding: out_pad_w, | |||||
| stride: stride_w, | |||||
| dilation: dilation_rate[1]); | |||||
| return new TensorShape(output_shape); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -99,7 +99,7 @@ namespace Tensorflow.Keras.Layers | |||||
| built = true; | built = true; | ||||
| } | } | ||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool training = false) | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = false) | |||||
| { | { | ||||
| var outputs = _convolution_op.Apply(inputs, kernel); | var outputs = _convolution_op.Apply(inputs, kernel); | ||||
| if (use_bias) | if (use_bias) | ||||
| @@ -119,5 +119,8 @@ namespace Tensorflow.Keras.Layers | |||||
| return outputs; | return outputs; | ||||
| } | } | ||||
| protected virtual int _get_channel_axis() | |||||
| => data_format == "channels_first" ? -1 - rank : -1; | |||||
| } | } | ||||
| } | } | ||||
| @@ -65,7 +65,7 @@ namespace Tensorflow.Keras.Layers | |||||
| built = true; | built = true; | ||||
| } | } | ||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool training = false) | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | { | ||||
| Tensor outputs = null; | Tensor outputs = null; | ||||
| var rank = inputs.rank; | var rank = inputs.rank; | ||||
| @@ -62,7 +62,7 @@ namespace Tensorflow.Keras.Layers | |||||
| built = true; | built = true; | ||||
| } | } | ||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool is_training = false) | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | { | ||||
| var dtype = inputs.dtype; | var dtype = inputs.dtype; | ||||
| if (dtype != tf.int32 && dtype != tf.int64) | if (dtype != tf.int32 && dtype != tf.int64) | ||||
| @@ -26,9 +26,9 @@ namespace Tensorflow.Keras.Layers | |||||
| .ToArray(); | .ToArray(); | ||||
| } | } | ||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool is_training = false) | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | { | ||||
| return base.Call(inputs, state: state, is_training: is_training); | |||||
| return base.Call(inputs, state: state, training: training); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -140,6 +140,51 @@ namespace Tensorflow.Keras.Layers | |||||
| Activation = GetActivationByName(activation) | Activation = GetActivationByName(activation) | ||||
| }); | }); | ||||
| /// <summary> | |||||
| /// Transposed convolution layer (sometimes called Deconvolution). | |||||
| /// </summary> | |||||
| /// <param name="filters"></param> | |||||
| /// <param name="kernel_size"></param> | |||||
| /// <param name="strides"></param> | |||||
| /// <param name="padding"></param> | |||||
| /// <param name="data_format"></param> | |||||
| /// <param name="dilation_rate"></param> | |||||
| /// <param name="activation"></param> | |||||
| /// <param name="use_bias"></param> | |||||
| /// <param name="kernel_initializer"></param> | |||||
| /// <param name="bias_initializer"></param> | |||||
| /// <param name="kernel_regularizer"></param> | |||||
| /// <param name="bias_regularizer"></param> | |||||
| /// <param name="activity_regularizer"></param> | |||||
| /// <returns></returns> | |||||
| public Conv2DTranspose Conv2DTranspose(int filters, | |||||
| TensorShape kernel_size = null, | |||||
| TensorShape strides = null, | |||||
| string padding = "valid", | |||||
| string data_format = null, | |||||
| TensorShape dilation_rate = null, | |||||
| string activation = null, | |||||
| bool use_bias = true, | |||||
| string kernel_initializer = null, | |||||
| string bias_initializer = null, | |||||
| string kernel_regularizer = null, | |||||
| string bias_regularizer = null, | |||||
| string activity_regularizer = null) | |||||
| => new Conv2DTranspose(new Conv2DArgs | |||||
| { | |||||
| Rank = 2, | |||||
| Filters = filters, | |||||
| KernelSize = kernel_size, | |||||
| Strides = strides == null ? (1, 1) : strides, | |||||
| Padding = padding, | |||||
| DataFormat = data_format, | |||||
| DilationRate = dilation_rate == null ? (1, 1) : dilation_rate, | |||||
| UseBias = use_bias, | |||||
| KernelInitializer = GetInitializerByName(kernel_initializer), | |||||
| BiasInitializer = GetInitializerByName(bias_initializer), | |||||
| Activation = GetActivationByName(activation) | |||||
| }); | |||||
| public Dense Dense(int units, | public Dense Dense(int units, | ||||
| Activation activation = null, | Activation activation = null, | ||||
| IInitializer kernel_initializer = null, | IInitializer kernel_initializer = null, | ||||
| @@ -19,7 +19,7 @@ namespace Tensorflow.Keras.Layers | |||||
| // output_shape = input_shape.dims[1^]; | // output_shape = input_shape.dims[1^]; | ||||
| } | } | ||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool is_training = false) | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | { | ||||
| return _merge_function(inputs); | return _merge_function(inputs); | ||||
| } | } | ||||
| @@ -36,6 +36,7 @@ namespace Tensorflow.Keras.Layers | |||||
| bool fused; | bool fused; | ||||
| int[] axis; | int[] axis; | ||||
| string _data_format; | string _data_format; | ||||
| TensorShape kernel_size; | |||||
| IInitializer beta_initializer => args.BetaInitializer; | IInitializer beta_initializer => args.BetaInitializer; | ||||
| IInitializer gamma_initializer => args.GammaInitializer; | IInitializer gamma_initializer => args.GammaInitializer; | ||||
| IInitializer moving_mean_initializer => args.MovingMeanInitializer; | IInitializer moving_mean_initializer => args.MovingMeanInitializer; | ||||
| @@ -120,10 +121,35 @@ namespace Tensorflow.Keras.Layers | |||||
| built = true; | built = true; | ||||
| } | } | ||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool training = false) | |||||
| public override TensorShape ComputeOutputShape(TensorShape input_shape) | |||||
| { | |||||
| return input_shape; | |||||
| } | |||||
| (Tensor, Tensor) _moments(Tensors inputs, int[] reduction_axes, bool keep_dims) | |||||
| { | |||||
| var (mean, variance) = _calculate_mean_and_var(inputs, reduction_axes, keep_dims); | |||||
| if (_support_zero_size_input()) | |||||
| throw new NotImplementedException(""); | |||||
| return (mean, variance); | |||||
| } | |||||
| (Tensor, Tensor) _calculate_mean_and_var(Tensors inputs, int[] reduction_axes, bool keep_dims) | |||||
| { | |||||
| return nn_impl.moments(inputs, reduction_axes, keep_dims: keep_dims); | |||||
| } | |||||
| bool _support_zero_size_input() | |||||
| { | |||||
| return false; | |||||
| } | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | { | ||||
| Tensor outputs = null; | Tensor outputs = null; | ||||
| var training_tensor = tf.logical_and(training, Trainable); | |||||
| var training_tensor = training == null | |||||
| ? tf.placeholder(tf.@bool, TensorShape.Scalar) | |||||
| : tf.logical_and(training.Value, Trainable); | |||||
| if (fused) | if (fused) | ||||
| { | { | ||||
| // var training = tf.convert_to_tensor(training); | // var training = tf.convert_to_tensor(training); | ||||
| @@ -131,7 +157,49 @@ namespace Tensorflow.Keras.Layers | |||||
| return outputs; | return outputs; | ||||
| } | } | ||||
| throw new NotImplementedException("BatchNormalization call"); | |||||
| var inputs_dtype = inputs.dtype.as_base_dtype(); | |||||
| var input_shape = inputs.shape; | |||||
| var ndims = len(input_shape); | |||||
| var reduction_axes = range(ndims).Where(x => !axis.Contains(x)).ToArray(); | |||||
| // Broadcasting only necessary for single-axis batch norm where the axis is | |||||
| // not the last dimension | |||||
| var broadcast_shape = range(ndims).Select(x => 1).ToArray(); | |||||
| broadcast_shape[axis[0]] = input_shape.dims[axis[0]]; | |||||
| var (scale, offset) = (gamma, beta); | |||||
| var training_value = tf_utils.constant_value(training_tensor); | |||||
| Tensor mean; | |||||
| Tensor variance; | |||||
| if (training_value.HasValue && training_value.Value == false) | |||||
| { | |||||
| (mean, variance) = (moving_mean.AsTensor(), moving_variance.AsTensor()); | |||||
| } | |||||
| else | |||||
| { | |||||
| var keep_dims = len(axis) > 1; | |||||
| (mean, variance) = _moments(inputs, reduction_axes, keep_dims: keep_dims); | |||||
| mean = tf_utils.smart_cond(training_tensor, | |||||
| () => new[] { mean }, | |||||
| () => new[] { ops.convert_to_tensor(moving_mean) }).FirstOrDefault(); | |||||
| variance = tf_utils.smart_cond(training_tensor, | |||||
| () => new[] { variance }, | |||||
| () => new[] { ops.convert_to_tensor(moving_variance) }).FirstOrDefault(); | |||||
| var (new_mean, new_variance) = (mean, variance); | |||||
| } | |||||
| mean = math_ops.cast(mean, inputs.dtype); | |||||
| variance = math_ops.cast(variance, inputs.dtype); | |||||
| var offset_tensor = math_ops.cast(offset, inputs.dtype); | |||||
| var scale_tensor = math_ops.cast(scale, inputs.dtype); | |||||
| outputs = nn_impl.batch_normalization(inputs, mean, variance, | |||||
| offset_tensor, scale_tensor, epsilon); | |||||
| // If some components of the shape got lost due to adjustments, fix that. | |||||
| outputs.set_shape(input_shape); | |||||
| return outputs; | |||||
| } | } | ||||
| private Tensor _fused_batch_norm(Tensor inputs, Tensor training) | private Tensor _fused_batch_norm(Tensor inputs, Tensor training) | ||||
| @@ -12,7 +12,7 @@ namespace Tensorflow.Keras.Layers | |||||
| { | { | ||||
| } | } | ||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool is_training = false) | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | { | ||||
| if (data_format == "channels_last") | if (data_format == "channels_last") | ||||
| return math_ops.reduce_mean(inputs, new int[] { 1, 2 }, false); | return math_ops.reduce_mean(inputs, new int[] { 1, 2 }, false); | ||||
| @@ -36,7 +36,7 @@ namespace Tensorflow.Keras.Layers | |||||
| input_spec = new InputSpec(ndim: 4); | input_spec = new InputSpec(ndim: 4); | ||||
| } | } | ||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool is_training = false) | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | { | ||||
| int[] pool_shape; | int[] pool_shape; | ||||
| int[] strides; | int[] strides; | ||||
| @@ -15,9 +15,12 @@ namespace Tensorflow.Keras.Layers | |||||
| this.args = args; | this.args = args; | ||||
| } | } | ||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool is_training = false) | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | { | ||||
| var output = tf_utils.smart_cond(is_training, | |||||
| if (training == null) | |||||
| training = false; | |||||
| var output = tf_utils.smart_cond(training.Value, | |||||
| () => tf.nn.dropout(inputs, | () => tf.nn.dropout(inputs, | ||||
| noise_shape: get_noise_shape(inputs), | noise_shape: get_noise_shape(inputs), | ||||
| seed: args.Seed, | seed: args.Seed, | ||||
| @@ -17,7 +17,7 @@ namespace Tensorflow.Keras.Layers | |||||
| this.args = args; | this.args = args; | ||||
| } | } | ||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool is_training = false) | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | { | ||||
| scale = math_ops.cast(args.Scale, args.DType); | scale = math_ops.cast(args.Scale, args.DType); | ||||
| offset = math_ops.cast(args.Offset, args.DType); | offset = math_ops.cast(args.Offset, args.DType); | ||||
| @@ -22,7 +22,7 @@ namespace Tensorflow.Keras.Layers | |||||
| _channels_first = args.DataFormat == "channels_first"; | _channels_first = args.DataFormat == "channels_first"; | ||||
| } | } | ||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool is_training = false) | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | { | ||||
| if (_channels_first) | if (_channels_first) | ||||
| { | { | ||||
| @@ -19,7 +19,7 @@ namespace Tensorflow.Keras.Layers | |||||
| this.args = args; | this.args = args; | ||||
| } | } | ||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool is_training = false) | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | { | ||||
| var shapes = new List<object>(); | var shapes = new List<object>(); | ||||
| shapes.Add(array_ops.shape(inputs)[0]); | shapes.Add(array_ops.shape(inputs)[0]); | ||||
| @@ -24,7 +24,7 @@ namespace Tensorflow.Keras.Layers | |||||
| inputSpec = new InputSpec(ndim: 4); | inputSpec = new InputSpec(ndim: 4); | ||||
| } | } | ||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool is_training = false) | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | { | ||||
| return keras.backend.resize_images(inputs, | return keras.backend.resize_images(inputs, | ||||
| size[0], size[1], | size[0], size[1], | ||||
| @@ -26,7 +26,7 @@ namespace Tensorflow.Keras.Layers | |||||
| this.input_spec = new InputSpec(ndim: 4); | this.input_spec = new InputSpec(ndim: 4); | ||||
| } | } | ||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool is_training = false) | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | { | ||||
| return keras.backend.spatial_2d_padding(inputs, | return keras.backend.spatial_2d_padding(inputs, | ||||
| padding: padding, | padding: padding, | ||||
| @@ -32,7 +32,7 @@ namespace Tensorflow.Keras.Layers | |||||
| built = true; | built = true; | ||||
| } | } | ||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool is_training = false) | |||||
| protected override Tensors Call(Tensors inputs, Tensor state = null, bool? training = null) | |||||
| { | { | ||||
| if (tf.Context.executing_eagerly()) | if (tf.Context.executing_eagerly()) | ||||
| return _defun_call(inputs); | return _defun_call(inputs); | ||||
| @@ -55,10 +55,6 @@ Keras is an API designed for human beings, not machines. Keras follows best prac | |||||
| <PackageReference Include="SharpZipLib" Version="1.3.1" /> | <PackageReference Include="SharpZipLib" Version="1.3.1" /> | ||||
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup> | |||||
| <ProjectReference Include="..\TensorFlowNET.Core\Tensorflow.Binding.csproj" /> | |||||
| </ItemGroup> | |||||
| <ItemGroup> | <ItemGroup> | ||||
| <None Include="..\..\LICENSE"> | <None Include="..\..\LICENSE"> | ||||
| <Pack>True</Pack> | <Pack>True</Pack> | ||||
| @@ -70,4 +66,8 @@ Keras is an API designed for human beings, not machines. Keras follows best prac | |||||
| <Folder Include="Engine\Interfaces\" /> | <Folder Include="Engine\Interfaces\" /> | ||||
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup> | |||||
| <ProjectReference Include="..\TensorFlowNET.Core\Tensorflow.Binding.csproj" /> | |||||
| </ItemGroup> | |||||
| </Project> | </Project> | ||||
| @@ -14,7 +14,9 @@ | |||||
| limitations under the License. | limitations under the License. | ||||
| ******************************************************************************/ | ******************************************************************************/ | ||||
| using System; | |||||
| using System.Linq; | using System.Linq; | ||||
| using static Tensorflow.Binding; | |||||
| namespace Tensorflow.Keras.Utils | namespace Tensorflow.Keras.Utils | ||||
| { | { | ||||
| @@ -63,5 +65,33 @@ namespace Tensorflow.Keras.Utils | |||||
| return ImageDataFormat.channels_last.ToString(); | return ImageDataFormat.channels_last.ToString(); | ||||
| return value.ToLower(); | return value.ToLower(); | ||||
| } | } | ||||
| public static int deconv_output_length(int input_length, | |||||
| int filter_size, | |||||
| string padding, | |||||
| int output_padding = -1, | |||||
| int stride = 0, | |||||
| int dilation = 1) | |||||
| { | |||||
| // Get the dilated kernel size | |||||
| filter_size = filter_size + (filter_size - 1) * (dilation - 1); | |||||
| // Infer length if output padding is None, else compute the exact length | |||||
| int length = -1; | |||||
| if (output_padding == -1) | |||||
| { | |||||
| if (padding == "valid") | |||||
| length = input_length * stride + max(filter_size - stride, 0); | |||||
| else if (padding == "full") | |||||
| length = input_length * stride - (stride + filter_size - 2); | |||||
| else if (padding == "same") | |||||
| length = input_length * stride; | |||||
| } | |||||
| else | |||||
| { | |||||
| throw new NotImplementedException(""); | |||||
| } | |||||
| return length; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -37,6 +37,17 @@ namespace Tensorflow.Keras.Utils | |||||
| return true; | return true; | ||||
| } | } | ||||
| public static Tensor[] smart_cond<T>(IVariableV1 pred, | |||||
| Func<T[]> true_fn = null, | |||||
| Func<T[]> false_fn = null, | |||||
| string name = null) | |||||
| { | |||||
| return control_flow_ops.cond(pred.AsTensor(), | |||||
| true_fn: true_fn, | |||||
| false_fn: false_fn, | |||||
| name: name); | |||||
| } | |||||
| public static Tensor[] smart_cond<T>(Tensor pred, | public static Tensor[] smart_cond<T>(Tensor pred, | ||||
| Func<T[]> true_fn = null, | Func<T[]> true_fn = null, | ||||
| Func<T[]> false_fn = null, | Func<T[]> false_fn = null, | ||||