Feature Extraction

Feature Extraction

Vi ska bygga ett program som använder Feature Extraction från modellen MobileNet.
Att använda delar av de särdrag som en modellen lärt sig kallas Feature Extraction och tekniken kallas Transfer Learning.
De bibliotek vi använder är ml5.js och p5.js.
Programmet kommer att kunna träna på saker som inte finns i MobileNet men drar nytta av att MobileNet har tränats på att känna igen särdrag i bilder vilket gör att det är väldigt effektivt att använda den här metoden. Programmet kommer att ha en canvas som via webkameran kan se vad användaren visar upp. Det ska finnas möjlighet att träna modellen för två olika föremål och bakgrund. Knapparna används för att samla data. Vid klick kommer det att visas med text hur många exempel som samlats in. Ett antal av varje objekt samlas in samt ungefär lika många med bakgrundsbild. Det kommer också att finnas en knapp startar träning av modellen. Knapparna heter bakgrund, example1, example2 och train.
Vi brukar använda engelska ord i programmen men här ser du att det står bakgrund på svenska. Anledningen för det är att ordet background är ett reserverat ord i p5.js och representerar en funktion för att byta bakgrundsfärg på canvasen, så det går inte att använda det.

Kod som står i de ljusgröna fälten nedan är den kod som ska skrivas i editorn. De grå fälten är där kod visas för en närmare beskrivning av delar av koden.

Starta en ny sketch via p5.js editor.

I index.html måste vi lägga till ett bibliotek för att få tillgång till ml5.js.
Efter de script som finns lägg till:

<script src="https://unpkg.com/ml5@latest/dist/ml5.min.js"></script>

I script.js skriver vi nu koden.
Vi skapar först globala variabler för modell, video och knappar. Antal exempel för varje bild sätts initialt till noll.
Vi använder här let eftersom vi vill ändra objektet senare i koden.

// Variables for model, video and text
let featureExtractor;
let classifier;
let video;
let label = "test";
// Buttons for collecting data and train model
let backgroundButton;
let example1Button;
let example2Button;
let trainButton;
let antalExample1 = 0;
let antalExample2 = 0;
let antalBakgrund = 0;

I funktionen setup skapas canvas och video. När ett video element skapas placeras det efter canvasen på sidan vilket vi inte vill så därför använder vi funktionen hide() för att dölja den. Testa att kommentera bort video.hide() för att se hur p5.js normalt placerar video. Att kommentera bort en rad görs med att sätta // framför det som ska bort.
Senare, i funktionen draw() kommer vi att visa videon på canvasen. Bakgrundsfärgen på canvasen sätts till svart.

