Wijnproeven met ML.NET

IT-ROCKSTAR VINCENT BITTER

PART 1

Als je mij goed kent, weet je dat ik een goed glas wijn op zijn tijd zeker kan waarderen. Ja echt, “op zijn tijd”, ik heb geen structureel probleem! Helaas maakt af en toe een glas wijn nuttigen, je nog geen connaisseur, waardoor het soms lastig is om een goede wijn uit te kiezen. Maar ik ben wel software ontwikkelaar, en zoals je weet, hebben software ontwikkelaars een oplossing voor alles… Tenminste, klanten verwachten dat op de één of andere manier. Dus ik vroeg mijzelf af: hoe zou je als software ontwikkelaar een goede wijn uitkiezen?

Machine Learning

Dagelijks verschijnen nieuwe wijnen op de markt en het aantal wijnen, dat nu al verkocht wordt, is (bijna) ontelbaar. Daardoor is het onmogelijk een actuele lijst bij te houden met de kwaliteit van alle wijnen. Om de kwaliteit van alle wijnen te kunnen bepalen, hebben we een zelflerend algoritme nodig. Dat is wat men noemt: Machine Learning!

ML.NET

Er zijn veel verschillende manieren om machine learning te implementeren. Een interessant project van Microsoft is ML.NET. Dit project is in mei 2019 voor het eerst officieel uitgebracht. Omdat de software nog vrij jong is, adviseer ik om een ‘beta’ label te tonen als je dit voor belangrijke functionaliteiten wilt gebruiken. Meer informatie over de status en mogelijkheden van ML.NET vind je op de officiële website.

Algoritme

Voordat we wijn gaan proeven gaan programmeren, moet je weten dat er veel algoritmes zijn voor machine learning. Deze algoritmes kunnen gebruikt worden in verschillende scenario’s en voor verschillende doelen. ML.NET ondersteunt diverse algoritmes, dus het is belangrijk de juiste te kiezen voor je project. Aangezien ik niet alle algoritmes ken en Microsoft waarschijnlijk nieuwe algoritmes toe zal blijven voegen, beschrijf ik in dit artikel de meest gebruikte:

Classification: bepaal een categorie voor items gebaseerd op één of meerdere input variabelen. In het geval van binary classification, zijn er slechts twee categorieën: waar (1) of niet waar (0). Dit wordt gebruikt voor beslissingsmodellen, zoals: is dit een goede of slechte wijn. Daarnaast is er multi-class classification, waarbij meer dan twee groepen worden ondersteund, zoals: uit welke regio komt deze wijn?

Clustering : verdeel items in groepen, op basis van hun eigenschappen. De bekendste manier van klusteren, is het K-Means algoritme. Voorbeeld: wat is de prijscategorie van deze wijn?

Recommendation: om aanbevelingen te doen op basis van eerder gemaakte keuzes van een gebruiker. Dit zou interessant kunnen zijn als je wijn gaat verkopen. Als je weet welke wijn een klant eerder gekocht heeft, kun je andere wijnen aanbevelen op basis van koopgedrag van andere klanten.

Transfer learning: gebruik een model van iemand anders. Objecten op afbeeldingen herkennen met machine learning, vereist een enorme hoeveelheid training data en uren GPU tijd om deze te verwerken. Het zou verspilde moeite zijn om een dergelijke bibliotheek aan te leggen, terwijl anderen dit al gedaan hebben. Microsoft raadt in dit soort gevallen het gebruik van TensorFlow aan.

Regression: voorspel waardes op basis van één of meerdere eigenschappen. Om deze waardes te voorspellen, wordt een model getraind op basis van historische data. Een typisch scenario waarin regressie wordt gebruikt, is om prijzen te bepalen. Hoewel het ook een goede kandidaat is om… de kwaliteit van wijn te voorspellen!

Voorbeeld code

Om de werking van de code in dit artikel te demonstreren, heb ik een Github repository gepubliceerd: https://github.com/vincentbitter/wine-ml

Aan de slag met ML.NET

Het zal je misschien verbazen, maar het opzetten van ML.NET kost slechts een paar minuten! Het is niet nodig om allerlei services of SDK’s te installeren. Je hebt alleen je normale .NET ontwikkel omgeving nodig. Ik gebruik hiervoor Visual Studio 2017 Enterprise (15.9.12 om te precies te zijn) met .NET Core 2.2.

Omdat ik nog geen bestaand project heb, maak ik eerst een nieuw .NET project aan. In dit geval kies ik voor de .NET Core 2.2 Console Application template, maar in principe kan ieder .NET Standard 2.0-gebaseerd project gebruikt worden.

