Web­tek­sten au­to­ma­tisch tag­gen met re­cur­ren­te neu­ra­le net­wer­ken

Tek­sten tag­gen met re­cur­ren­te neu­ra­le net­wer­ken

C’t Magazine - - Inhoud - Se­bas­ti­an Sta­bin­ger

Nor­ma­le neu­ra­le net­wer­ken zijn niet li­ne­air en kun­nen geen staps­ge­wij­ze pro­ce­du­res le­ren. Maar re­cur­ren­te neu­ra­le net­wer­ken zoe­ken zelf naar aan­wij­zin­gen voor de vol­gen­de stap. Dat werkt pas echt goed met Long Short­Term Me­mo­ry (LSTM). Als voor­beeld leg­gen we uit hoe je een LSTM kunt trai­nen om de ar­ti­ke­len van een gro­te web­si­te te tag­gen.

Op een gro­te web­si­te staan tal­lo­ze ar­ti­ke­len waar­aan tref­woor­den of­te­wel tags moe­ten wor­den toe­ge­we­zen. Daar wil­len we graag mi­ni­maal één van de 100 meest voor­ko­men­de tags bij heb­ben. Een goe­de le­zer haalt mis­schien 400 woor­den per mi­nuut en moet min­stens 1000 woor­den van een be­richt le­zen om een goe­de keu­ze uit die top 100 te ma­ken. We gaan even uit van een si­te met zo'n 50.000 ar­ti­ke­len. Bij acht uur door­le­zen per dag, zou een mens voor het tag­gen meer dan 2000 uur no­dig heb­ben, ruim 250 da­gen. Dat wil nie­mand doen.

Op ba­sis van de fre­quen­tie waar­mee be­paal­de woor­den of af­lei­din­gen er­van voor­ko­men in een tekst, kun je soms au­to­ma­tisch tags toe­wij­zen. Maar een pro­gram­meur zoe­ken om dit hand­ma­tig te im­ple­men­te­ren, bij­voor­beeld met re­gu­lie­re ex­pres­sies, is mis­schien niet de meest ef­fi­ci­ën­te op­los­sing.

Als er ge­noeg voor­beel­den be­schik­baar zijn, kan de com­pu­ter ook met ma­chi­ne le­arning ont­dek­ken wel­ke tags bij een tekst pas­sen. Voor klas­siek ma­chi­naal le­ren zou je als­nog zo'n pro­gram­meur no­dig heb­ben om co­de schrij­ven voor het be­re­ke­nen van een fea­tu­re-vec­tor. Dat is veel werk. De keu­ze valt dus op deep le­arning (die­pe neu­ra­le net­wer­ken). Daar­bij kun je met­een aan de slag met de tek­sten als in­put.

Er zijn in­mid­dels neu­ra­le net­wer­ken die goed schaal­baar zijn en vlot draai­en op krach­ti­ge gra­fi­sche kaar­ten. Maar tek­sten heb­ben een ver­ve­len­de ei­gen­schap

voor neu­ra­le net­wer­ken. Ze ver­schil­len qua leng­te en de be­te­ke­nis volgt uit de volg­or­de van woor­den, niet uit hun ab­so­lu­te po­si­ties. Een een­vou­dig feed-for­ward­net­werk moet de sa­men­hang voor een en­kel woord meer­de­re ke­ren le­ren, al­leen om­dat het zo­wel op de twee­de als op de vijf­de po­si­tie in een tekst voor­komt. Dat is in prin­ci­pe wel mo­ge­lijk, maar het ver­eist gi­gan­ti­sche hoe­veel­he­den trai­nings­da­ta en zo veel pa­ra­me­ters dat het niet in het ge­heu­gen van een gra­fi­sche kaart past.

Re­cur­ren­te net­wer­ken

