ABONAMENTE VIDEO REDACȚIA
RO
EN
NOU
Numărul 150
Numărul 149 Numărul 148 Numărul 147 Numărul 146 Numărul 145 Numărul 144 Numărul 143 Numărul 142 Numărul 141 Numărul 140 Numărul 139 Numărul 138 Numărul 137 Numărul 136 Numărul 135 Numărul 134 Numărul 133 Numărul 132 Numărul 131 Numărul 130 Numărul 129 Numărul 128 Numărul 127 Numărul 126 Numărul 125 Numărul 124 Numărul 123 Numărul 122 Numărul 121 Numărul 120 Numărul 119 Numărul 118 Numărul 117 Numărul 116 Numărul 115 Numărul 114 Numărul 113 Numărul 112 Numărul 111 Numărul 110 Numărul 109 Numărul 108 Numărul 107 Numărul 106 Numărul 105 Numărul 104 Numărul 103 Numărul 102 Numărul 101 Numărul 100 Numărul 99 Numărul 98 Numărul 97 Numărul 96 Numărul 95 Numărul 94 Numărul 93 Numărul 92 Numărul 91 Numărul 90 Numărul 89 Numărul 88 Numărul 87 Numărul 86 Numărul 85 Numărul 84 Numărul 83 Numărul 82 Numărul 81 Numărul 80 Numărul 79 Numărul 78 Numărul 77 Numărul 76 Numărul 75 Numărul 74 Numărul 73 Numărul 72 Numărul 71 Numărul 70 Numărul 69 Numărul 68 Numărul 67 Numărul 66 Numărul 65 Numărul 64 Numărul 63 Numărul 62 Numărul 61 Numărul 60 Numărul 59 Numărul 58 Numărul 57 Numărul 56 Numărul 55 Numărul 54 Numărul 53 Numărul 52 Numărul 51 Numărul 50 Numărul 49 Numărul 48 Numărul 47 Numărul 46 Numărul 45 Numărul 44 Numărul 43 Numărul 42 Numărul 41 Numărul 40 Numărul 39 Numărul 38 Numărul 37 Numărul 36 Numărul 35 Numărul 34 Numărul 33 Numărul 32 Numărul 31 Numărul 30 Numărul 29 Numărul 28 Numărul 27 Numărul 26 Numărul 25 Numărul 24 Numărul 23 Numărul 22 Numărul 21 Numărul 20 Numărul 19 Numărul 18 Numărul 17 Numărul 16 Numărul 15 Numărul 14 Numărul 13 Numărul 12 Numărul 11 Numărul 10 Numărul 9 Numărul 8 Numărul 7 Numărul 6 Numărul 5 Numărul 4 Numărul 3 Numărul 2 Numărul 1
×
▼ LISTĂ EDIȚII ▼
Numărul 108
Abonament PDF

Roslyn Source Generators

Daniel Costea
Senior Software Developer @ EU Agency



PROGRAMARE

Pentru machine learning punctul central este modelul. Fie vă antrenați propriul model de machine learning, fie aveți unul de consumat în codul de producție, trebuie să cunoașteți câteva informații despre modul în care a fost instruit, cum ar fi eticheta (în engleza Label sau caracteristica țintă), modelele de date (de intrare și ieșire) și scenariul care a fost folosit pentru antrenare. Împreună cu aceste detalii, este foarte important să cunoașteți acuratețea modelului dvs. de machine learning. În unele cazuri ați putea avea instrumente precum MLOps pentru a avea grijă de aceste detalii, dar poate că nu aveți.

