@@ -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); | |||||
} | |||||
} | } | ||||
} | } |