Een WinML-invoegtoepassing maken

Deze handleiding laat zien hoe u een systeemeigen C#-invoegtoepassing maakt die gebruikmaakt van Windows Machine Learning (WinML) in uw Electron-app. Met WinML kunt u lokaal machine learning modellen (ONNX-indeling) uitvoeren op Windows-apparaten voor taken zoals afbeeldingsclassificatie, objectdetectie en meer.

Vereiste voorwaarden

Voordat u aan deze handleiding begint, moet u het volgende doen:

Opmerking

WinML wordt uitgevoerd op elk Windows 10 (1809+) of Windows 11 apparaat. Voor de beste prestaties worden apparaten met GPU's of NPU's aanbevolen, maar de API werkt ook op CPU.

Belangrijk

De WinML-invoegtoepassing vereist de experimental Windows App SDK. Als u 'Stabiele SDK's' hebt geselecteerd tijdens winapp init de installatiehandleiding, moet u de SDK-versie bijwerken. Bewerk winapp.yaml en wijzig de versie Microsoft.WindowsAppSDK in 2.0.0-experimental3 en voer vervolgens npx winapp restore uit om bij te werken.

Stap 1: Een systeemeigen C#-invoegtoepassing maken

Laten we een systeemeigen invoegtoepassing maken die Gebruikmaakt van WinML-API's. We gebruiken een C#-sjabloon die gebruikmaakt van node-api-dotnet om JavaScript en C# te overbrugmen.

npx winapp node create-addon --template cs --name winMlAddon

Hiermee maakt u een winMlAddon/ map met:

  • addon.cs - Uw C#-code die WinML-API's aanroept
  • winMlAddon.csproj - Project bestand met verwijzingen naar Windows SDK en Windows App SDK
  • README.md - Documentatie over het gebruik van de invoegtoepassing

Met de opdracht wordt ook een build-winMlAddon script aan uw package.json toegevoegd voor het bouwen van de invoegtoepassing en een clean-winMlAddon script voor het opschonen van buildartefacten:

{
  "scripts": {
    "build-winMlAddon": "dotnet publish ./winMlAddon/winMlAddon.csproj -c Release",
    "clean-winMlAddon": "dotnet clean ./winMlAddon/winMlAddon.csproj"
  }
}

De sjabloon bevat automatisch verwijzingen naar beide SDK's, zodat u onmiddellijk Windows API's kunt aanroepen.

Laten we controleren of alles correct is ingesteld door de invoegtoepassing te bouwen:

# Build the C# addon
npm run build-winMlAddon

Opmerking

U kunt ook een C++-invoegtoepassing maken met behulp van npx winapp node create-addon (zonder de --template vlag). C++ invoegtoepassingen gebruiken node-addon-api en bieden directe toegang tot Windows API's met maximale prestaties. Zie de handleiding voor de C++-meldingsinvoegtoepassing voor een overzicht of de volledige opdrachtdocumentatie voor meer opties.

Stap 2: Het SqueezeNet-model downloaden en voorbeeldcode ophalen

We gebruiken het Classificeer Afbeeldingen voorbeeld uit de AI Dev Gallery als referentie. In dit voorbeeld wordt het SqueezeNet 1.1-model gebruikt voor afbeeldingsclassificatie.

2.1. Het model downloaden

  1. De AI Dev Gallery installeren
  2. Navigeer naar het voorbeeld van de classificatieafbeelding
  3. Download het SqueezeNet 1.1-model (het ondersteunt CPU, GPU en NPU)
  4. Klik op Openen met map om het .onnx bestand te zoeken

SqueezeNet downloaden vanuit AI Dev Gallery

  1. Kopieer het bestand squeezenet1.1.onnx naar een map models/ in de hoofdmap van uw project

Opmerking

Het model kan ook rechtstreeks worden gedownload vanuit de ONNX Model Zoo GitHub opslagplaats

Stap 3: Vereiste NuGet-pakketten toevoegen

Voordat u de WinML-code toevoegt, moeten we extra NuGet-pakketten toevoegen die vereist zijn voor het verwerken van afbeeldingen, ONNX Runtime en GenAI-ondersteuning.

