| @@ -0,0 +1,15 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Text; | |||
| using Tensorflow.IO; | |||
| using Tensorflow.Summaries; | |||
| namespace Tensorflow | |||
| { | |||
| public static partial class tf | |||
| { | |||
| public static Summary summary = new Summary(); | |||
| public static Tensor scalar(string name, Tensor tensor) | |||
| => summary.scalar(name, tensor); | |||
| } | |||
| } | |||
| @@ -17,5 +17,65 @@ namespace Tensorflow | |||
| return _op; | |||
| } | |||
| /// <summary> | |||
| /// Outputs a <c>Summary</c> protocol buffer with scalar values. | |||
| /// </summary> | |||
| /// <param name="tags"> | |||
| /// Tags for the summary. | |||
| /// </param> | |||
| /// <param name="values"> | |||
| /// Same shape as <c>tags. Values for the summary. | |||
| /// </param> | |||
| /// <param name="name"> | |||
| /// If specified, the created operation in the graph will be this one, otherwise it will be named 'ScalarSummary'. | |||
| /// </param> | |||
| /// <returns> | |||
| /// Scalar. Serialized <c>Summary</c> protocol buffer. | |||
| /// The Operation can be fetched from the resulting Tensor, by fetching the Operation property from the result. | |||
| /// </returns> | |||
| /// <remarks> | |||
| /// The input <c>tags</c> and <c>values</c> must have the same shape. The generated summary | |||
| /// has a summary value for each tag-value pair in <c>tags</c> and <c>values</c>. | |||
| /// </remarks> | |||
| public static Tensor scalar_summary(string tags, Tensor values, string name = "ScalarSummary") | |||
| { | |||
| var dict = new Dictionary<string, object>(); | |||
| dict["tags"] = tags; | |||
| dict["values"] = values; | |||
| var op = _op_def_lib._apply_op_helper("ScalarSummary", name: name, keywords: dict); | |||
| return op.output; | |||
| } | |||
| /// <summary> | |||
| /// Merges summaries. | |||
| /// </summary> | |||
| /// <param name="inputs"> | |||
| /// Can be of any shape. Each must contain serialized <c>Summary</c> protocol | |||
| /// buffers. | |||
| /// </param> | |||
| /// <param name="name"> | |||
| /// If specified, the created operation in the graph will be this one, otherwise it will be named 'MergeSummary'. | |||
| /// </param> | |||
| /// <returns> | |||
| /// Scalar. Serialized <c>Summary</c> protocol buffer. | |||
| /// The Operation can be fetched from the resulting Tensor, by fetching the Operation property from the result. | |||
| /// </returns> | |||
| /// <remarks> | |||
| /// This op creates a | |||
| /// [<c>Summary</c>](https://www.tensorflow.org/code/tensorflow/core/framework/summary.proto) | |||
| /// protocol buffer that contains the union of all the values in the input | |||
| /// summaries. | |||
| /// | |||
| /// When the Op is run, it reports an <c>InvalidArgument</c> error if multiple values | |||
| /// in the summaries to merge use the same tag. | |||
| /// </remarks> | |||
| public static Tensor merge_summary(Tensor[] inputs, string name = "MergeSummary") | |||
| { | |||
| var dict = new Dictionary<string, object>(); | |||
| dict["inputs"] = inputs; | |||
| var op = _op_def_lib._apply_op_helper("MergeSummary", name: name, keywords: dict); | |||
| return op.output; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,74 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using static Tensorflow.Python; | |||
| namespace Tensorflow.Summaries | |||
| { | |||
| public class Summary | |||
| { | |||
| public Tensor merge_all(string key = ops.GraphKeys.SUMMARIES, string scope= null, string name= null) | |||
| { | |||
| var summary_ops = ops.get_collection(key, scope: scope); | |||
| if (summary_ops == null) | |||
| return null; | |||
| else | |||
| return merge((summary_ops as List<ITensorOrOperation>).Select(x => x as Tensor).ToArray(), name: name); | |||
| } | |||
| /// <summary> | |||
| /// Merges summaries. | |||
| /// </summary> | |||
| /// <param name="inputs"></param> | |||
| /// <param name="collections"></param> | |||
| /// <param name="name"></param> | |||
| /// <returns></returns> | |||
| public Tensor merge(Tensor[] inputs, string[] collections = null, string name = null) | |||
| { | |||
| return with(ops.name_scope(name, "Merge", inputs), delegate | |||
| { | |||
| var val = gen_logging_ops.merge_summary(inputs: inputs, name: name); | |||
| collect(val, collections?.ToList(), new List<string>()); | |||
| return val; | |||
| }); | |||
| } | |||
| public Tensor scalar(string name, Tensor tensor, string[] collections = null, string family = null) | |||
| { | |||
| var (tag, scope) = summary_scope(name, family: family, values: new Tensor[] { tensor }); | |||
| var val = gen_logging_ops.scalar_summary(tags: tag, values: tensor, name: scope); | |||
| collect(val, collections?.ToList(), new List<string> { ops.GraphKeys.SUMMARIES }); | |||
| return val; | |||
| } | |||
| /// <summary> | |||
| /// Adds keys to a collection. | |||
| /// </summary> | |||
| /// <param name="val"The value to add per each key.></param> | |||
| /// <param name="collections">A collection of keys to add.</param> | |||
| /// <param name="default_collections">Used if collections is None.</param> | |||
| public void collect(ITensorOrOperation val, List<string> collections, List<string> default_collections) | |||
| { | |||
| if (collections == null) | |||
| collections = default_collections; | |||
| foreach (var key in collections) | |||
| ops.add_to_collection(key, val); | |||
| } | |||
| public (string, string) summary_scope(string name, string family = null, string default_name = null, Tensor[] values = null) | |||
| { | |||
| string scope_base_name = string.IsNullOrEmpty(family) ? name : $"{family}/{name}"; | |||
| return with(ops.name_scope(scope_base_name, default_name: default_name, values), scope => | |||
| { | |||
| var tag = scope._name_scope; | |||
| if (string.IsNullOrEmpty(family)) | |||
| tag = tag.Remove(tag.Length - 1); | |||
| else | |||
| tag = $"{family}/{tag.Remove(tag.Length - 1)}"; | |||
| return (tag, scope._name_scope); | |||
| }); | |||
| } | |||
| } | |||
| } | |||
| @@ -47,6 +47,9 @@ namespace Tensorflow | |||
| /// </summary> | |||
| public static string UPDATE_OPS = "update_ops"; | |||
| // Key to collect summaries. | |||
| public const string SUMMARIES = "summaries"; | |||
| // Used to store v2 summary names. | |||
| public static string _SUMMARY_COLLECTION = "_SUMMARY_V2"; | |||
| @@ -45,6 +45,8 @@ namespace TensorFlowNET.Examples.ImageProcess | |||
| tf.train.import_meta_graph("graph/InceptionV3.meta"); | |||
| Tensor bottleneck_tensor = graph.OperationByName("module_apply_default/hub_output/feature_vector/SpatialSqueeze"); | |||
| Tensor resized_image_tensor = graph.OperationByName("Placeholder"); | |||
| Tensor final_tensor = graph.OperationByName("final_result"); | |||
| Tensor ground_truth_input = graph.OperationByName("input/GroundTruthInput"); | |||
| var sw = new Stopwatch(); | |||
| @@ -63,11 +65,45 @@ namespace TensorFlowNET.Examples.ImageProcess | |||
| bottleneck_dir, jpeg_data_tensor, | |||
| decoded_image_tensor, resized_image_tensor, | |||
| bottleneck_tensor, tfhub_module); | |||
| // Create the operations we need to evaluate the accuracy of our new layer. | |||
| var (evaluation_step, _) = add_evaluation_step(final_tensor, ground_truth_input); | |||
| // Merge all the summaries and write them out to the summaries_dir | |||
| var merged = tf.summary.merge_all(); | |||
| }); | |||
| return false; | |||
| } | |||
| /// <summary> | |||
| /// Inserts the operations we need to evaluate the accuracy of our results. | |||
| /// </summary> | |||
| /// <param name="result_tensor"></param> | |||
| /// <param name="ground_truth_tensor"></param> | |||
| /// <returns></returns> | |||
| private (Tensor, Tensor) add_evaluation_step(Tensor result_tensor, Tensor ground_truth_tensor) | |||
| { | |||
| Tensor evaluation_step = null, correct_prediction = null, prediction = null; | |||
| with(tf.name_scope("accuracy"), scope => | |||
| { | |||
| with(tf.name_scope("correct_prediction"), delegate | |||
| { | |||
| prediction = tf.argmax(result_tensor, 1); | |||
| correct_prediction = tf.equal(prediction, ground_truth_tensor); | |||
| }); | |||
| with(tf.name_scope("accuracy"), delegate | |||
| { | |||
| evaluation_step = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)); | |||
| }); | |||
| }); | |||
| tf.summary.scalar("accuracy", evaluation_step); | |||
| return (evaluation_step, prediction); | |||
| } | |||
| /// <summary> | |||
| /// Ensures all the training, testing, and validation bottlenecks are cached. | |||
| /// </summary> | |||
| @@ -95,12 +131,15 @@ namespace TensorFlowNET.Examples.ImageProcess | |||
| get_or_create_bottleneck(sess, image_lists, label_name, index, image_dir, category, | |||
| bottleneck_dir, jpeg_data_tensor, decoded_image_tensor, | |||
| resized_input_tensor, bottleneck_tensor, module_name); | |||
| how_many_bottlenecks++; | |||
| if (how_many_bottlenecks % 100 == 0) | |||
| print($"{how_many_bottlenecks} bottleneck files created."); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| private void get_or_create_bottleneck(Session sess, Dictionary<string, Dictionary<string, string[]>> image_lists, | |||
| private float[] get_or_create_bottleneck(Session sess, Dictionary<string, Dictionary<string, string[]>> image_lists, | |||
| string label_name, int index, string image_dir, string category, string bottleneck_dir, | |||
| Tensor jpeg_data_tensor, Tensor decoded_image_tensor, Tensor resized_input_tensor, | |||
| Tensor bottleneck_tensor, string module_name) | |||
| @@ -116,6 +155,9 @@ namespace TensorFlowNET.Examples.ImageProcess | |||
| image_dir, category, sess, jpeg_data_tensor, | |||
| decoded_image_tensor, resized_input_tensor, | |||
| bottleneck_tensor); | |||
| var bottleneck_string = File.ReadAllText(bottleneck_path); | |||
| var bottleneck_values = Array.ConvertAll(bottleneck_string.Split(','), x => float.Parse(x)); | |||
| return bottleneck_values; | |||
| } | |||
| private void create_bottleneck_file(string bottleneck_path, Dictionary<string, Dictionary<string, string[]>> image_lists, | |||
| @@ -132,13 +174,16 @@ namespace TensorFlowNET.Examples.ImageProcess | |||
| var bottleneck_values = run_bottleneck_on_image( | |||
| sess, image_data, jpeg_data_tensor, decoded_image_tensor, | |||
| resized_input_tensor, bottleneck_tensor); | |||
| var values = bottleneck_values.Data<float>(); | |||
| var bottleneck_string = string.Join(",", values); | |||
| File.WriteAllText(bottleneck_path, bottleneck_string); | |||
| } | |||
| /// <summary> | |||
| /// Runs inference on an image to extract the 'bottleneck' summary layer. | |||
| /// </summary> | |||
| /// <param name="sess">Current active TensorFlow Session.</param> | |||
| /// <param name="image_path">Path of raw JPEG data.</param> | |||
| /// <param name="image_data">Data of raw JPEG data.</param> | |||
| /// <param name="image_data_tensor">Input data layer in the graph.</param> | |||
| /// <param name="decoded_image_tensor">Output of initial image resizing and preprocessing.</param> | |||
| /// <param name="resized_input_tensor">The input node of the recognition graph.</param> | |||