| @@ -64,10 +64,8 @@ namespace Tensorflow.Eager | |||||
| } | } | ||||
| } | } | ||||
| var flattened_inputs = args.Take(op_def.InputArg.Count) | |||||
| .Select(x => x as Tensor) | |||||
| .ToArray(); | |||||
| var flattened_attrs = args.Skip(op_def.InputArg.Count).ToArray(); | |||||
| var flattened_attrs = new List<object>(op_def.InputArg.Count); | |||||
| var flattened_inputs = new List<Tensor>(op_def.InputArg.Count); | |||||
| c_api.TFE_OpSetDevice(op, device_name, status.Handle); | c_api.TFE_OpSetDevice(op, device_name, status.Handle); | ||||
| status.Check(true); | status.Check(true); | ||||
| @@ -80,31 +78,36 @@ namespace Tensorflow.Eager | |||||
| { | { | ||||
| int len = (args[kFastPathExecuteInputStartIndex + i] as object[]).Length; | int len = (args[kFastPathExecuteInputStartIndex + i] as object[]).Length; | ||||
| c_api.TFE_OpSetAttrInt(op, input_arg.NumberAttr, len); | c_api.TFE_OpSetAttrInt(op, input_arg.NumberAttr, len); | ||||
| if (op_exec_info.run_callbacks) | |||||
| { | |||||
| flattened_attrs.Add(input_arg.NumberAttr); | |||||
| flattened_attrs.Add(len); | |||||
| } | |||||
| attr_list_sizes[input_arg.NumberAttr] = len; | attr_list_sizes[input_arg.NumberAttr] = len; | ||||
| if (len > 0) | if (len > 0) | ||||
| { | { | ||||
| var fast_input_array = (object[])args[i]; | var fast_input_array = (object[])args[i]; | ||||
| // First item adds the type attr. | // First item adds the type attr. | ||||
| if (!AddInputToOp(fast_input_array[i], true, input_arg, op, status)) | |||||
| if (!AddInputToOp(fast_input_array[i], true, input_arg, flattened_attrs, flattened_inputs, op, status)) | |||||
| return null; | return null; | ||||
| for (var j = 1; j < len; j++) | for (var j = 1; j < len; j++) | ||||
| { | { | ||||
| // Since the list is homogeneous, we don't need to re-add the attr. | // Since the list is homogeneous, we don't need to re-add the attr. | ||||
| if (!AddInputToOp(fast_input_array[j], false, input_arg, op, status)) | |||||
| if (!AddInputToOp(fast_input_array[j], false, input_arg, flattened_attrs, flattened_inputs, op, status)) | |||||
| return null; | return null; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| else if (!string.IsNullOrEmpty(input_arg.TypeListAttr)) | else if (!string.IsNullOrEmpty(input_arg.TypeListAttr)) | ||||
| { | { | ||||
| throw new NotImplementedException(""); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| // The item is a single item. | // The item is a single item. | ||||
| AddInputToOp(args[i], true, input_arg, op, status); | |||||
| AddInputToOp(args[i], true, input_arg, flattened_attrs, flattened_inputs, op, status); | |||||
| } | } | ||||
| } | } | ||||
| @@ -133,7 +136,7 @@ namespace Tensorflow.Eager | |||||
| if (!RunCallbacks( | if (!RunCallbacks( | ||||
| op_exec_info, | op_exec_info, | ||||
| kFastPathExecuteInputStartIndex + op_def.InputArg.Count(), | kFastPathExecuteInputStartIndex + op_def.InputArg.Count(), | ||||
| flattened_inputs, flattened_attrs, flat_result)) | |||||
| flattened_inputs.ToArray(), flattened_attrs.ToArray(), flat_result)) | |||||
| { | { | ||||
| return null; | return null; | ||||
| } | } | ||||
| @@ -187,6 +190,8 @@ namespace Tensorflow.Eager | |||||
| bool AddInputToOp(object inputs, | bool AddInputToOp(object inputs, | ||||
| bool add_type_attr, | bool add_type_attr, | ||||
| ArgDef input_arg, | ArgDef input_arg, | ||||
| List<object> flattened_attrs, | |||||
| List<Tensor> flattened_inputs, | |||||
| IntPtr op, | IntPtr op, | ||||
| Status status) | Status status) | ||||
| { | { | ||||
| @@ -197,6 +202,7 @@ namespace Tensorflow.Eager | |||||
| { | { | ||||
| case EagerTensor input: | case EagerTensor input: | ||||
| input_handle = input.EagerTensorHandle; | input_handle = input.EagerTensorHandle; | ||||
| flattened_inputs.Add(input); | |||||
| break; | break; | ||||
| case EagerTensor[] input_list: | case EagerTensor[] input_list: | ||||
| input_handle = input_list[0].EagerTensorHandle; | input_handle = input_list[0].EagerTensorHandle; | ||||
| @@ -211,6 +217,8 @@ namespace Tensorflow.Eager | |||||
| { | { | ||||
| var dtype = c_api.TFE_TensorHandleDataType(input_handle); | var dtype = c_api.TFE_TensorHandleDataType(input_handle); | ||||
| c_api.TFE_OpSetAttrType(op, input_arg.TypeAttr, dtype); | c_api.TFE_OpSetAttrType(op, input_arg.TypeAttr, dtype); | ||||
| flattened_attrs.Add(input_arg.TypeAttr); | |||||
| flattened_attrs.Add(dtype); | |||||
| } | } | ||||
| c_api.TFE_OpAddInput(op, input_handle, status.Handle); | c_api.TFE_OpAddInput(op, input_handle, status.Handle); | ||||
| @@ -34,7 +34,7 @@ namespace Tensorflow.Eager | |||||
| public EagerTensor Resolve() | public EagerTensor Resolve() | ||||
| { | { | ||||
| _id = get_uid(); | |||||
| _id = ops.uid(); | |||||
| if (_handle == IntPtr.Zero) | if (_handle == IntPtr.Zero) | ||||
| _handle = c_api.TFE_TensorHandleResolve(EagerTensorHandle, tf.status.Handle); | _handle = c_api.TFE_TensorHandleResolve(EagerTensorHandle, tf.status.Handle); | ||||
| @@ -55,8 +55,5 @@ namespace Tensorflow.Eager | |||||
| //print($"deleting DeleteTensorHandle {Id} {EagerTensorHandle.ToString("x16")}"); | //print($"deleting DeleteTensorHandle {Id} {EagerTensorHandle.ToString("x16")}"); | ||||
| c_api.TFE_DeleteTensorHandle(EagerTensorHandle); | c_api.TFE_DeleteTensorHandle(EagerTensorHandle); | ||||
| } | } | ||||
| static long _uid = 0; | |||||
| long get_uid() => _uid++; | |||||
| } | } | ||||
| } | } | ||||
| @@ -24,8 +24,8 @@ namespace Tensorflow.Gradients | |||||
| /// </summary> | /// </summary> | ||||
| public class GradientTape : IDisposable | public class GradientTape : IDisposable | ||||
| { | { | ||||
| static bool _recording; | |||||
| public static bool Recording => _recording; | |||||
| bool _recording; | |||||
| public bool Recording => _recording; | |||||
| bool _persistent; | bool _persistent; | ||||
| bool _watch_accessed_variables; | bool _watch_accessed_variables; | ||||
| ResourceVariable[] _watched_variables; | ResourceVariable[] _watched_variables; | ||||
| @@ -13,12 +13,14 @@ namespace Tensorflow.Gradients | |||||
| "FusedBatchNormGradV3" => new[] { 5 }, | "FusedBatchNormGradV3" => new[] { 5 }, | ||||
| "FusedBatchNormV2" => new[] { 2 }, | "FusedBatchNormV2" => new[] { 2 }, | ||||
| "FusedBatchNormV3" => new[] { 2 }, | "FusedBatchNormV3" => new[] { 2 }, | ||||
| "ReadVariableOp" => new int[0], | |||||
| _ => null | _ => null | ||||
| }; | }; | ||||
| public static int[] OpGradientUnusedOutputIndices(string op_name) | public static int[] OpGradientUnusedOutputIndices(string op_name) | ||||
| => op_name switch | => op_name switch | ||||
| { | { | ||||
| "ReadVariableOp" => new int[0], | |||||
| "SoftmaxCrossEntropyWithLogits" => new[] { 0 }, | "SoftmaxCrossEntropyWithLogits" => new[] { 0 }, | ||||
| "TensorArrayConcat" => new[] { 0 }, | "TensorArrayConcat" => new[] { 0 }, | ||||
| "TensorArrayConcatV2" => new[] { 0 }, | "TensorArrayConcatV2" => new[] { 0 }, | ||||
| @@ -64,6 +64,22 @@ namespace Tensorflow.Gradients | |||||
| return new Tensor[] { r1, r2 }; | return new Tensor[] { r1, r2 }; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Copies the gradient to all inputs. | |||||
| /// </summary> | |||||
| /// <param name="op"></param> | |||||
| /// <param name="grads"></param> | |||||
| /// <returns></returns> | |||||
| [RegisterGradient("AddN")] | |||||
| public static Tensor[] _AddNGrad(Operation op, Tensor[] grads) | |||||
| { | |||||
| var grad = grads[0]; | |||||
| return Enumerable.Range(0, len(op.inputs)) | |||||
| .Select(x => grad) | |||||
| .ToArray(); | |||||
| } | |||||
| [RegisterGradient("Cumsum")] | [RegisterGradient("Cumsum")] | ||||
| public static Tensor[] _CumsumGrad(Operation op, Tensor[] grads) | public static Tensor[] _CumsumGrad(Operation op, Tensor[] grads) | ||||
| { | { | ||||
| @@ -124,6 +124,16 @@ namespace Tensorflow | |||||
| /// </remarks> | /// </remarks> | ||||
| public static Tensor diag(Tensor diagonal, string name = null) | public static Tensor diag(Tensor diagonal, string name = null) | ||||
| { | { | ||||
| if (tf.context.executing_eagerly()) | |||||
| { | |||||
| var results = tf.Runner.TFE_FastPathExecute(tf.context, tf.context.device_name, | |||||
| "Diag", name, | |||||
| null, | |||||
| diagonal); | |||||
| return results[0]; | |||||
| } | |||||
| var op = tf._op_def_lib._apply_op_helper("Diag", name: name, args: new { diagonal }); | var op = tf._op_def_lib._apply_op_helper("Diag", name: name, args: new { diagonal }); | ||||
| return op.output; | return op.output; | ||||
| @@ -131,6 +141,16 @@ namespace Tensorflow | |||||
| public static Tensor expand_dims(Tensor input, int axis, string name = null) | public static Tensor expand_dims(Tensor input, int axis, string name = null) | ||||
| { | { | ||||
| if (tf.context.executing_eagerly()) | |||||
| { | |||||
| var results = tf.Runner.TFE_FastPathExecute(tf.context, tf.context.device_name, | |||||
| "ExpandDims", name, | |||||
| null, | |||||
| input, tf.convert_to_tensor(axis)); | |||||
| return results[0]; | |||||
| } | |||||
| var _op = tf._op_def_lib._apply_op_helper("ExpandDims", name: name, args: new { input, dim = axis }); | var _op = tf._op_def_lib._apply_op_helper("ExpandDims", name: name, args: new { input, dim = axis }); | ||||
| return _op.outputs[0]; | return _op.outputs[0]; | ||||
| @@ -39,5 +39,8 @@ namespace Tensorflow.Util | |||||
| } | } | ||||
| public override bool IsInvalid => handle == IntPtr.Zero; | public override bool IsInvalid => handle == IntPtr.Zero; | ||||
| public override string ToString() | |||||
| => $"0x{handle.ToString("x16")}"; | |||||
| } | } | ||||
| } | } | ||||
| @@ -28,10 +28,10 @@ namespace Tensorflow.Util | |||||
| } | } | ||||
| public void push_back(Tk key, Tv value) | public void push_back(Tk key, Tv value) | ||||
| => Add(key, value); | |||||
| => this[key] = value; | |||||
| public void emplace(Tk key, Tv value) | public void emplace(Tk key, Tv value) | ||||
| => Add(key, value); | |||||
| => this[key] = value; | |||||
| public bool find(Tk key) | public bool find(Tk key) | ||||
| => ContainsKey(key); | => ContainsKey(key); | ||||
| @@ -22,56 +22,21 @@ namespace Tensorflow | |||||
| { | { | ||||
| public partial class ResourceVariable | public partial class ResourceVariable | ||||
| { | { | ||||
| public static Tensor operator +(ResourceVariable x, int y) => op_helper("add", x, y); | |||||
| public static Tensor operator +(ResourceVariable x, float y) => op_helper("add", x, y); | |||||
| public static Tensor operator +(ResourceVariable x, double y) => op_helper("add", x, y); | |||||
| public static Tensor operator +(ResourceVariable x, ResourceVariable y) => op_helper("add", x, y); | |||||
| public static Tensor operator -(ResourceVariable x, int y) => op_helper("sub", x, y); | |||||
| public static Tensor operator -(ResourceVariable x, float y) => op_helper("sub", x, y); | |||||
| public static Tensor operator -(ResourceVariable x, double y) => op_helper("sub", x, y); | |||||
| public static Tensor operator -(ResourceVariable x, Tensor y) => op_helper("sub", x, y); | |||||
| public static Tensor operator -(ResourceVariable x, ResourceVariable y) => op_helper("sub", x, y); | |||||
| public static Tensor operator *(ResourceVariable x, ResourceVariable y) => op_helper("mul", x, y); | |||||
| public static Tensor operator *(ResourceVariable x, NDArray y) => op_helper("mul", x, y); | |||||
| public static Tensor operator <(ResourceVariable x, Tensor y) => op_helper("less", x, y); | |||||
| public static Tensor operator >(ResourceVariable x, Tensor y) => op_helper("greater", x, y); | |||||
| private static Tensor op_helper<T>(string default_name, ResourceVariable x, T y) | |||||
| => tf_with(ops.name_scope(null, default_name, new { x, y }), scope => | |||||
| { | |||||
| string name = scope; | |||||
| var xVal = x.value(); | |||||
| var yTensor = ops.convert_to_tensor(y, xVal.dtype.as_base_dtype(), "y"); | |||||
| Tensor result = null; | |||||
| switch (default_name) | |||||
| { | |||||
| case "add": | |||||
| result = x.dtype == TF_DataType.TF_STRING ? | |||||
| gen_math_ops.add(xVal, yTensor, name) : | |||||
| gen_math_ops.add_v2(xVal, yTensor, name); | |||||
| break; | |||||
| case "sub": | |||||
| result = gen_math_ops.sub(xVal, yTensor, name); | |||||
| break; | |||||
| case "mul": | |||||
| result = gen_math_ops.mul(xVal, yTensor, name: name); | |||||
| break; | |||||
| case "less": | |||||
| result = gen_math_ops.less(xVal, yTensor, name); | |||||
| break; | |||||
| case "greater": | |||||
| result = gen_math_ops.greater(xVal, yTensor, name); | |||||
| break; | |||||
| default: | |||||
| throw new NotImplementedException(""); | |||||
| } | |||||
| // x.assign(result); | |||||
| // result.ResourceVar = x; | |||||
| return result; | |||||
| }); | |||||
| public static Tensor operator +(ResourceVariable x, int y) => x.value() + y; | |||||
| public static Tensor operator +(ResourceVariable x, float y) => x.value() + y; | |||||
| public static Tensor operator +(ResourceVariable x, double y) => x.value() + y; | |||||
| public static Tensor operator +(ResourceVariable x, ResourceVariable y) => x.value() + y.value(); | |||||
| public static Tensor operator -(ResourceVariable x, int y) => x.value() - y; | |||||
| public static Tensor operator -(ResourceVariable x, float y) => x.value() - y; | |||||
| public static Tensor operator -(ResourceVariable x, double y) => x.value() - y; | |||||
| public static Tensor operator -(ResourceVariable x, Tensor y) => x.value() - y; | |||||
| public static Tensor operator -(ResourceVariable x, ResourceVariable y) => x.value() - y.value(); | |||||
| public static Tensor operator *(ResourceVariable x, ResourceVariable y) => x.value() * y.value(); | |||||
| public static Tensor operator *(ResourceVariable x, NDArray y) => x.value() * y; | |||||
| public static Tensor operator <(ResourceVariable x, Tensor y) => x.value() < y; | |||||
| public static Tensor operator >(ResourceVariable x, Tensor y) => x.value() > y; | |||||
| } | } | ||||
| } | } | ||||
| @@ -277,7 +277,7 @@ namespace Tensorflow | |||||
| return ops.control_dependencies(null); | return ops.control_dependencies(null); | ||||
| } | } | ||||
| private static int uid_number = 0; | |||||
| private static int uid_number = -1; | |||||
| /// <summary> | /// <summary> | ||||
| /// A unique (within this program execution) integer. | /// A unique (within this program execution) integer. | ||||
| @@ -24,6 +24,25 @@ namespace TensorFlowNET.UnitTest.Gradient | |||||
| Assert.AreEqual((float)grad, 3.0f); | Assert.AreEqual((float)grad, 3.0f); | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Calcute the gradient of w * w * w | |||||
| /// 高阶梯度 | |||||
| /// </summary> | |||||
| [TestMethod] | |||||
| public void HighGradient() | |||||
| { | |||||
| var x = tf.Variable(1.0f); | |||||
| using var tape1 = tf.GradientTape(); | |||||
| using var tape2 = tf.GradientTape(); | |||||
| var y = x * x * x; | |||||
| tape2.Dispose(); | |||||
| var dy_dx = tape2.gradient(y, x); | |||||
| Assert.AreEqual((float)dy_dx, 3.0f); | |||||
| tape1.Dispose(); | |||||
| var d2y_d2x = tape1.gradient(dy_dx, x); | |||||
| Assert.AreEqual((float)d2y_d2x, 6.0f); | |||||
| } | |||||
| [TestMethod] | [TestMethod] | ||||
| public void ConstantMultiply() | public void ConstantMultiply() | ||||
| { | { | ||||
| @@ -56,5 +75,33 @@ namespace TensorFlowNET.UnitTest.Gradient | |||||
| var dz_dy = tape.gradient(z, y); | var dz_dy = tape.gradient(z, y); | ||||
| Assert.AreEqual((float)dz_dy, 8.0f); | Assert.AreEqual((float)dz_dy, 8.0f); | ||||
| } | } | ||||
| [TestMethod] | |||||
| public void ConditionalMultiply() | |||||
| { | |||||
| Func<Tensor, int, Tensor> func = (x, y) => | |||||
| { | |||||
| Tensor output = tf.constant(1.0f); | |||||
| foreach (var i in range(y)) | |||||
| { | |||||
| if (i > 1) | |||||
| output = tf.multiply(output, x); | |||||
| } | |||||
| return output; | |||||
| }; | |||||
| Func<Tensor, int, Tensor> grad = (x, y) => | |||||
| { | |||||
| using var tape = tf.GradientTape(); | |||||
| tape.watch(x); | |||||
| var output = func(x, y); | |||||
| var grad = tape.gradient(output, x); | |||||
| return grad; | |||||
| }; | |||||
| var x = tf.constant(2.0f); | |||||
| var result = grad(x, 4); | |||||
| Assert.AreEqual((float)result, 4.0f); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||