Sådan tuner vi vores machine learning-algoritme, der finder emneord

10. april 2018 kl. 05:091
Sådan tuner vi vores machine learning-algoritme, der finder emneord
Illustration: Bas Nastassia/Shutterstock.
Med optimeringer kan vi presse lidt flere procents succes ud af vores algoritme, der gætter emneord ud fra en nyskrevet Version2-artikel. Men kan det bruges det til noget?
Artiklen er ældre end 30 dage
Manglende links i teksten kan sandsynligvis findes i bunden af artiklen.

På Version2 har vi talt om muligheden for at finde emneord med maskinlæring. Et emneord er et ord, der beskriver en artikels emne, såsom Databeskyttelsesloven, Sundheds-it, GDPR, Java, Ledelse, eller hvad det nu kunne være.

I en tidligere artikel satte vi handling bag snakken: Med den klassiske algoritme Naive Bayes fra 1960'erne, skabte vi en metode til at gætte på emneord.

Når emnet var sundheds-it, kunne algoritmen finde 7 af 12 artikler om emnet, ud af 297 test-artikler. Der var 5, den ikke kunne finde, og den pegede ikke galt.

Det regnede vi om til fire procent-tal mellem nul og hundrede:

  • Precision - hvor tit gættede algoritmen 'sundheds-it' rigtigt?

  • Recall - hvor stor procentdel af sundheds-it-artiklerne fandt den?

  • Accuracy - træfsikkerhed

  • F1 - et slags gennemsnit af precision og recall, som kan bruges til at optimere algoritmen med.

Vi havde super-god precision på 100 pct., men vores recall var 58 pct., så der var en del, den ikke fandt.

Artiklen fortsætter efter annoncen

Men hvad er egentlig ‘godt’ i sådan en algoritme? Som vi var inde på i sidste artikel, handler det om anvendelsen.

Prøven fandt ikke fatal hjertefejl

Hvis det drejer sig om en medicinsk prøve, vil vi være tilbøjelige til at acceptere flere falske positive, hvis det kan nedsætte antallet af falske negative, hvor en farlig tilstand hos patienten måske bliver overset.

I vores tilfælde, hvor algoritmen skal komme med forslag til emneord, hvorefter journalisterne selv bestemmer, om de vil følge forslaget eller ej, virker en ligelig vægtning af precision og recall som en god ide. Vi ønsker mange pletskud og vil også ramme en stor del af sundheds-it-artiklerne. Derfor er f1-målet, som er et (harmonisk) gennemsnit af precision og recall, det rigtige mål for os.

Men hvad var det nu, vi skulle bruge det hele til?

Artiklen fortsætter efter annoncen

Vi vil gerne have, at algoritmen skal foreslå emner til en ny, frisklavet artikel. Det kræver, at algoritmen rent faktisk giver et bud. Hvis eksempelvis precision er 100% og recall 10%, så kommer der kun et bud hver tiende gang, selvom den gætter helt rigtigt i testsættet.

I tilfældet med sundheds-it vil vi godt give køb på precision, hvis vi kan kan få højere recall. Til den opgave benytter vi som sagt f1-målet, der vægter sol og vind - precision og recall - ligeligt.

Laplace smoothing revisited

I sidste artikel byggede vi de centrale ord-estimater, logPHat, med denne formel, hvor hyppighedstabellen er optællingen af ordenes hyppighed i artikler om sundheds-it:

  1. for (ord in hyppighedstabel):
  2. logPHat = log( (hyppighedstabel(ord) + 1)
  3. / (antalOrdISundhedsItKlassen + antalUnikkeOrdIAlleArtikler) )
  4. sundhedsItEstimatTabel.put(ord, logPHat)

'+ 1' i tælleren og '+ antalUnikkeOrdIAlleArtikler' i nævneren var en måde at kompensere for det forhold, at vi måske støder på ord i testsættet, som ikke optræder i træningssættet.

Teknikken kaldes som nævnt i forrige artikler for Laplace smoothing, men argumentationen bag siger i og for sig ikke, at det lige præcis skal være 1, der lægges til. Vi modificerer nu formlen, så det ekstra bidrag er et komma-tal k. Det ser sådan ud:

  1. for (ord in hyppighedstabel):
  2. logPHat = log( (hyppighedstabel(ord) + k)
  3. / (antalOrdISundhedsItKlassen
  4. + k * antalUnikkeOrdIAlleArtikler) )
  5. sundhedsItEstimatTabel.put(ord, logPHat)

Nu har vi en fri parameter ‘k’, som vi kan bruge til at tune algoritmen.

Vi skal også ændre testalgoritmen tilsvarende. Den ser nu sådan:

  1. sundhedsItLogEstimat = log(artiklerOmSundhedsIt / antalArtikler)
  2. for (ord in artikel):
  3. logPHat = sundhedsItEstimatTabel.get(ord)
  4. if (logPHat == null):
  5. logPHat = log( k / (antalOrdISundhedsItKlassen
  6. + k * antalUnikkeOrdIAlleArtikler) )
  7. sundhedsItLogEstimat += logPHat

Vi kører nu vores emneord igennem algoritmen ligesom sidst, men varierer k fra 0,1 til 2,0 i små skridt. Den værdi af k, der giver højest f1, har vundet.

Artiklen fortsætter efter annoncen

Ofte ender det med, at vi tuner for meget, så algoritmen forveksler testsættets ejendommeligheder med den virkelige verden, et fænomen, der kaldes ‘overfitting.’

