From 050fd2c63ed4fed29dda4cbed558ffa349ce275c Mon Sep 17 00:00:00 2001 From: Oceania2018 Date: Sat, 3 Oct 2020 07:16:18 -0500 Subject: [PATCH] Add ConcreteFunction. --- src/TensorFlowNET.Core/APIs/tf.image.cs | 3 + src/TensorFlowNET.Core/Data/DatasetManager.cs | 3 + .../Data/TensorSliceDataset.cs | 10 +++ .../Functions/ConcreteFunction.cs | 3 +- src/TensorFlowNET.Core/Graphs/AutoGraph.cs | 2 - .../Graphs/AutoGraphAttribute.cs | 70 +++++++++++++------ src/TensorFlowNET.Core/Graphs/FuncGraph.cs | 5 ++ ...eprocessing.paths_and_labels_to_dataset.cs | 9 ++- .../Operations/image_ops_impl.cs | 24 ++++--- src/TensorFlowNET.Core/Tensors/tensor_util.cs | 15 +++- src/TensorFlowNET.Core/ops.cs | 2 + test/TensorFlowNET.UnitTest/ImageTest.cs | 63 ++++++++++++++++- .../img_test/TestCrop.cs | 56 --------------- 13 files changed, 165 insertions(+), 100 deletions(-) delete mode 100644 test/TensorFlowNET.UnitTest/img_test/TestCrop.cs diff --git a/src/TensorFlowNET.Core/APIs/tf.image.cs b/src/TensorFlowNET.Core/APIs/tf.image.cs index ff20ae22..92013c13 100644 --- a/src/TensorFlowNET.Core/APIs/tf.image.cs +++ b/src/TensorFlowNET.Core/APIs/tf.image.cs @@ -208,6 +208,9 @@ namespace Tensorflow => image_ops_impl.non_max_suppression_padded(boxes, scores, max_output_size, iou_threshold, score_threshold, pad_to_max_output_size, name, sorted_input, canonicalized_coordinates, tile_size); + public Tensor resize(Tensor image, TensorShape size) + => image_ops_impl.resize_images(image, tf.constant(size)); + public Tensor resize_bilinear(Tensor images, Tensor size, bool align_corners = false, bool half_pixel_centers = false, string name = null) => gen_image_ops.resize_bilinear(images, size, align_corners: align_corners, half_pixel_centers: half_pixel_centers, name: name); diff --git a/src/TensorFlowNET.Core/Data/DatasetManager.cs b/src/TensorFlowNET.Core/Data/DatasetManager.cs index 2e5485a4..f9324c2c 100644 --- a/src/TensorFlowNET.Core/Data/DatasetManager.cs +++ b/src/TensorFlowNET.Core/Data/DatasetManager.cs @@ -25,6 +25,9 @@ namespace Tensorflow public IDatasetV2 from_tensor_slices(Tensor features, Tensor labels) => new TensorSliceDataset(features, labels); + public IDatasetV2 from_tensor_slices(string[] array) + => new TensorSliceDataset(array); + public IDatasetV2 from_tensor_slices(NDArray array) => new TensorSliceDataset(array); diff --git a/src/TensorFlowNET.Core/Data/TensorSliceDataset.cs b/src/TensorFlowNET.Core/Data/TensorSliceDataset.cs index cbf9b847..03b949a7 100644 --- a/src/TensorFlowNET.Core/Data/TensorSliceDataset.cs +++ b/src/TensorFlowNET.Core/Data/TensorSliceDataset.cs @@ -11,6 +11,16 @@ namespace Tensorflow.Data { public class TensorSliceDataset : DatasetSource { + public TensorSliceDataset(string[] array) + { + var element = tf.constant(array); + _tensors = new[] { element }; + var batched_spec = new[] { element.ToTensorSpec() }; + structure = batched_spec.Select(x => x._unbatch()).ToArray(); + + variant_tensor = ops.tensor_slice_dataset(_tensors, output_shapes); + } + public TensorSliceDataset(NDArray array) { var element = tf.constant(array); diff --git a/src/TensorFlowNET.Core/Functions/ConcreteFunction.cs b/src/TensorFlowNET.Core/Functions/ConcreteFunction.cs index f05bdbc4..1dede9fe 100644 --- a/src/TensorFlowNET.Core/Functions/ConcreteFunction.cs +++ b/src/TensorFlowNET.Core/Functions/ConcreteFunction.cs @@ -33,8 +33,6 @@ namespace Tensorflow.Functions new Operation[] { input }, new Operation[] { output }, null); - - c_api.TFE_ContextAddFunction(tf.Context.Handle, _handle, tf.Status.Handle); } tf.enable_eager_execution(); @@ -54,6 +52,7 @@ namespace Tensorflow.Functions public void Dispose() { c_api.TFE_ContextRemoveFunction(tf.Context.Handle, Name, tf.Status.Handle); + c_api.TF_DeleteFunction(_handle); } } } diff --git a/src/TensorFlowNET.Core/Graphs/AutoGraph.cs b/src/TensorFlowNET.Core/Graphs/AutoGraph.cs index 91f74563..cfed2078 100644 --- a/src/TensorFlowNET.Core/Graphs/AutoGraph.cs +++ b/src/TensorFlowNET.Core/Graphs/AutoGraph.cs @@ -26,8 +26,6 @@ namespace Tensorflow.Graphs new Operation[] { input1, input2 }, new Operation[] { output }, null); - - c_api.TFE_ContextAddFunction(tf.Context.Handle, func_handle, tf.Status.Handle); } tf.enable_eager_execution(); diff --git a/src/TensorFlowNET.Core/Graphs/AutoGraphAttribute.cs b/src/TensorFlowNET.Core/Graphs/AutoGraphAttribute.cs index d46f55fe..5a0c353c 100644 --- a/src/TensorFlowNET.Core/Graphs/AutoGraphAttribute.cs +++ b/src/TensorFlowNET.Core/Graphs/AutoGraphAttribute.cs @@ -1,4 +1,4 @@ -/*using MethodBoundaryAspect.Fody.Attributes; +using MethodBoundaryAspect.Fody.Attributes; using System; using System.Collections.Generic; using System.Linq; @@ -8,49 +8,73 @@ using static Tensorflow.Binding; namespace Tensorflow.Graphs { - public sealed class AutoGraphAspect : OnMethodBoundaryAspect + [AllowChangingInputArguments] + public sealed class AutoGraphAttribute : OnMethodBoundaryAspect { FuncGraph graph; - IntPtr func_handle; + Tensor[] originalInputs; + string func_name; + static Dictionary> functions = new Dictionary>(); public override void OnEntry(MethodExecutionArgs args) { + func_name = $"autograph_{args.Instance}.{args.Method.Name}"; + + if (functions.ContainsKey(func_name)) + { + args.ReturnValue = functions[func_name](args.Arguments.Select(x => x as Tensor).ToArray()); + args.FlowBehavior = FlowBehavior.Return; + return; + } + tf.compat.v1.disable_eager_execution(); + + // make function as an Operation by autograph + graph = new FuncGraph(func_name); + graph.as_default(); + + originalInputs = new Tensor[args.Arguments.Length]; // convert args to placeholder - for (var i = 0; i < args.Arguments.Length; i++) { if (args.Arguments[i] is EagerTensor tensor) + { + originalInputs[i] = tensor; args.Arguments[i] = tf.placeholder(tensor.dtype, shape: tensor.TensorShape); + } } - - // make function as an Operation by autograph - graph = new FuncGraph("autograph_add"); - graph.as_default(); } public override void OnExit(MethodExecutionArgs args) { - var output = (Tensor)args.Method.Invoke(args.Instance, args.Arguments); + var output = (Tensor)args.ReturnValue; + var inputs = args.Arguments.Select(x => x as Tensor).ToArray(); var opers = graph._nodes_by_name.Values.Select(x => x as Operation).ToArray(); - func_handle = graph.ToGraph(opers, - new Operation[] { }, - new Operation[] { }, + + graph.ToGraph(opers, + inputs.Select(x => x.op).ToArray(), + new Operation[] { output.op }, null); + graph.Dispose(); + tf.enable_eager_execution(); - c_api.TFE_ContextAddFunction(tf.Context.Handle, func_handle, tf.Status.Handle); + Func function = (x) => + { + var result = tf.Runner.TFE_Execute(tf.Context, + tf.Context.DeviceName, + func_name, + x, + null, + 1); - var a1 = tf.constant(1); - var b1 = tf.constant(2); + return result[0]; + }; + // cache function. + functions[func_name] = function; - var result = tf.Runner.TFE_Execute(tf.Context, - tf.Context.DeviceName, - "autograph_add", - new[] { a1, b1 }, - null, - 1); - graph.Dispose(); + // run function + args.ReturnValue = function(originalInputs); } } -}*/ +} diff --git a/src/TensorFlowNET.Core/Graphs/FuncGraph.cs b/src/TensorFlowNET.Core/Graphs/FuncGraph.cs index 3da3b267..7cefab0e 100644 --- a/src/TensorFlowNET.Core/Graphs/FuncGraph.cs +++ b/src/TensorFlowNET.Core/Graphs/FuncGraph.cs @@ -47,8 +47,13 @@ namespace Tensorflow.Graphs IntPtr.Zero, null, status.Handle); + status.Check(true); c_api.TF_GraphCopyFunction(outer_graph, func_handle, IntPtr.Zero, status.Handle); + status.Check(true); + + c_api.TFE_ContextAddFunction(tf.Context.Handle, func_handle, status.Handle); + status.Check(true); return func_handle; } diff --git a/src/TensorFlowNET.Core/Keras/Preprocessings/Preprocessing.paths_and_labels_to_dataset.cs b/src/TensorFlowNET.Core/Keras/Preprocessings/Preprocessing.paths_and_labels_to_dataset.cs index 65cbee7a..c8300382 100644 --- a/src/TensorFlowNET.Core/Keras/Preprocessings/Preprocessing.paths_and_labels_to_dataset.cs +++ b/src/TensorFlowNET.Core/Keras/Preprocessings/Preprocessing.paths_and_labels_to_dataset.cs @@ -16,7 +16,10 @@ namespace Tensorflow.Keras int num_classes, string interpolation) { - Shape shape = (image_paths.Length, image_size.dims[0], image_size.dims[1], num_channels); + var path_ds = tf.data.Dataset.from_tensor_slices(image_paths); + var img_ds = path_ds.map(x => path_to_image(x, image_size, num_channels, interpolation)); + + /*Shape shape = (image_paths.Length, image_size.dims[0], image_size.dims[1], num_channels); Console.WriteLine($"Allocating memory for shape{shape}, {NPTypeCode.Float}"); var data = np.zeros(shape, NPTypeCode.Float); @@ -35,13 +38,13 @@ namespace Tensorflow.Keras var label_ds = tf.keras.preprocessing.dataset_utils.labels_to_dataset(labels, label_mode, num_classes); img_ds = tf.data.Dataset.zip(img_ds, label_ds); } - else + else*/ throw new NotImplementedException(""); return img_ds; } - Tensor path_to_image(string path, TensorShape image_size, int num_channels, string interpolation) + Tensor path_to_image(Tensor path, TensorShape image_size, int num_channels, string interpolation) { var img = tf.io.read_file(path); img = tf.image.decode_image( diff --git a/src/TensorFlowNET.Core/Operations/image_ops_impl.cs b/src/TensorFlowNET.Core/Operations/image_ops_impl.cs index abc00600..86434bfc 100644 --- a/src/TensorFlowNET.Core/Operations/image_ops_impl.cs +++ b/src/TensorFlowNET.Core/Operations/image_ops_impl.cs @@ -1668,8 +1668,6 @@ new_height, new_width"); public static Tensor decode_image(Tensor contents, int channels = 0, TF_DataType dtype = TF_DataType.TF_UINT8, string name = null, bool expand_animations = true) { - Tensor substr = null; - Func _jpeg = () => { int jpeg_channels = channels; @@ -1695,8 +1693,7 @@ new_height, new_width"); { var result = convert_image_dtype(gen_image_ops.decode_gif(contents), dtype); if (!expand_animations) - // result = array_ops.gather(result, 0); - throw new NotImplementedException(""); + result = array_ops.gather(result, 0); return result; }); }; @@ -1728,18 +1725,16 @@ new_height, new_width"); Func check_gif = () => { - var is_gif = math_ops.equal(substr, "\x47\x49\x46", name: "is_gif"); - return control_flow_ops.cond(is_gif, _gif, _bmp, name: "cond_gif"); + return control_flow_ops.cond(is_gif(contents), _gif, _bmp, name: "cond_gif"); }; Func check_png = () => { - return control_flow_ops.cond(_is_png(contents), _png, check_gif, name: "cond_png"); + return control_flow_ops.cond(is_png(contents), _png, check_gif, name: "cond_png"); }; return tf_with(ops.name_scope(name, "decode_image"), scope => { - substr = tf.strings.substr(contents, 0, 3); return control_flow_ops.cond(is_jpeg(contents), _jpeg, check_png, name: "cond_jpeg"); }); } @@ -2089,7 +2084,7 @@ new_height, new_width"); }); } - public static Tensor _is_png(Tensor contents, string name = null) + static Tensor is_png(Tensor contents, string name = null) { return tf_with(ops.name_scope(name, "is_png"), scope => { @@ -2098,6 +2093,17 @@ new_height, new_width"); }); } + static Tensor is_gif(Tensor contents, string name = null) + { + return tf_with(ops.name_scope(name, "is_gif"), scope => + { + var substr = tf.strings.substr(contents, 0, 3); + var gif = tf.constant(new byte[] { 0x47, 0x49, 0x46 }, TF_DataType.TF_STRING); + var result = math_ops.equal(substr, gif, name: name); + return result; + }); + } + public static Tensor convert_image_dtype(Tensor image, TF_DataType dtype, bool saturate = false, string name = null) { diff --git a/src/TensorFlowNET.Core/Tensors/tensor_util.cs b/src/TensorFlowNET.Core/Tensors/tensor_util.cs index 3d8b8270..13dda9ce 100644 --- a/src/TensorFlowNET.Core/Tensors/tensor_util.cs +++ b/src/TensorFlowNET.Core/Tensors/tensor_util.cs @@ -148,9 +148,18 @@ namespace Tensorflow // If shape is not given, get the shape from the numpy array. if (shape == null) { - shape = nparray.shape; - is_same_size = true; - shape_size = nparray.size; + if(numpy_dtype == TF_DataType.TF_STRING) + { + // scalar string + shape = new int[0]; + shape_size = 0; + } + else + { + shape = nparray.shape; + is_same_size = true; + shape_size = nparray.size; + } } else { diff --git a/src/TensorFlowNET.Core/ops.cs b/src/TensorFlowNET.Core/ops.cs index f42e49bd..935414ea 100644 --- a/src/TensorFlowNET.Core/ops.cs +++ b/src/TensorFlowNET.Core/ops.cs @@ -470,6 +470,8 @@ namespace Tensorflow return varVal._TensorConversionFunction(dtype: dtype, name: name, as_ref: as_ref); case TensorShape ts: return constant_op.constant(ts.dims, dtype: dtype, name: name); + case string str: + return constant_op.constant(value, dtype: tf.@string, name: name); case int[] dims: return constant_op.constant(dims, dtype: dtype, name: name); case object[] objects: diff --git a/test/TensorFlowNET.UnitTest/ImageTest.cs b/test/TensorFlowNET.UnitTest/ImageTest.cs index 395f5fe4..47ec6a7c 100644 --- a/test/TensorFlowNET.UnitTest/ImageTest.cs +++ b/test/TensorFlowNET.UnitTest/ImageTest.cs @@ -1,4 +1,6 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NumSharp; using System; using System.Collections.Generic; using System.IO; @@ -26,12 +28,69 @@ namespace TensorFlowNET.UnitTest.Basics contents = tf.io.read_file(imgPath); } - [Ignore] [TestMethod] public void decode_image() { var img = tf.image.decode_image(contents); Assert.AreEqual(img.name, "decode_image/cond_jpeg/Merge:0"); } + + [TestMethod, Ignore] + public void resize_image() + { + var image = tf.constant(new int[5, 5] + { + {1, 0, 0, 0, 0 }, + {0, 1, 0, 0, 0 }, + {0, 0, 1, 0, 0 }, + {0, 0, 0, 1, 0 }, + {0, 0, 0, 0, 1 } + }); + //image = image[tf.newaxis, ..., tf.newaxis]; + + var img = tf.image.resize(contents, (3, 5)); + Assert.AreEqual(img.name, "decode_image/cond_jpeg/Merge:0"); + } + + [TestMethod] + public void TestCropAndResize() + { + var graph = tf.Graph().as_default(); + + // 3x3 'Image' with numbered coordinates + var input = np.array(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f); + var image = tf.reshape(input, new int[] { 1, 3, 3, 1 }); + + // 4x4 'Image' with numbered coordinates + var input2 = np.array(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 10f, 11f, 12f, 13f, 14f, 15f); + var image2 = tf.reshape(input2, new int[] { 1, 4, 4, 1 }); + // create one box over the full image that flips it (y1 > y2) + var box = tf.reshape(np.array(1f, 0f, 0f, 1f), new int[] { 1, 4 }); + var boxInd = tf.Variable(np.array(0)); + // crop first 3x3 imageto size 1x1 + var cropSize1_1 = tf.Variable(np.array(1, 1)); + // don't crop second 4x4 image + var cropSize2_2 = tf.Variable(np.array(4, 4)); + + var init = tf.global_variables_initializer(); + using (Session sess = tf.Session()) + { + sess.run(init); + + var cropped = tf.image.crop_and_resize(image, box, boxInd, cropSize1_1); + + var result = sess.run(cropped); + // check if cropped to 1x1 center was succesfull + result.size.Should().Be(1); + result[0, 0, 0, 0].Should().Be(4f); + + cropped = tf.image.crop_and_resize(image2, box, boxInd, cropSize2_2); + result = sess.run(cropped); + // check if flipped and no cropping occured + result.size.Should().Be(16); + result[0, 0, 0, 0].Should().Be(12f); + + } + } } } diff --git a/test/TensorFlowNET.UnitTest/img_test/TestCrop.cs b/test/TensorFlowNET.UnitTest/img_test/TestCrop.cs deleted file mode 100644 index f1897d8f..00000000 --- a/test/TensorFlowNET.UnitTest/img_test/TestCrop.cs +++ /dev/null @@ -1,56 +0,0 @@ -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using NumSharp; -using Tensorflow; -using Tensorflow.UnitTest; -using static Tensorflow.Binding; - -namespace TensorFlowNET.UnitTest.img_test -{ - [TestClass] - public class TestCrop : GraphModeTestBase - { - [TestMethod] - public void TestCropAndResize() - { - var graph = tf.Graph().as_default(); - - // 3x3 'Image' with numbered coordinates - var input = np.array(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f); - var image = tf.reshape(input, new int[] { 1, 3, 3, 1 }); - - // 4x4 'Image' with numbered coordinates - var input2 = np.array(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 10f, 11f, 12f, 13f, 14f, 15f); - var image2 = tf.reshape(input2, new int[] { 1, 4, 4, 1 }); - // create one box over the full image that flips it (y1 > y2) - var box = tf.reshape(np.array(1f, 0f, 0f, 1f), new int[] {1, 4}); - var boxInd = tf.Variable(np.array(0)); - // crop first 3x3 imageto size 1x1 - var cropSize1_1 = tf.Variable(np.array(1, 1)); - // don't crop second 4x4 image - var cropSize2_2 = tf.Variable(np.array(4, 4)); - - var init = tf.global_variables_initializer(); - using (Session sess = tf.Session()) - { - sess.run(init); - - var cropped = tf.image.crop_and_resize(image, box, boxInd, cropSize1_1); - - var result = sess.run(cropped); - // check if cropped to 1x1 center was succesfull - result.size.Should().Be(1); - result[0, 0, 0, 0].Should().Be(4f); - - cropped = tf.image.crop_and_resize(image2, box, boxInd, cropSize2_2); - result = sess.run(cropped); - // check if flipped and no cropping occured - result.size.Should().Be(16); - result[0, 0, 0, 0].Should().Be(12f); - - } - - } - - } -}