diff --git a/src/NnCase.Cli/Program.cs b/src/NnCase.Cli/Program.cs index 9360de31e6..af21ba57fd 100644 --- a/src/NnCase.Cli/Program.cs +++ b/src/NnCase.Cli/Program.cs @@ -3,11 +3,14 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Numerics.Tensors; using System.Threading.Tasks; using CommandLine; using NnCase.Converter.Converters; using NnCase.Converter.Data; using NnCase.Converter.Model; +using NnCase.Converter.Model.Layers; +using NnCase.Converter.Model.Layers.K210; using NnCase.Converter.Transforms; using NnCase.Converter.Transforms.K210; @@ -68,6 +71,28 @@ static async Task Main(string[] args) graph = tfc.Graph; break; } + case "test": + { + var inputs = new[] + { + new InputLayer(new[]{-1,3,8,8}){ Name ="input" } + }; + var conv2d = new K210Conv2d(inputs[0].Output.Dimensions, K210Conv2dType.Conv2d, + new DenseTensor(new[] { 32, 3, 3, 3 }), null, K210PoolType.None, ActivationFunctionType.Relu); + conv2d.Input.SetConnection(inputs[0].Output); + + var spconv2d = new K210SeparableConv2d(conv2d.Output.Dimensions, new DenseTensor(new[] { 1, 32, 3, 3, 3 }), + new DenseTensor(new[] { 32, 64, 1, 1 }), null, K210PoolType.LeftTop, ActivationFunctionType.Relu); + spconv2d.Input.SetConnection(conv2d.Output); + + var outputs = new[] + { + new OutputLayer(spconv2d.Output.Dimensions){Name = "output"} + }; + outputs[0].Input.SetConnection(spconv2d.Output); + graph = new Graph(inputs, outputs); + } + break; default: throw new ArgumentException("input-format"); } @@ -104,7 +129,7 @@ static async Task Main(string[] args) } Transform.Process(graph, new Transform[] { - new K210SeprableConv2dTransform(), + new K210SeparableConv2dTransform(), new K210SpaceToBatchNdAndValidConv2dTransform(), new K210SameConv2dTransform(), new K210Stride2Conv2dTransform(), @@ -131,6 +156,17 @@ await k210c.ConvertAsync(new ImageDataset( } break; } + case "k210script": + { + { + var dim = graph.Inputs.First().Output.Dimensions.ToArray(); + var k210c = new GraphToScriptConverter(graph); + await k210c.ConvertAsync( + Path.GetDirectoryName(options.Output), + Path.GetFileNameWithoutExtension(options.Output)); + } + break; + } default: throw new ArgumentException("output-format"); } diff --git a/src/NnCase.Converter/Converters.cs b/src/NnCase.Converter/Converters.cs index 70038c28c4..4c24b98c76 100644 --- a/src/NnCase.Converter/Converters.cs +++ b/src/NnCase.Converter/Converters.cs @@ -22,7 +22,7 @@ public static async Task ExportK210Code(string modelPath, string datasetDir, str tfc.Convert(); var graph = tfc.Graph; Transform.Process(graph, new Transform[] { - new K210SeprableConv2dTransform(), + new K210SeparableConv2dTransform(), new K210SpaceToBatchNdAndValidConv2dTransform(), new K210SameConv2dTransform(), new K210Stride2Conv2dTransform(), diff --git a/src/NnCase.Converter/Converters/GraphToScriptConverter.cs b/src/NnCase.Converter/Converters/GraphToScriptConverter.cs index a0de9c7f94..c5c197faaa 100644 --- a/src/NnCase.Converter/Converters/GraphToScriptConverter.cs +++ b/src/NnCase.Converter/Converters/GraphToScriptConverter.cs @@ -1,13 +1,78 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using NnCase.Converter.Model; +using NnCase.Converter.Model.Layers; +using NnCase.Converter.Model.Layers.K210; using RazorLight; namespace NnCase.Converter.Converters { + public abstract class ScriptLayerConfig + { + + } + + public class ScriptInputLayerConfig : ScriptLayerConfig + { + public string Name { get; set; } + + public long[] Dimensions { get; set; } + + public int Output { get; set; } + } + + public class ScriptOutputLayerConfig : ScriptLayerConfig + { + public string Name { get; set; } + + public int Input { get; set; } + } + + public class ScriptConv2dLayerConfig : ScriptLayerConfig + { + public int KernelSize { get; set; } + + public int Filters { get; set; } + + public int Stride { get; set; } + + public ActivationFunctionType Activation { get; set; } + + public int Input { get; set; } + + public int Output { get; set; } + } + + public class ScriptSeparableConv2dLayerConfig : ScriptLayerConfig + { + public int KernelSize { get; set; } + + public int Filters { get; set; } + + public int Stride { get; set; } + + public ActivationFunctionType Activation { get; set; } + + public int Input { get; set; } + + public int Output { get; set; } + } + + public class ScriptGenerationContext + { + public string Prefix { get; set; } + + public IReadOnlyList Layers { get; set; } + + public IReadOnlyDictionary Outputs { get; set; } + + public string OutputName { get; set; } + } + public class GraphToScriptConverter { private readonly Graph _graph; @@ -22,9 +87,132 @@ public GraphToScriptConverter(Graph graph) .Build(); } - public async Task ConvertAsync(Graph graph, string outputDir, string prefix) + public async Task ConvertAsync(string outputDir, string prefix) + { + var context = new ConvertContext(); + + foreach (var layer in _graph.Outputs) + ConvertLayer(layer, context); + + var scriptGenContext = new ScriptGenerationContext + { + Prefix = prefix, + Layers = context.Layers, + Outputs = context.Outputs, + OutputName = _graph.Outputs.First().Name + }; + var code = await _templateEngine.CompileRenderAsync("Model", scriptGenContext); + File.WriteAllText(Path.Combine(outputDir, $"{prefix}.py"), code); + } + + private void ConvertLayer(Layer layer, ConvertContext context) + { + if (!context.ProcessMap.GetValueOrDefault(layer)) + { + context.ProcessMap[layer] = true; + + foreach (var conn in layer.InputConnectors) + { + var nextLayer = conn.Connection?.From.Owner; + if (nextLayer != null) + ConvertLayer(nextLayer, context); + } + + switch (layer) + { + case InputLayer l: + ConvertInputLayer(l, context); + break; + case OutputLayer l: + ConvertOutputLayer(l, context); + break; + case K210Conv2d l: + ConvertK210Conv2d(l, context); + break; + case K210SeparableConv2d l: + ConvertK210SeparableConv2d(l, context); + break; + default: + throw new NotSupportedException(nameof(layer)); + } + } + } + + private void ConvertInputLayer(InputLayer layer, ConvertContext context) + { + context.Layers.Add(new ScriptInputLayerConfig + { + Name = layer.Name, + Dimensions = layer.Output.Dimensions.ToNHWC().ToArray(), + Output = context.AddOutput(layer.Output) + }); + } + + private void ConvertOutputLayer(OutputLayer layer, ConvertContext context) { + context.Layers.Add(new ScriptOutputLayerConfig + { + Name = layer.Name, + Input = context.Outputs[layer.Input.Connection.From] + }); + } + + private void ConvertK210Conv2d(K210Conv2d layer, ConvertContext context) + { + if (layer.Conv2dType != K210Conv2dType.Conv2d) + throw new NotSupportedException("Depthwise conv2d is not supported."); + + context.Layers.Add(new ScriptConv2dLayerConfig + { + KernelSize = layer.KernelWidth, + Stride = GetStride(layer.PoolType), + Filters = layer.OutputChannels, + Activation = layer.FusedActivationFunction, + Input = context.Outputs[layer.Input.Connection.From], + Output = context.AddOutput(layer.Output) + }); + } + + private void ConvertK210SeparableConv2d(K210SeparableConv2d layer, ConvertContext context) + { + context.Layers.Add(new ScriptSeparableConv2dLayerConfig + { + KernelSize = layer.KernelWidth, + Stride = GetStride(layer.PoolType), + Filters = layer.OutputChannels, + Activation = layer.FusedActivationFunction, + Input = context.Outputs[layer.Input.Connection.From], + Output = context.AddOutput(layer.Output) + }); + } + + private static int GetStride(K210PoolType poolType) + { + switch (poolType) + { + case K210PoolType.None: + return 1; + case K210PoolType.LeftTop: + return 2; + default: + throw new NotSupportedException(nameof(poolType)); + } + } + + private class ConvertContext + { + public Dictionary ProcessMap = new Dictionary(); + + public List Layers = new List(); + + public Dictionary Outputs = new Dictionary(); + public int AddOutput(OutputConnector output) + { + var id = Outputs.Count; + Outputs.Add(output, id); + return id; + } } } } diff --git a/src/NnCase.Converter/Model/Layers/K210/K210SeparableConv2d.cs b/src/NnCase.Converter/Model/Layers/K210/K210SeparableConv2d.cs index 2a7c56cc1b..1e476276a0 100644 --- a/src/NnCase.Converter/Model/Layers/K210/K210SeparableConv2d.cs +++ b/src/NnCase.Converter/Model/Layers/K210/K210SeparableConv2d.cs @@ -13,8 +13,6 @@ public class K210SeparableConv2d : Layer public OutputConnector Output { get; } - public K210Conv2dType Conv2dType { get; } - public Tensor DwWeights { get; } public Tensor PwWeights { get; } diff --git a/src/NnCase.Converter/NnCase.Converter.csproj b/src/NnCase.Converter/NnCase.Converter.csproj index e0890d3c9f..16f726b86a 100644 --- a/src/NnCase.Converter/NnCase.Converter.csproj +++ b/src/NnCase.Converter/NnCase.Converter.csproj @@ -12,8 +12,13 @@ $(DefineConstants);ENABLE_SPAN_T;UNSAFE_BYTEBUFFER;CHANNEL_WISE + + + + + @@ -34,8 +39,4 @@ - - - - diff --git a/src/NnCase.Converter/Templates/Script/Model.cshtml b/src/NnCase.Converter/Templates/Script/Model.cshtml new file mode 100644 index 0000000000..0b1483cd85 --- /dev/null +++ b/src/NnCase.Converter/Templates/Script/Model.cshtml @@ -0,0 +1,112 @@ +@using Microsoft.AspNetCore.Html +@using RazorLight +@using NnCase.Converter.Converters +@inherits TemplatePage +@functions { +string ToString(long[] dims) +{ + var sb = new System.Text.StringBuilder("["); + for (int i = 0; i < dims.Length; i++) + { + sb.Append(dims[i] == -1 ? "None" : dims[i].ToString()); + if (i != dims.Length - 1) + sb.Append(", "); + } + + sb.Append(']'); + return sb.ToString(); +} +} +import tensorflow as tf +save_dir = './@Model.Prefix/' + +def add_act(input, act): + if act == 'relu': + return tf.nn.relu(input) + elif act == 'relu6': + return tf.nn.relu6(input) + else: + return input + +def conv2d(input, kernel_size, filters, stride, act, is_training): + if stride == 2 and kernel_size == 3: + out = tf.space_to_batch_nd(input, [1, 1], [[1, 1], [1, 1]]) + out = tf.layers.conv2d(out, filters, kernel_size, [stride, stride], 'valid', use_bias=False) + else: + out = tf.layers.conv2d(input, filters, kernel_size, [stride, stride], 'same', use_bias=False) + out = tf.layers.batch_normalization(out, training=is_training) + return add_act(out, act) + +def separable_conv2d(input, kernel_size, filters, stride, act, is_training): + if stride == 2 and kernel_size == 3: + out = tf.space_to_batch_nd(input, [1, 1], [[1, 1], [1, 1]]) + out = tf.layers.separable_conv2d(out, filters, kernel_size, [stride, stride], 'valid', use_bias=False) + else: + out = tf.layers.separable_conv2d(input, filters, kernel_size, [stride, stride], 'same', use_bias=False) + out = tf.layers.batch_normalization(out, training=is_training) + return add_act(out, act) + +def model(is_training): + inputs = [] + outputs = [] + tensors = {} + +@for (int i = 0; i < Model.Layers.Count; i++) +{ + switch (Model.Layers[i]) + { + case ScriptInputLayerConfig l: + @:tensors[@l.Output] = tf.placeholder(tf.float32, @ToString(l.Dimensions), name='@l.Name') + @:inputs.append(tensors[@l.Output]) + break; + case ScriptOutputLayerConfig l: + @:outputs.append(tf.identity(tensors[@l.Input], name='@l.Name')) + break; + case ScriptConv2dLayerConfig l: + @:tensors[@l.Output] = conv2d(tensors[@l.Input], @l.KernelSize, @l.Filters, @l.Stride, '@l.Activation.ToString().ToLowerInvariant()', is_training) + break; + case ScriptSeparableConv2dLayerConfig l: + @:tensors[@l.Output] = separable_conv2d(tensors[@l.Input], @l.KernelSize, @l.Filters, @l.Stride, '@l.Activation.ToString().ToLowerInvariant()', is_training) + break; + default: + break; + } +} + + return { + 'inputs': inputs, + 'outputs': outputs + } + +def save(): + with tf.Session() as sess: + model(True) + + init_op = tf.global_variables_initializer() + saver = tf.train.Saver() + sess.run(init_op) + + save_path = saver.save(sess, save_dir + 'model.ckpt') + print("Model saved in path: %s" % save_path) + +def freeze(): + from tensorflow.python.tools import freeze_graph + + with tf.Session() as sess: + model(False) + # export graph + input_graph_name = '@(Model.Prefix).pb' + tf.train.write_graph(sess.graph, save_dir, input_graph_name, as_text=False) + + checkpoint_path = tf.train.latest_checkpoint(save_dir) + # freeze graph + input_graph_path = save_dir + input_graph_name + input_saver_def_path = '' + input_binary = True + output_node_names = '@Model.OutputName' + restore_op_name = 'save/restore_all' + filename_tensor_name = 'save/Const:0' + output_graph_path = save_dir + '@(Model.Prefix).pb' + clear_devices = False + freeze_graph.freeze_graph(input_graph_path, input_saver_def_path, input_binary, checkpoint_path, output_node_names, + restore_op_name, filename_tensor_name, output_graph_path, clear_devices, '') \ No newline at end of file diff --git a/src/NnCase.Converter/Transforms/K210/K210SeprableConv2dTransform.cs b/src/NnCase.Converter/Transforms/K210/K210SeparableConv2dTransform.cs similarity index 98% rename from src/NnCase.Converter/Transforms/K210/K210SeprableConv2dTransform.cs rename to src/NnCase.Converter/Transforms/K210/K210SeparableConv2dTransform.cs index 5fbce03ae5..3397e50612 100644 --- a/src/NnCase.Converter/Transforms/K210/K210SeprableConv2dTransform.cs +++ b/src/NnCase.Converter/Transforms/K210/K210SeparableConv2dTransform.cs @@ -8,7 +8,7 @@ namespace NnCase.Converter.Transforms.K210 { - public class K210SeprableConv2dTransform : Transform + public class K210SeparableConv2dTransform : Transform { protected override bool OnTryMatch(Layer layer, TransformContext context) { @@ -73,7 +73,6 @@ public override void Process(TransformContext context) var output = conv2d.Output; space.Input.ClearConnection(); - var newDwConv2d = new DepthwiseConv2d(input.Dimensions, dwConv2d.Weights, dwConv2d.Bias, Padding.Same, 1, 1, dwConv2d.FusedActivationFunction); var newConv2d = new K210Conv2d(newDwConv2d.Output.Dimensions, K210Conv2dType.Conv2d, conv2d.Weights, conv2d.Bias, K210PoolType.LeftTop, conv2d.FusedActivationFunction); diff --git a/src/NnCase.Designer/Modules/ModelDesigner/ViewModels/GraphViewModel.cs b/src/NnCase.Designer/Modules/ModelDesigner/ViewModels/GraphViewModel.cs index 67881490cc..915dc782a9 100644 --- a/src/NnCase.Designer/Modules/ModelDesigner/ViewModels/GraphViewModel.cs +++ b/src/NnCase.Designer/Modules/ModelDesigner/ViewModels/GraphViewModel.cs @@ -1,14 +1,17 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; +using NnCase.Converter.Converters; using NnCase.Converter.Model; using NnCase.Converter.Model.Layers; using NnCase.Designer.Modules.Inspector; using NnCase.Designer.Modules.ModelDesigner.ViewModels.Layers; +using Ookii.Dialogs.Wpf; using Splat; namespace NnCase.Designer.Modules.ModelDesigner.ViewModels @@ -29,7 +32,7 @@ public GraphViewModel(string title) _inspectorTool = Locator.Current.GetService(); } - public void Build(BuildGraphContext context) + public async void Build(BuildGraphContext context) { var outputLayers = Layers.OfType().ToList(); if (outputLayers.Count == 0) @@ -38,13 +41,37 @@ public void Build(BuildGraphContext context) } else { - foreach (var layer in outputLayers) - layer.BuildModel(context); - foreach (var layer in outputLayers) - layer.BuildConnections(context); - - context.Graph = new Graph(context.Layers.Values.OfType().ToList(), - context.Layers.Values.OfType().ToList()); + try + { + foreach (var layer in outputLayers) + layer.BuildModel(context); + foreach (var layer in outputLayers) + layer.BuildConnections(context); + + context.Graph = new Graph(context.Layers.Values.SelectMany(x => x).OfType().ToList(), + context.Layers.Values.SelectMany(x => x).OfType().ToList()); + + var dlg = new VistaSaveFileDialog + { + Title = "Save script file", + Filter = "Python script (*.py)|*.py", + ValidateNames = true + }; + + if (dlg.ShowDialog() == true) + { + var k210c = new GraphToScriptConverter(context.Graph); + await k210c.ConvertAsync( + Path.GetDirectoryName(dlg.FileName), + Path.GetFileNameWithoutExtension(dlg.FileName)); + } + + MessageBox.Show("Export completed.", "NnCase", MessageBoxButton.OK, MessageBoxImage.Information); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "NnCase", MessageBoxButton.OK, MessageBoxImage.Error); + } } } @@ -108,7 +135,7 @@ internal void OnConnectionDragCompleted(Point currentDragPoint, ConnectionViewMo var existingConnection = nearbyConnector.Connection; if (existingConnection != null) Connections.Remove(existingConnection); - + newConnection.To = nearbyConnector; } } diff --git a/src/NnCase.Designer/Modules/ModelDesigner/ViewModels/Layers/InputLayerViewModel.cs b/src/NnCase.Designer/Modules/ModelDesigner/ViewModels/Layers/InputLayerViewModel.cs index ee2c124eb8..1318e1b46c 100644 --- a/src/NnCase.Designer/Modules/ModelDesigner/ViewModels/Layers/InputLayerViewModel.cs +++ b/src/NnCase.Designer/Modules/ModelDesigner/ViewModels/Layers/InputLayerViewModel.cs @@ -62,7 +62,7 @@ private void UpdateOutput() protected override void BuildModelCore(BuildGraphContext context) { - var model = new InputLayer(Output.Dimensions); + var model = new InputLayer(Output.Dimensions) { Name = Name }; context.OutputConnectors[Output] = model.Output; context.Layers[this] = new[] { model }; } diff --git a/src/NnCase.Designer/Modules/ModelDesigner/ViewModels/Layers/K210/K210Conv2dViewModel.cs b/src/NnCase.Designer/Modules/ModelDesigner/ViewModels/Layers/K210/K210Conv2dViewModel.cs index acdf3e7a9c..e80d0a218c 100644 --- a/src/NnCase.Designer/Modules/ModelDesigner/ViewModels/Layers/K210/K210Conv2dViewModel.cs +++ b/src/NnCase.Designer/Modules/ModelDesigner/ViewModels/Layers/K210/K210Conv2dViewModel.cs @@ -33,7 +33,7 @@ public class K210Conv2dViewModel : LayerViewModel public OutputConnectorViewModel Output { get; } - private ActivationFunctionType _activation; + private ActivationFunctionType _activation = ActivationFunctionType.Relu; public ActivationFunctionType Activation { get => _activation; diff --git a/src/NnCase.Designer/Modules/ModelDesigner/ViewModels/Layers/K210/K210SeparableConv2dViewModel.cs b/src/NnCase.Designer/Modules/ModelDesigner/ViewModels/Layers/K210/K210SeparableConv2dViewModel.cs index b8d782407e..4625b9fe43 100644 --- a/src/NnCase.Designer/Modules/ModelDesigner/ViewModels/Layers/K210/K210SeparableConv2dViewModel.cs +++ b/src/NnCase.Designer/Modules/ModelDesigner/ViewModels/Layers/K210/K210SeparableConv2dViewModel.cs @@ -16,7 +16,7 @@ public class K210SeparableConv2dViewModel : LayerViewModel public OutputConnectorViewModel Output { get; } - private ActivationFunctionType _activation; + private ActivationFunctionType _activation = ActivationFunctionType.Relu; public ActivationFunctionType Activation { get => _activation; diff --git a/src/NnCase.Designer/Modules/ModelDesigner/ViewModels/Layers/OutputLayerViewModel.cs b/src/NnCase.Designer/Modules/ModelDesigner/ViewModels/Layers/OutputLayerViewModel.cs index abdef6620a..6972f4ee8c 100644 --- a/src/NnCase.Designer/Modules/ModelDesigner/ViewModels/Layers/OutputLayerViewModel.cs +++ b/src/NnCase.Designer/Modules/ModelDesigner/ViewModels/Layers/OutputLayerViewModel.cs @@ -18,7 +18,7 @@ public OutputLayerViewModel() protected override void BuildModelCore(BuildGraphContext context) { - var model = new OutputLayer(Input.Connection.From.Dimensions); + var model = new OutputLayer(Input.Connection.From.Dimensions) { Name = Name }; context.InputConnectors[Input] = model.Input; context.Layers[this] = new[] { model }; }