From 13910860907908f03ad3b918b35d4eab3cdffd51 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Wed, 2 Oct 2019 12:23:44 -0700 Subject: [PATCH 1/8] exploration code for C# or NOT --- ResNetBlock/CSharpOrNot.cs | 174 +++++++++++++++++++++++++++++ ResNetBlock/ResNetBlock.csproj | 3 +- ResNetBlock/ResNetSampleProgram.cs | 2 +- 3 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 ResNetBlock/CSharpOrNot.cs diff --git a/ResNetBlock/CSharpOrNot.cs b/ResNetBlock/CSharpOrNot.cs new file mode 100644 index 0000000..a1c5805 --- /dev/null +++ b/ResNetBlock/CSharpOrNot.cs @@ -0,0 +1,174 @@ +namespace Gradient.Samples { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Drawing; + using System.Drawing.Imaging; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using System.Text.RegularExpressions; + using static System.Linq.Enumerable; + + static class CSharpOrNot + { + const int Width = 64, Height = 64; + const int CharWidth = 10, CharHeight = 16; + // being opinionated here + const string Tab = " "; + const char Whitespace = '\u00FF'; + + public static void Run(string directory) { + var filesByExtension = ReadCodeFiles(directory, + includeExtensions: IncludeExtensions, + codeFilter: lines => lines.Length >= 10); + + var random = new Random(); + + var blockSize = new Size(Width, Height); + + byte[] codeBytes = new byte[Width*Height]; + var renderTarget = new Bitmap(Width, Height, PixelFormat.Format8bppIndexed); + SetGrayscalePalette(renderTarget); + var output = new Bitmap(Width * CharWidth, Height * CharHeight, PixelFormat.Format8bppIndexed); + SetGrayscalePalette(output); + + int extensionIndex = random.Next(filesByExtension.Length); + while (true) { + var files = filesByExtension[extensionIndex]; + int fileIndex = random.Next(files.Count); + string[] lines = files[fileIndex]; + int y = random.Next(Math.Max(1, lines.Length - Height)); + int x = random.Next(Math.Max(1, (int)lines.Average(line => line.Length) - Width)); + Render(lines, new Point(x, y), blockSize, destination: codeBytes); + ToBitmap(codeBytes, target: renderTarget); + Upscale(renderTarget, output); + break; + } + + output.Save("code.png", ImageFormat.Png); + var startInfo = new ProcessStartInfo(Path.GetFullPath("code.png")) { + UseShellExecute = true, + Verb = "open", + }; + Process.Start(startInfo); + } + + static List[] ReadCodeFiles(string directory, string[] includeExtensions, + Func codeFilter) { + var files = Range(0, includeExtensions.Length) + .Select(_ => new List()) + .ToArray(); + + foreach (string filePath in Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories)) { + string extension = Path.GetExtension(filePath); + int extensionIndex = Array.IndexOf(includeExtensions, extension); + if (extensionIndex < 0) continue; + string[] code = ReadCode(filePath); + if (!codeFilter(code)) continue; + files[extensionIndex].Add(code); + } + + return files; + } + + static string[] ReadCode(string path) + => File.ReadAllLines(path) + .Select(line => line.Replace("\t", Tab)) + // replace non-ASCII characters with underscore + .Select(line => Regex.Replace(line, @"[^\u0000-\u007F]", "_")) + // make all whitespace stand out + .Select(line => Regex.Replace(line, @"[\u0000-\u0020]", Whitespace.ToString())) + .ToArray(); + + static void Render(string[] lines, Point startingPoint, Size size, byte[] destination) { + if (size.IsEmpty) throw new ArgumentException(); + if (destination.Length < size.Width * size.Height) throw new ArgumentException(); + if (startingPoint.Y >= lines.Length) throw new ArgumentException(); + + for (int y = 0; y < size.Height; y++) { + int sourceY = y + startingPoint.Y; + int destOffset = y * size.Width; + if (sourceY >= lines.Length) { + Array.Fill(destination, (byte)255, + startIndex: destOffset, + count: size.Width*size.Height - destOffset); + return; + } + + for (int x = 0; x < size.Width; x++) { + int sourceX = x + startingPoint.X; + if (sourceX >= lines[sourceY].Length) { + Array.Fill(destination, (byte)255, + startIndex: destOffset, + count: size.Width - x); + break; + } + + destination[destOffset] = (byte)lines[sourceY][sourceX]; + destOffset++; + } + } + } + + static void ToBitmap(byte[] brightness, Bitmap target) { + if (target.PixelFormat != PixelFormat.Format8bppIndexed) + throw new NotSupportedException("The only supported pixel format is " + PixelFormat.Format8bppIndexed); + + var bitmapData = target.LockBits(new Rectangle(new Point(), target.Size), + ImageLockMode.WriteOnly, + PixelFormat.Format8bppIndexed); + + Marshal.Copy(source: brightness, + startIndex: 0, length: bitmapData.Width * bitmapData.Height, + destination: bitmapData.Scan0); + + target.UnlockBits(bitmapData); + } + + static void Upscale(Bitmap source, Bitmap target) { + if (target.Width % source.Width != 0 || target.Height % source.Height != 0) + throw new ArgumentException(); + + int scaleY = target.Height / source.Height; + int scaleX = target.Width / source.Width; + + var sourceData = source.LockBits(new Rectangle(new Point(), source.Size), + ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed); + var targetData = target.LockBits(new Rectangle(new Point(), target.Size), + ImageLockMode.WriteOnly, + PixelFormat.Format8bppIndexed); + + for (int sourceY = 0; sourceY < sourceData.Height; sourceY++) + for (int sourceX = 0; sourceX < sourceData.Width; sourceX++) { + byte brightness = Marshal.ReadByte(sourceData.Scan0, sourceY * sourceData.Width + sourceX); + for(int targetY = sourceY*scaleY; targetY < (sourceY+1)*scaleY; targetY++) + for(int targetX = sourceX*scaleX; targetX < (sourceX+1)*scaleX; targetX++) + Marshal.WriteByte(targetData.Scan0, targetY*targetData.Width+targetX, brightness); + } + } + + static void SetGrayscalePalette(Bitmap bitmap) { + ColorPalette pal = bitmap.Palette; + + for (int i = 0; i < 256; i++) { + pal.Entries[i] = Color.FromArgb(255, i, i, i); + } + + bitmap.Palette = pal; + } + + static readonly string[] IncludeExtensions = { + ".cs", + ".dart", + ".go", + ".java", + ".hs", // Haskell + ".m", // Objective-C + ".py", + ".rs", // Rust + ".u", // Unison + ".xml", + }; + } +} diff --git a/ResNetBlock/ResNetBlock.csproj b/ResNetBlock/ResNetBlock.csproj index 0efa9e7..5e129a8 100644 --- a/ResNetBlock/ResNetBlock.csproj +++ b/ResNetBlock/ResNetBlock.csproj @@ -3,11 +3,12 @@ Exe netcoreapp2.0 - latest + 8.0 Gradient.Samples + diff --git a/ResNetBlock/ResNetSampleProgram.cs b/ResNetBlock/ResNetSampleProgram.cs index 53759c5..4739387 100644 --- a/ResNetBlock/ResNetSampleProgram.cs +++ b/ResNetBlock/ResNetSampleProgram.cs @@ -46,7 +46,7 @@ static void Main() { Console.Title = nameof(ResNetSampleProgram); GradientLog.OutputWriter = Console.Out; GradientSetup.UseEnvironmentFromVariable(); - Run(); + CSharpOrNot.Run("."); } } } From 782769414ae8878c469c816f14dd76215c5d2722 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Thu, 3 Oct 2019 19:07:43 -0700 Subject: [PATCH 2/8] quite a bit of refactoring in CSharpOrNot --- ResNetBlock/CSharpOrNot.cs | 237 ++++++++++++++++++++++++++------- ResNetBlock/ResNetBlock.cs | 25 +++- ResNetBlock/ResNetBlock.csproj | 2 +- 3 files changed, 208 insertions(+), 56 deletions(-) diff --git a/ResNetBlock/CSharpOrNot.cs b/ResNetBlock/CSharpOrNot.cs index a1c5805..638a907 100644 --- a/ResNetBlock/CSharpOrNot.cs +++ b/ResNetBlock/CSharpOrNot.cs @@ -8,50 +8,170 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text.RegularExpressions; + using numpy; + using SharPy.Runtime; + using tensorflow; + using tensorflow.keras; + using tensorflow.keras.callbacks; + using tensorflow.keras.layers; + using tensorflow.keras.optimizers; using static System.Linq.Enumerable; - static class CSharpOrNot + public static class CSharpOrNot { - const int Width = 64, Height = 64; - const int CharWidth = 10, CharHeight = 16; + public static Model CreateModel(int classCount) { + var activation = tf.keras.activations.selu_fn; + const int filterCount = 32; + int[] resNetFilters = {filterCount, filterCount, filterCount}; + var model = new Sequential(new Layer[] { + Conv2D.NewDyn(filters: filterCount, kernel_size: 5, padding: "same", activation: activation), + new MaxPool2D(pool_size: 2), + new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), + new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), + new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), + new AvgPool2D(pool_size: 2), + new Flatten(), + new Dense(units: 32, activation: activation, activity_regularizer: tf.keras.regularizers.l2()), + new Dense(units: classCount, activation: tf.nn.softmax_fn), + }); + + model.compile( + optimizer: new Adam(kwargs: new PythonDict { + ["clipnorm"] = 1.0f, + }), + loss: "sparse_categorical_crossentropy", + metrics: new dynamic[] { "accuracy" }); + + return model; + } + + const int Epochs = 20; + const int BatchSize = 1000; + public const int Width = 64, Height = 64; + public static readonly Size Size = new Size(Width, Height); // being opinionated here const string Tab = " "; const char Whitespace = '\u00FF'; + const float ValidationSplit = 0.1f, TestSplit = 0.1f; + const int TrainingSamples = 200000, TestSamples = 10000, ValidationSamples = 10000; + const int SamplePart = 10; public static void Run(string directory) { + GradientSetup.EnsureInitialized(); + var filesByExtension = ReadCodeFiles(directory, includeExtensions: IncludeExtensions, codeFilter: lines => lines.Length >= 10); - var random = new Random(); - - var blockSize = new Size(Width, Height); - - byte[] codeBytes = new byte[Width*Height]; - var renderTarget = new Bitmap(Width, Height, PixelFormat.Format8bppIndexed); - SetGrayscalePalette(renderTarget); - var output = new Bitmap(Width * CharWidth, Height * CharHeight, PixelFormat.Format8bppIndexed); - SetGrayscalePalette(output); - - int extensionIndex = random.Next(filesByExtension.Length); - while (true) { - var files = filesByExtension[extensionIndex]; - int fileIndex = random.Next(files.Count); - string[] lines = files[fileIndex]; - int y = random.Next(Math.Max(1, lines.Length - Height)); - int x = random.Next(Math.Max(1, (int)lines.Average(line => line.Length) - Width)); - Render(lines, new Point(x, y), blockSize, destination: codeBytes); - ToBitmap(codeBytes, target: renderTarget); - Upscale(renderTarget, output); - break; + var random = new Random(42); + tf.set_random_seed(42); + + var test = Split(random, filesByExtension, TestSplit, out filesByExtension); + + byte[] trainData = Sample(random, filesByExtension, Size, TrainingSamples/SamplePart, + out int[] trainValues); + byte[] testData = Sample(random, test, Size, TestSamples / SamplePart, + out int[] testValues); + + //var checkpoint = new ModelCheckpoint( + // filepath: Path.Combine(Environment.CurrentDirectory, "weights.{epoch:02d}-{val_loss:.4f}.hdf5"), + // save_best_only: true, save_weights_only: true); + var checkpointBest = new ModelCheckpoint( + filepath: Path.Combine(Environment.CurrentDirectory, "weights.best.hdf5"), + save_best_only: true, save_weights_only: true); + + Console.Write("loading data to TensorFlow..."); + var timer = Stopwatch.StartNew(); + ndarray @in = InputToNumPy(trainData, sampleCount: TrainingSamples / SamplePart, + width: Width, height: Height); + ndarray expectedOut = OutputToNumPy(trainValues); + Console.WriteLine($"OK in {timer.ElapsedMilliseconds / 1000}s"); + + var model = CreateModel(classCount: IncludeExtensions.Length); + model.fit_dyn(@in, expectedOut, epochs: Epochs, shuffle: false, batch_size: BatchSize, + validation_split: 0.1, callbacks: new[] { checkpointBest }); + + model.summary(); + + var fromCheckpoint = CreateModel(classCount: IncludeExtensions.Length); + fromCheckpoint.load_weights(Path.GetFullPath("weights.best.hdf5")); + + @in = InputToNumPy(testData, sampleCount: TestSamples/SamplePart, width: Width, height: Height); + expectedOut = OutputToNumPy(testValues); + + var evaluationResults = fromCheckpoint.evaluate(@in, expectedOut); + Console.WriteLine($"reloaded: loss: {evaluationResults[0]} acc: {evaluationResults[1]}"); + + evaluationResults = model.evaluate(@in, expectedOut); + Console.WriteLine($"original: loss: {evaluationResults[0]} acc: {evaluationResults[1]}"); + } + + public static ndarray InputToNumPy(byte[] inputs, int sampleCount, int width, int height) + => (dynamic)inputs.Select(b => (float)b).ToArray().NumPyCopy() + .reshape(new[] { sampleCount, height, width, 1 }) / 255.0f; + static ndarray OutputToNumPy(int[] expectedOutputs) + => expectedOutputs.ToNumPyArray(); + + static byte[] Sample(Random random, List[] filesByExtension, Size size, int count, + out int[] extensionIndices) { + byte[] result = new byte[count * size.Width * size.Height]; + byte[] sampleBytes = new byte[size.Width * size.Height]; + extensionIndices = new int[count]; + for (int sampleIndex = 0; sampleIndex < count; sampleIndex++) { + Sample(random, filesByExtension, size, sampleBytes, out extensionIndices[sampleIndex]); + Array.Copy(sampleBytes, sourceIndex: 0, length: sampleBytes.Length, + destinationArray: result, destinationIndex: sampleIndex*sampleBytes.Length); } - output.Save("code.png", ImageFormat.Png); - var startInfo = new ProcessStartInfo(Path.GetFullPath("code.png")) { - UseShellExecute = true, - Verb = "open", - }; - Process.Start(startInfo); + return result; + } + + static List[] Split(Random random, List[] filesByExtension, float ratio, + out List[] rest) + { + var result = new List[filesByExtension.Length]; + rest = new List[filesByExtension.Length]; + for (int extensionIndex = 0; extensionIndex < filesByExtension.Length; extensionIndex++) + { + result[extensionIndex] = Split(random, filesByExtension[extensionIndex], ratio, + out rest[extensionIndex]); + } + return result; + } + + static List Split(Random random, ICollection collection, float ratio, out List rest) { + if (ratio >= 1 || ratio <= 0) throw new ArgumentOutOfRangeException(nameof(ratio)); + int resultLength = (int)(collection.Count * ratio); + if (resultLength == 0 || resultLength == collection.Count) + throw new ArgumentException(); + + var array = collection.ToArray(); + random.Shuffle(array); + + rest = array.Skip(resultLength).ToList(); + return array.Take(resultLength).ToList(); + } + + static void Shuffle(this Random rng, T[] array) { + int n = array.Length; + while (n > 1) { + int k = rng.Next(n--); + T temp = array[n]; + array[n] = array[k]; + array[k] = temp; + } + } + + static void Sample(Random random, List[] filesByExtension, Size blockSize, + byte[] target, out int extensionIndex) + { + extensionIndex = random.Next(filesByExtension.Length); + var files = filesByExtension[extensionIndex]; + int fileIndex = random.Next(files.Count); + string[] lines = files[fileIndex]; + int y = random.Next(Math.Max(1, lines.Length - 1)); + int x = random.Next(Math.Max(1, lines[y].Length)); + Render(lines, new Point(x, y), blockSize, destination: target); } static List[] ReadCodeFiles(string directory, string[] includeExtensions, @@ -72,7 +192,7 @@ static List[] ReadCodeFiles(string directory, string[] includeExtensio return files; } - static string[] ReadCode(string path) + public static string[] ReadCode(string path) => File.ReadAllLines(path) .Select(line => line.Replace("\t", Tab)) // replace non-ASCII characters with underscore @@ -81,7 +201,7 @@ static string[] ReadCode(string path) .Select(line => Regex.Replace(line, @"[\u0000-\u0020]", Whitespace.ToString())) .ToArray(); - static void Render(string[] lines, Point startingPoint, Size size, byte[] destination) { + public static void Render(string[] lines, Point startingPoint, Size size, byte[] destination) { if (size.IsEmpty) throw new ArgumentException(); if (destination.Length < size.Width * size.Height) throw new ArgumentException(); if (startingPoint.Y >= lines.Length) throw new ArgumentException(); @@ -111,7 +231,7 @@ static void Render(string[] lines, Point startingPoint, Size size, byte[] destin } } - static void ToBitmap(byte[] brightness, Bitmap target) { + public static void ToBitmap(byte[] brightness, Bitmap target) { if (target.PixelFormat != PixelFormat.Format8bppIndexed) throw new NotSupportedException("The only supported pixel format is " + PixelFormat.Format8bppIndexed); @@ -119,14 +239,16 @@ static void ToBitmap(byte[] brightness, Bitmap target) { ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); - Marshal.Copy(source: brightness, - startIndex: 0, length: bitmapData.Width * bitmapData.Height, - destination: bitmapData.Scan0); - - target.UnlockBits(bitmapData); + try { + Marshal.Copy(source: brightness, + startIndex: 0, length: bitmapData.Width * bitmapData.Height, + destination: bitmapData.Scan0); + } finally { + target.UnlockBits(bitmapData); + } } - static void Upscale(Bitmap source, Bitmap target) { + public static void Upscale(Bitmap source, Bitmap target) { if (target.Width % source.Width != 0 || target.Height % source.Height != 0) throw new ArgumentException(); @@ -135,20 +257,35 @@ static void Upscale(Bitmap source, Bitmap target) { var sourceData = source.LockBits(new Rectangle(new Point(), source.Size), ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed); - var targetData = target.LockBits(new Rectangle(new Point(), target.Size), - ImageLockMode.WriteOnly, - PixelFormat.Format8bppIndexed); + try { + var targetData = target.LockBits(new Rectangle(new Point(), target.Size), + ImageLockMode.WriteOnly, + PixelFormat.Format8bppIndexed); - for (int sourceY = 0; sourceY < sourceData.Height; sourceY++) - for (int sourceX = 0; sourceX < sourceData.Width; sourceX++) { - byte brightness = Marshal.ReadByte(sourceData.Scan0, sourceY * sourceData.Width + sourceX); - for(int targetY = sourceY*scaleY; targetY < (sourceY+1)*scaleY; targetY++) - for(int targetX = sourceX*scaleX; targetX < (sourceX+1)*scaleX; targetX++) - Marshal.WriteByte(targetData.Scan0, targetY*targetData.Width+targetX, brightness); + try { + for (int sourceY = 0; sourceY < sourceData.Height; sourceY++) + for (int sourceX = 0; sourceX < sourceData.Width; sourceX++) { + byte brightness = Marshal.ReadByte(sourceData.Scan0, + sourceY * sourceData.Width + sourceX); + for (int targetY = sourceY * scaleY; + targetY < (sourceY + 1) * scaleY; + targetY++) + for (int targetX = sourceX * scaleX; + targetX < (sourceX + 1) * scaleX; + targetX++) + Marshal.WriteByte(targetData.Scan0, + targetY * targetData.Width + targetX, + brightness); + } + } finally { + target.UnlockBits(targetData); + } + } finally { + source.UnlockBits(sourceData); } } - static void SetGrayscalePalette(Bitmap bitmap) { + public static void SetGrayscalePalette(Bitmap bitmap) { ColorPalette pal = bitmap.Palette; for (int i = 0; i < 256; i++) { @@ -158,7 +295,7 @@ static void SetGrayscalePalette(Bitmap bitmap) { bitmap.Palette = pal; } - static readonly string[] IncludeExtensions = { + public static readonly string[] IncludeExtensions = { ".cs", ".dart", ".go", diff --git a/ResNetBlock/ResNetBlock.cs b/ResNetBlock/ResNetBlock.cs index 820f636..7bbaa3e 100644 --- a/ResNetBlock/ResNetBlock.cs +++ b/ResNetBlock/ResNetBlock.cs @@ -11,13 +11,18 @@ class ResNetBlock: Model { const int PartCount = 3; readonly PythonList convs = new PythonList(); readonly PythonList batchNorms = new PythonList(); - public ResNetBlock(int kernelSize, int[] filters) { + readonly PythonFunctionContainer activation; + readonly int outputChannels; + public ResNetBlock(int kernelSize, int[] filters, PythonFunctionContainer activation = null) { + this.activation = activation ?? tf.keras.activations.relu_fn; for (int part = 0; part < PartCount; part++) { this.convs.Add(this.Track(part == 1 - ? Conv2D.NewDyn(filters[part], kernel_size: kernelSize, padding: "same") + ? Conv2D.NewDyn(filters: filters[part], kernel_size: kernelSize, padding: "same") : Conv2D.NewDyn(filters[part], kernel_size: (1, 1)))); this.batchNorms.Add(this.Track(new BatchNormalization())); } + + this.outputChannels = filters[PartCount - 1]; } public override dynamic call(IEnumerable inputs, ImplicitContainer training, IGraphNodeBase mask) { @@ -43,12 +48,22 @@ object callImpl(IGraphNodeBase inputs, dynamic training) { result = this.convs[part].apply(result); result = this.batchNorms[part].apply(result, kwargs: batchNormExtraArgs); if (part + 1 != PartCount) - result = tf.nn.relu(result); + result = ((dynamic)this.activation)(result); } - result += (Tensor)result + inputs; + result = (Tensor)result + inputs; + + return ((dynamic)this.activation)(result); + } + + public override dynamic compute_output_shape(TensorShape input_shape) { + if (input_shape.ndims == 4) { + var outputShape = input_shape.as_list(); + outputShape[3] = this.outputChannels; + return new TensorShape(outputShape); + } - return tf.nn.relu(result); + return input_shape; } } } diff --git a/ResNetBlock/ResNetBlock.csproj b/ResNetBlock/ResNetBlock.csproj index 5e129a8..4e6f367 100644 --- a/ResNetBlock/ResNetBlock.csproj +++ b/ResNetBlock/ResNetBlock.csproj @@ -8,7 +8,7 @@ - + From 3934f3ce5567346056ed908cc8b701aa6265e664 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Thu, 3 Oct 2019 19:51:19 -0700 Subject: [PATCH 3/8] stable training with 1.14-gpu --- ResNetBlock/CSharpOrNot.cs | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/ResNetBlock/CSharpOrNot.cs b/ResNetBlock/CSharpOrNot.cs index 638a907..4df8458 100644 --- a/ResNetBlock/CSharpOrNot.cs +++ b/ResNetBlock/CSharpOrNot.cs @@ -20,32 +20,31 @@ public static class CSharpOrNot { public static Model CreateModel(int classCount) { - var activation = tf.keras.activations.selu_fn; + var activation = tf.keras.activations.elu_fn; const int filterCount = 32; int[] resNetFilters = {filterCount, filterCount, filterCount}; var model = new Sequential(new Layer[] { - Conv2D.NewDyn(filters: filterCount, kernel_size: 5, padding: "same", activation: activation), + Conv2D.NewDyn(filters: filterCount, kernel_size: 5, padding: "same"), + Activation.NewDyn(activation), new MaxPool2D(pool_size: 2), new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), new AvgPool2D(pool_size: 2), new Flatten(), - new Dense(units: 32, activation: activation, activity_regularizer: tf.keras.regularizers.l2()), - new Dense(units: classCount, activation: tf.nn.softmax_fn), + new Dense(units: 32, activation: activation), + new Dense(units: classCount, activation: activation), }); model.compile( - optimizer: new Adam(kwargs: new PythonDict { - ["clipnorm"] = 1.0f, - }), - loss: "sparse_categorical_crossentropy", + optimizer: new Adam(epsilon: 1e-5), + loss: tf.keras.losses.mean_squared_error_fn, metrics: new dynamic[] { "accuracy" }); return model; } - const int Epochs = 20; + const int Epochs = 100; const int BatchSize = 1000; public const int Width = 64, Height = 64; public static readonly Size Size = new Size(Width, Height); @@ -84,16 +83,20 @@ public static void Run(string directory) { var timer = Stopwatch.StartNew(); ndarray @in = InputToNumPy(trainData, sampleCount: TrainingSamples / SamplePart, width: Width, height: Height); - ndarray expectedOut = OutputToNumPy(trainValues); + ndarray expectedOut = OutputToNumPy(trainValues); Console.WriteLine($"OK in {timer.ElapsedMilliseconds / 1000}s"); var model = CreateModel(classCount: IncludeExtensions.Length); - model.fit_dyn(@in, expectedOut, epochs: Epochs, shuffle: false, batch_size: BatchSize, - validation_split: 0.1, callbacks: new[] { checkpointBest }); - + model.build(new TensorShape(null, Height, Width, 1)); model.summary(); + model.fit_dyn(@in, expectedOut, epochs: Epochs, shuffle: false, batch_size: BatchSize, + validation_split: 0.1, callbacks: new ICallback[] { + //checkpointBest + }, + verbose: TrainingVerbosity.LinePerEpoch); var fromCheckpoint = CreateModel(classCount: IncludeExtensions.Length); + fromCheckpoint.build(new TensorShape(null, Height, Width, 1)); fromCheckpoint.load_weights(Path.GetFullPath("weights.best.hdf5")); @in = InputToNumPy(testData, sampleCount: TestSamples/SamplePart, width: Width, height: Height); @@ -109,8 +112,9 @@ public static void Run(string directory) { public static ndarray InputToNumPy(byte[] inputs, int sampleCount, int width, int height) => (dynamic)inputs.Select(b => (float)b).ToArray().NumPyCopy() .reshape(new[] { sampleCount, height, width, 1 }) / 255.0f; - static ndarray OutputToNumPy(int[] expectedOutputs) - => expectedOutputs.ToNumPyArray(); + static ndarray OutputToNumPy(int[] expectedOutputs) + => (ndarray)np.eye(IncludeExtensions.Length, dtype: PythonClassContainer.Instance) + .__getitem__(expectedOutputs.ToNumPyArray().reshape(-1)); static byte[] Sample(Random random, List[] filesByExtension, Size size, int count, out int[] extensionIndices) { From bd1c37b2e5b5a87d26a6dac259560faaf0676ff2 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Fri, 4 Oct 2019 23:46:26 -0700 Subject: [PATCH 4/8] CSharp or NOT v3 model code --- ResNetBlock/CSharpOrNot.cs | 183 ++++++++++++++++++++++------- ResNetBlock/ResNetSampleProgram.cs | 4 +- 2 files changed, 141 insertions(+), 46 deletions(-) diff --git a/ResNetBlock/CSharpOrNot.cs b/ResNetBlock/CSharpOrNot.cs index 4df8458..dfd5434 100644 --- a/ResNetBlock/CSharpOrNot.cs +++ b/ResNetBlock/CSharpOrNot.cs @@ -7,10 +7,12 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; + using System.Text; using System.Text.RegularExpressions; using numpy; using SharPy.Runtime; using tensorflow; + using tensorflow.core.protobuf.config_pb2; using tensorflow.keras; using tensorflow.keras.callbacks; using tensorflow.keras.layers; @@ -21,49 +23,62 @@ public static class CSharpOrNot { public static Model CreateModel(int classCount) { var activation = tf.keras.activations.elu_fn; - const int filterCount = 32; - int[] resNetFilters = {filterCount, filterCount, filterCount}; + const int filterCount = 8; + int[] resNetFilters = { filterCount, filterCount, filterCount }; var model = new Sequential(new Layer[] { + new Dropout(rate: 0.05), Conv2D.NewDyn(filters: filterCount, kernel_size: 5, padding: "same"), Activation.NewDyn(activation), new MaxPool2D(pool_size: 2), new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), + new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), + new MaxPool2D(), + new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), + new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), + new MaxPool2D(), + new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), + new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), new AvgPool2D(pool_size: 2), new Flatten(), - new Dense(units: 32, activation: activation), - new Dense(units: classCount, activation: activation), + new Dense(units: classCount, activation: tf.nn.softmax_fn), }); model.compile( - optimizer: new Adam(epsilon: 1e-5), - loss: tf.keras.losses.mean_squared_error_fn, + optimizer: new Adam(), + loss: tf.keras.losses.sparse_categorical_crossentropy_fn, metrics: new dynamic[] { "accuracy" }); return model; } - const int Epochs = 100; - const int BatchSize = 1000; + const int Epochs = 600; + const int BatchSize = 32; public const int Width = 64, Height = 64; public static readonly Size Size = new Size(Width, Height); // being opinionated here const string Tab = " "; const char Whitespace = '\u00FF'; - const float ValidationSplit = 0.1f, TestSplit = 0.1f; - const int TrainingSamples = 200000, TestSamples = 10000, ValidationSamples = 10000; - const int SamplePart = 10; + const float TestSplit = 0.1f; + const int TrainingSamples = 400000, TestSamples = 20000; + const int SamplePart = 1; - public static void Run(string directory) { - GradientSetup.EnsureInitialized(); - - var filesByExtension = ReadCodeFiles(directory, + public static void Run(params string[] directories) { + var filesByExtension = ReadCodeFiles(directories, includeExtensions: IncludeExtensions, codeFilter: lines => lines.Length >= 10); + Train(filesByExtension); + } + public static void Train(List[] filesByExtension) { + GradientSetup.EnsureInitialized(); + + dynamic config = config_pb2.ConfigProto(); + config.gpu_options.allow_growth = true; + tf.keras.backend.set_session(Session.NewDyn(config: config)); - var random = new Random(42); - tf.set_random_seed(42); + var random = new Random(4242); + tf.set_random_seed(4242); var test = Split(random, filesByExtension, TestSplit, out filesByExtension); @@ -78,30 +93,44 @@ public static void Run(string directory) { var checkpointBest = new ModelCheckpoint( filepath: Path.Combine(Environment.CurrentDirectory, "weights.best.hdf5"), save_best_only: true, save_weights_only: true); + // not present in 1.10, but present in 1.14 + ((dynamic)checkpointBest).save_freq = "epoch"; Console.Write("loading data to TensorFlow..."); var timer = Stopwatch.StartNew(); ndarray @in = InputToNumPy(trainData, sampleCount: TrainingSamples / SamplePart, width: Width, height: Height); - ndarray expectedOut = OutputToNumPy(trainValues); + ndarray expectedOut = OutputToNumPy(trainValues); Console.WriteLine($"OK in {timer.ElapsedMilliseconds / 1000}s"); + string runID = DateTime.Now.ToString("s").Replace(':', '-'); + string logDir = Path.Combine(".", "logs", runID); + Directory.CreateDirectory(logDir); + var tensorboard = new TensorBoard(log_dir: logDir); + var model = CreateModel(classCount: IncludeExtensions.Length); model.build(new TensorShape(null, Height, Width, 1)); model.summary(); - model.fit_dyn(@in, expectedOut, epochs: Epochs, shuffle: false, batch_size: BatchSize, - validation_split: 0.1, callbacks: new ICallback[] { - //checkpointBest - }, - verbose: TrainingVerbosity.LinePerEpoch); + + GC.Collect(); + + var valIn = InputToNumPy(testData, sampleCount: TestSamples / SamplePart, width: Width, height: Height); + var valOut = OutputToNumPy(testValues); + + fit(model, @in, expectedOut, + batchSize: BatchSize, + epochs: Epochs, + callbacks: new ICallback[] { + checkpointBest, + tensorboard, + }, validationInput: valIn, validationTarget: valOut); + + model.save_weights(Path.GetFullPath("weights.final.hdf5")); var fromCheckpoint = CreateModel(classCount: IncludeExtensions.Length); fromCheckpoint.build(new TensorShape(null, Height, Width, 1)); fromCheckpoint.load_weights(Path.GetFullPath("weights.best.hdf5")); - @in = InputToNumPy(testData, sampleCount: TestSamples/SamplePart, width: Width, height: Height); - expectedOut = OutputToNumPy(testValues); - var evaluationResults = fromCheckpoint.evaluate(@in, expectedOut); Console.WriteLine($"reloaded: loss: {evaluationResults[0]} acc: {evaluationResults[1]}"); @@ -112,9 +141,8 @@ public static void Run(string directory) { public static ndarray InputToNumPy(byte[] inputs, int sampleCount, int width, int height) => (dynamic)inputs.Select(b => (float)b).ToArray().NumPyCopy() .reshape(new[] { sampleCount, height, width, 1 }) / 255.0f; - static ndarray OutputToNumPy(int[] expectedOutputs) - => (ndarray)np.eye(IncludeExtensions.Length, dtype: PythonClassContainer.Instance) - .__getitem__(expectedOutputs.ToNumPyArray().reshape(-1)); + static ndarray OutputToNumPy(int[] expectedOutputs) + => expectedOutputs.ToNumPyArray(); static byte[] Sample(Random random, List[] filesByExtension, Size size, int count, out int[] extensionIndices) { @@ -178,13 +206,73 @@ static void Sample(Random random, List[] filesByExtension, Size blockS Render(lines, new Point(x, y), blockSize, destination: target); } + static void fit(Model @this, numpy.I_ArrayLike input, numpy.I_ArrayLike targetValues, + int? stepsPerEpoch = null, int? validationSteps = null, + int batchSize = 1, + int epochs = 1, + TrainingVerbosity verbosity = TrainingVerbosity.ProgressBar, + IEnumerable callbacks = null, + numpy.I_ArrayLike validationInput = null, + numpy.I_ArrayLike validationTarget = null) { + if (input == null) throw new ArgumentNullException(nameof(input)); + if (targetValues == null) throw new ArgumentNullException(nameof(targetValues)); + if (stepsPerEpoch <= 0) throw new ArgumentOutOfRangeException(nameof(stepsPerEpoch)); + if (validationSteps <= 0) + throw new ArgumentOutOfRangeException(nameof(validationSteps)); + if (validationSteps != null && stepsPerEpoch == null) + throw new ArgumentException( + $"Can't set {nameof(validationSteps)} without setting {nameof(stepsPerEpoch)}", + paramName: nameof(validationSteps)); + + var validation = validationInput == null && validationTarget == null + ? (((numpy.I_ArrayLike, numpy.I_ArrayLike)?)null) + : validationInput != null && validationTarget != null + ? (validationInput, validationTarget) + : throw new ArgumentException( + $"Both (or none) {nameof(validationInput)} and {nameof(validationTarget)} must be provided"); + + @this.fit_dyn(input, targetValues, + epochs: epochs, + batch_size: batchSize, + verbose: (int)verbosity, + callbacks: callbacks, + validation_data: validation, + shuffle: false, + steps_per_epoch: stepsPerEpoch, + validation_steps: validationSteps + ); + } + + public static List[] ReadCodeFiles(IEnumerable directories, + string[] includeExtensions, + Func codeFilter) { + var files = Range(0, includeExtensions.Length) + .Select(_ => new List()) + .ToArray(); + + foreach (string directory in directories) { + ReadCodeFiles(files, directory, includeExtensions, codeFilter); + } + + return files; + } + static List[] ReadCodeFiles(string directory, string[] includeExtensions, Func codeFilter) { var files = Range(0, includeExtensions.Length) .Select(_ => new List()) .ToArray(); + ReadCodeFiles(files, directory, includeExtensions, codeFilter); + + return files; + } + + static void ReadCodeFiles(List[] files, string directory, string[] includeExtensions, Func codeFilter) { foreach (string filePath in Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories)) { + if (filePath.Contains( + Path.DirectorySeparatorChar + "out" + Path.DirectorySeparatorChar)) + continue; string extension = Path.GetExtension(filePath); int extensionIndex = Array.IndexOf(includeExtensions, extension); if (extensionIndex < 0) continue; @@ -192,23 +280,32 @@ static List[] ReadCodeFiles(string directory, string[] includeExtensio if (!codeFilter(code)) continue; files[extensionIndex].Add(code); } - - return files; } public static string[] ReadCode(string path) => File.ReadAllLines(path) .Select(line => line.Replace("\t", Tab)) - // replace non-ASCII characters with underscore - .Select(line => Regex.Replace(line, @"[^\u0000-\u007F]", "_")) - // make all whitespace stand out - .Select(line => Regex.Replace(line, @"[\u0000-\u0020]", Whitespace.ToString())) + .Select(line => { + var result = new StringBuilder(line.Length); + // replace non-ASCII characters with underscore + // make all whitespace stand out + foreach (char c in line) { + result.Append( + c <= 32 ? Whitespace + : c >= 255 ? '_' + : c); + } + return result.ToString(); + }) .ToArray(); public static void Render(string[] lines, Point startingPoint, Size size, byte[] destination) { if (size.IsEmpty) throw new ArgumentException(); if (destination.Length < size.Width * size.Height) throw new ArgumentException(); - if (startingPoint.Y >= lines.Length) throw new ArgumentException(); + if (startingPoint.Y == lines.Length) { + Array.Fill(destination, (byte)Whitespace); + return; + } for (int y = 0; y < size.Height; y++) { int sourceY = y + startingPoint.Y; @@ -301,15 +398,13 @@ public static void SetGrayscalePalette(Bitmap bitmap) { public static readonly string[] IncludeExtensions = { ".cs", - ".dart", - ".go", - ".java", - ".hs", // Haskell - ".m", // Objective-C ".py", - ".rs", // Rust - ".u", // Unison - ".xml", + ".h", + ".cc", + ".c", + ".tcl", + ".java", + ".sh", }; } } diff --git a/ResNetBlock/ResNetSampleProgram.cs b/ResNetBlock/ResNetSampleProgram.cs index 4739387..a2892c0 100644 --- a/ResNetBlock/ResNetSampleProgram.cs +++ b/ResNetBlock/ResNetSampleProgram.cs @@ -42,11 +42,11 @@ public static void Run(int epochs = 5) { model.summary(); } - static void Main() { + static void Main(string[] args) { Console.Title = nameof(ResNetSampleProgram); GradientLog.OutputWriter = Console.Out; GradientSetup.UseEnvironmentFromVariable(); - CSharpOrNot.Run("."); + CSharpOrNot.Run(args); } } } From 395fa286b8e87069173618ecb31c3665100aa458 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Fri, 11 Oct 2019 16:24:50 -0700 Subject: [PATCH 5/8] use Preview 6.4 in CSharpOrNot --- ResNetBlock/CSharpOrNot.cs | 44 ++-------------------------------- ResNetBlock/ResNetBlock.csproj | 2 +- 2 files changed, 3 insertions(+), 43 deletions(-) diff --git a/ResNetBlock/CSharpOrNot.cs b/ResNetBlock/CSharpOrNot.cs index dfd5434..37aff40 100644 --- a/ResNetBlock/CSharpOrNot.cs +++ b/ResNetBlock/CSharpOrNot.cs @@ -8,9 +8,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text; - using System.Text.RegularExpressions; using numpy; - using SharPy.Runtime; using tensorflow; using tensorflow.core.protobuf.config_pb2; using tensorflow.keras; @@ -117,9 +115,8 @@ public static void Train(List[] filesByExtension) { var valIn = InputToNumPy(testData, sampleCount: TestSamples / SamplePart, width: Width, height: Height); var valOut = OutputToNumPy(testValues); - fit(model, @in, expectedOut, - batchSize: BatchSize, - epochs: Epochs, + model.fit(@in, expectedOut, + batchSize: BatchSize, epochs: Epochs, callbacks: new ICallback[] { checkpointBest, tensorboard, @@ -206,43 +203,6 @@ static void Sample(Random random, List[] filesByExtension, Size blockS Render(lines, new Point(x, y), blockSize, destination: target); } - static void fit(Model @this, numpy.I_ArrayLike input, numpy.I_ArrayLike targetValues, - int? stepsPerEpoch = null, int? validationSteps = null, - int batchSize = 1, - int epochs = 1, - TrainingVerbosity verbosity = TrainingVerbosity.ProgressBar, - IEnumerable callbacks = null, - numpy.I_ArrayLike validationInput = null, - numpy.I_ArrayLike validationTarget = null) { - if (input == null) throw new ArgumentNullException(nameof(input)); - if (targetValues == null) throw new ArgumentNullException(nameof(targetValues)); - if (stepsPerEpoch <= 0) throw new ArgumentOutOfRangeException(nameof(stepsPerEpoch)); - if (validationSteps <= 0) - throw new ArgumentOutOfRangeException(nameof(validationSteps)); - if (validationSteps != null && stepsPerEpoch == null) - throw new ArgumentException( - $"Can't set {nameof(validationSteps)} without setting {nameof(stepsPerEpoch)}", - paramName: nameof(validationSteps)); - - var validation = validationInput == null && validationTarget == null - ? (((numpy.I_ArrayLike, numpy.I_ArrayLike)?)null) - : validationInput != null && validationTarget != null - ? (validationInput, validationTarget) - : throw new ArgumentException( - $"Both (or none) {nameof(validationInput)} and {nameof(validationTarget)} must be provided"); - - @this.fit_dyn(input, targetValues, - epochs: epochs, - batch_size: batchSize, - verbose: (int)verbosity, - callbacks: callbacks, - validation_data: validation, - shuffle: false, - steps_per_epoch: stepsPerEpoch, - validation_steps: validationSteps - ); - } - public static List[] ReadCodeFiles(IEnumerable directories, string[] includeExtensions, Func codeFilter) { diff --git a/ResNetBlock/ResNetBlock.csproj b/ResNetBlock/ResNetBlock.csproj index 4e6f367..e8eab65 100644 --- a/ResNetBlock/ResNetBlock.csproj +++ b/ResNetBlock/ResNetBlock.csproj @@ -8,7 +8,7 @@ - + From 0d3f3381b44f6f87497fb8b7e7643498eb398b84 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Fri, 11 Oct 2019 17:52:12 -0700 Subject: [PATCH 6/8] refactored CSharpOrNot sample into a separate project --- CSharpOrNot/App.xaml | 8 + CSharpOrNot/App.xaml.cs | 10 + CSharpOrNot/BitmapTools.cs | 73 +++++ CSharpOrNot/CSharpOrNot.cs | 115 ++++++++ CSharpOrNot/CSharpOrNot.csproj | 26 ++ CSharpOrNot/CSharpOrNotProgram.cs | 31 +++ CSharpOrNot/CSharpOrNotWindow.xaml | 20 ++ CSharpOrNot/CSharpOrNotWindow.xaml.cs | 150 +++++++++++ CSharpOrNot/DataTools.cs | 34 +++ CSharpOrNot/TrainCommand.cs | 200 ++++++++++++++ CSharpOrNot/UICommand.cs | 27 ++ CSharpOrNot/nuget.config | 11 + Gradient-Samples.sln | 8 +- ResNetBlock/CSharpOrNot.cs | 370 -------------------------- ResNetBlock/ResNetBlock.cs | 2 +- ResNetBlock/ResNetSampleProgram.cs | 4 +- 16 files changed, 715 insertions(+), 374 deletions(-) create mode 100644 CSharpOrNot/App.xaml create mode 100644 CSharpOrNot/App.xaml.cs create mode 100644 CSharpOrNot/BitmapTools.cs create mode 100644 CSharpOrNot/CSharpOrNot.cs create mode 100644 CSharpOrNot/CSharpOrNot.csproj create mode 100644 CSharpOrNot/CSharpOrNotProgram.cs create mode 100644 CSharpOrNot/CSharpOrNotWindow.xaml create mode 100644 CSharpOrNot/CSharpOrNotWindow.xaml.cs create mode 100644 CSharpOrNot/DataTools.cs create mode 100644 CSharpOrNot/TrainCommand.cs create mode 100644 CSharpOrNot/UICommand.cs create mode 100644 CSharpOrNot/nuget.config delete mode 100644 ResNetBlock/CSharpOrNot.cs diff --git a/CSharpOrNot/App.xaml b/CSharpOrNot/App.xaml new file mode 100644 index 0000000..d23d681 --- /dev/null +++ b/CSharpOrNot/App.xaml @@ -0,0 +1,8 @@ + + + + + + diff --git a/CSharpOrNot/App.xaml.cs b/CSharpOrNot/App.xaml.cs new file mode 100644 index 0000000..e8656a6 --- /dev/null +++ b/CSharpOrNot/App.xaml.cs @@ -0,0 +1,10 @@ +namespace Gradient.Samples { + using Avalonia; + using Avalonia.Markup.Xaml; + + public class App : Application { + public override void Initialize() { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/CSharpOrNot/BitmapTools.cs b/CSharpOrNot/BitmapTools.cs new file mode 100644 index 0000000..7fa9dfb --- /dev/null +++ b/CSharpOrNot/BitmapTools.cs @@ -0,0 +1,73 @@ +namespace Gradient.Samples { + using System; + using System.Drawing; + using System.Drawing.Imaging; + using System.Runtime.InteropServices; + + static class BitmapTools { + public static void ToBitmap(byte[] brightness, Bitmap target) { + if (target.PixelFormat != PixelFormat.Format8bppIndexed) + throw new NotSupportedException("The only supported pixel format is " + PixelFormat.Format8bppIndexed); + + var bitmapData = target.LockBits(new Rectangle(new Point(), target.Size), + ImageLockMode.WriteOnly, + PixelFormat.Format8bppIndexed); + + try { + Marshal.Copy(source: brightness, + startIndex: 0, length: bitmapData.Width * bitmapData.Height, + destination: bitmapData.Scan0); + } finally { + target.UnlockBits(bitmapData); + } + } + + // default .NET upscaling tries to interpolate, which we avoid here + public static void Upscale(Bitmap source, Bitmap target) { + if (target.Width % source.Width != 0 || target.Height % source.Height != 0) + throw new ArgumentException(); + + int scaleY = target.Height / source.Height; + int scaleX = target.Width / source.Width; + + var sourceData = source.LockBits(new Rectangle(new Point(), source.Size), + ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed); + try { + var targetData = target.LockBits(new Rectangle(new Point(), target.Size), + ImageLockMode.WriteOnly, + PixelFormat.Format8bppIndexed); + + try { + for (int sourceY = 0; sourceY < sourceData.Height; sourceY++) + for (int sourceX = 0; sourceX < sourceData.Width; sourceX++) { + byte brightness = Marshal.ReadByte(sourceData.Scan0, + sourceY * sourceData.Width + sourceX); + for (int targetY = sourceY * scaleY; + targetY < (sourceY + 1) * scaleY; + targetY++) + for (int targetX = sourceX * scaleX; + targetX < (sourceX + 1) * scaleX; + targetX++) + Marshal.WriteByte(targetData.Scan0, + targetY * targetData.Width + targetX, + brightness); + } + } finally { + target.UnlockBits(targetData); + } + } finally { + source.UnlockBits(sourceData); + } + } + + public static void SetGreyscalePalette(Bitmap bitmap) { + ColorPalette pal = bitmap.Palette; + + for (int i = 0; i < 256; i++) { + pal.Entries[i] = Color.FromArgb(255, i, i, i); + } + + bitmap.Palette = pal; + } + } +} diff --git a/CSharpOrNot/CSharpOrNot.cs b/CSharpOrNot/CSharpOrNot.cs new file mode 100644 index 0000000..bb70bac --- /dev/null +++ b/CSharpOrNot/CSharpOrNot.cs @@ -0,0 +1,115 @@ +namespace Gradient.Samples { + using System; + using System.Drawing; + using System.IO; + using System.Linq; + using System.Text; + using numpy; + using tensorflow; + using tensorflow.keras; + using tensorflow.keras.layers; + + static class CSharpOrNot + { + public static Model CreateModel(int classCount) { + var activation = tf.keras.activations.elu_fn; + const int filterCount = 8; + int[] resNetFilters = { filterCount, filterCount, filterCount }; + return new Sequential(new Layer[] { + new Dropout(rate: 0.05), + Conv2D.NewDyn(filters: filterCount, kernel_size: 5, padding: "same"), + Activation.NewDyn(activation), + new MaxPool2D(pool_size: 2), + new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), + new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), + new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), + new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), + new MaxPool2D(), + new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), + new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), + new MaxPool2D(), + new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), + new ResNetBlock(kernelSize: 3, filters: resNetFilters, activation: activation), + new AvgPool2D(pool_size: 2), + new Flatten(), + new Dense(units: classCount, activation: tf.nn.softmax_fn), + }); + } + + public const int Width = 64, Height = 64; + public static readonly Size Size = new Size(Width, Height); + // being opinionated here + const string Tab = " "; + const char Whitespace = '\u00FF'; + public static readonly string[] IncludeExtensions = { + ".cs", + ".py", + ".h", + ".cc", + ".c", + ".tcl", + ".java", + ".sh", + }; + + public static ndarray GreyscaleImageBytesToNumPy(byte[] inputs, int imageCount, int width, int height) + => (dynamic)inputs.Select(b => (float)b).ToArray().NumPyCopy() + .reshape(new[] { imageCount, height, width, 1 }) / 255.0f; + + public static string[] ReadCode(string filePath) + => File.ReadAllLines(filePath) + .Select(line => line.Replace("\t", Tab)) + .Select(line => { + var result = new StringBuilder(line.Length); + // replace non-ASCII characters with underscore + // also make all whitespace stand out + foreach (char c in line) { + result.Append( + c <= 32 ? Whitespace + : c >= 255 ? '_' + : c); + } + return result.ToString(); + }) + .ToArray(); + + /// + /// Copies a rectangular block of text into a byte array + /// + public static void RenderTextBlockToGreyscaleBytes(string[] lines, + Point startingPoint, Size size, + byte[] destination) + { + if (size.IsEmpty) throw new ArgumentException(); + if (destination.Length < size.Width * size.Height) throw new ArgumentException(); + if (startingPoint.Y == lines.Length) { + Array.Fill(destination, (byte)Whitespace); + return; + } + + for (int y = 0; y < size.Height; y++) { + int sourceY = y + startingPoint.Y; + int destOffset = y * size.Width; + if (sourceY >= lines.Length) { + Array.Fill(destination, (byte)255, + startIndex: destOffset, + count: size.Width*size.Height - destOffset); + return; + } + + for (int x = 0; x < size.Width; x++) { + int sourceX = x + startingPoint.X; + if (sourceX >= lines[sourceY].Length) { + Array.Fill(destination, (byte)255, + startIndex: destOffset, + count: size.Width - x); + break; + } + + destination[destOffset] = (byte)lines[sourceY][sourceX]; + destOffset++; + } + } + } + } +} diff --git a/CSharpOrNot/CSharpOrNot.csproj b/CSharpOrNot/CSharpOrNot.csproj new file mode 100644 index 0000000..0744b34 --- /dev/null +++ b/CSharpOrNot/CSharpOrNot.csproj @@ -0,0 +1,26 @@ + + + Exe + netcoreapp3.0 + + + Gradient.Samples + + + + %(Filename) + + + Designer + + + + + + + + + + + + diff --git a/CSharpOrNot/CSharpOrNotProgram.cs b/CSharpOrNot/CSharpOrNotProgram.cs new file mode 100644 index 0000000..1f9db23 --- /dev/null +++ b/CSharpOrNot/CSharpOrNotProgram.cs @@ -0,0 +1,31 @@ +namespace Gradient.Samples { + using System; + using System.Linq; + using Avalonia; + using Avalonia.Logging.Serilog; + using Gradient; + using ManyConsole.CommandLineUtils; + using tensorflow; + using tensorflow.core.protobuf.config_pb2; + + static class CSharpOrNotProgram { + public static int Main(string[] args) { + GradientSetup.OptInToUsageDataCollection(); + GradientSetup.UseEnvironmentFromVariable(); + + dynamic config = config_pb2.ConfigProto(); + config.gpu_options.allow_growth = true; + tf.keras.backend.set_session(Session.NewDyn(config: config)); + + return ConsoleCommandDispatcher.DispatchCommand( + ConsoleCommandDispatcher.FindCommandsInSameAssemblyAs(typeof(CSharpOrNotProgram)), + args, Console.Out); + } + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .LogToDebug(); + } +} diff --git a/CSharpOrNot/CSharpOrNotWindow.xaml b/CSharpOrNot/CSharpOrNotWindow.xaml new file mode 100644 index 0000000..c8ffe8d --- /dev/null +++ b/CSharpOrNot/CSharpOrNotWindow.xaml @@ -0,0 +1,20 @@ + + + + + + + + + + + diff --git a/CSharpOrNot/CSharpOrNotWindow.xaml.cs b/CSharpOrNot/CSharpOrNotWindow.xaml.cs new file mode 100644 index 0000000..3b5b830 --- /dev/null +++ b/CSharpOrNot/CSharpOrNotWindow.xaml.cs @@ -0,0 +1,150 @@ +namespace Gradient.Samples { + using System; + using System.Drawing.Imaging; + using System.IO; + using System.Linq; + using Avalonia; + using Avalonia.Controls; + using Avalonia.Interactivity; + using Avalonia.Markup.Xaml; + using Gradient; + using MoreLinq; + using numpy; + using tensorflow; + using tensorflow.keras; + using static Gradient.Samples.CSharpOrNot; + using Image = Avalonia.Controls.Image; + using Point = System.Drawing.Point; + using Bitmap = System.Drawing.Bitmap; + using PixelFormat = System.Drawing.Imaging.PixelFormat; + + public class CSharpOrNotWindow : Window + { + readonly TextBox codeDisplay; + readonly TextBlock codeWindow; + readonly TextBlock language; + readonly Image codeImage; + readonly Button openFileButton; + string[] code; + readonly Model model; + bool loaded = false; + public CSharpOrNotWindow() { + this.InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + this.codeDisplay = this.Get("CodeDisplay"); + this.codeDisplay.PropertyChanged += this.CodeDisplayOnPropertyChanged; + + this.codeWindow = this.Get("CodeWindow"); + this.language = this.Get("Language"); + this.codeImage = this.Get("CodeImage"); + this.openFileButton = this.Get