| @@ -80,6 +80,11 @@ namespace Tensorflow.Eager | |||||
| Tensor[] op_outputs) | Tensor[] op_outputs) | ||||
| => (out_grads, unneeded_gradients) => | => (out_grads, unneeded_gradients) => | ||||
| { | { | ||||
| if(!ops.gradientFunctions.ContainsKey(op_name)) | |||||
| { | |||||
| throw new Exception($"gradientFunctions not find op_name: {op_name}"); | |||||
| } | |||||
| if (ops.gradientFunctions[op_name] == null) | if (ops.gradientFunctions[op_name] == null) | ||||
| return new Tensor[op_inputs.Length]; | return new Tensor[op_inputs.Length]; | ||||
| @@ -229,6 +229,37 @@ namespace Tensorflow.Gradients | |||||
| }; | }; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Gradient function for Conv2D. | |||||
| /// </summary> | |||||
| /// <param name="op"></param> | |||||
| /// <param name="grads"></param> | |||||
| /// <returns></returns> | |||||
| [RegisterGradient("DepthwiseConv2dNative")] | |||||
| public static Tensor[] _DepthwiseConv2DGrad(Operation op, Tensor[] grads) | |||||
| { | |||||
| var dilations = op.get_attr_list<int>("dilations"); | |||||
| var strides = op.get_attr_list<int>("strides"); | |||||
| var padding = op.get_attr<string>("padding"); | |||||
| var explicit_paddings = op.get_attr_list<int>("explicit_paddings"); | |||||
| var data_format = op.get_attr<string>("data_format"); | |||||
| var shape = gen_array_ops.shape_n(new Tensor[] { op.inputs[0], op.inputs[1] }); | |||||
| return new Tensor[] | |||||
| { | |||||
| gen_nn_ops.depthwise_conv2d_native_backprop_input( | |||||
| shape[0], op.inputs[1], grads[0], | |||||
| strides, padding, explicit_paddings, | |||||
| dilations: dilations, | |||||
| data_format: data_format), | |||||
| gen_nn_ops.depthwise_conv2d_native_backprop_filter(op.inputs[0], shape[1], grads[0], | |||||
| strides, padding, | |||||
| dilations: dilations, | |||||
| explicit_paddings: explicit_paddings, | |||||
| data_format: data_format) | |||||
| }; | |||||
| } | |||||
| [RegisterGradient("FusedBatchNorm")] | [RegisterGradient("FusedBatchNorm")] | ||||
| public static Tensor[] _FusedBatchNormGrad(Operation op, Tensor[] grads) | public static Tensor[] _FusedBatchNormGrad(Operation op, Tensor[] grads) | ||||
| => _BaseFusedBatchNormGrad(op, 0, grads); | => _BaseFusedBatchNormGrad(op, 0, grads); | ||||
| @@ -95,6 +95,19 @@ namespace Tensorflow.Keras.Layers | |||||
| bool use_bias = true, | bool use_bias = true, | ||||
| string kernel_initializer = "glorot_uniform", | string kernel_initializer = "glorot_uniform", | ||||
| string bias_initializer = "zeros"); | string bias_initializer = "zeros"); | ||||
| public ILayer DepthwiseConv2D(Shape kernel_size = null, | |||||
| Shape strides = null, | |||||
| string padding = "valid", | |||||
| string data_format = null, | |||||
| Shape dilation_rate = null, | |||||
| int groups = 1, | |||||
| int depth_multiplier = 1, | |||||
| string activation = null, | |||||
| bool use_bias = false, | |||||
| string kernel_initializer = "glorot_uniform", | |||||
| string bias_initializer = "zeros", | |||||
| string depthwise_initializer = "glorot_uniform" | |||||
| ); | |||||
| public ILayer Dense(int units); | public ILayer Dense(int units); | ||||
| public ILayer Dense(int units, | public ILayer Dense(int units, | ||||
| @@ -249,6 +249,9 @@ namespace Tensorflow | |||||
| case sbyte val: | case sbyte val: | ||||
| tensor_proto.IntVal.AddRange(new[] { (int)val }); | tensor_proto.IntVal.AddRange(new[] { (int)val }); | ||||
| break; | break; | ||||
| case byte val: | |||||
| tensor_proto.IntVal.AddRange(new[] { (int)val }); | |||||
| break; | |||||
| case int val: | case int val: | ||||
| tensor_proto.IntVal.AddRange(new[] { val }); | tensor_proto.IntVal.AddRange(new[] { val }); | ||||
| break; | break; | ||||
| @@ -262,7 +265,7 @@ namespace Tensorflow | |||||
| tensor_proto.DoubleVal.AddRange(new[] { val }); | tensor_proto.DoubleVal.AddRange(new[] { val }); | ||||
| break; | break; | ||||
| default: | default: | ||||
| throw new Exception("make_tensor_proto Not Implemented"); | |||||
| throw new Exception($"make_tensor_proto Not Implemented {values.GetType().Name}"); | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,167 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Text; | |||||
| using System; | |||||
| using Tensorflow.Keras.ArgsDefinition; | |||||
| using Tensorflow.Keras.Saving; | |||||
| using Tensorflow.Common.Types; | |||||
| using Tensorflow.Keras.Utils; | |||||
| using Tensorflow.Operations; | |||||
| using Newtonsoft.Json; | |||||
| using System.Security.Cryptography; | |||||
| namespace Tensorflow.Keras.Layers | |||||
| { | |||||
| public class DepthwiseConv2DArgs: Conv2DArgs | |||||
| { | |||||
| /// <summary> | |||||
| /// depth_multiplier: The number of depthwise convolution output channels for | |||||
| /// each input channel.The total number of depthwise convolution output | |||||
| /// channels will be equal to `filters_in* depth_multiplier`. | |||||
| /// </summary> | |||||
| [JsonProperty("depth_multiplier")] | |||||
| public int DepthMultiplier { get; set; } = 1; | |||||
| [JsonProperty("depthwise_initializer")] | |||||
| public IInitializer DepthwiseInitializer { get; set; } | |||||
| } | |||||
| public class DepthwiseConv2D : Conv2D | |||||
| { | |||||
| /// <summary> | |||||
| /// depth_multiplier: The number of depthwise convolution output channels for | |||||
| /// each input channel.The total number of depthwise convolution output | |||||
| /// channels will be equal to `filters_in* depth_multiplier`. | |||||
| /// </summary> | |||||
| int DepthMultiplier = 1; | |||||
| IInitializer DepthwiseInitializer; | |||||
| int[] strides; | |||||
| int[] dilation_rate; | |||||
| string getDataFormat() | |||||
| { | |||||
| return data_format == "channels_first" ? "NCHW" : "NHWC"; | |||||
| } | |||||
| static int _id = 1; | |||||
| public DepthwiseConv2D(DepthwiseConv2DArgs args):base(args) | |||||
| { | |||||
| args.Padding = args.Padding.ToUpper(); | |||||
| if(string.IsNullOrEmpty(args.Name)) | |||||
| name = "DepthwiseConv2D_" + _id; | |||||
| this.DepthMultiplier = args.DepthMultiplier; | |||||
| this.DepthwiseInitializer = args.DepthwiseInitializer; | |||||
| } | |||||
| public override void build(KerasShapesWrapper input_shape) | |||||
| { | |||||
| //base.build(input_shape); | |||||
| var shape = input_shape.ToSingleShape(); | |||||
| int channel_axis = data_format == "channels_first" ? 1 : -1; | |||||
| var input_channel = channel_axis < 0 ? | |||||
| shape.dims[shape.ndim + channel_axis] : | |||||
| shape.dims[channel_axis]; | |||||
| var arg = args as DepthwiseConv2DArgs; | |||||
| if (arg.Strides.ndim != shape.ndim) | |||||
| { | |||||
| if (arg.Strides.ndim == 2) | |||||
| { | |||||
| this.strides = new int[] { 1, (int)arg.Strides[0], (int)arg.Strides[1], 1 }; | |||||
| } | |||||
| else | |||||
| { | |||||
| this.strides = conv_utils.normalize_tuple(new int[] { (int)arg.Strides[0] }, shape.ndim, "strides"); | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| this.strides = arg.Strides.dims.Select(o=>(int)(o)).ToArray(); | |||||
| } | |||||
| if (arg.DilationRate.ndim != shape.ndim) | |||||
| { | |||||
| this.dilation_rate = conv_utils.normalize_tuple(new int[] { (int)arg.DilationRate[0] }, shape.ndim, "dilation_rate"); | |||||
| } | |||||
| long channel_data = data_format == "channels_first" ? shape[0] : shape[shape.Length - 1]; | |||||
| var depthwise_kernel_shape = this.kernel_size.dims.concat(new long[] { | |||||
| channel_data, | |||||
| this.DepthMultiplier | |||||
| }); | |||||
| this.kernel = this.add_weight( | |||||
| shape: depthwise_kernel_shape, | |||||
| initializer: this.DepthwiseInitializer != null ? this.DepthwiseInitializer : this.kernel_initializer, | |||||
| name: "depthwise_kernel", | |||||
| trainable: true, | |||||
| dtype: DType, | |||||
| regularizer: this.kernel_regularizer | |||||
| ); | |||||
| var axes = new Dictionary<int, int>(); | |||||
| axes.Add(-1, (int)input_channel); | |||||
| inputSpec = new InputSpec(min_ndim: rank + 2, axes: axes); | |||||
| if (use_bias) | |||||
| { | |||||
| bias = add_weight(name: "bias", | |||||
| shape: ((int)channel_data), | |||||
| initializer: bias_initializer, | |||||
| trainable: true, | |||||
| dtype: DType); | |||||
| } | |||||
| built = true; | |||||
| _buildInputShape = input_shape; | |||||
| } | |||||
| protected override Tensors Call(Tensors inputs, Tensors state = null, | |||||
| bool? training = false, IOptionalArgs? optional_args = null) | |||||
| { | |||||
| Tensor outputs = null; | |||||
| outputs = gen_nn_ops.depthwise_conv2d_native( | |||||
| inputs, | |||||
| filter: this.kernel.AsTensor(), | |||||
| strides: this.strides, | |||||
| padding: this.padding, | |||||
| dilations: this.dilation_rate, | |||||
| data_format: this.getDataFormat(), | |||||
| name: name | |||||
| ); | |||||
| if (use_bias) | |||||
| { | |||||
| if (data_format == "channels_first") | |||||
| { | |||||
| throw new NotImplementedException("call channels_first"); | |||||
| } | |||||
| else | |||||
| { | |||||
| outputs = gen_nn_ops.bias_add(outputs, ops.convert_to_tensor(bias), | |||||
| data_format: this.getDataFormat(), name: name); | |||||
| } | |||||
| } | |||||
| if (activation != null) | |||||
| outputs = activation.Apply(outputs); | |||||
| return outputs; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -210,6 +210,38 @@ namespace Tensorflow.Keras.Layers | |||||
| Activation = keras.activations.GetActivationFromName(activation) | Activation = keras.activations.GetActivationFromName(activation) | ||||
| }); | }); | ||||
| public ILayer DepthwiseConv2D(Shape kernel_size = null, | |||||
| Shape strides = null, | |||||
| string padding = "valid", | |||||
| string data_format = null, | |||||
| Shape dilation_rate = null, | |||||
| int groups = 1, | |||||
| int depth_multiplier = 1, | |||||
| string activation = null, | |||||
| bool use_bias = false, | |||||
| string kernel_initializer = "glorot_uniform", | |||||
| string bias_initializer = "zeros", | |||||
| string depthwise_initializer = "glorot_uniform" | |||||
| ) | |||||
| => new DepthwiseConv2D(new DepthwiseConv2DArgs | |||||
| { | |||||
| Rank = 2, | |||||
| Filters = 1, | |||||
| KernelSize = (kernel_size == null) ? (5, 5) : kernel_size, | |||||
| Strides = strides == null ? (1) : strides, | |||||
| Padding = padding, | |||||
| DepthMultiplier = depth_multiplier, | |||||
| DataFormat = data_format, | |||||
| DilationRate = dilation_rate == null ? (1) : dilation_rate, | |||||
| Groups = groups, | |||||
| UseBias = use_bias, | |||||
| KernelInitializer = GetInitializerByName(kernel_initializer), | |||||
| DepthwiseInitializer = GetInitializerByName(depthwise_initializer == null ? kernel_initializer : depthwise_initializer), | |||||
| BiasInitializer = GetInitializerByName(bias_initializer), | |||||
| Activation = keras.activations.GetActivationFromName(activation), | |||||
| }); | |||||
| /// <summary> | /// <summary> | ||||
| /// Transposed convolution layer (sometimes called Deconvolution). | /// Transposed convolution layer (sometimes called Deconvolution). | ||||
| /// </summary> | /// </summary> | ||||
| @@ -33,6 +33,40 @@ namespace Tensorflow.Keras.UnitTest | |||||
| return ret; | return ret; | ||||
| } | } | ||||
| public void AssertArray(int[] f1, int[] f2) | |||||
| { | |||||
| bool ret = false; | |||||
| for (var i = 0; i < f1.Length; i++) | |||||
| { | |||||
| ret = f1[i] == f2[i]; | |||||
| if (!ret) | |||||
| break; | |||||
| } | |||||
| if (!ret) | |||||
| { | |||||
| Assert.Fail($"Array not Equal:[{string.Join(",", f1)}] [{string.Join(",", f2)}]"); | |||||
| } | |||||
| } | |||||
| public void AssertArray(float[] f1, float[] f2) | |||||
| { | |||||
| bool ret = false; | |||||
| var tolerance = .00001f; | |||||
| for (var i = 0; i < f1.Length; i++) | |||||
| { | |||||
| ret = Math.Abs(f1[i] - f2[i]) <= tolerance; | |||||
| if (!ret) | |||||
| break; | |||||
| } | |||||
| if (!ret) | |||||
| { | |||||
| Assert.Fail($"Array float not Equal:[{string.Join(",", f1)}] [{string.Join(",", f2)}]"); | |||||
| } | |||||
| } | |||||
| public bool Equal(double[] d1, double[] d2) | public bool Equal(double[] d1, double[] d2) | ||||
| { | { | ||||
| bool ret = false; | bool ret = false; | ||||
| @@ -1,6 +1,8 @@ | |||||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | using Microsoft.VisualStudio.TestTools.UnitTesting; | ||||
| using System.Linq; | |||||
| using Tensorflow.NumPy; | using Tensorflow.NumPy; | ||||
| using static Tensorflow.KerasApi; | using static Tensorflow.KerasApi; | ||||
| using static Tensorflow.Binding; | |||||
| namespace Tensorflow.Keras.UnitTest.Layers | namespace Tensorflow.Keras.UnitTest.Layers | ||||
| { | { | ||||
| @@ -193,5 +195,128 @@ namespace Tensorflow.Keras.UnitTest.Layers | |||||
| Assert.AreEqual(x.dims[2], y.shape[2]); | Assert.AreEqual(x.dims[2], y.shape[2]); | ||||
| Assert.AreEqual(filters, y.shape[3]); | Assert.AreEqual(filters, y.shape[3]); | ||||
| } | } | ||||
| [TestMethod] | |||||
| public void BasicDepthwiseConv2D() | |||||
| { | |||||
| var conv = keras.layers.DepthwiseConv2D(kernel_size:3, strides:1, activation: null, | |||||
| padding:"same", depthwise_initializer: "ones"); | |||||
| var x = np.arange(2 * 9* 9* 3).reshape((2, 9, 9, 3)); | |||||
| var x2 = ops.convert_to_tensor(x, TF_DataType.TF_FLOAT); | |||||
| var y = conv.Apply(x2); | |||||
| print($"input:{x2.shape} DepthwiseConv2D.out: {y.shape}"); | |||||
| Assert.AreEqual(4, y.shape.ndim); | |||||
| var arr = y.numpy().reshape((2, 9, 9, 3)); | |||||
| AssertArray(x[new int[] { 1, 1, 1 }].ToArray<int>(), new int[] { 273, 274, 275 }); | |||||
| AssertArray(arr[new int[] { 1, 1, 1 }].ToArray<float>(), new float[] { 2457f, 2466f, 2475f }); | |||||
| var bn = keras.layers.BatchNormalization(); | |||||
| var y2 = bn.Apply(y); | |||||
| arr = y2.numpy().ToArray<float>(); | |||||
| double delta = 0.0001; // 误差范围 | |||||
| Assert.AreEqual(arr[0], 59.97002f, delta); | |||||
| Assert.AreEqual(arr[1], 63.96802f, delta); | |||||
| } | |||||
| [TestMethod] | |||||
| public void BasicDepthwiseConv2D_strides_2() | |||||
| { | |||||
| var conv = keras.layers.DepthwiseConv2D(kernel_size: 3, strides: (1, 2, 2, 1), activation: null, | |||||
| padding: "same", depthwise_initializer: "ones"); | |||||
| var x = np.arange(2 * 9 * 9 * 3).reshape((2, 9, 9, 3)); | |||||
| var x2 = ops.convert_to_tensor(x, TF_DataType.TF_FLOAT); | |||||
| var y = conv.Apply(x2); | |||||
| print($"input:{x2.shape} DepthwiseConv2D.out: {y.shape}"); | |||||
| Assert.AreEqual(4, y.shape.ndim); | |||||
| var arr = y.numpy().reshape((2, 5, 5, 3)); | |||||
| AssertArray(x[new int[] { 1, 1, 1 }].ToArray<int>(), new int[] { 273, 274, 275 }); | |||||
| AssertArray(arr[new int[] { 1, 1, 1 }].ToArray<float>(), new float[] { 2727f, 2736f, 2745f }); | |||||
| var bn = keras.layers.BatchNormalization(); | |||||
| var y2 = bn.Apply(y); | |||||
| arr = y2.numpy().ToArray<float>(); | |||||
| double delta = 0.0001; // 误差范围 | |||||
| Assert.AreEqual(arr[0], 59.97002f, delta); | |||||
| Assert.AreEqual(arr[1], 63.96802f, delta); | |||||
| } | |||||
| [TestMethod] | |||||
| public void BasicDepthwiseConv2D_strides_3() | |||||
| { | |||||
| var conv = keras.layers.DepthwiseConv2D(kernel_size: 3, strides: 3, activation: null, | |||||
| padding: "same", depthwise_initializer: "ones"); | |||||
| var x = np.arange(2 * 9 * 9 * 3).reshape((2, 9, 9, 3)); | |||||
| var x2 = ops.convert_to_tensor(x, TF_DataType.TF_FLOAT); | |||||
| var y = conv.Apply(x2); | |||||
| print($"input:{x2.shape} DepthwiseConv2D.out: {y.shape}"); | |||||
| Assert.AreEqual(4, y.shape.ndim); | |||||
| var arr = y.numpy().reshape((2, 3, 3, 3)); | |||||
| AssertArray(x[new int[] { 1, 1, 1 }].ToArray<int>(), new int[] { 273, 274, 275 }); | |||||
| AssertArray(arr[new int[] { 1, 1, 1 }].ToArray<float>(), new float[] { 3267f, 3276f, 3285f }); | |||||
| var bn = keras.layers.BatchNormalization(); | |||||
| var y2 = bn.Apply(y); | |||||
| arr = y2.numpy().ToArray<float>(); | |||||
| double delta = 0.0001; // 误差范围 | |||||
| Assert.AreEqual(arr[0], 269.86508f, delta); | |||||
| Assert.AreEqual(arr[1], 278.8606f, delta); | |||||
| } | |||||
| [TestMethod] | |||||
| public void BasicDepthwiseConv2D_UseBias() | |||||
| { | |||||
| var conv = keras.layers.DepthwiseConv2D(kernel_size: 3, strides: 1, activation: null, | |||||
| use_bias: true, padding: "same", | |||||
| depthwise_initializer: "ones", | |||||
| bias_initializer:"ones" | |||||
| ); | |||||
| var weight = conv.get_weights(); | |||||
| var x = np.arange(9 * 9 * 3).reshape((1, 9, 9, 3)); | |||||
| var x2 = ops.convert_to_tensor(x, TF_DataType.TF_FLOAT); | |||||
| var y = conv.Apply(x2); | |||||
| Assert.AreEqual(4, y.shape.ndim); | |||||
| var arr = y.numpy().ToArray<float>(); | |||||
| Assert.AreEqual(arr[0], 61f); | |||||
| Assert.AreEqual(arr[1], 65f); | |||||
| var bn = keras.layers.BatchNormalization(); | |||||
| var y2 = bn.Apply(y); | |||||
| arr = y2.numpy().ToArray<float>(); | |||||
| double delta = 0.0001; // 误差范围 | |||||
| Assert.AreEqual(arr[0], 60.96952f, delta); | |||||
| Assert.AreEqual(arr[1], 64.96752f, delta); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -20,6 +20,20 @@ namespace TensorFlowNET.UnitTest | |||||
| return Math.Abs(f1 - f2) <= tolerance; | return Math.Abs(f1 - f2) <= tolerance; | ||||
| } | } | ||||
| public bool Equal(long[] l1, long[] l2) | |||||
| { | |||||
| if (l1.Length != l2.Length) | |||||
| return false; | |||||
| for (var i = 0; i < l1.Length; i++) | |||||
| { | |||||
| if (l1[i] != l2[i]) | |||||
| return false; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| public bool Equal(float[] f1, float[] f2) | public bool Equal(float[] f1, float[] f2) | ||||
| { | { | ||||
| bool ret = false; | bool ret = false; | ||||