3.1. Directory.packages.props bijwerken

Voeg de volgende pakketversies toe aan het Directory.packages.props bestand in de hoofdmap van uw project (moet zijn gemaakt toen u de invoegtoepassing maakte):

<Project>
  <PropertyGroup>
    <!-- Enable central package versioning -->
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
  </PropertyGroup>
  <ItemGroup>
    <PackageVersion Include="Microsoft.JavaScript.NodeApi" Version="0.9.17" />
    <PackageVersion Include="Microsoft.JavaScript.NodeApi.Generator" Version="0.9.17" />
    <!-- Add these packages for WinML -->
+   <PackageVersion Include="Microsoft.ML.OnnxRuntime.Extensions" Version="0.14.0" />
+   <PackageVersion Include="System.Drawing.Common" Version="9.0.9" />
+   <PackageVersion Include="Microsoft.Extensions.AI" Version="9.9.1" />
+   <PackageVersion Include="Microsoft.ML.OnnxRuntimeGenAI.Managed" Version="0.10.1" />
+   <PackageVersion Include="Microsoft.ML.OnnxRuntimeGenAI.WinML" Version="0.10.1" />
    
    <!-- These versions may be updated automatically during restore to match yaml -->
    <PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.0.0-experimental3" />
    <PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7175" />
  </ItemGroup>
</Project>

3.2. WinMlAddon.csproj bijwerken

Open winMlAddon/winMlAddon.csproj en voeg de pakketverwijzingen toe aan het volgende <ItemGroup>:

<ItemGroup>
  <PackageReference Include="Microsoft.JavaScript.NodeApi" />
  <PackageReference Include="Microsoft.JavaScript.NodeApi.Generator" />
  <!-- Add these packages for WinML -->
+ <PackageReference Include="Microsoft.ML.OnnxRuntime.Extensions" />
+ <PackageReference Include="System.Drawing.Common" />
+ <PackageReference Include="Microsoft.Extensions.AI" />
+ <PackageReference Include="Microsoft.ML.OnnxRuntimeGenAI.Managed" />
+ <PackageReference Include="Microsoft.ML.OnnxRuntimeGenAI.WinML" />
  
  <PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
  <PackageReference Include="Microsoft.WindowsAppSDK" />
</ItemGroup>

Wat deze pakketten doen:

  • Microsoft.ML.OnnxRuntime.Extensions - biedt extra operators en hulpprogramma's voor ONNX Runtime
  • System.Drawing.Common - Hiermee schakelt u het laden en bewerken van afbeeldingen in voorverwerking
  • Microsoft. Extensions.AI - AI-abstracties voor .NET
  • Microsoft.ML.OnnxRuntimeGenAI.Managed - Beheerde bindingen voor ONNX Runtime GenAI
  • Microsoft.ML.OnnxRuntimeGenAI.WinML - WinML-integratie voor ONNX Runtime GenAI

Stap 4: De voorbeeldcode toevoegen

In de AI Dev Gallery ziet u de volledige implementatie voor afbeeldingsclassificatie met SqueezeNet:

SqueezeNet-voorbeeldcode

We hebben deze code voor Electron aangepast en u kunt de volledige implementatie vinden in het elektron-winml-monster. De winMlAddon/ map bevat de gewijzigde code uit de AI Dev Gallery.

Kopieer de hele winMlAddon/ map uit samples/electron-winml/winMlAddon/ naar de hoofdmap van uw project, waarbij u de map vervangt die u in stap 1 hebt gemaakt. Het voorbeeld bevat meer dan meerdere bestanden addon.cs (helperklassen in Utils/, een chatclient, enzovoort) die nodig zijn om de invoegtoepassing te bouwen en uit te voeren.

Belangrijk

U moet de hele map kopiëren, niet alleen addon.cs. De invoegtoepassing is afhankelijk van helperbestanden in de Utils/ submap (Prediction.cs, ImageNet.csBitmapFunctions.cs, enzovoort).

Belangrijke implementatiedetails