function setup() {
  createCanvas(320, 270);
  video = createCapture(VIDEO);
  video.hide();
  background(0);

Vidare i funktionen setup initierar vi modellen:

  featureExtractor = ml5.featureExtractor("MobileNet",{numLabels: 3}, modelReady);

De tre parametrar som står inom parentesen är:

MobileNet dvs vilken modell vill vi extrahera features från

{numLabels: 3} är hur många olika kategorier vi vill klassificera

modelReady är vilken funktion som ska kallas på när modellen är laddad in i programmet

Vi initierar också classifier vilken visar på att vi vill klassificera med hjälp av featureExtractor

  classifier = featureExtractor.classification( video, videoReady);

parametrarna här är:

video  vilket sager att vi vill klassificera från video input

videoReady anger vilken funktion som ska kallas på när classifier är laddad in i programmet

Efter setup() och draw() kan vi skriva funktionerna modelReady och videoReady.
Funktionerna kommer att skriva till konsolen att de är redo.

function modelReady() {
  console.log("Model is ready!!!");
}

function videoReady() {
  console.log("Video is ready!!!");
}

Vidare i setup() skapas alla knappar (buttons). backgroundButton, exemple1Button och exemple2Button har liknande kod. Det är bara texten som ska stå på knappen som skiljer sig.

Först skapas knappen med createButton(). Det som står inom parenteserna är den test som ska visas på knappen.
På nästa rad ger vi knappen en class(”btn”). Anledningen till det är så vi kan referera till knapparna i style.css och ge dem en annan stil.
Därefter skapar vi en funktion för vad som ska hända när knappen klickas på:

antalBakgrund++ gör så att värdet på variabeln antalBakgrund ökar med 1 för varje klick

classifier.addImage(”bakgrund”); gör att en bild tas och sparas till modellen classifier med label

bakgrund backgroundButton.html(”bakgrund  ” + antalBakgrund); Här ändrar vi vad som ska stå på knappen. Det är html() som används för att göra det. Vi säger att det ska stå texten bakgrund genom att skriva det inom ” ”. Med plustecken lägger vi till att det också ska stå värdet som antalBakgrund har efter varje klick.

backgroundButton = createButton("bakgrund");
  backgroundButton.class("btn");
  backgroundButton.mouseClicked(function () {
    antalBakgrund++;
    classifier.addImage("bakgrund");
    backgroundButton.html("bakgrund  " + antalBakgrund);
  });
// De andra två knapparna
example1Button = createButton("example1");
  example1Button.class("btn");
  example1Button.mouseClicked(function () {
    antalExample1++;
    classifier.addImage("example1");
    example1Button.html("example1  " + antalExample1);
  });

  example2Button = createButton("example2");
  example2Button.class("btn");
  example2Button.mouseClicked(function () {
    antalExample2++;
    classifier.addImage("example2");
    example2Button.html("example2 " + antalExample2);
  });

För knappen för att starta träningen skapas först knappen och den får klassen btn.
I funktionen för vad som ska hända när den klickas på så skriver vi  classifier.train(whileTraining);det vill säga börja träna modellen och kalla på funktionen whileTraining. Funktionen whileTraining finns inte än men vi ska snart skriva den efter funktionen videoReady.

  trainButton = createButton("train");
  trainButton.class("btn");
  trainButton.mousePressed(function () {
    classifier.train(whileTraining);
  });

Hela koden för funktionen setup.

function setup() {
  createCanvas(320, 270);
  video = createCapture(VIDEO);
  video.hide();
  background(0);

  featureExtractor = ml5.featureExtractor("MobileNet",{numLabels: 3}, modelReady);
  classifier = featureExtractor.classification( video, videoReady);
  //console.log(classifier)
  
  backgroundButton = createButton("bakgrund");
  backgroundButton.class("btn");
  // onClick för att kunna räkna
  backgroundButton.mouseClicked(function () {
    antalBakgrund++;
    classifier.addImage("bakgrund");
    backgroundButton.html("bakgrund  " + antalBakgrund);
  });

  example1Button = createButton("example1");
  example1Button.class("btn");
  // onClick för att kunna räkna
  example1Button.mouseClicked(function () {
    antalExample1++;
    classifier.addImage("example1");
    example1Button.html("example1  " + antalExample1);
  });

  example2Button = createButton("example2");
  example2Button.class("btn");
  example2Button.mouseClicked(function () {
    antalExample2++;
    classifier.addImage("example2");
    example2Button.html("example2 " + antalExample2);
  });

  trainButton = createButton("train");
  trainButton.class("btn");
  trainButton.mousePressed(function () {
  classifier.train(whileTraining);
  });
}

Funktionen whileTraining.

Parametern loss som skrivs in i funktionen är en siffra som visar på det totala fel som modellen har för varje gång all data körs genom modellen. I det här fallet är det 20 gånger. Se mer i den blå faktarutan nedan.

Vi använder en if-sats med else för att bestämma vad som ska ske under träning. Om du behöver uppdatera dig på if och else kan du titta här.

Om (if) det inte genereras någon mer loss, det vill säga att modellen körts i 20 epochs så vill vi att det skrivs ut Training Complete i konsolen. Modellen ska börja klassificera och kalla på funktionen gotResults. Den funktionen ska vi sen skriva längst ner i koden efter funktionen whileTraining.

I else anger vi att om det fortfarande genereras loss vilket innebär att modellen inte är klar med sin träning än så vill vi att det i konsolen skrivs ut vilken loss det är.

function whileTraining(loss) {
  if (loss == null) {
    console.log("Training Complete");
    classifier.classify(gotResults);
  } else {
    console.log(loss);
  }
}

Du kan se parametrar för modellen genom att skriva console.log(classifier) i setup direkt efter där classifier initieras och sen starta programmet igen. Om du klickar pilen på den nya rad som skapas i konsollen och sen klickar på pilen invid config får du fram olika parametrar som styr modellen. De som jag fet-markerat nedan förklaras med text efter.
De här parametrarna kan man ändra på men för vår modell fungerar de givna värdena bra
config: Object
epochs: 20 // hur många gånger all data ska köras igenom modellen
version: 1
hiddenUnits: 100
numLabels: 3
learningRate: 0.0001 // hur mycket modellen ska vrida på sina parametrar för att förbättra sig
batchSize: 0.4
layer: ”conv_pw_13_relu”
alpha: 0.25
model: null
url: ”https://tfhub.dev/google/imagenet/mobilenet_v1_025_224/classification/1”

Nu ska vi skriva sista funktionen gotResults. Den har två parametrar error och results vilka hanterar vad som ska ske om något går fel respektive om allt fungerar. Om( if ) det är error ska ett felmeddelande skrivas ut i konsolen annars ( else ) ska variabeln label ändras till att visa vilken label modellen förutsäger att det är. result[0].label betyder att vi vill visa det första resultatet [0]  i en array som heter result. Vi vill ha dess label inskriven i där det i början av programmet står test.

Därefter kallar vi på classifier.classify(gotResults) igen så att modellen fortsätter att klassificera hela tiden.

function gotResults(error, result) {
  if (error) {
    console.error(error);
  } else {
    label = result[0].label;
    classifier.classify(gotResults);
  }
}

Det enda som är kvar nu är att ge knapparna lite stil.
I style.css skriv:

html, body {
  margin: 0;
  padding: 0;
}
canvas {
  display: block;
}
.btn{
  display:flex;
  background: pink;
  padding:5px 10px;
  border-radius: 5px;
  margin: 10px 10px 10px 50px;
  font-size: 24px;
}

Hela koden för programmet finns att se här.