SoundClassifier with SpeechCommand 18w
Här ska vi bygga ett program som lyssnar efter att man säger vissa ord. Detta kallas för keyword spotting. Vi kommer att använda ml5.js tillsammans med en tränad modell som heter SpeechCommands 18w. Den känner igen följande ord:
talen ”zero” till ”nine”, “up” och “down”, “left” och “right”, ”go” och ”stop” samt ”yes” och ”no”
Programmet ska visa några blobbar. Blobbarna lyssnar efter keywords och agerar utifrån vilket kommando som ges. Talen 0 – 9 ger antal blobbar i olika storlekar. “down”, “left” och “right”, ”go” och ”stop” är kommandon för hur blobbarna ska röra sig. ”yes” och ”no” gör att de ökar respektive minskar i storlek.
Under canvasen ska det stå vilken label som programmet förutser och med vilken säkerhet den förutser just den labeln.

De bibliotek vi använder är ml5.js och p5.js.
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>
Det behövs också en bild som har en transparent bakgrund. Formatet på bilden ska vara .png och storlek på bilden ca 350 * 350 pixlar.

Om du inte har någon bild du vill använda finns min blob. Högerklicka på bilden och välj Spara bild som och använd den till att ladda upp i p5.js editor.
I förklaringen till koden kommer vi, för enkelhet, att använda ordet blob även om du kanske laddat upp annan bild.
För att ladda upp bilden i p5.js editor klicka på pilen bredvid SketchFiles och välj upload file
Det finns några variabler som vi ska använda på lite olika platser i koden. För att kunna göra det behöver variablerna vara globala, dvs skapade utanför alla funktioner.
I script.js skriver vi nu koden
Vi skapar dem med let och ger dem inget initialt värde om de kommer att behöva ändra värden i programmet. Annars använder vi const. Om du behöver uppdatera dig på olika namn för variabler/konstanter kan du titta här.
let classifier;
let label;
let confidence;
let words;
let blobs = [];
let blobImg;
const options = {
probabilityThreshold: 0.95
};
Variablerna är skapade för följande syften:
let classifier; är den modell vi ska använda för att klassificera ord
let label; är den variabel som modellen tror att användaren säger
let confidence; är hur säker modellen är på att den klassificerat rätt ord. Anges som decimaltal
let words; är det vi vill skriva under canvasen och som visar vilka ord modellen kan klassificera
let blobs = []; visar att vi vill skapa en array av olika blobs
let blobImg; är den bild vi vill använda i programmet ( här min blob )
const options = {
probabilityThreshold: 0.95}; options är en del av modellen som gör att vi kan bestämma när den ska agera. Här väljer vi en option vilken säger att modellen ska vara minst 95% säker på att den klassificerar ordet vi säger till någon av de ord den känner igen.
Eftersom vår blob kommer att dyka upp i olika antal, i olika storlekar och röra sig i olika hastigheter så skulle det vara svårt att skriva kod för varje blob.
Vi kommer därför här att introducera något i JavaScript som heter Class.
En class kan användas för ett objekt som ska återanvändas i många olika situationer. Man kan tänka på det som att man skapar en grundmodell för vad en blob är. Hur ser den ut, vilka egenskaper har den mm.
Man brukar kalla det för att man gör en ”blueprint” av objektet. Blueprint är egentligen ett ord som för yngre inte betyder något men ursprungligen menades det att en blueprint var en avgjutning av själva originalet och sen kunde man med den göra kopior.
Vi börjar med att skapa en blueprint för en blob kallad Blobby. Det görs i JavaScript genom att man skapar en class som innehar allt det som är specifikt för en blob.
I JavaScript skapas en class med ordet class och därefter namnet på class med Stor bokstav i början.
I en class används också ett ord som kan kännas lite konstigt. Det är ordet this vilket används så att varje gång ett nytt objekt skapas kommer egenskaperna som skapas med this. kunna sättas till ett unikt värde för just det objektet. Ex. en blob skapas med diametern 30 en annan med diametern 80.
Vi kommer inte att dyka djupt ner i vad this keyword är utan hänvisar då till denna youtube film om du vill veta mer.
Kopiera bara koden gällande Blob class och lägg in den i sketch.js.
class Blobby{…} är en mall för hur en blob ska se ut och fungera. När man sen vill skapa en blob skriver man blob = new Blobby()
constructor() {….} I constructor står allt det som ska skapas direkt när en new Blobby() kallas på. Där står att den nya blobben ska ha x-position (x-Pos) och y-position (yPos) som är slumpmässig (random) mellan 0 och canvasens bredd (width) / canvasens höjd (height). xInc och yInc står för Increment (öka) i x- respektive y-led. Diametern på blobben ska vara slumpmässigt mellan 30 och 80 pixlar.
Sen kommer två metoder
move()..vilken talar om hur blobben ska röra sig . Om den till exempel åker utanför canvasen på höger sida på ska den dyka upp på vänster sida.
display() säger att en ny blob ska visas som en bild med position och storlek som specificerades i constructor().
Koden för Blobby kan skriva längst ner i sketch.js
Hela koden för class Blobby :
class Blobby { constructor() { this.xPos = random(width); this.yPos = random(height); this.xInc = 0; this.yInc = 0; this.diameter = random(30, 80); } move() { this.xPos += this.xInc; this.yPos += this.yInc; if (this.xPos < 0){ this.xPos = width; } if (this.xPos > width){ this.xPos = 0; } if (this.yPos < 0){ this.yPos = height; } if (this.yPos > height){ this.yPos = 0; } } display() { image(blobImg, this.xPos, this.yPos, this.diameter, this.diameter); } }
Saker som tar lång tid att ladda läggs i en funktion som är fördefinierad i p5.js. Den heter preload(). Den funktionen körs klart så inget i setup() påbörjas innan den är klar. Det är för att motverka krockar i programmet.
Här laddar vi upp modellen ml5.soundclassifier och säger att den ska använda SpeechCommands18w och att vi vill använda en option options.
function preload() { classifier = ml5.soundClassifier('SpeechCommands18w', options); blobImg = loadImage("blob.png"); }
Den option som finns för den här modellen kallas probabilityThreshold det vill säga en gräns för när vi tycker att sannolikheten för modellens gissning är ”god nog”. Här valde ci 95% när vi initierade våra variabler.
Med den här koden:
const options = {
probabilityThreshold: 0.95
};
säger vi att vi vill bara att modellen ska klassificera när den är 95% (eller över) säker på att den har uppfattat rätt ord. Anledning till att vi sätter den så högt är att modellen annars lätt kan störas av brus i rummet.
I funktionen setup skapar vi en canvas och 10 blobbar med en for-loop. Att skapa en blob från class Blobby görs genom: blob = new Blobby()
Om du behöver uppdatera dig på for-loop kan du titta här.
function setup() {
createCanvas(710, 400);
// Create 10 blobs
for (let i = 0; i < 10; i++) {
blobs[i] = new Blobby();
}
Vidare i funktionen setup skapar vi den text som visas under canvasen. createDiv() skapar ett fält. Det som står innanför ( ) är det som ska visas som text på skärmen
label = createDiv('Label: ...'); confidence = createDiv('Confidence: ...'); words = createDiv('Commands: “zero” to “nine”, “up”, “down”, “left”, and “right”, “go” and “stop”, “yes” and “no”') }
classifier.classify(gotResult; betyder att modellen ska börja klassificera och sen kalla på funktionen gotResults. Den funktionen finns inte än men den ska innehålla all logik om vad blobbarna ska göra vid olika keywords och den måste vi skapa sen.
imageMode(CENTER); gör så att blobbarnas ritas ut/placeras från centrum på blobben. Standard är att bilder ritas ut/placeras från övre vänstra hörnet.
classifier.classify(gotResult); imageMode(CENTER);
Funktionen gotResults innehåller logik för alla 18 keywords. Jag försöker bryta ner delar av funktionen här och förklara dem och lägger sen in hela koden längre ner så du kan kopiera den.
Funktionen har två parametrar error och results. Om (if) något går fel när modellen klassificerar ska det visas ett felmeddelande i konsolen.
function gotResult(error, results) { if (error) { console.error(error); } label.html('Label: ' + results[0].label);
Om det inte är error så ska texten för vilken label det är skrivas in efter där det står Label på skärmen. Det görs med
label.html(’Label: ’ + results[0].label);
label.html() betyder att den text som finns i variabeln label ska ändras till det som står i ( )
’Label: ’ + results[0].label ’Label’ är text och därefter ska man lägga till +results[0].label.
När man får resultat från modellen hamnar det i en array som heter results. Där listas alla förutsägelser som modellen gör och den gissning som har högst sannolikhet för att vara rätt läggs på index 0 i arrayen. Därav väljer vi att skriva ut label för den förutsägelse som modellen tror att det är.
Sen kommer en if-sats med 17 else if. En för varje ord som modellen tränats på.
För orden, för antal 0-9, tömmer man den array som heter blobs genom att skriva blobs = [ ] det vill säga blobs är en tom array. Därefter skapas en for-loop som lägger till talets (här tre) antal blobbar i arrayen blobs. Med funktionen draw() kommer dessa blobbar sen att ritas ut.
Om du behöver uppdatera dig på if-sats kan du titta här.
Om du behöver uppdatera dig på for-loop kan du titta här.
else if (results[0].label == 'three') { blobs = []; for (let i = 0; i < 3; i++) { blobs[i] = new Blobby(); }
För ordet right ökas xInc med 5. För up och down är det yInc som minskar respektive ökar med 5.För stop sätts både xInc och yInc till 0.
För yes och no ökas/minskas blobbarnas diameter med ett random värde mellan 5 och 10 pixlar
Sist i funktionen skrivs koden för att visa vilken säkerhet (Confidence) som modellen har för sin förutsägelse.
confidence.html('Confidence: ' + nf(results[0].confidence, 0, 2));
+ nf(results[0].confidence, 0, 2); nf() står för number Format och gör att den confidence som modellen har för det första resultatet i array results kommer att formateras som text och kan skrivas ut på skärmen. Siffran 0 säger att det ska vara 0 värdesiffror på vänster sida om kommatecknet (punkt här eftersom det är amerikanska regler som gäller). Siffran 2 säger att det ska rundas av till 2 siffror på höger sida om kommatecknet.
Hela koden för funktionen gotResult:
function gotResult(error, results) { // Display error in the console if (error) { console.error(error); } label.html('Label: ' + results[0].label); if (results[0].label == 'left') { for (let i = 0; i < blobs.length; i++) { blobs[i].xInc = -5; blobs[i].yInc = 0; } } else if (results[0].label == 'right') { for (let i = 0; i < blobs.length; i++) { blobs[i].xInc = 5; blobs[i].yInc = 0; } } else if (results[0].label == 'up') { for (let i = 0; i < blobs.length; i++) { blobs[i].xInc = 0; blobs[i].yInc = -5; } } else if (results[0].label == 'down') { for (let i = 0; i < blobs.length; i++) { blobs[i].xInc = 0; blobs[i].yInc = 5; } } else if (results[0].label == 'stop') { for (let i = 0; i < blobs.length; i++) { blobs[i].xInc = 0; blobs[i].yInc = 0; } } else if (results[0].label == 'go') { for (let i = 0; i < blobs.length; i++) { blobs[i].xInc = random(-10,10); blobs[i].yInc =random(-10,10); } } else if (results[0].label == 'yes') { for (let i = 0; i < blobs.length; i++) { blobs[i].diameter += random(5,10); } } else if (results[0].label == 'no') { for (let i = 0; i < blobs.length; i++) { blobs[i].diameter -= random(5,10); } } else if (results[0].label == 'zero') { blobs = []; } else if (results[0].label == 'one') { blobs = []; for (let i = 0; i < 1; i++) { blobs[i] = new Blobby(); } } else if (results[0].label == 'two') { blobs = []; for (let i = 0; i < 2; i++) { blobs[i] = new Blobby(); } } else if (results[0].label == 'three') { blobs = []; for (let i = 0; i < 3; i++) { blobs[i] = new Blobby(); } }else if (results[0].label == 'four') { blobs = []; for (let i = 0; i < 4; i++) { blobs[i] = new Blobby(); } }else if (results[0].label == 'five') { blobs = []; for (let i = 0; i < 5; i++) { blobs[i] = new Blobby(); } }else if (results[0].label == 'six') { blobs = []; for (let i = 0; i < 6; i++) { blobs[i] = new Blobby(); } }else if (results[0].label == 'seven') { blobs = []; for (let i = 0; i < 7; i++) { blobs[i] = new Blobby(); } }else if (results[0].label == 'eight') { blobs = []; for (let i = 0; i < 8; i++) { blobs[i] = new Blobby(); } } else if (results[0].label == 'nine') { blobs = []; for (let i = 0; i < 9; i++) { blobs[i] = new Blobby(); } } confidence.html('Confidence: ' + nf(results[0].confidence, 0, 2)); // Round the confidence to 0.01 }
Till sist ska funktionen draw() skapas.
Bakgrunden på canvasen sätts till en blue-ish färg. Därefter loopar vi igenom arrayen blobs med en for-loop och kallar på de två metoderna vi skapade i class Blobby,
blobs[i].move();
blobs[i].display();
Hela koden för funktionen draw:
function draw() { background('#091951'); for (let i = 0; i < blobs.length; i++) { blobs[i].move(); blobs[i].display(); } }
Hela koden hittar du här.