ABONAMENTE VIDEO REDACȚIA
RO
EN
×
▼ LISTĂ EDIȚII ▼
Numărul 113
Abonament PDF

Cum să construim un robot inteligent de 100 USD cu ML.NET, IoT Libraries și Raspberry Pi

Daniel Costea
Senior Software Developer @ EU Agency
PROGRAMARE

Există mii de proiecte despre construirea roboților, dar eu mă refer la acei roboți foarte simpli, cu părți în mișcare și inteligență artificială. Am văzut roboți programați în Python, C, Java și alte limbaje, dar cum rămâne cu .NET?

Au trecut deja șase ani de când Microsoft a lansat .NET Core și astfel am avut acces la noi platforme precum Linux, Mac sau ARM și există șase milioane de dezvoltatori .NET pe tot globul. Cei mai mulți dintre ei vor fi mai mult decât fericiți că nu mai trebuie să se confrunte cu probleme de integrare încercând să valorifice în proiectele lor machine learning, deep learning și funcționalități IoT.

În acest articol, vă voi prezenta o suită de tehnologii open-source, cross-platform, code-first din ecosistemul .NET pentru a construi un robot cu un buget de 100 USD.

Din motive practice, am ales Linux (Raspbian OS este o distribuție Debian pentru Raspberry Pi) și Raspberry Pi 3b+ pentru hardware. .NET Core pentru procesoarele ARM are suport începând cu versiunea 2.0, iar ML.NET are suport începând cu versiunea 1.6. Nu toate scenariile de machine learning sau de deep learning sunt încă suportate de către frameworkul ML.NET, dar noi funcționalități sunt în lucru și ne așteptăm la tot mai multe cu fiecare versiune nouă. De exemplu, TensorFlow pare să aibă suport pentru Linux ARM64 (distribuțiile pe 64 de biți oferă suport pentru prezicerea modelelor TensorFlow, dar trebuie să compilați sursele binare, deoarece nu sunt oferite pentru descărcare). Deci, sintetizând, putem face machine learning pe procesoarele ARM, dar trebuie să ne bazăm pe o altă platformă care să aibă procesor x64 pentru puțin ajutor pentru a antrena și a prezice modele de machine deep learning.

Sper că predicția pentru modelele TensorFlow va fi suportată în curând pe arhitecturile ARM64 și mai târziu și antrenarea modelelor, pentru a implementa un robot autonom cu adevărat (pentru că nu trebuie să ne bazăm pe ajutor extern de la platforme compatibile precum x64!). În acest fel,diagrama va fi mult simplificată, după cum urmează:

Până atunci, să ne raportăm la prima noastră diagramă.

Microsoft ML.NET

ML.NET este construit pe .NET Core și .NET Standard (moștenind capacitatea de a rula pe Linux, macOS și Windows), fiind conceput ca o platformă extensibilă. Așadar, puteți utiliza modele create cu alte frameworkuri populare (cum ar fi TensorFlow, ONNX, CNTK). ML.NET este un framework on-premise, open-source, cross-platform, bazat pe cod, dezvoltat de Microsoft și comunitatea .NET.

.NET IoT Libraries

.NET IoT Libraries este un pachet nuget recent dezvoltat de Microsoft și comunitatea .NET care vă oferă acces la layerul GPIO și puteți utiliza o mare varietate de senzori (semnalele digitale sunt acceptate de RPi, dar senzorii analogici pot fi utilizați cu convertoare ADC), o mulțime de shielduri și haturi.

Putem opera fie la nivel inferior cu layerul GPIO și să trimitem semnale digitale către pini, fie cu Iot.Device.Bindings, care este o colecție de wrappere în jurul diferitelor tipuri de senzori, hats, shields, afișaje digitale...

dotnet add package System.Device.Gpio
dotnet add package Iot.Device.Bindings