Het tweede dat we moeten doen, is de ML.NET NuGet package toevoegen. Deze heet Microsoft.ML. Let erop dat je versie 1.3.1 installeert. Dit kan via Manage NuGet Packages... in Visual Studio. Of met het volgende commando:

dotnet add package Microsoft.ML --version 1.3.1


Data verzamelen

De applicatie moet de kwaliteit van wijn gaan voorspellen. Om dat te doen, is een grote set aan data nodig, zodat de applicatie getraind kan worden in wijnproeven. Een mooi begin, is de dataset van Paulo Cortez, welke bijna 5000 witte varianten van de Portugese “Vinho Verde” bevat. Rode wijnen negeren we even, omdat ik het onwaarschijnlijk acht dat je één formule kunt maken om de kwaliteit van zowel witte als rode wijnen te voorspellen.

In het voorbeeld project heb ik het .csv-bestand toegevoegd, met de optie Copy to Output Directory. Dit geeft ons de mogelijkheid om LoadFromTextFile van ML.NET te gebruiken. Het is ook mogelijk om andere bronnen in te laden, waaronder een SQL database of URL. Je gebruikt dan LoadFromEnumerable.

Om de data te kunnen mappen, is een eenvoudige class nodig met de juiste properties. Als type worden floats gebruikt. Het is ook mogelijk naar andere types te mappen, maar dat maakt ons leven een stuk moeilijker, terwijl ik in dit artikel juist de eenvoudige basis van ML.NET wil laten zien.

using Microsoft.ML.Data;

 

namespace WineML

{
    class WineData

    {

        [LoadColumn(0)]

        public float FixedAcidity;
        [LoadColumn(1)]
        
public float VolatileAcidity;

        [LoadColumn(2)]

        public float CitricAcid;

        [LoadColumn(3)]

        public float ResidualSugar;

        [LoadColumn(4)]

        public float Chlorides;
        [LoadColumn(5)]

        public float FreeSulfurDioxide;

        [LoadColumn(6)]

        public float TotalSulfurDioxide;

        [LoadColumn(7)]

        public float Density;

        [LoadColumn(8)]
        public float Ph;

        [LoadColumn(9)]

        public float Sulphates;
        [LoadColumn(10)]

        public float Alcohol;

        [LoadColumn(11)]

        public float Quality;

    }

}

Om te bewijzen dat onze voorspelling goed genoeg is, moeten we ons getrainde model straks ook valideren. Het is gebruikelijk om 70% van de beschikbare data te gebruiken om het model te trainen en 30% om te valideren. De dataset is daarom gesplitst in twee delen: winequality-white-train.csv and winequality-white-validate.csv.

var mlContext = new MLContext();

var dataPath = Path.Combine(Environment.CurrentDirectory, "Data", "winequality-white-train.csv");

var trainingData = mlContext.Data.LoadFromTextFile(dataPath, separatorChar: ';', hasHeader: true);

PART 2

Train het model

We hebben de data beschikbaar in ons project, maar we hebben deze nog niet in ML.NET geladen om het model te trainen. De eerste stap is om de rijen naar een model te mappen. We hebben de CSV al naar een IDataView ingelezen, maar zullen ons machine learning model moeten vertellen hoe deze geïnterpreteerd moet worden. In eerste instantie zullen we alle beschikbare velden gebruiken, behalve de Quality kolom, want dat is het veld dat we willen voorspellen. Uiteindelijk gebruiken we de FastTree regressie trainer om het model te trainen.

var model = mlContext.Transforms

    .CopyColumns(

        outputColumnName: "Label",

        inputColumnName: nameof(WineData.Quality))

    .Append(

        mlContext.Transforms.Concatenate(

            "Features",

            nameof(WineData.FixedAcidity),

            nameof(WineData.VolatileAcidity),

            nameof(WineData.CitricAcid),

            nameof(WineData.ResidualSugar),

            nameof(WineData.Chlorides),

            nameof(WineData.FreeSulfurDioxide),

            nameof(WineData.TotalSulfurDioxide),

            nameof(WineData.Density),

            nameof(WineData.Ph),

            nameof(WineData.Sulphates),

            nameof(WineData.Alcohol)))

    .Append(mlContext.Regression.Trainers.FastTree())

    .Fit(trainingData);


Valideer het model

Gaaf, we hebben een model! Maar is het bruikbaar? Dat weten we niet! Daarom moeten we het model valideren. Zonder validatie, is het onmogelijk aan te geven hoe betrouwbaar het model is. Dit is dus echt een belangrijke stap. Microsoft helpt ons hierbij met de Evaluate methode, welke verschillende statistieken over ons model berekent:

