Machine Learning für .NET Developer

Wie einfach kann ein .NET Entwickler AI/KI Funktionen in seine App oder Website bringen? Natürlich gibt es überall wie bei ChatGPT eine APP, für die man mehr oder weniger löhnen muss. Mein Ziel ist es auf eigenen Maschinen (neudeutsch Onpremise) die Algorithmen zu nutzen.

Dabei habe ich es mir viel leichter vorgestellt. Gerade Aufbereitung der Daten ist wesentlich mehr Aufwand als geglaubt. Deswegen greife ich für meinen ersten ML Blog Artikel auf einen alten aber bekannten Datensatz zur Handschrift Erkennung von Zahlen. Die MNIST Datenbank (Modified National Institute of Standards and Technology database) ist eine öffentlich verfügbare Datenbank von handgeschriebenen Ziffern. Wobei jede Ziffer als 28 × 28 Pixel großes Graustufen-Bild gespeichert ist.

Das führt zum ersten Problem, den Formaten. Jedes Machine Learning Framework kocht hier eine eigene Suppe. Zwar gibt es ein übergreifendes Format von Microsoft ONNX, aber das scheint nicht der Standard zu sein.

Nach Download des MNIST Datasets findet man vier Dateien in einem Binärformat mit Namen wie train-images-idx3-ubyte. Das ist das IDX Format.

Ich möchte hier zeigen wie man mit Visual Studio mit wenigen Klicks und dem auf ML.NET basierenden Model Builder eine Klassifizierung vornimmt. Also Bild als PNG und zehn Klassen je Ziffer. Dafür benötigt ML.NET ein Unterverzeichnis pro Label. Also habe ich erst einen Konverter geschrieben. Einer der Standard Aufgaben wenn es um das erstellen einer Learning Pipeline geht.

