- Je somt uitlijningen met een begrensde dynamische tabel en correcte skipvoorwaarden.
- Je gebruikt de juiste beta-conventie voor posteriors en gradiënt p min q.
Een reeks van 64 frames met maar twee mogelijke klassen heeft al meer klassepaden dan je zinvol kunt uitschrijven. Toch wil je de kans van één bekende transcriptie berekenen en daarop leren. Dat lukt door gedeelde tussenberekeningen één keer te bewaren.
Denk aan routes door een raster. Veel routes komen op hetzelfde punt uit en hebben vanaf daar dezelfde vervolgmogelijkheden. CTC telt hun massa op in een dynamische tabel, zonder iedere volledige route apart te onthouden.
We gebruiken weer blankkans 0,6 en A-kans 0,4 op twee frames, met doel A. Dezelfde berekening die eerder 0,64 gaf, wordt nu een voorbeeld voor de tabel. Later gebruiken we de tussenwaarden ook om echte trainingsgradiënten te bepalen.
1. Houd bij hoeveel van het doel is bereikt
Plaats blanks vóór, tussen en na de doellabels. Voor doel A wordt de uitgebreide toestandrij [blank,A,blank]. Voor U doellabels zijn er zo S=2U+1 toestanden; verschillende blankposities blijven afzonderlijke toestanden.
logits = np.log([[.6,.4],[.6,.4]])
forward = ctc_forward(logits,[1])
print(forward['expanded'].tolist())
# [0, 1, 0]
Je mag in dezelfde toestand blijven of één toestand vooruitgaan. Een sprong van twee mag alleen naar een niet-blank label dat verschilt van het label twee posities terug. Die laatste regel voorkomt dat een dubbele A zonder tussengelegen blank ongemerkt één A wordt.
De code bewaart logmassa's in alpha; noem zo'n waarde a op frame t en toestand s. I bevat de toegestane ouderwaarden van het vorige frame, en c_s is het klasselabel van deze toestand. Logsumexp, afgekort LSE, telt massa's op terwijl ze in logruimte blijven.
Op het eerste frame mag alleen de eerste blank of het eerste doellabel beginnen. Andere cellen krijgen −∞: logaritme van nul massa, dus onbereikbaar. Dat is een geldige tabelwaarde en geen foutieve modelinvoer.
for row in np.exp(forward['alpha']):
print(row.round(3).tolist())
# [0.6, 0.4, 0.0]
# [0.36, 0.4, 0.24]
Aan het einde mag het doel met of zonder laatste blank zijn afgerond. Daarom tel je de laatste twee toestandsmassa's op: 0,4+0,24=0,64. Een leeg doel heeft maar één toestand, waarvoor alleen die laatste cel telt.
Maak ctc_forward met de gegeven invoerhelpers en zelfstandige uitvoerarrays. Een gewone som van loggetallen zou producten in plaats van kanssommen opleveren. Gebruik daarom steeds logaddexp voor samenkomende ouders.
Volg de toestanden van een gegeven transcriptie. Vergelijk de voorwaartse logmassa met de posterior: die tweede kijkt ook naar de rest van de audio.
Uitgebreid doel: _ A _. De positie s onderscheidt verschillende toestanden met hetzelfde label; _ staat voor blank.
| s | Label | log α | Posterior |
|---|---|---|---|
| 0 | blank | -0,511 | 0,375 |
| 1 | A | -0,916 | 0,625 |
| 2 | blank | −∞ | 0 |
| Klasse | Kans p | Posterior q | p − q |
|---|---|---|---|
| blank | 0,6 | 0,375 | 0,225 |
| A | 0,4 | 0,625 | -0,225 |
- Logkans gehele transcriptie
- -0,446
- Verlies −logkans
- 0,446
De eindkans gebruikt op het laatste frame toestanden s=1 en s=2. Blanks en eventuele herhalingen worden verwerkt in de toestanden, niet achteraf uit de som gegooid.
De transcriptie is hier gegeven om haar kans en trainingsgradiënt te berekenen. Dit is geen decoder voor de onbekende beste reeks. −∞ betekent een onbereikbare toestand; beta sluit de huidige emissie uit.
Bij 64 frames en zestien doellabels zijn er 33 toestanden, dus 2112 tabelcellen. De volgende berekening blijft daardoor klein, ook wanneer het aantal volledige klassepaden al onpraktisch groot is. Aangrenzende gelijke labels blijven hun verplichte tussenblank houden.
large = ctc_forward(
np.zeros((64,2)),[1]*16)
print(large['alpha'].shape)
# (64, 33)
2. Verbind de doelskans aan een trainingssignaal
Noem de totale logdoelkans L. Het verlies voor één transcriptie is −L, de negatieve logkans of negative log-likelihood (NLL). Voor ons voorbeeld is dat ongeveer 0,446; het is geen gemiddelde per frame.
De achterwaartse tabel beta bewaart logmassa van alle toegestane vervolgen. Onze beta sluit de huidige emissie uit, terwijl alpha die juist bevat. Daardoor wordt de kans op toestand s, gegeven het doel, met de volgende formule berekend.
Tel toestanden met hetzelfde klasselabel bij elkaar op tot klasseposterior Q. Die verdeling vertelt welke klasse op ieder frame bij de bekende transcriptie past, over alle mogelijke uitlijningen heen. De gradiënt naar de logits is vervolgens de modelkans p min Q.
In ons voorbeeld loopt op het eerste frame 0,40 van de totale doelmassa 0,64 via A: dat geeft 0,625. Q is op beide frames [0,375;0,625], dus de logitgradiënt is [0,225;−0,225]. Een stap tegen deze gradiënt verlaagt de voorkeur voor blank en verhoogt die voor A.
backward = ctc_backward(logits,[1])
print(round(backward['loss'],3))
for row in backward['gradient']:
print(row.round(3).tolist())
# 0.446
# [0.225, -0.225]
# [0.225, -0.225]
Maak ctc_backward met dezelfde skipregels, nu voor de opvolgers. De doeltranscriptie is nodig om het trainingssignaal te bepalen. Tijdens voorspellen gebruik je alleen de audio en het model; een posterior op basis van het juiste antwoord hoort daar niet bij.
02Wat neem je mee?
- De tabel berekent een gegeven doelskans met T×S cellen, zonder alle paden uit te schrijven.
- Alpha en beta moeten precies afspreken welke emissie ze meetellen.
- Het CTC-trainingssignaal leert uit gehele transcripties zonder vooraf gegeven framegrenzen.
Deze doelsom zoekt nog niet automatisch de beste transcriptie. Het bouwproject gebruikt de eerder geleerde greedy-decoder en meet daarna de echte reeksfouten.