Ori s-ar putea să vă gândiți la un alt scenariu pentru a îmbunătăți funcționalitatea MLOps prin generarea de modele de date de intrare și ieșire (care sunt puternic tastate clase C# cu proprietăți și adnotări de date specifice de machine learning). Sau puteți genera un cod boilerplate pentru validare sau consumare a modelului de machine learning pentru scenarii mai complexe, cum ar fi serviciile web, blazor și aplicațiile consolă.

Dacă sunteți Data Scientist, poate doriți să începeți antrenarea modelului de machine learning de la zero. Dacă sunteți un programator software puteți prefera Model Builder, care este un instrument vizual excelent pentru a vă ajuta să creați un model ML.NET pornind de la date. Împreună cu modelul de machine learning, sunt generate modelele de date de intrare și de ieșire și chiar cod boilerplate pentru a antrena și consuma modelul.

În caz că intenționați să vă antrenați modelul ML.NET din linia de comandă, puteți valorifica instrumentul ML.NET CLI care acoperă aproape toate scenariile pe care le puteți găsi în Model Builder.

Instalați în global tools după cum urmează:

dotnet tool install -g mlnet

Prin urmare, de ce avem nevoie de o altă abordare pentru a obține modelele de date de intrare și ieșire sau un cod boilerplate pentru a antrena și consuma un model de machine learning?

Suntem pe cale să aflăm mai târziu în articol, dar permiteți-mi să vă prezint mai întâi Source Generators.

Introducere în Roslyn Source Generators

Roslyn este un set de compilatoare open source și API de analiză cod pentru .NET, iar generatoarele sursă Roslyn (disponibile cu C # 9) permit metaprogramarea la compilare. Aceasta înseamnă cod care poate fi creat la compilare și adăugat la rezultatul compilării.

Prin definiție, metaprogramarea este o tehnică de programare în care programele de calculator au capacitatea de a trata alte programe ca propriile lor date. Așadar,un program poate fi conceput pentru a citi / genera / analiza / sau transforma alte programe și chiar să se modifice în timp ce rulează.

Din perspectiva C #, generatoarele de surse ne permit:

Din perspectiva performanței, acesta este cel mai important lucru, deoarece aceasta este metaprogramarea la compilare. Cu siguranță, sunteți familiarizați și cu tipul de metaprogramare reflection, doar că acesta se desfășoară în runtime.

În rândurile de mai jos expunem operațiile desfășurate în compilator (dintr-o perspectivă high-level):

  1. Se citește fișierul cod sursă C # de pe disc.

  2. Se analizează textul din fișierul C # și se transformă într-un model de obiect.

  3. Se construiește arborele de sintaxă concret din modelul obiectului (vă rugăm să rețineți aici, modelul conține totul, cuvinte cheie, spații albe, astfel încât să puteți reveni de la un arbore de sintaxă înapoi la codul sursă).

  4. Arborele de sintaxă este trimis în faza de compilare.

  5. Rezultatul compilării are acum arborele dvs. de sintaxă și conține informațiile simbolice.

  6. Arborele de sintaxă și informațiile simbolice sunt trimise către un generator sursă.

  7. Generatorul sursă emite codul.

  8. Codul sursă generat este adăugat la rezultatul compilării proiectului principal.

Vă rog să rețineți că nu există niciun mecanism pentru a șterge sau înlocui codul sursă existent sau deja generat.

Puteți inspecta arborele de sintaxă într-un mod programatic, din clasa Source Generator, dar dacă doriți să vedeți cum arată, puteți verifica la adresa web https://sharplab.io/ sau chiar puteți construi un arbore, utilizând Roslyn quoter.

Să ne imaginăm următorul scenariu

Ești gata să integrezi și să consumi modelul de machine learning în codul tău.

Dar, ghinion, aveți doar modelul ML.NET (care este de fapt un fișier zip) și nu aveți nici codul sursa asociat (sau generat, pentru acesta). Este greu să găsești totul atunci când acestea sunt răspândite în mai multe foldere și proiecte. Prin urmare, ce poți face?

Desigur, poți arunca o privire în fișierul zip al modelului ML.NET pentru fișierul schemă. Schema are în interior toate numele și tipurile caracteristicilor (features) și puteți utiliza aceste informații pentru a scrie manual clasele C# pentru modelele de date de intrare și de ieșire.

Dar pentru un model nativ ML.NET, fișierul schemă este un fișier binar. Cum nu este prea elegant a extrage ceea ce aveți nevoie dintr-un fișier binar, este posibil să preferați o abordare programatică pentru a face acești pași. De aceea, în acest articol voi exemplifica un caz de utilizare pragmatic, începând de la fișierul zip al modelului ML.NET pentru a genera părțile lipsă, modelele de date de intrare și de ieșire și un cod boilerplate cu scopul de a antrena și consuma modelul de machine learning în diferite moduri.

După cum am menționat anterior, modelul ML.NET este un fișier zip care conține fișiere cu date despre weights și biases rezultate în urma antrenamentului. Pe lângă acestea, este prezent fișierul binar al schemei.

Vrem să înțelegem antetul schemei pentru a extrage numele și tipul caracteristicilor și adnotărilor asociate, dacă acestea există.

Următorul tabel descrie structura fișierului schemă.

Offsets Type Name and Description
0 ulong Signature: The magic number of this
file.
8 ulong Version: Indicates the version of the
data file.
16 ulong CompatibleVersion: Indicates the minimum
reader version that can interpret this
file, possibly with some data loss.
24 long TableOfContentsOffset: The offset to the
column table of contents structure.
32 long TailOffset: The eight-byte tail
signature starts at this offset. So, the
entire dataset stream should be
considered to have byte length of eight
plus this value.
40 long RowCount: The number of rows in this
data file.
48 int ColumnCount: The number of columns in
this data file.

Acest lucru este util în generarea modelelor de date de intrare și de ieșire. Cu toate acestea, trebuie să extragem scenariul care a fost utilizat pentru antrenarea modelului. Pentru a distinge dacă este vorba despre o clasificare binară, o clasificare multiplă, o regresie sau altele, avem nevoie să găsim care este eticheta (Label sau caracteristica țintă).

Din cauză că nu am reușit să găsesc informații despre scenariu și despre eticheta din modelul ML.NET, am adăugat în setarea AdditionalFiles din fișierul appsettings.csproj câteva atribute personalizate, numite Scenario și Label.

Să scriem niște cod

În mod ideal, schema poate fi citită dintr-un model ML.NET prin următoarea bucată de cod:

ITransformer model = mlContext.Model
  .Load("C:/Temp/MLModel0.zip", out var modelSchema);

Din păcate, acest lucru nu a avut prea mult succes, efect al dependențelor bibliotecii Microsoft.ML care nu se încarcă corect. Așa că a trebuit să urmez abordarea descrisă de acest articol citind schema model din fișierul zip.

Un generator sursă, fie un proiect, un dll sau un pachet nuget, este o bibliotecă netstandard 2.0 cu dependențe de bibliotecile Microsoft.CodeAnalysis.Analyzers și Microsoft.CodeAnalysis.CSharp. Cum limbajul C# utilizat pentru un generator sursă este C# 9, trebuie să specificați acest lucru în atributul LangVersion prin preview" sau "9.0".

Din perspectiva codării, un generator sursă este o clasă adnotată cu atributul Generator și care implementează interfața ISourceGenerator pentru metodele Initialize și Execute.

[Generator]
public class DataModelsGenerator : ISourceGenerator
{
  public void Initialize(
   GeneratorInitializationContext context)
    {
    }

  public void Execute(
   GeneratorExecutionContext context)
    {
    }
}

Proiectul care folosește generatoarele sursă are o referință la biblioteca generatoare sursă. Referința include atributele OutputItemType="Analyzer" și ReferenceOutputAssembly="false", ceea ce înseamnă că dll-ul generat nu este adăugat la biblioteca construită și funcționează ca un analyzer. Dacă sunteți familiarizați cu Roslyn Analyzers, știți exact ce înseamnă asta.

În mod implicit, clasele C# generate nu sunt emise ca fișiere fizice, dar putem rezolva acest lucru setând câteva proprietăți, inclusiv calea de ieșire pentru fișierele emise, după cum urmează:

<LangVersion>preview</LangVersion>

<EmitCompilerGeneratedFiles>
  true
</EmitCompilerGeneratedFiles>

<CompilerGeneratedFilesOutputPath>
 $(BaseIntermediateOutputPath)GeneratedMLNETFiles
</CompilerGeneratedFilesOutputPath>

Până în acest moment, avem scheletul unui proiect folosind un generator sursă. Următorul pa este să trecem la adăugarea funcționalității care citește schema modelului de machine learning din fișierul zip ML.NET și generează modelele de date de intrare și de ieșire C# pe baza schemei precum numele și tipurile de caracteristicilor (features).

[Generator]
public class DataModelsGenerator : ISourceGenerator
{
  const string ModelInput = nameof(ModelInput);
  const string ModelOutput = nameof(ModelOutput);
  const string Predictor = nameof(Predictor);
  const string Program = nameof(Program);

  public void Initialize(
  GeneratorInitializationContext context) { }

  public void Execute(
  GeneratorExecutionContext context)
  {
    (Scenario? scenario, _) = 
      GetAdditionalFileOptions(context);

    var zipFiles = context.AdditionalFiles
     .Where(f => Path.GetExtension(f.Path)
     .Equals(„.zip”, 
        StringComparison.OrdinalIgnoreCase));

     var zipFile = zipFiles.ToArray()[0].Path;

     Stream zip = null;

     zip = StreamHelper.GetZipFileStream(zipFile);
     using var reader = new BinaryReader(zip, 
       Encoding.UTF8);

     var features = StreamHelper
       .ExtractFeatures(reader);

     StringBuilder modelInputBuilder = SyntaxHelper
       .ModelInputBuilder(features, ModelInput);

     SourceText sourceText1 = SourceText
       .From(modelInputBuilder.ToString(), 
        Encoding.UTF8);

     context.AddSource($”{ModelInput}.cs”, 
     sourceText1);

     StringBuilder modelOutputBuilder = SyntaxHelper
    .ModelOutputBuilder(ModelOutput, scenario.Value);

     SourceText sourceText2 = SourceText
    .From(modelOutputBuilder.ToString(), 
       Encoding.UTF8);

     context.AddSource($”{ModelOutput}.cs”, 
     sourceText2);

     StringBuilder clientBuilder = SyntaxHelper
     .PredictorBuilder(Predictor, zipFile);

     SourceText sourceText3 = SourceText
     .From(clientBuilder.ToString(), Encoding.UTF8);

     context.AddSource($”{Predictor}.cs”, 
       sourceText3);

     StringBuilder webapiBuilder = SyntaxHelper
      .ProgramBuilder(Program, zipFile);

     SourceText sourceText4 = SourceText
      .From(webapiBuilder.ToString(), Encoding.UTF8);

     context.AddSource($”{Program}.cs”, sourceText4);
   }

  private (Scenario?, AdditionalText) 
  GetAdditionalFileOptions(
    GeneratorExecutionContext context)
   {
    var file = context.AdditionalFiles.First();
    if (Path.GetExtension(file.Path).Equals(„.zip”,
    StringComparison.OrdinalIgnoreCase))

     {
      context.AnalyzerConfigOptions.GetOptions(file)
      .TryGetValue(„build_metadata.additionalfiles
      .Scenario”, out string scenarioValue);

      Enum.TryParse(scenarioValue, true, 
      out Scenario scenario);

         return (scenario, file);
     }

     return (null, null);
  }
}

Metodele SyntaxHelper. Nu sunt incluse în codul de mai sus, dar puteți găsi întreaga soluție pe github. Acum avem clasa generatorului sursă, o putem referi și folosi în proiectul nostru. Acesta este un cod C# proaspăt generat care folosește informațiile din fișierul binar care conține schema modelul zip ML.NET.

using System;
using Microsoft.ML.Data;

namespace GeneratedDataModels
{
  public class ModelInput
  {
    [LoadColumn(0)]
    public float Temperature { get; set; }

    [LoadColumn(1)]
    public float Luminosity { get; set; }

    [LoadColumn(2)]
    public float Infrared { get; set; }

    [LoadColumn(3)]
    public float Distance { get; set; }

    [LoadColumn(4)]
    public string CreatedAt { get; set; }

    [LoadColumn(5)]
    public string Label { get; set; }

  }
}

using System;
using Microsoft.ML.Data;

namespace GeneratedDataModels
{
  public class ModelOutput
  {
    [ColumnName("PredictedLabel")]
    public string Prediction { get; set; }

    public float[] Score { get; set; }
  }
}

De aici este ușor să generăm un cod boilerplate pentru a consuma modelul ML.NET începând (doar) de la fișierul zip ML.NET.

using System;
using Microsoft.ML;

namespace GeneratedDataModels
{
  public class Predictor
  {
    public static ModelOutput Predict(ModelInput
     sampleData)
    {
      MLContext mlContext = new MLContext(seed: 1);
      ITransformer model = mlContext.Model
        .Load(„C:/Temp/MLModel1.zip”, 
         out var modelSchema);

      var predictor = mlContext.Model
      .CreatePredictionEngine<ModelInput, 
       ModelOutput>(model);

      var predicted = predictor.Predict(sampleData);
      return predicted;
    }
  }
}

Sau poate vă place un serviciu webapi:

using Microsoft.Extensions.ML;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using System.Text.Json;
using System.Threading.Tasks;

namespace GeneratedDataModels
{
  class Program
  {
    static void Main(string[] args)
    {
      WebHost.CreateDefaultBuilder()
        .ConfigureServices(services => {
          services.AddPredictionEnginePool
            <ModelInput,ModelOutput>()
            .FromFile(„C:/Temp/MLModel1.zip”);
        })
        .Configure(app => {
          app.UseHttpsRedirection();
          app.UseRouting();
          app.UseEndpoints(routes => {
            routes.MapPost(„/predict”, 
            PredictHandler);
          });
        })
        .Build()
        .Run();
    }

    static async Task PredictHandler(HttpContext http)
    {
      var predEngine = http.RequestServices
      .GetRequiredService<PredictionEnginePool
      <ModelInput,ModelOutput>>();

      var input = await JsonSerializer
        .DeserializeAsync<ModelInput>
        (http.Request.Body);

      var prediction = predEngine.Predict(input);
      await http.Response.WriteAsJsonAsync(prediction);
    }
  }
}

Cantitatea de cod de mai sus este pentru a vă demonstra că generatoarele sursă sunt de mare ajutor pentru generarea codului redundant și plictisitor, totul realizându-se la momentul compilării! Într-adevăr, vă puteți gândi la source generators ca la un analizor de cod.

Dar nu trebuie să ne oprim aici, putem genera cod pentru a valida modelul ML.NET, pentru a măsura calitatea modelului sau pentru a sugera pipelines de antrenare a modelului.

Un alt caz de utilizare poate fi crearea propriului constructor de modele pornind de la antetul setului de date. Singura limită este propria imaginație.

Resources

  1. Roslyn Source Generators:

  2. ML.NET:

NUMĂRUL 149 - Development with AI

Sponsori

  • Accenture
  • BT Code Crafters
  • Accesa
  • Bosch
  • Betfair
  • MHP
  • BoatyardX
  • .msg systems
  • P3 group
  • Ing Hubs
  • Cognizant Softvision
  • Colors in projects