Da ich nichts dazu lernen will, läuft die Logik nur einmal und in einem extra Projekt und das ziemlich lange.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Drawing;
   4:  using System.Drawing.Imaging;
   5:  using System.IO;
   6:  using System.Linq;
   7:  var images = MnistReader.ReadTrainingData();
   8:  foreach (var image in images)
   9:  {
  10:      var pixels = image.Data;
  11:      var bitmap = new Bitmap(28, 28);
  12:      for (int x = 0; x < 28; x++)
  13:      {
  14:          for (int y = 0; y < 28; y++)
  15:          {
  16:              bitmap.SetPixel(y, x, Color.FromArgb(255 - pixels[x, y],
255 - pixels[x, y], 255 - pixels[x, y]));
  17:          }
  18:      }
  19:      var datei = Path.Combine(@"C:\stateof\mnist\Export2\",
image.Label.ToString());
  20:      if (!Directory.Exists(datei))
  21:      {
  22:          Directory.CreateDirectory(datei);
  23:      }
  24:      bitmap.Save(datei+"\\"+ Guid.NewGuid().ToString() +
"
.png", ImageFormat.Png);
  25:  }

 

Das alles und noch ein paar geklaute Codezeilen für das drum herum einfach in einer Konsolen Anwendung und der program.cs

   1:  public static class MnistReader
   2:  {    private const string TrainImages = @"C:\stateof\mnist\train-images-idx3-ubyte\train-images-idx3-ubyte";
   3:      private const string TrainLabels = @"C:\stateof\mnist\train-labels-idx1-ubyte\train-labels-idx1-ubyte";
   4:      private const string TestImages = "t10k-images.idx3-ubyte";
   5:      private const string TestLabels = "t10k-labels.idx1-ubyte";
   6:      public static IEnumerable<Image> ReadTrainingData()
   7:      {
   8:          foreach (var item in Read(TrainImages, TrainLabels))
   9:          {
  10:              yield return item;
  11:          }
  12:      }
  13:      public static IEnumerable<Image> ReadTestData()
  14:      {
  15:          foreach (var item in Read(TestImages, TestLabels))
  16:          {
  17:              yield return item;
  18:          }
  19:      }
  20:      private static IEnumerable<Image> Read(string imagesPath, string labelsPath)
  21:      {
  22:          BinaryReader labels = new BinaryReader(
new FileStream(labelsPath, FileMode.Open));
  23:          BinaryReader images = new BinaryReader(
new FileStream(imagesPath, FileMode.Open));
  24:          int magicNumber = images.ReadBigInt32();
  25:          int numberOfImages = images.ReadBigInt32();
  26:          int width = images.ReadBigInt32();
  27:          int height = images.ReadBigInt32();
  28:          int magicLabel = labels.ReadBigInt32();
  29:          int numberOfLabels = labels.ReadBigInt32();
  30:          for (int i = 0; i < numberOfImages; i++)
  31:          {
  32:              var bytes = images.ReadBytes(width * height);
  33:              var arr = new byte[height, width];
  34:              arr.ForEach((j, k) => arr[j, k] = bytes[j * height + k]);
  35:              yield return new Image()
  36:              {
  37:                  Data = arr,
  38:                  Label = labels.ReadByte()
  39:              };
  40:          }
  41:      }
  42:  }
  43:  public class Image
  44:  {
  45:      public byte Label { get; set; }
  46:      public byte[,] Data { get; set; }
  47:  }
  48:   
  49:  public static class Extensions
  50:  {
  51:      public static int ReadBigInt32(this BinaryReader br)
  52:      {
  53:          var bytes = br.ReadBytes(sizeof(Int32));
  54:          if (BitConverter.IsLittleEndian) Array.Reverse(bytes);
  55:          return BitConverter.ToInt32(bytes, 0);
  56:      }
  57:      public static void ForEach<T>(this T[,] source, Action<int, int> action)
  58:      {
  59:          for (int w = 0; w < source.GetLength(0); w++)
  60:          {
  61:              for (int h = 0; h < source.GetLength(1); h++)
  62:              {
  63:                  action(w, h);
  64:              }
  65:          }
  66:      }
  67:  }

 

Das Ergebnis ist ein Ordner je Ziffer und viele Bilder mit einer GUID als Namen

mnist1

Mit diesen Datensatz kann nun eine neues Visual Studio Command Line Projekt erstellt werden. Im Projekt Explorer rechtsklick- hinzufügen- Machine Learning Modell.

Die Aufgabe ist es die Bilder in Klassen einzuteilen. Pro Bild gibt es nur eine Möglichkeit zu finden. Von 0-9. Dazu wählten man den passenden Assistenten für Maschinelles Sehen

mnist2

Im nächsten Schritt wird der Export Ordner mit den Bildern in den Unterordnern ausgewählt

mnist3

Im Ordner 1 sind lauter Einsen. Dann kann man schon auf trainieren drücken. Das schöne am Model Designer und Auto ML ist, er sucht sich schon die passenden Parameter. Man muss also keine Kenntnisse zu Algorithmen und Optimierungsparameter haben- vorerst.

Nur knapp 2h später habe ich ein Model

mnist4

Das kann man nun testen und erhält eigentlich beeindruckende Ergebnisse

mnist5

Wie man sieht Wahrscheinlichkeiten pro Label. Nicht mehr ganz so toll, mit einem selbst gezeichneter 8 und vor allem in anderen Dimensionen als die Trainingsgrafiken.

mnist7

Im sozusagen finalen Schritt, bietet der Assistent dann auch noch den .net C# Code auf, um ML.NET und das soeben erstellte Modell, in einer eigenen Anwendung echt nutzen zu können.

   1:  using MLModel1_ConsoleApp1;
   2:  using System.IO;
   3:  var imageBytes = File.ReadAllBytes(@"C:.....png");
   4:  MLModel1.ModelInput sampleData = new MLModel1.ModelInput()
   5:  {
   6:      ImageSource = imageBytes,
   7:  };
   8:  var sortedScoresWithLabel = MLModel1.PredictAllLabels(sampleData);
   9:  Console.WriteLine($"{"Class",-40}{"Score",-20}");
  10:  Console.WriteLine($"{"-----",-40}{"-----",-20}");
  11:  foreach (var score in sortedScoresWithLabel)
  12:  {
  13:      Console.WriteLine($"{score.Key,-40}{score.Value,-20}");
  14:  }
Kommentare sind geschlossen