Re­cur­ren­te net­wer­ken wil­len dit pro­bleem op­los­sen. Dat doen ze door de in­put stap voor stap te ver­wer­ken. Een re­cur­rent net­werk leest uit de voor­beeld­tek­sten bij­voor­beeld steeds één woord om out­put op te le­ve­ren. Die wordt bij een vol­gen­de stap weer ge­bruikt als in­put sa­men met het vol­gen­de woord. Die com­bi­na­tie le­vert weer out­put op om te ge­brui­ken bij het vol­gen­de woord. Zo wordt steeds een vol­gen­de stap be­re­kend tot­dat het laat­ste woord 'ge­le­zen' is. De laat­ste out­put dient als eind­re­sul­taat. Dit wordt bij het trai­nen van het op­ti­ma­li­sa­tie­al­go­rit­me ver­ge­le­ken met de ge­wens­te uit­komst.

Re­cur­ren­te ('te­rug­ke­ren­de') net­wer­ken zijn dank­zij hun staps­ge­wij­ze be­na­de­ring in staat om reek­sen met va­ri­a­be­le leng­tes te ver­wer­ken. Bo­ven­dien ge­brui­ken ze bij el­ke stap de­zelf­de pa­ra­me­ters, waar­door het aan­tal daar­van bin­nen de per­ken blijft. Door­dat de pa­ra­me­ters bij el­ke stap het­zelf­de zijn, speelt de po­si­tie geen rol meer en gaat het al­leen om de volg­or­de. Als 'goed' bij­voor­beeld volgt op 'niet', kan het net­werk le­ren zelf de juis­te aan­wij­zin­gen te ge­ne­re­ren om de ne­ga­tie te ver­wer­ken. De aan­wij­zin­gen voor de vol­gen­de stap vor­men het ge­heu­gen van het net­werk. Re­cur­ren­te neu­ra­le net­wer­ken (RNN's) zijn ge­schikt voor pro­ble­men waar­bij tijd een rol speelt, zo­als het her­ken­nen van be­we­gin­gen, ge­lui­den of hand­schrif­ten. De be­oor­de­lin­gen van in­put door het net­werk wor­den daar­om ge­woon­lijk als op­een­vol­gen­de stap­pen ge­zien, ook al gaat het niet om ech­te op­een­vol­gen­de si­tu­a­ties, zo­als bij het le­zen van de tek­sten.

Om­dat RNN's zich­zelf be­ïn­vloe­den, is het moei­lijk je­zelf voor te stel­len wat er wan­neer ge­beurt. Dat wordt mak­ke­lij­ker als je de op­een­vol­gen­de stap­pen uit­zet langs een tijd-as. Dan zie je meer­de­re neu­ra­le net­wer­ken naast el­kaar, die de­zelf­de pa­ra­me­ters de­len. De ont­hou­den waar­den gaan via de re­cur­sie­ve ver­bin­ding steeds van de ene stap naar de vol­gen­de. Het net­werk dat zich zo ont­vouwt, train je net als al­le neu­ra­le net­wer­ken door te kij­ken naar de af­ge­lei­de van de door het net­werk be­re­ken­de func­tie (back­pro­pa­ga­ti­on). Die geeft aan wel­ke pa­ra­me­ters in wel­ke ma­te aan fou­ten heb­ben bij­ge­dra­gen.

Long Short­Term Me­mo­ry

He­laas heb­ben een­vou­di­ge RNN's moei­te met het toe­pas­sen van in­for­ma­tie die meer dan een paar stap­pen te­rug is ver­kre­gen. Dit pro­bleem wordt ver­oor­zaakt door­dat ou­de in­for­ma­tie bij el­ke vol­gen­de stap weer door de­len van het neu­ra­le net­werk moet. Back­pro­pa­ga­ti­on biedt wel­is­waar bij el­ke stap uit­sluit­sel over wel­ke fac­to­ren heb­ben bij­ge­dra­gen aan fou­ten. Maar als het aan­deel niet al te groot is, rol­len daar over meer­de­re stap­pen al­leen klei­ne ge­tal­len uit. Die ge­bruikt het op­ti­ma­li­sa­tie­al­go­rit­me om de ge­wich­ten aan te pas­sen, maar dat ge­beurt te wei­nig als kort van te­vo­ren geen be­lang­rij­ke in­for­ma­tie is op­ge­do­ken. Wis­kun­dig ge­zien wordt de gra­di­ënt te klein voor het ge­wens­te leer­re­sul­taat. Dit wordt ook wel het 'va­nis­hing gra­dient pro­blem' ge­noemd. Van­we­ge de­ze be­per­kin­gen wor­den ge­wo­ne RNN's in de prak­tijk nau­we­lijks ge­bruikt.

Om dit pro­bleem op te los­sen, heb­ben Ho­chrei­ter en Sch­mid­hu­ber in 1997 het zo­ge­he­ten Long Short-Term Me­mo­ry (LSTM) be­dacht. Het pro­bleem van de gra­di­ënt wordt ver­me­den door een ge­raf­fi­neer­de in­ter­ne struc­tuur. Om gro­te­re gra­di­ën­ten te

krij­gen, draai­den Ho­chrei­ter en Sch­mid­hu­ber de werk­wij­ze om en leer­den ze LSTM's om in­for­ma­tie nor­ma­li­ter on­ge­wij­zigd van stap naar stap door te ge­ven. In­for­ma­tie weg­gooi­en of nieu­we in­for­ma­tie toe­voe­gen is een be­slis­sing die neu­ro­nen bij de ac­tu­e­le stap moe­ten ne­men.

Om dit te be­rei­ken, houdt het LSTM een in­ter­ne toe­stand ct-1 bij van de vo­ri­ge stap. Die wordt los van het ei­gen­lij­ke neu­ra­le net­werk door­ge­ge­ven aan de vol­gen­de stap (ct). De in­for­ma­tie hoeft dus niet te wor­den bij­ge­hou­den door ge­ac­ti­veer­de neu­ro­nen, waar­mee het pro­bleem wordt ver­me­den van de va­nis­hing gra­dient. Hoe­veel in­for­ma­tie een LSTM-net­werk kan ont­hou­den, hangt af van het aan­tal waar­den in ct.

Het sys­teem heeft een me­cha­nis­me no­dig om de­ze in­ter­ne toe­stand te ver­an­de­ren. Ho­chrei­ter en Sch­mid­hu­ber na­men daar­bij flip­flops als voor­beeld, maar ver­vin­gen de lo­gi­sche poor­ten – of­te­wel ga­tes – door neu­ro­nen. De­ze neu­ro­nen ge­brui­ken een sig­mo­ï­de­func­tie om een ac­ti­va­tie tus­sen 0 en 1 te krij­gen. Om in­for­ma­tie te ver­ge­ten, ver­me­nig­vul­digt het LSTM de in­ter­ne toe­stand met de ac­ti­va­tie van de for­get-ga­te. Bij de zo ver­kre­gen toe­stand telt het LSTM ver­vol­gens de ac­ti­va­ties op van een twee­de ga­te. Die be­staat ei­gen­lijk uit twee neu­ro­nen. De eer­ste be­paalt met de tan­gens hy­per­bo­li­cus een ac­ti­ve­ring die be­waard kan wor­den (in­put­ga­te). Of dat in­der­daad ge­beurt, be­paalt de twee­de neu­ron (up­da­te-ga­te) met een sig­mo­ï­de-ac­ti­ve­rings­func­tie. Het LSTM ver­me­nig­vul­digt de ac­ti­va­ties en telt ze dan op bij de in­ter­ne toe­stand. In­put- en up­da­te-ga­te wer­ken erg nauw sa­men en er zijn LSTM-va­ri­an­ten waar­bij ze wor­den ver­van­gen door een ge­meen­schap­pe­lij­ke ga­te (bij­voor­beeld ga­ted re­cur­rent units). De ei­gen­lij­ke uit­voer (y) die ook naar ho­ge­re la­gen wordt door­ge­ge­ven, be­paalt het LSTM via een neu­ron met tan­gens hy­per­bo­li­cus als ac­ti­ve­rings­func­tie op ba­sis van de in­ter­ne toe­stand na de up­da­te (ct). Maar die ac­ti­va­tie gaat ook niet on­ge­fil­terd door naar de vol­gen­de stap, om­dat eerst nog de out­put-ga­te be­paalt welk deel van de ac­ti­va­tie het LSTM door­geeft.

Te­gen­woor­dig zijn LSTM's bij ver­schil­len­de toe­pas­sin­gen de stan­daard­ar­chi­tec­tuur voor RNN's en wor­den er al­ler­lei prak­ti­sche pro­ble­men mee op­ge­lost. Zo ma­ken bij­voor­beeld Goog­le Trans­la­te en Ap­ple ge­bruik van LSTM's. Ama­zons Alexa, Ap­ples Si­ri en Goog­les Al­lo ge­brui­ken al­le­maal LSTM's voor de spraak­her­ken­ning. Ook het tag­gen van web­si­te­tek­sten is een ty­pi­sche toe­pas­sing.

Trai­nen

Een LSTM wordt door­gaans ge­traind op reek­sen da­ta, waar­bij het net­werk zich ont­rolt langs de re­cur­ren­te ver­bin­din­gen. Het op­ti­ma­li­sa­tie­al­go­rit­me traint het LSTM aan­van­ke­lijk net als een nor­maal feed-for­ward-net­werk, met het ver­schil dat voor el­ke stap de­zelf­de ge­wich­ten voor het net­werk wor­den ge­bruikt. Als het al­go­rit­me dus be­paalt dat ge­wicht g1 bij stap t1 ver­kleind moet wor­den en bij stap t2 ver­groot, dan zal de waar­de van g1 al met al on­ge­wij­zigd blij­ven.

Tot slot leert het net­werk door het trai­nen met he­le reek­sen om bruik­ba­re in­for­ma­tie uit de hui­di­ge stap te be­wa­ren in de in­ter­ne toe­stand, zo­dat ze bij een la­te­re stap lei­den tot be­te­re uit­voer van het net­werk. Het be­wa­ren en be­nut­ten van de in­for­ma­tie moet daar­voor in de­zelf­de reeks ge­beu­ren, zo­dat het aan­pas­sen van de ge­bruik­te ge­wich­ten ui­t­ein­de­lijk iets op­le­vert. Het net­werk be­paalt daar­voor zelf­stan­dig op ba­sis van de trai­nings­da­ta wel­ke in­for­ma­tie in wel­ke si­tu­a­tie be­lang­rijk is en hoe die in de in­ter­ne toe­stand ge­re­pre­sen­teerd moet wor­den. Ver­geet daar­bij niet dat ook een LSTM in fei­te niets an­ders doet dan de pa­ra­me­ters van een gi­gan­ti­sche for­mu­le zo­da­nig aan­pas­sen dat de uit­kom­sten be­ter bij de ge­wens­te re­sul­ta­ten pas­sen dan vóór de trai­ning. Al­le 'slim­me' trucs en me­tho­des die een LSTM na het trai­nen ge­bruikt om het be­tref­fen­de pro­bleem op te los­sen, ont­staan min of meer van­zelf uit de ge­ge­ven da­ta­set.

Dat is mis­schien ook het be­lang­rijk­ste in­zicht in kunst­ma­ti­ge in­tel­li­gen­tie het af- ge­lo­pen de­cen­ni­um: met be­hulp van gro­te hoe­veel­he­den da­ta, veel re­ken­ca­pa­ci­teit en een­vou­di­ge al­go­rit­men, ont­staan in­tel­li­gen­te sys­te­men schijn­baar van­uit het niets.

Een zelf­ge­maak­te LSTM

Met fra­me­works zo­als Ten­sor­Flow kun je ver­ba­zing­wek­kend snel een ei­gen LSTM in Py­thon pro­gram­me­ren. Nog snel­ler gaat het met Keras, dat werkt met Ten­sor­Flow of The­a­no. Keras ab­stra­heert de ge­brui­ke­lij­ke stap­pen om een neu­raal net­werk op te zet­ten. Daar­door kun je met en­ke­le re­gels Py­thon-co­de je ei­gen LSTM op­zet­ten, in­clu­sief be­nut­ten van de gpu.

Voor het trai­nen heb­ben we de nieuws­be­rich­ten van een voor­beeld­site ge­con­ver­teerd naar lijs­ten van woord­num­mers. De woor­den heb­ben we ge­sor­teerd op fre­quen­tie, zo­dat de trai­ning mak­ke­lijk kan wor­den be­perkt tot de 10.000 vaakst voor­ko­men­de woor­den. Lees­te­kens gel­den als woor­den en het ge­tal nul geeft aan dat iets geen woord is. De 10.000 meest fre­quen­te woor­den is een com­pro­mis: de 100 meest voor­ko­men­de woor­den om­vat­ten veel lid­woor­den, voor­zet­sels en hulp­werk­woor­den die voor het tag­gen van ar­ti­ke­len wei­nig zin­vol zijn. Maar als een wei­nig ge­bruikt woord in de trai­nings­da­ta te vaak ver­schijnt bij een spe­ci­fiek tref­woord, dan leert het net­werk voor­al de­ze com­bi­na­tie. Het faalt dan als het tref­woord bij nieu­we tek­sten in een an­de­re con­text ver­schijnt. Als een neu­raal net­werk zich ge­draagt als een luie leer­ling, wordt dat over­fit­ting ge­noemd: het net­werk leert al­leen wat bij de trai­nings­da­ta past, zon­der al­ge­me­ne pa­tro­nen te ont­dek­ken.

Om veel tek­sten pa­ral­lel te kun­nen ver­wer­ken, heb­ben we ze met nul­len aan­ge­vuld tot de­zelf­de leng­te en met gzip-com­pres­sie naar een hdf5-be­stand ge­schre­ven. Dat staat sa­men met de bronQuick­ty­pe

co­de in de GitHub-re­po­si­to­ry bij de link aan het eind van dit ar­ti­kel.

Da­ta pre­pa­re­ren

Het voor­beeld gaat uit van Ten­sor­Flow als back-end in Keras en ge­bruikt Num­py om de da­ta te pre­pa­re­ren. Bo­ven­dien wordt Sa­cred ge­bruikt om de con­fi­gu­ra­tie te be­he­ren. De­ze fra­me­works in­stal­leer je met pip:

pip in­stall wheel pip in­stall num­py ten­sor­flow-gpu keras pip in­stall sa­cred

Om het re­sul­taat van de trai­ning te con­tro­le­ren, is het han­dig een deel van de da­ta apart te hou­den voor va­li­da­tie en niet te ge­brui­ken bij het trai­nen. Je moet een goe­de dwars­door­sne­de van de da­ta ge­brui­ken, zo­dat het net­werk niet wordt ge­traind met nieuws uit 2016 en ver­vol­gens nieu­we ar­ti­ke­len moet va­li­de­ren. We heb­ben de voor­be­rei­din­gen ver­pakt in een Sa­cred-in­gre­dient dat je kunt ge­brui­ken om met­een tot de kern te ko­men:

from sa­cred im­port Ex­pe­ri­ment from voor­beeld­site_­da­ta­set im­port : .voor­beeld­site_in­gre­dient,

: lo­ad_­da­ta, get_wor­d_­count ex = Ex­pe­ri­ment(‘LSTM_Clas­si­fi­ca­ti­on’, in­gre­dients=[voor­beeld­site_in­gre­dient])

Sa­cred zorgt voor het bij­hou­den van de con­fi­gu­ra­tie­va­ri­a­be­len en biedt een com­mand­line-in­ter­fa­ce. De va­ri­a­be­len staan in de met @ex.con­fig in­ge­rich­te func­tie my_ con­fig(). Wat daar­in wordt ge­de­fi­ni­eerd, geeft Sa­cred au­to­ma­tisch door aan de met @ex.au­to­main in­ge­rich­te func­tie train_­net­work(). De­ze struc­tuur maakt het mak­ke­lij­ker om de met my_­con­fig() ge­de­fi­ni­eer­de hy­per­pa­ra­me­ters sys­te­ma­tisch uit te pro­be­ren. Als je dit on­dui­de­lijk vindt, zet je in plaats daar­van de in­houd van my_­con­fig() aan het be­gin van train_­net­work().

Lees eerst de ge­pre­pa­reer­de da­ta­set

in:

X_­train, y_­train, X_­test, y_­test =:

.lo­ad_­da­ta()

In X_­train en X_­test staan de lijs­ten met woord­num­mers, ter­wijl in y_­train en y_ test de vec­to­ren staan met de juis­te bij­be­ho­ren­de tags. Die vec­to­ren zien er­uit als kans­ver­de­lin­gen. Ge­schik­te tags krij­gen een 'kans' van 1,0 ter­wijl on­ge­schik­te er een van 0,0 krij­gen. Wel­ke tags bij de­ze ge­tal­len ho­ren, ver­raadt de func­tie get_­ca­te­go­ry_­list() uit voor­beeld­site_­da­ta­set.py.

Het net­werk de­fi­ni­ë­ren

Keras be­vat de klasse Se­quen­ti­al(), voor neu­ra­le net­wer­ken waar­bij de ene laag zijn uit­voer steeds door­geeft als in­voer voor de vol­gen­de laag. Die struc­tuur wordt bij de mees­te neu­ra­le net­wer­ken ge­bruikt.

from keras.mo­dels im­port Se­quen­ti­al mo­del = Se­quen­ti­al()

Met add()voeg je la­gen toe aan het mo­del. De eer­ste laag speelt een bij­zon­de­re rol, om­dat die de in­te­ger-woord­num­mers in de trai­nings­da­ta moet om­zet­ten naar vec­to­ren met flo­a­ting­point-ge­tal­len. Neu­ra­le net­wer­ken kun­nen na­me­lijk al­leen re­ke­nen met flo­a­ting­point-ten­so­ren. De con­ver­sie wordt ge­daan door een Em­bed­ding()-laag. Daar­voor kun je met de pa­ra­me­ter em­bed­ding_­vec­tor_­di­men­si­o­na­li­ty de diep­te van de re­sul­te­ren­de vec­tor be­pa­len. We heb­ben met 128 di­men­sies goe­de re­sul­ta­ten be­reikt. De em­bed­ding-laag om­vat pa­ra­me­ters die sa­men met de pa­ra­me­ters van de neu­ro­nen wor­den ge­traind:

from keras.lay­ers.em­bed­dings im­port :

.Em­bed­ding mo­del.add(Em­bed­ding(get_wor­d_­count(), em­bed­ding_­vec­tor_­di­men­si­o­na­li­ty,

in­put_length=X_­train.sha­pe[1])) Hier­na heb­ben we een Dro­pout-laag toe­ge­voegd, die geen te trai­nen pa­ra­me­ters be­vat. Die stelt een met em­bed­ding_­dro­pout_ fac­tor be­paald aan­deel wil­le­keu­ri­ge ge­tal­len in de ten­sor op 0,0. Dat ver­kleint de kans dat net net­werk spe­ci­fie­ke voor­beel­den te uit­voe­rig traint. Dro­pout is naast het uit­fil­te­ren van zeld­za­me woor­den een twee­de ma­nier om over­fit­ting te ver­mij­den. Neu­ra­le net­wer­ken fo­cus­sen vaak op de cor­re­la­tie tus­sen af­zon­der­lij­ke ge­tal­len en een ge­wens­te klasse. Als een ge­tal soms (door dro­pout) ont­breekt, zal het net­werk zich eer­der rich­ten op ab­strac­te pa­tro­nen. Op de lan­ge ter­mijn leidt dat tot een be­te­re trai­ning. Maar het trai­nen duurt met dro­pout wel lan­ger.

from keras.lay­ers im­port Dro­pout mo­del.add(Dro­pout(—

em­bed­ding_­dro­pout_­fac­tor))

Daar­na vol­gen nog een aan­tal la­gen met LSTM-een­he­den:

from keras.lay­ers im­port LSTM mo­del.add(LSTM(units=100, re­tur­n_­se­quen­ces=True, re­cur­rent_­dro­pout=0.1,

dro­pout=0.1))

De pa­ra­me­ter units be­paalt de di­men­si­o­ne­ring van de in­ter­ne toe­stand en daar­mee ook de uit­voer van de­ze LSTM-laag. De waar­de 100 be­te­kent dat het net­werk 100 waar­den als ge­heu­gen ge­bruikt en dat de laag voor el­ke in­put ook 100 waar­den als out­put mee­geeft. Hoe ho­ger de di­men­sio-

ne­ring, des te meer het net­werk kan ont­hou­den. Maar daar­mee stijgt ook snel het aan­tal te trai­nen neu­ro­nen. Kies je een te gro­te waar­de, dan duurt de trai­ning lan­ger en kan het net­werk zelfs te groot wor­den voor het werk­ge­heu­gen.

Keras on­der­steunt twee ty­pen LSTMla­gen. Bij het stan­daar­ty­pe ( re­tur­n_ se­quen­ces=Fal­se) wordt een he­le se­rie da­ta door de LSTM-laag ge­stuurd en al­leen de laat­ste out­put van het net­werk door­ge­ge­ven aan de vol­gen­de laag. Dat is wen­se­lijk bij de laat­ste LSTM-laag in je net­werk. Al­le eer­de­re la­gen moe­ten via re­tur­n_­se­quen­ces=True ook tus­sen­waar­den door­ge­ven, zo­dat de vol­gen­de la­gen daar­mee ver­der kun­nen wer­ken.

De twee dro­pout-pa­ra­me­ters stel­len net als bij de Dro­pout()-laag het op­ge­ge­ven aan­deel wil­le­keu­ri­ge waar­den op nul. Maar hier­bij gaat het om het aan­deel bij de da­ta die van stap naar stap wor­den door­ge­ge­ven. Op die da­ta heeft de Dro­pout()-laag geen in­vloed.

Om het net­werk te trai­nen, ver­ge­lijkt Keras de out­put van het net­werk met de ver­wach­te out­put. Die ge­ge­vens moe­ten de­zelf­de om­vang heb­ben en mo­gen al­leen waar­den be­vat­ten tus­sen 0,0 en 1,0. Het een­vou­digst re­a­li­seer je dat met een dich­te laag van 100 neu­ro­nen met sig­moi­de-ac­ti­va­tie­func­tie:

from keras.lay­ers im­port Den­se mo­del.add(Den­se(y_­train.sha­pe[1],

ac­ti­va­ti­on=’sig­moid’))

Nu om­vat mo­del het neu­ra­le net­werk. Om de pa­ra­me­ters te trai­nen, kie­zen we een op­ti­ma­li­sa­tie­al­go­rit­me voor de af­ne­men­de gra­di­ënt:

from keras.op­ti­mi­zers im­port Adam op­ti­mi­zer = Adam(lr=0.001, de­cay=0.00)

Keras werkt met Ten­sor­Flow of The­a­no, die bei­de op ba­sis van het net­werk, de af­ge­lei­de en het op­ti­ma­li­sa­tie­al­go­rit­me een graaf op­bou­wen en com­pi­le­ren. Geef bij de­ze stap aan wel­ke ver­lies­func­tie Adam moet re­du­ce­ren en wel­ke waar­den Keras ver­der be­re­kent om het suc­ces van de trai­ning te be­pa­len: mo­del.com­pi­le(— loss=’bi­na­ry_­cros­sen­tro­py’,

op­ti­mi­zer=op­ti­mi­zer, me­trics=[‘bi­na­ry_ac­cu­r­a­cy’, c_­sco­re])

De bi­na­ry_ac­cu­r­a­cy stijgt snel naar bij­na 98 pro­cent, om­dat het net­werk aan­van­ke­lijk leert om al­le tags als on­ge­schikt te waar­de­ren. Het wordt pas in­te­res­sant bij de laat­ste twee pro­cent­pun­ten, wat je al­leen aan die waar­de moei­lijk kunt af­le­zen. We heb­ben daar­om de 'Cri­tics sco­re' (c_­sco­re) be­dacht, waar­bij het net­werk voor el­ke goed ge­ko­zen tag een punt krijgt en voor el­ke fout ge­ko­zen tag een punt af­trek. Om te zor­gen dat de waar­de bij meer­de­re juis­te tags niet ho­ger wordt dan 1, deelt c_­sco­re het pun­ten­aan­tal door het aan­tal juis­te tags. Het net­werk be­gint de trai­ning met flink ne­ga­tie­ve sco­res en be­reikt daar­na vrij snel de 0 ter­wijl het al­le tags ver­werpt. Ver­vol­gens klimt het met goed toe­ge­we­zen tags lang­zaam naar waar­den tus­sen 0,3 en 0,4.

Start de trai­ning met mo­del.fit(). Geef aan die func­tie de da­ta­sets mee voor trai­ning en va­li­da­tie, het aan­tal keer dat Keras de ge­he­le da­ta­set door­loopt (epoch_­no) en een batch_­si­ze die be­paalt hoe­veel da­ta­sets Keras pa­ral­lel ver­werkt:

mo­del.fit(X_­train, y_­train, va­li­da­ti­on_­da­ta=(X_­test, y_­test),

epochs=epoch_­no, batch_­si­ze=batch_­si­ze)

Hoe gro­ter de bat­ches, hoe be­ter de gra­fi­sche kaart wordt be­nut bij het trai­nen. Maar als je de pa­ra­me­ter te groot kiest, past het net­werk niet meer in het RAM en wordt de trai­ning af­ge­bro­ken. Op een Te­sla P100-kaart kon­den we pri­ma trai­nen met een batch­groot­te van 512, maar voor een ou­de­re GTX 970 moesten we de waar­de ver­la­gen naar 256. Of je kaart goed wordt be­nut, con­tro­leer je met de tool nvi­di­a­s­mi.

Op een snel­le gra­fi­sche kaart duur­de de trai­ning een paar uur. Met een en­ke­le laag van 100 LSTM-een­he­den had­den we al meer dan zes uur no­dig op de P100. Het duurt nog lan­ger als je meer la­gen toe­voegt, met meer dan 500 woor­den uit el­ke da­ta­set traint of meer dan 150 door­lo­pen ge­bruikt. On­ze co­de schrijft steeds de bes­te ge­wich­ten na el­ke door­loop naar het be­stand weights.hdf5 en be­waart ze ook in de Sa­cred-da­ta­ba­se. Bij elk nieuw ex­pe­ri­ment wordt weights.hdf5 over­schre­ven.

Ver­be­te­ring

Na het trai­nen kun je met het net­werk nieu­we tek­sten tag­gen. Daar­voor geef je ze als in­put mee aan de func­tie mo­del.pre­dict() Als uit­voer krijg je dan de kans­ver­de­ling van de 100 tags. De tekst moet eerst zijn om­ge­zet in een lijst van ge­he­le ge­tal­len. De lijst met woor­den ha­len we met de func­tie get_wor­d_­list() uit voor­beeld­site_­da­ta­set.py. Als een woord daar­in niet voor­komt, wordt het op 0 ge­steld.

Om het per­fec­te net­werk voor het tag­gen te vin­den, vol­staat het om de in­stel­lin­gen in my_­con­fig() in train_lstm. py aan te pas­sen. We heb­ben ook ex­pe­ri­men­ten ge­start met een ful­ly-con­nec­ted neu­raal net­werk dat voor elk woord een kans­ver­de­ling be­re­ken­de die voor de ge­he­le tekst werd ge­mid­deld (train_fc.py). Dit net­werk kwam nau­we­lijks uit bo­ven een c_­sco­re van 0. Met een een­vou­dig re­cur­rent net­werk (train_­sim­pleRNN.py) haal­den we een licht po­si­tie­ve sco­re van 3 pro­cent. Met de juis­te pa­ra­me­ters scoort het LSTM met 39 pro­cent veel be­ter.

(mdt)

Om het re­sul­taat van de trai­ning te con­tro­le­ren, moet je een deel van de da­ta apart hou­den voor va­li­da­tie en

niet ge­brui­ken bij het trai­nen

Een gra­fiek van de ver­lies­func­tie bij trai­nen met te wei­nig dro­pout. Bij de eer­ste 70 door­lo­pen met trai­nings- en test­da­ta gaat het goed met het neu­ra­le net­werk. Maar daar­na richt het zich te veel op voor­beel­den uit de trai­nings­da­ta. Dat zorgt er­voor dat het bij door­loop 120 slech­ter scoort met nieu­we da­ta dan daar­voor.

Newspapers in Dutch

Newspapers from Netherlands

© PressReader. All rights reserved.