F-alfa målet

F1-målet findes også i en anden variant, med navnet F-alfa, hvor man kan vægte precision højere end recall og vice versa:

F_a = (1 + a^2) * (precision * recall) / (a^2 * precision + recall)

F_0,5 lægger således mere vægt på precision, mens F_2 lægger mere vægt på recall.


Derfor tester vi efter optimering på et nyt, uberørt testsæt. Det giver os et bud på, hvor realistisk en f1-værdi vi kan forvente, når vi slipper algoritmen løs i den virkelige verden.

Det betyder, at vi nu har tre sæt af artikler: Vores træningssæt, vores udviklingstestsæt (eller ‘devtestsæt’), og det endelige testsæt.

Vi har ændret lidt i størrelsen af vores datasæt siden sidst, for at få plads til både devtestsæt og testsæt.

Det har sat vores resultat fra tidligere en smule tilbage, så vores eksempel fra sidst - sundheds-it - har nu f1 på 57% (og precision og recall på 80% og 44%.)

Der er plads til forbedringer.

Vi finder nu den optimale værdi af laplace-parameteren k, som beskrevet ovenfor, og det giver for sundheds-it et f1-mål på 75%, med k = 0,3, på baggrund af devtestsættet.

Det var jo en knald-god forbedring, op fra de 57%, vi startede med - men har vi overfittet?

Vi tester nu på vores uberørte testsæt - og så sætter realismen ind:

Her får vi en f1 på kun 56%.

I fortvivlelsens dal (på hype-kurven)

For dælen da - det er lavere end i udgangspunktet.

Det skyldes, at vi startede med devtestsættet og derefter afprøvede med testsættet. Tallene, der kommer ud af algoritmen, afhænger af, hvilket specifikt testsæt vi benytter, også selvom antallet af artikler er det samme i begge sæt.

Gudskelov ser tingene lidt anderledes ud, hvis vi går et skridt baglæns og udregner f1 med k=1,0 og testsættet. Her får vi en f1 på 50%, så tuningen med laplace-k'et gav os en realistisk forbedring på 6 procentpoint. Og det er vel værd at tage med.

Men tilbage til det oprindelige spørgsmål: Kan det bruges til noget?

De 56% i f1 er på baggrund af en precision på 47% og recall på 70%. Den gætter altså sundheds-it rigtigt omkring halvdelen af gangene, og finder 7 ud af 10 mulige.

Hvad siger journalisterne?

Jakob mener, det kan bruges. Magnus peger på, at det »mest irriterende« er falske negative, hvor algoritmen ikke kommer med bud på emnet, og at falske positive ikke er så problematisk - man kan jo bare lade være med at bruge forslaget, hvis man synes, det lyder tosset.

I den forstand klarer algoritmen opgaven med sundheds-it meget godt, ved at afdække omkring 70% af artiklerne, som handler om sundheds-it.

Udover laplace-k'et er der også andre muligheder for at optimere. Sagkundskaben fortæller, at netop med artikler, kan det være en god ide at tælle ord i overskrifter to gange, når hyppighederne opgøres, da disse ord i højere grad afspejler indholdet.

Andre muligheder er at udplukke devtestsættet løbende over hele artikelmængden, og derefter udregne gennemsnitlige logPhat-værdier. Den teknik skulle især være god, hvis man ikke har for mange artikler eller dokumenter at rutte med.

Not a number

En lille parentes: Nogle gange får vi resultatet ‘NaN’, not a number, ved udregningen. Det udskifter vi blot med 0 procent, der i vores læsning betyder ‘maksimalt skidt resultat.’


I en kommende artikel implementerer vi algoritmen i Version2's CMS-system. Lige nu er planen at benytte en bookmarklet (og det var Jakobs ide, skal det siges.) Det bliver ikke lige foreløbigt, men bliv på kanalen.

Se resultater og download koden

Resultaterne kan ses i regnearket her.

Det ville fylde for meget at gengive den Java-kode, vi har brugt i artiklen. Så den kan downloades her (Google Drive kan sige nogle fjollede ting undervejs, men bare tryk 'download.').

Eksemplet indeholder de samme datasæt, der gennemgås her i artiklen. Ligesom sidst er ordene i artiklerne er randomiseret. Algoritmen er ligeglad med rækkefølgen af ordene, så det gør ingen forskel for eksemplet.

Koden er skrevet så eksemplet er nemt at forstå og er ikke optimeret. Der er et par hardcodede filstier, der kan tilrettes, hvis der er bøvl. Det er kommenteret i kildeteksten.

1 kommentar.  Hop til debatten
Denne artikel er gratis...

...men det er dyrt at lave god journalistik. Derfor beder vi dig overveje at tegne abonnement på Version2.

Digitaliseringen buldrer derudaf, og it-folkene tegner fremtidens Danmark. Derfor er det vigtigere end nogensinde med et kvalificeret bud på, hvordan it bedst kan være med til at udvikle det danske samfund og erhvervsliv.

Og der har aldrig været mere akut brug for en kritisk vagthund, der råber op, når der tages forkerte it-beslutninger.

Den rolle har Version2 indtaget siden 2006 - og det bliver vi ved med.

Debatten
Log ind eller opret en bruger for at deltage i debatten.
settingsDebatindstillinger
1
19. april 2018 kl. 14:52

(Google Drive kan sige nogle fjollede ting undervejs, men bare tryk 'download.'

*host* GitHub *host*