Pentru partea de mișcare precum motoarele, rotile și controllerul de motoare, am cumpărat un kit foarte ieftin (costa 18 EURO, https://camjam.me/?page_id=1035). Kitul are propriul software scris în Python, dar am ignorat complet partea software. (Am aruncat o privire pentru a-mi da seama cum folosește Python shieldul pentru motoare și am portat câteva clase pentru a lucra cu ele).

Să începem!

Nu am studii aprofundate în electronică, dar având o experiență anterioară de lucru cu senzori analogici, prima mea intenție a fost să adăug un convertor analog-digital (ADC) precum MCP3008. Însă, surpriză, am avut niște probleme la instalarea MCP3008 împreuna cu shieldul motoarelor pe același Raspberry Pi, pentru că ocupă niște pini comuni precum pinii MISO și MOSI (atât MCP3008 cât și shieldul folosesc interfața SPI) și nu știam cum să le fac să funcționeze împreună. Prin urmare, o alternativă mai ușoară a fost utilizarea senzorilor digitali.

Ce este un senzor digital?

Raspberry Pi are o gamă largă de pini GPIO și lucrează cu semnale digitale (on/off sau high/low), dar o valoare binară nu este foarte bogată în informații în comparație cu o gamă largă de valori.

Cei mai mulți senzori digitali sau hibrizi (cu DO/AO sau ieșire digitală/analogică) sunt echipați cu potențiometre manuale pentru reglarea pragului de trecere (sau threshold, care este o valoare binară de high/low). După cum am afirmat mai sus, o valoare binară care depinde de un prag reglabil manual nu este o opțiune foarte bună pentru ceva cum ar fi un robot autonom. Prefer să am o gamă largă de valori ca ieșire pentru setul meu de date pe care intenționez să îl folosesc pentru machine learning.

Așadar ce alte alternative avem?

Ce este un senzor I2C?

Magistrala I2C (bus I2C) este o modalitate simplă și flexibilă de a transfera date digitale între două dispozitive electronice. I2C este alegerea preferată pentru senzori pentru mulți furnizori. Pentru a folosi I2C pe Raspberry Pi, trebuie să activăm magistrala I2C care rulează toolul raspi-config în modul privilegiat.

sudo raspi-config

Raspberry Pi 3b+ are o pereche de pini GPIO rezervați doar pentru o singură magistrală I2C (pinii SDC și SLC). Magistralele 0 și 2 sunt rezervate. Magistrala 1 fiind setată implicit, intenționăm să o folosim pentru a citi luminozitatea. De asemenea, dorim să adăugăm magistrala 3 și 4 pentru a citi valorile infraroșu și temperatură/umiditate.

i2c bus 0 - reserved
i2c bus 1 - illuminance
i2c bus 2 - reserved
i2c bus 3 - infrared
i2c bus 4 - temperature/humidity

Pentru a utiliza mai multe magistrale I2C, a trebuit să reconfigurez pinii adăugând liniile următoare în fișierul de setări /boot/config.txt:

dtoverlay=i2c-gpio,bus=4,
i2c_gpio_delay_us=1,
i2c_gpio_sda=27,
i2c_gpio_scl=22

dtoverlay=i2c-gpio,bus=3,
i2c_gpio_delay_us=1,
i2c_gpio_sda=25,
i2c_gpio_scl=24

După repornire, noile magistrale pot fi verificate după cum urmează:

sudo i2cdetect -l

Veți vedea acum că magistrala I2C 3 și 4 sunt de asemenea afișate. Rulați și:

sudo i2cdetect -y 3
sudo i2cdetect -y 4

Lucrul cu senzori binari digitali folosind layerul GPIO este banal (să presupunem că avem un senzor binar digital conectat la pinul GPIO 23):

public double ReadInfrared()
{
  _gpioController.OpenPin(23, PinMode.Input);
  var infrared = _gpioController.Read(23);
  _gpioController.ClosePin(23);
  return infrared == PinValue.High ? 0 : 1;
}

Operarea cu senzori digitali I2C (IoT.Device.Bindings):

I2cConnectionSettings settings = 
  new I2cConnectionSettings(busId: 1, 
 (int)I2cAddress.AddPinLow);

I2cDevice device = I2cDevice.Create(settings);

using (Bh1750fvi sensor = new Bh1750fvi(device))
{
    // read illuminance(Lux)
    double illuminance = sensor.Illuminance;
}

Iată senzorii I2C folosiți pentru acest proiect:

Bh1750fvi - Senzor de luminozitate

Sht3x - Senzor de temperatură și umiditate

Mlx90614 - Senzor infraroșu

Controlul motoarelor

Controlul motoarele are o logică simplă. Pur și simplu trimitem comenzi fiecărui motor, așteptăm atâta timp cât trebuie să menținem starea și apoi trimitem comenzile de oprire.

_gpioCtrl.Write(_settings.RightBackwardPin, 
  PinValue.Low);
_gpioCtrl.Write(_settings.RightForwardPin, 
  PinValue.High);
_gpioCtrl.Write(_settings.LeftBackwardPin, 
  PinValue.Low);
_gpioCtrl.Write(_settings.LeftForwardPin, 
  PinValue.High);

System.Threading.Thread.Sleep(milliseconds);

_gpioCtrl.Write(_settings.LeftBackwardPin, 
  PinValue.Low);
_gpioCtrl.Write(_settings.LeftForwardPin, 
  PinValue.Low);
_gpioCtrl.Write(_settings.RightBackwardPin, 
  PinValue.Low);
_gpioCtrl.Write(_settings.RightForwardPin, 
PinValue.Low);

Construirea unui model de machine learning

Nu sunt data scientist, iar pentru scenariul nostru putem folosi Model Builder (sau Automate ML).

Model Builder automatizează căutarea celui mai bun trainer pentru un anumit scenariu, antrenând un model și măsurând calitatea acestuia pentru un anumit interval de timp.

Acest aspect pare să facă realizarea modelelor de machine learning banală, dar nu aș vrea să se înțeleagă că nu mai este nevoie de un data scientist. Ceea ce vreau să evidențiez este că nu trebuie să fi un data scientist pentru a folosi machine learning. De cele mai multe ori, veți obține un model funcțional, dar precizia acestui modelul poate fi crescută accentuat prin pregătirea corectă și eficientă a datelor.

Următoarea imagine arată pipeline-ul de training și unele instrumente, cum ar fi cross-validation, confusion matrix, correlation matrix și permutation feature importance. Acestea sunt folosite pentru a crește calitatea modelului de machine learning.

Înainte de orice altceva avem nevoie de date și intenționăm să citim aceste date de la senzori.

Alți senzori

Am avut șansa să vedem câțiva dintre senzorii utilizați pentru robotul nostru, dar vom avea nevoie și de senzorul ultrasonic de proximitate.

HC-SR04 - Ultrasonic Proximity Sensor

Observațiile din setul de date sunt etichetate, ceea ce înseamnă că avem o observație țintă (target feature) numită "IsAlarm" pentru fiecare observație din setul de date.

Suportul ML.NET pentru arhitectura ARM și pentru Blazor

Începând cu ML.NET versiunea 1.6.0 putem antrena și consuma modele în Blazor si pe procesoarele ARM. Tot ce trebuie să faceți este să adăugați următoarele setări în fișierul csproj.

<PropertyGroup>
  <TargetFramework>net6.0</TargetFramework>
    <EnableMLUnsupportedPlatformTargetCheck>
     false
    </EnableMLUnsupportedPlatformTargetCheck>
</PropertyGroup>

...

<ItemGroup>
    <PackageReference Include=”Microsoft.ML” 
    Version=”1.6.0” />

    <PackageReference Include=”Microsoft.ML.FastTree” 
    Version=”1.6.0” />
</ItemGroup>

Captura video

O altă parte a inteligenței artificiale folosită pentru robotul nostru este viziunea computerizată, dar cu Iot.Device.Media acest lucru este foarte simplu.

using Iot.Device.Media;
var settings = new VideoConnectionSettings(
    busId: 0, 
    captureSize: (width, height), 
    pixelFormat: PixelFormat.JPEG
);

var device = Iot.Device.Media
  .VideoDevice.Create(settings);

Dacă citim datele senzorului într-o buclă și le afișăm, obținem următoarele date.

Modelul Inception - Transfer Learning

Learning transfer este o metodă de machine learning în care un model dezvoltat pentru o sarcină este reutilizat ca punct de plecare al unei alte sarcini. Putem folosi bine-cunoscutul model Inception, care este antrenat în primul rând pentru a detecta 1000 de clase diferite, însă de cele mai multe ori nu trebuie să clasificăm obiectul în acele clase predefinite. Mai degrabă vrem să definim propriile noastre clase (nici mai mult, nici mai puțin) și să reantrenăm modelul (aici începe partea de learning transfer, care păstrează partea de extragere a caracteristicilor, dar înlocuiește partea de clasificare de la sfârșit în model).

var data = mlContext.Data
  .LoadFromTextFile(path: tsv, 
  hasHeader: false);

var pipeline = mlContext.Transforms
 .Conversion.MapValueToKey(
    outputColumnName: LabelToKey,
    inputColumnName: nameof(ImageNetData.Label))
 .Append(mlContext.Transforms.LoadImages(
    outputColumnName: INPUT_LAYER,
    imageFolder: imagesFolder,
    inputColumnName: nameof(ImageNetData.ImagePath)))
 .Append(mlContext.Transforms.ResizeImages(
    outputColumnName: INPUT_LAYER,
    imageWidth: ImageNetSettings.imageWidth,
    imageHeight: ImageNetSettings.imageHeight,
    inputColumnName: INPUT_LAYER))
 .Append(mlContext.Transforms.ExtractPixels(
    outputColumnName: INPUT_LAYER,
    interleavePixelColors: ImageNetSettings
    .channelsLast,
    offsetImage: ImageNetSettings.mean))     
    .Append(mlContext.Model.LoadTensorFlowModel
   (inceptionModel)
    .ScoreTensorFlowModel(
        inputColumnNames: new[] { INPUT_LAYER },
        outputColumnNames: new[] { OUTPUT_LAYER },
        addBatchDimensionInput: true))
    .Append(mlContext.MulticlassClassification
    .Trainers
    .LbfgsMaximumEntropy(
        labelColumnName: LabelToKey,
        featureColumnName: OUTPUT_LAYER))
    .Append(mlContext.Transforms.Conversion
    .MapKeyToValue(PredictedLabelValue, 
    PredictedLabel))
    .AppendCacheCheckpoint(mlContext);

    ITransformer model = pipeline.Fit(data);
    var predictionEngine = mlContext
    .Model.CreatePredictionEngine(model);

    var trainData = model.Transform(data);
    mlContext.Model.Save(model, trainData.Schema, 
    modelLocation);

Concluzie

Avem un robot capabil să ia decizii prin procesarea datelor de la senzori folosind machine learning și prin procesarea imaginilor de la cameră folosind deep learning. Astfel de decizii pot fi exprimate prin comandarea motoarelor sale, prin urmare robotul nostru este capabil să detecteze pericolul de incendiu, citind senzorii și validând aceste pericole prin imagini de la camera sa.

Dezvoltarea viitoare a proiectului va include o autonomizare completă a robotului nostru prin mutarea părții de deep learning de pe x64 în Raspberry Pi (după cum am menționat la începutul acestui articol).

Referințe

  1. Apex Robot repository - https://github.com/dcostea/Apex.Robot

  2. .NET Core IoT repository - https://github.com/dotnet/iot/blob/main/Documentation/README.md

  3. Microsoft ML.NET repository - https://github.com/dotnet/machinelearning

  4. Model Builder - https://dotnet.microsoft.com/apps/machinelearning-ai/ml-dotnet/model-builder

  5. Jon Wood youtube channel - https://www.youtube.com/channel/UCrDke-1ToEZOAPDfrPGNdQw

  6. Rubik's code blog - https://rubikscode.net/blog/

Conferință

VIDEO: NUMĂRULUI 120

Sponsori

  • BT Code Crafters
  • Accesa
  • Bosch
  • Betfair
  • FlowTraders
  • MHP
  • Connatix
  • BoatyardX
  • metro.digital
  • AboutYou
  • Colors in projects

VIDEO: EXTRA