diff --git a/graph/InceptionV3.meta b/graph/InceptionV3.meta
index 69dc6ff4..c23a3a36 100644
Binary files a/graph/InceptionV3.meta and b/graph/InceptionV3.meta differ
diff --git a/src/TensorFlowNET.Core/APIs/tf.summary.cs b/src/TensorFlowNET.Core/APIs/tf.summary.cs
new file mode 100644
index 00000000..3932c969
--- /dev/null
+++ b/src/TensorFlowNET.Core/APIs/tf.summary.cs
@@ -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);
+ }
+}
diff --git a/src/TensorFlowNET.Core/Operations/gen_logging_ops.cs b/src/TensorFlowNET.Core/Operations/gen_logging_ops.cs
index 6f11dd0e..0af59d63 100644
--- a/src/TensorFlowNET.Core/Operations/gen_logging_ops.cs
+++ b/src/TensorFlowNET.Core/Operations/gen_logging_ops.cs
@@ -17,5 +17,65 @@ namespace Tensorflow
return _op;
}
+
+ ///
+ /// Outputs a Summary protocol buffer with scalar values.
+ ///
+ ///
+ /// Tags for the summary.
+ ///
+ ///
+ /// Same shape as tags. Values for the summary.
+ ///
+ ///
+ /// If specified, the created operation in the graph will be this one, otherwise it will be named 'ScalarSummary'.
+ ///
+ ///
+ /// Scalar. Serialized Summary protocol buffer.
+ /// The Operation can be fetched from the resulting Tensor, by fetching the Operation property from the result.
+ ///
+ ///
+ /// The input tags and values must have the same shape. The generated summary
+ /// has a summary value for each tag-value pair in tags and values.
+ ///
+ public static Tensor scalar_summary(string tags, Tensor values, string name = "ScalarSummary")
+ {
+ var dict = new Dictionary();
+ dict["tags"] = tags;
+ dict["values"] = values;
+ var op = _op_def_lib._apply_op_helper("ScalarSummary", name: name, keywords: dict);
+ return op.output;
+ }
+
+ ///
+ /// Merges summaries.
+ ///
+ ///
+ /// Can be of any shape. Each must contain serialized Summary protocol
+ /// buffers.
+ ///
+ ///
+ /// If specified, the created operation in the graph will be this one, otherwise it will be named 'MergeSummary'.
+ ///
+ ///
+ /// Scalar. Serialized Summary protocol buffer.
+ /// The Operation can be fetched from the resulting Tensor, by fetching the Operation property from the result.
+ ///
+ ///
+ /// This op creates a
+ /// [Summary](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 InvalidArgument error if multiple values
+ /// in the summaries to merge use the same tag.
+ ///
+ public static Tensor merge_summary(Tensor[] inputs, string name = "MergeSummary")
+ {
+ var dict = new Dictionary();
+ dict["inputs"] = inputs;
+ var op = _op_def_lib._apply_op_helper("MergeSummary", name: name, keywords: dict);
+ return op.output;
+ }
}
}
diff --git a/src/TensorFlowNET.Core/Summaries/Summary.cs b/src/TensorFlowNET.Core/Summaries/Summary.cs
new file mode 100644
index 00000000..57559a55
--- /dev/null
+++ b/src/TensorFlowNET.Core/Summaries/Summary.cs
@@ -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).Select(x => x as Tensor).ToArray(), name: name);
+ }
+
+ ///
+ /// Merges summaries.
+ ///
+ ///
+ ///
+ ///
+ ///
+ 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());
+ 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 { ops.GraphKeys.SUMMARIES });
+ return val;
+ }
+
+ ///
+ /// Adds keys to a collection.
+ ///
+ ///
+ /// A collection of keys to add.
+ /// Used if collections is None.
+ public void collect(ITensorOrOperation val, List collections, List 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);
+ });
+ }
+ }
+}
diff --git a/src/TensorFlowNET.Core/ops.GraphKeys.cs b/src/TensorFlowNET.Core/ops.GraphKeys.cs
index 2d319180..00763dc2 100644
--- a/src/TensorFlowNET.Core/ops.GraphKeys.cs
+++ b/src/TensorFlowNET.Core/ops.GraphKeys.cs
@@ -47,6 +47,9 @@ namespace Tensorflow
///
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";
diff --git a/test/TensorFlowNET.Examples/ImageProcess/RetrainImageClassifier.cs b/test/TensorFlowNET.Examples/ImageProcess/RetrainImageClassifier.cs
index f9a5bef9..9c7530a0 100644
--- a/test/TensorFlowNET.Examples/ImageProcess/RetrainImageClassifier.cs
+++ b/test/TensorFlowNET.Examples/ImageProcess/RetrainImageClassifier.cs
@@ -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;
}
+ ///
+ /// Inserts the operations we need to evaluate the accuracy of our results.
+ ///
+ ///
+ ///
+ ///
+ 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);
+ }
+
///
/// Ensures all the training, testing, and validation bottlenecks are cached.
///
@@ -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> image_lists,
+ private float[] get_or_create_bottleneck(Session sess, Dictionary> 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> 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();
+ var bottleneck_string = string.Join(",", values);
+ File.WriteAllText(bottleneck_path, bottleneck_string);
}
///
/// Runs inference on an image to extract the 'bottleneck' summary layer.
///
/// Current active TensorFlow Session.
- /// Path of raw JPEG data.
+ /// Data of raw JPEG data.
/// Input data layer in the graph.
/// Output of initial image resizing and preprocessing.
/// The input node of the recognition graph.