var predictions = model.Transform(validationData);

var metrics = mlContext.Regression.Evaluate(predictions, "Label", "Score");

 

Console.WriteLine($"RSquared Score: {metrics.RSquared:0.##}");

Console.WriteLine($"Root Mean Squared Error: {metrics.RootMeanSquaredError:#.##}");


Zorg ervoor dat je andere data gebruikt voor de validatie dan voor de training. Dit is de reden dat we de dataset eerder gesplitst hebben in twee .csv bestanden. De statistieken bevatten onder andere de R Squared en Root Mean Squared Error. De R Squared ligt tussen 0 en 1. Hoe hoger hoe beter! De Root Mean Squared Error moet zo dicht mogelijk bij 0 liggen.

# De resultaten van onze dataset:

RSquared Score: 0.22

Root Mean Squared Error: .72


Voorspel de kwaliteit van wijn

We zijn er klaar voor! Ons model is getraind en we kunnen aan de slag om een voorspeller te bouwen voor de kwaliteit van wijn. De resultaten van de voorspelling zullen opgeslagen worden in een WinePrediction object:

using Microsoft.ML.Data;

 

namespace WineML

{

    public class WinePrediction

    {

        [ColumnName("Score")]

        public float Quality;

    }

}

Nadat we een PredictionEngine gemaakt hebben met de CreatePredictionEngine methode, kunnen we eindelijk een voorspelling doen:

var predictionFunction = mlContext.Model.CreatePredictionEngine<WineData, WinePrediction>(model);

var prediction = predictionFunction.Predict(new WineData {

    FixedAcidity = 7.6F,

    VolatileAcidity = 0.17F,

    CitricAcid = 0.27F,

    ResidualSugar = 4.6F,

    Chlorides = 0.05F,

    FreeSulfurDioxide = 23,

    TotalSulfurDioxide = 98,

    Density = 0.99422F,

    Ph = 3.08F,

    Sulphates = 0.47F,

    Alcohol = 9.5F,

    Quality = 0 // We are gonna predict this. The expected value is 6

});

Console.WriteLine($"{prediction.Quality:0.##}");


Zoals je kunt zien in de voorbeeldcode, is de officiële kwaliteit van de wijn vastgesteld op een 6. Het resultaat van onze PredictionEngine…. is…. 5.69! Dat scheelt niet veel! Dit was de eerste willekeurige rij uit de validatie dataset die ik getest heb, dus ik heb niet vals gespeeld!

Let op het [ColumnName] attribuut in het model. Deze is ingesteld op “Score”, wat niet aangepast mag worden! De score is een vast veld dat gebruikt wordt om het resultaat van de voorspelling in weg te schrijven. Als je dit verandert, weet ML.NET dus niet meer waar de uitkomst van de voorspelling opgeslagen moet worden.

 

Vereenvoudig het model

Het is erg indrukwekkend hoe goed ons model de kwaliteit van wijn kan voorspellen. Maar even terug naar onze missie: een goede wijn kiezen als we in de winkel staan. Hoe realistisch is het dat we alle informatie gaan verzamelen die gevraagd wordt in dit model? Niet bepaald… Daarom moeten we proberen het aantal parameters te minimaliseren. De makkelijkste manier om dat te doen, is de R Squared van elke kolom apart te berekenen en dan alleen het beste veld te gebruiken voor het model. Dat is natuurlijk niet de meest geweldige oplossing, want het kan prima zijn dat de combinatie van twee kolommen een veel hogere betrouwbaarheid oplevert dan één enkele, maar we willen ons leven zo simpel mogelijk maken.

 

Calculate RSquared for FixedAcidity... 0

Calculate RSquared for VolatileAcidity... 0

Calculate RSquared for CitricAcid... 0.04

Calculate RSquared for ResidualSugar... -0.03

Calculate RSquared for Chlorides... 0.06

Calculate RSquared for FreeSulfurDioxide... 0.02

Calculate RSquared for TotalSulfurDioxide... 0

Calculate RSquared for Density... -0.02

Calculate RSquared for Ph... -0.08

Calculate RSquared for Sulphates... -0.03

Calculate RSquared for Alcohol... 0.11

 

De conclusie van ons onderzoek is: de hoeveelheid alcohol is de meest belangrijke factor voor de kwaliteit van wijn! Makkelijk te onthouden en makkelijk om te controleren, want het staat op ieder etiket! Pak gewoon de fles met de meeste alcohol en je hebt gegarandeerd een leuke avond!

Proost!

Dit artikel is als eerste verschenen op 6 augustus 2019 op blog.vincentbitter.nl waarna deze door Vincent Bitter vertaald is voor Team Rockstars IT.