Laten we de belangrijke onderdelen van de implementatie en de belangrijkste verschillen van de AI Dev Gallery-code markeren:

1. Projecthoofdpadvereiste

In tegenstelling tot de AI Dev Gallery-code vereist onze Electron-invoegtoepassing de JavaScript-code om het hoofdpad van het project door te geven. Dit is nodig omdat:

  • De invoegtoepassing moet het ONNX-modelbestand in de models/ map zoeken
  • Systeemeigen afhankelijkheden (DLL's) moeten worden geladen vanuit specifieke mappen
[JSExport]
public static async Task<Addon> CreateAsync(string projectRoot)
{
    if (!Path.Exists(projectRoot))
    {
        throw new Exception("Project root is invalid.");
    }

    var addon = new Addon(projectRoot);
    addon.PreloadNativeDependencies();

    string modelPath = Path.Join(projectRoot, "models", @"squeezenet1.1-7.onnx");
    await addon.InitModel(modelPath, ExecutionProviderDevicePolicy.DEFAULT, null, false, null);

    return addon;
}

Hiermee selecteert u automatisch de beste uitvoeringsprovider (CPU, GPU of NPU) op basis van de mogelijkheden van het apparaat.

2. Systeemeigen afhankelijkheden vooraf laden

De invoegtoepassing bevat een PreloadNativeDependencies() methode voor het laden van vereiste DLL's. Deze benadering werkt zowel voor ontwikkelings- als productiescenario's zonder DLL's naar de hoofdmap van het project te hoeven kopiëren:

private void PreloadNativeDependencies()
{
    // Loads required DLLs from the winMlAddon build output
    // This ensures dependencies are available regardless of the execution context
}

Dit wordt aangeroepen tijdens de initialisatie voordat het model wordt geladen, zodat alle systeemeigen bibliotheken beschikbaar zijn.

3. Electron Forge configureren voor het verpakken

Om ervoor te zorgen dat de invoegtoepassing correct werkt in productieversies, moet u uw packager configureren voor:

  1. Systeemeigen bestanden uitpakken - DLL's, ONNX-modellen en .node-bestanden moeten toegankelijk zijn buiten het ASAR-archief
  2. Onnodige bestanden uitsluiten - Houd de pakketgrootte klein door buildartefacten en tijdelijke bestanden uit te sluiten

Werk uw forge.config.js bij voor Electron Forge.

// From samples/electron-winml/forge.config.js
module.exports = {
  packagerConfig: {
    asar: {
      // Unpack native files so they can be accessed by the addon
      unpack: "**/*.{dll,exe,node,onnx}"
    },
    ignore: [
      // Exclude .winapp folder (SDK packages and headers)
      /^\/.winapp\//,
      // Exclude MSIX packages
      "\\.msix$",
      // Exclude winMlAddon source files, but keep the dist folder
      /^\/winMlAddon\/(?!dist).+/
    ]
  },
  // ... rest of your config
};

Wat dit doet:

  1. asar.unpack - Extraheert DLL's, uitvoerbare bestanden, .node binaire bestanden en ONNX-modellen naar app.asar.unpacked/

    • Hierdoor zijn ze tijdens runtime toegankelijk via bestandssysteempaden
    • Met de JavaScript-code worden paden automatisch aangepast (zie de app.asar bovenstaande → app.asar.unpacked vervanging)
  2. ignore - Uitgesloten van het uiteindelijke pakket:

    • .winapp/ - SDK-pakketten en -headers (niet nodig tijdens runtime)
    • .msix bestanden - Verpakte uitvoer
    • winMlAddon/ bronbestanden: alleen de dist/ map met gecompileerde binaire bestanden behouden

Opmerking

Als u een ander verpakkingshulpprogramma gebruikt (electron-builder, enzovoort), moet u vergelijkbare instellingen configureren voor het uitpakken van systeemeigen afhankelijkheden en het uitsluiten van ontwikkelbestanden. Raadpleeg de documentatie van uw packager voor ASAR-uitpakopties.

4. Afbeeldingsclassificatie

De ClassifyImage methode verwerkt een afbeelding en retourneert voorspellingen:

[JSExport]
public async Task<Prediction[]> ClassifyImage(string imagePath)
{
    // Loads the image, preprocesses it, and runs inference
    // Returns top predictions with labels and confidence scores
}

De volledige implementatie verwerkt het volgende:

  • Afbeelding laden en vooraf verwerken (formaat wijzigen, normaliseren)
  • De modeldeductie uitvoeren
  • Resultaten na de verwerking om de beste voorspellingen te krijgen met labels en betrouwbaarheidsscores

Opmerking

De volledige broncode bevat voorverwerking van afbeeldingen, het maken van tensor en het parseren van resultaten. Controleer de voorbeeld-implementatie op alle details.

De code begrijpen

De invoegtoepassing biedt de volgende hoofdfuncties:

  1. CreateAsync - Initialiseert de invoegtoepassing en laadt het SqueezeNet-model
  2. ClassifyImage : neemt een afbeeldingspad en retourneert classificatievoorspellingen

WinML selecteert automatisch het beste uitvoeringsapparaat (CPU, GPU of NPU) op basis van beschikbaarheid.

Stap 5: de C#-invoegtoepassing bouwen

Bouw nu de invoegtoepassing:

npm run build-winMlAddon

Hiermee compileert u uw C#-code met behulp van systeemeigen AOT (compilatie vooraf), die:

  • Maakt een .node binaire bestand (native addon formaat)
  • Verwijdert ongebruikte code voor een kleinere bundel.
  • Vereist geen .NET-runtime op doelcomputers.
  • Biedt systeemeigen prestaties

De gecompileerde invoegtoepassing bevindt zich in winMlAddon/dist/winMlAddon.node.

Stap 6: De invoegtoepassing testen

We gaan nu testen of de invoegtoepassing werkt door deze aan te roepen vanuit het hoofdproces. Open src/main.js en volg deze stappen:

6.1. De invoegtoepassing laden

Voeg bovenaan de vereiste instructies toe:

const winMlAddon = require('../winMlAddon/dist/winMlAddon.node');

6.2. Een testfunctie maken

Voeg deze functie toe om afbeeldingsclassificatie te testen:

const testWinML = async () => {
  console.log('Testing WinML addon...');
  
  try {
    let projectRoot = path.join(__dirname, '..');
    // Adjust path for packaged apps
    if (projectRoot.includes('app.asar')) {
      projectRoot = projectRoot.replace('app.asar', 'app.asar.unpacked');
    }
    
    const addon = await winMlAddon.Addon.createAsync(projectRoot);
    console.log('Model loaded successfully!');
    
    // Classify a sample image
    const imagePath = path.join(projectRoot, 'test-images', 'sample.jpg');
    const predictions = await addon.classifyImage(imagePath);
    
    console.log('Top predictions:');
    predictions.slice(0, 5).forEach((pred, i) => {
      console.log(`${i + 1}. ${pred.label}: ${(pred.confidence * 100).toFixed(2)}%`);
    });
  } catch (error) {
    console.error('Error testing WinML:', error.message);
  }
};

Belangrijkste punten:

  • De padaanpassing (app.asarapp.asar.unpacked) zorgt ervoor dat de code werkt in zowel ontwikkel- als verpakte apps
  • Hiermee worden de uitgepakte systeemeigen bestanden geopend die zijn geconfigureerd in forge.config.js

6.3. De testfunctie aanroepen

Voeg deze regel toe aan het einde van de createWindow() functie:

testWinML();

6.4. Testafbeeldingen voorbereiden

Afbeeldingsclassificatie testen:

  1. test-images/ Een map maken in de hoofdmap van uw project
  2. Voeg een testafbeelding toe met de naam sample.jpg (de code verwacht deze exacte bestandsnaam)
  3. Het SqueezeNet-model herkent 1000 verschillende ImageNet-klassen (dieren, objecten, scènes, enzovoort)

Wanneer u de app uitvoert, ziet u de classificatieresultaten in de console.

Tip

Zie het electron-winml-voorbeeld voor een volledige implementatie met IPC-handlers, dialoogvensters voor bestandsselectie en een gebruikersinterface.

Stap 7: Foutopsporingsidentiteit bijwerken

Om ervoor te zorgen dat de Windows App SDK is geladen en beschikbaar is voor gebruik, moeten we ervoor zorgen dat we de foutopsporingsidentiteit instellen die ervoor zorgt dat het framework wordt geladen wanneer onze app wordt uitgevoerd. Telkens wanneer u Package.appxmanifest wijzigt of bronnen wijzigt die in het manifest worden genoemd (zoals app-pictogrammen), moet u de debug-identiteit van uw app bijwerken. Rennen:

npx winapp node add-electron-debug-identity

Deze opdracht:

  1. Leest uw Package.appxmanifest gegevens voor het ophalen van app-details en -mogelijkheden
  2. Registreert electron.exe in uw node_modules met een tijdelijke identiteit
  3. Hiermee kunt u API's waarvoor authenticatie vereist is testen zonder volledige MSIX-verpakkingen

Opmerking

Deze opdracht maakt al deel uit van het postinstall script dat we in de installatiehandleiding hebben toegevoegd, dus deze wordt automatisch uitgevoerd na npm install. U moet deze echter handmatig uitvoeren wanneer u het volgende doet:

  • Wijzigen Package.appxmanifest (mogelijkheden, identiteit of eigenschappen wijzigen)
  • App-assets bijwerken (pictogrammen, logo's, enzovoort)

Voer nu uw app uit:

npm start

Controleer de console-uitvoer. U ziet nu de WinML-testresultaten.

⚠️ Bekend probleem: app-crashes of een leeg venster (klik om uit te vouwen)

Er is een bekende Windows-bug bij sparse packaging van Electron-toepassingen die ervoor zorgt dat de app vastloopt bij het starten of geen webinhoud weergeeft. Het probleem is opgelost in Windows, maar is nog niet doorgegeven aan alle apparaten.

Zie inrichting van de ontwikkelomgeving voor een tijdelijke oplossing.

Volgende stappen

Gefeliciteerd! U hebt een systeemeigen invoegtoepassing gemaakt waarmee machine learning-modellen kunnen worden uitgevoerd met WinML. 🎉

U kunt nu het volgende doen:

Of bekijk andere handleidingen:

Aanpassen voor uw model

Als u uw ONNX-model volledig wilt integreren, moet u het volgende doen:

  1. Inzicht in de invoer van uw model : afbeeldingen, tensoren, reeksen, enzovoort.
  2. Juiste invoerbindingen maken : converteer uw gegevens naar de indeling die WinML verwacht
  3. De uitvoer verwerken: de voorspellingen van het model parseren en interpreteren
  4. Fouten probleemloos afhandelen - Model laden en inferentie kunnen mislukken

Aanvullende informatiebronnen

Troubleshooting

Build mislukt met NU1010: PackageReference-items definiëren geen bijbehorende PackageVersion

Zorg ervoor dat alle pakketten waarnaar wordt verwezen winMlAddon.csproj overeenkomende vermeldingen bevatten in Directory.packages.props. Zie stap 3 voor de volledige lijst met vereiste pakketten.

'geen geldige Win32-toepassing' bij het laden van de invoegtoepassing

Dit betekent dat de invoegtoepassing is gebouwd voor een andere architectuur dan uw Node.js/Electron-runtime. Controleer de architectuur van uw Node.js:

node -e "console.log(process.arch)"

Bouw vervolgens de invoegtoepassing opnieuw met het overeenkomende doel:

# For x64 Node.js:
dotnet publish ./winMlAddon/winMlAddon.csproj -c Release -r win-x64

# For ARM64 Node.js:
dotnet publish ./winMlAddon/winMlAddon.csproj -c Release -r win-arm64

Als u onlangs uw Node.js-installatie hebt gewijzigd, installeert u deze ook opnieuw node_modules om het overeenkomende binaire electronenbestand op te halen:

rm -rf node_modules package-lock.json
npm install

Krijg hulp

Veel plezier met machine learning! 🤖