@@ -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, | ||||