Werkplaats / Notebook
In jouw eigen tempo
← Alle bouwprojecten
Multimodaal & generatief · PYTHON NOTEBOOK

Train en toets een eigen CTC-framehoofd

Verbind zelfgemaakte toonachtige audio, spectrale kenmerken, echte CTC-training en eerlijke evaluatie.

180 min5 cellen
De opdracht, leerdoelen en voorkennis
  • Je traint een klein lineair framehoofd uit audio en volledige transcripties zonder gegeven framegrenzen.
  • Je vergelijkt begin- en eindparameters op dezelfde nieuwe audio en toont drie echte rapportfiguren.

Lees eerst: Pas de stapgrootte per parameter aan · Bereken CTC-uitlijningen en gradiënten.

Je kunt audio in frames knippen, kenmerken berekenen en de kans van een gehele transcriptie bepalen. Nu verbind je die onderdelen tot een model dat werkelijk leert. Het krijgt nergens een lijst met juiste labels per frame cadeau.

We gebruiken twee toonachtige oefenlabels: A rond 500 Hz en B rond 1000 Hz, met variatie in duur, fase, amplitude en ruis. De gegeven toonhelper maakt de audio. Dit kleine experiment onderzoekt CTC op een controleerbare taak; de resultaten gelden voor deze synthetische toonreeksen.

Jij schrijft de datatrekking, training, decoder, evaluatie en figuren. De zichtbare helpers bevatten de eerder uitgewerkte audiokenmerken en CTC-methoden. Iedere cel bewaart haar resultaat voor de volgende; bij projectcontrole worden alle cellen opnieuw in een schone omgeving uitgevoerd.

1. Trek transcripties en bijpassende audio

Maak acoustic_example met de opgegeven generator. Trek eerst een lengte van nul tot en met drie, dan die hoeveelheid labels 1 of 2, en geef de labels aan tone_audio. Bewaar uitsluitend audio en een zelfstandige kopie van de gehele transcriptie.

example = acoustic_example(
    np.random.default_rng(5))
print(example['labels'].tolist())
print(len(example['audio'])//128)
# [2, 1]
# 12

De twaalf frames hoeven geen twaalf doellabels te krijgen. Het doel is hier BA, onafhankelijk van de tijd die beide tonen innemen. Een leeg getrokken doel levert ook een geldig voorbeeld op; de nakijker controleert daarom lege transcripties naast gewone reeksen.

01
CHECKPOINTMaak audio met alleen een gehele transcriptie

2. Leer van echte CTC-gradiënten

Maak train_ctc_head met een klein lineair uitvoerhoofd. Zeven kenmerken worden met een matrix W van vorm 7×3 en bias b omgezet naar drie logits per frame. Dit hoofd heeft geen terugkoppeling of taalmodel; de CTC-doelfunctie verzorgt de som over uitlijningen.

Z=FW+b

De gegeven CTC-helper levert de logitgradiënt G. Reken die terug met FᵀG voor de gewichten en de som van G over frames voor de bias. Neem daarna het gemiddelde over de minibatch en voer echte Adamupdates uit, met biascorrecties en epsilon buiten de wortel.

dW=FTG,db=∑tGt
trained = train_ctc_head()
print(trained['steps'])
print(len(trained['curve']))
# 600
# 31

Bewaar de beginparameters apart en registreer het werkelijke minibatchverlies bij update één, iedere twintig updates en de laatste update. Het CTC-verlies is per reeks; deel het niet ongemerkt nogmaals door het aantal frames. Nieuwe batches kunnen de curve laten schommelen.

Curve is een lijst dicts met exact step en loss: een Python-int voor de update en een Python-float voor het verlies. Bewaar oplopende stappen zonder een dubbele eindstap.

02
CHECKPOINTVoer echte CTC- en Adamupdates uit

3. Decodeer zonder het juiste antwoord

Maak decode_ctc_head met uitsluitend modelparameters en audio als invoer. Bereken kenmerken, logits, stabiele framekansen en de greedy-transcriptie. De juiste transcriptie is alleen nodig voor training en evaluatie, niet om deze voorspelling te maken.

decoded = decode_ctc_head(
    trained['parameters'],
    example['audio'])
print(decoded['labels'])
# [2, 1]

Kijk ook naar de framekansen: meerdere frames kunnen hetzelfde label dragen voordat collapse er één symbool van maakt. Voor een herhaald label moet de voorspelling er een blank tussen kunnen zetten. Een model dat altijd blank kiest, kan lege voorbeelden goed hebben en toch alle gewone transcripties missen.

03
CHECKPOINTVoorspel zonder transcriptie als invoer

4. Meet hele reeksen en losse bewerkingen

Een volledige reeks is alleen goed als alle labels in de juiste volgorde kloppen. Voor fijnere informatie tellen we de minimale inserties, deleties en substituties, elk met kost één. Bouw label_edit_distance met een tabel waarvan de beginrij en beginkolom de benodigde aantallen bewerkingen op een lege reeks tellen.

Iedere volgende cel kiest het minimum van links plus één, boven plus één en diagonaal plus de eventuele vervangingskost. Voor voorspelling A en doel AA geeft dat deze kleine tabel. Rechtsonder staat één: er ontbreekt één A.

| Voorspelling / doel | leeg | A | AA | |---|---:|---:|---:| | leeg | 0 | 1 | 2 | | A | 1 | 0 | 1 |

print(label_edit_distance([1],[1,1]))
# 1

De label error rate is de som van alle bewerkingen gedeeld door het totale aantal doellabels. Hier zou dat 1/2 zijn. Bij uitsluitend lege doelen is de noemer nul; geef dan None en bewaar de aantallen, zodat je geen foutpercentage verzint.

Maak evaluate_ctc_head met één nieuw corpus en gebruik exact dezelfde audio voor begin- en eindparameters. Geef de echte reeksaccuracy, labelbewerkingen, verliezen en voorbeeldgegevens terug. Door inserties kan label error rate groter dan één worden; knip die waarde niet af.

report = evaluate_ctc_head(trained)
for key in ['initial','trained']:
    metric = report['metrics'][key]
    print(metric['sequence_accuracy'])
# 0.25
# 1.0

Deze score hoort bij onze vaste configuratie en toondata. Kies instellingen op training en validatie en bevries ze vóór je een aparte eindtest gebruikt. Een dalende trainingscurve alleen is geen bewijs dat nieuwe transcripties goed zijn.

Controleer curverijen op de afgesproken stappen en verlies binnen [0, 1e6], zonder bool. Het rapport bewaart example met audio, target, features en initial/trained; beide modeldicts bevatten prediction en probabilities. Kies het eerste niet-lege doel, anders voorbeeld nul.

04
CHECKPOINTMeet volledige reeksen en labelbewerkingen

5. Teken het rapport dat je net hebt gemeten

Maak plot_ctc_report met alleen report als invoer. De drie figuren tonen curve en gepaarde accuracy, werkelijke audio en zeven kenmerken, en de begin-/eindkansen van blank, A en B. Labels en legenda moeten leesbaar blijven zonder de data te verbergen.

figures = plot_ctc_report(report)
print(len(figures))
# 3

Opnieuw trainen tijdens plotten zou rapport en figuur uit elkaar kunnen laten lopen. Houd de drie figuren geopend en geef ze terug, zodat de notebookuitvoer ze werkelijk kan tonen. De nakijker vergelijkt de getekende waarden met het aangeleverde rapport.

05
CHECKPOINTToon het werkelijke CTC-rapport

Wat neem je mee?

  • CTC leert uit gehele transcripties, zonder vooraf gegeven framegrenzen.
  • Volledige reeksaccuracy en labelbewerkingen vertellen verschillende dingen.
  • Bewaar beginparameters, echte updates, meetdata en zichtbare figuren bij elkaar.

Hierna onderzoek je wat modellen buiten zo'n klein, gecontroleerd lab nodig hebben om bruikbaar in de wereld te werken.

Bronnen: Graves e.a. 2006 — Connectionist Temporal Classification ↗ · Gecontroleerd 2026-10-29.

Python start zodra jij code uitvoert.
[1]

1. Trek audio en transcriptie

Vul acoustic_example in met de opgegeven generator: eerst de transcriptielengte, dan labels 1/2 en vervolgens de gegeven toonhelper. Bewaar audio en een zelfstandige labelkopie; alleen de opgegeven generator mag bewust worden voortgezet.

De helpers hieronder zijn zichtbaar gegeven: audiobewerking, CTC, toonproductie, initialisatie en validators. Hun rekenmethoden komen uit de eerdere lessen; geen helper levert het juiste label aan de decoder.

Het vaste voorbeeld met seed 5 heeft transcriptie [2, 1] en twaalf frames. Een lege transcriptie is ook geldig, dus controleer de arrayvorm en niet of er minstens één doellabel is.

Bekijk voorbeeld
import numpy as np

def acoustic_example(rng):
    if not isinstance(rng,np.random.Generator):raise ValueError('Een opgegeven generator vereist')
    labels=rng.integers(1,3,size=int(rng.integers(0,4)))
    return {'audio':tone_audio(labels,rng),'labels':labels.copy()}

# Gegeven helpers

def _ctc_int(value,minimum,maximum):
    if type(value) is not int or not minimum<=value<=maximum:raise ValueError('Geheel getal buiten de oefengrens')
    return value

def _ctc_logits(value):
    raw=np.asarray(value)
    if raw.ndim!=2 or raw.dtype.kind not in 'iuf' or not 1<=raw.shape[0]<=64 or not 2<=raw.shape[1]<=8:
        raise ValueError('Een matrix met 1–64 tijden en 2–8 klassen vereist')
    result=raw.astype(float,copy=True)
    if not np.isfinite(result).all() or np.max(np.abs(result))>100:raise ValueError('Eindige logits binnen [-100,100] vereist')
    return result

def _ctc_ids(value,maximum_length):
    raw=np.asarray(value)
    if raw.ndim!=1 or len(raw)>maximum_length or (raw.size and raw.dtype.kind not in 'iu'):
        raise ValueError('Een begrensde vector met gehele labelindices vereist')
    if raw.size and (np.any(raw<0) or np.any(raw>7)):raise ValueError('Labelindex buiten [0,7]')
    return raw.astype(int,copy=True)

def ctc_collapse(path,blank=0):
    path=_ctc_ids(path,64);blank=_ctc_int(blank,0,7);result=[];previous=None
    for value in path:
        label=int(value)
        if label!=blank and label!=previous:result.append(label)
        previous=label
    return result

def ctc_greedy(logits,blank=0):
    logits=_ctc_logits(logits);blank=_ctc_int(blank,0,logits.shape[1]-1)
    return ctc_collapse(np.argmax(logits,axis=1),blank)

def _ctc_setup(logits,labels,blank):
    logits=_ctc_logits(logits);labels=_ctc_ids(labels,16);blank=_ctc_int(blank,0,logits.shape[1]-1)
    if np.any(labels>=logits.shape[1]) or np.any(labels==blank):raise ValueError('Doellabels zijn geldige klassen zonder blank')
    minimum=len(labels)+int(np.sum(labels[1:]==labels[:-1]))
    if len(logits)<minimum:raise ValueError('Te weinig tijdstappen voor deze doelreeks')
    shifted=logits-logits.max(axis=1,keepdims=True);log_probs=shifted-np.log(np.exp(shifted).sum(axis=1,keepdims=True))
    expanded=np.full(2*len(labels)+1,blank,dtype=int);expanded[1::2]=labels
    skip=np.zeros(len(expanded),dtype=bool)
    if len(expanded)>2:skip[2:]=(expanded[2:]!=blank)&(expanded[2:]!=expanded[:-2])
    return log_probs,expanded,skip

def ctc_forward(logits,labels,blank=0):
    log_probs,expanded,skip=_ctc_setup(logits,labels,blank);times=len(log_probs);states=len(expanded)
    alpha=np.full((times,states),-np.inf);alpha[0,0]=log_probs[0,blank]
    if states>1:alpha[0,1]=log_probs[0,expanded[1]]
    for time in range(1,times):
        previous=alpha[time-1];incoming=np.logaddexp(previous,np.concatenate([[-np.inf],previous[:-1]]))
        if states>2:
            jump=np.concatenate([[-np.inf,-np.inf],previous[:-2]])
            incoming=np.where(skip,np.logaddexp(incoming,jump),incoming)
        alpha[time]=incoming+log_probs[time,expanded]
    log_probability=float(alpha[-1,0] if states==1 else np.logaddexp(alpha[-1,-1],alpha[-1,-2]))
    return {'log_probability':log_probability,'alpha':alpha,'expanded':expanded,'log_probs':log_probs,'probabilities':np.exp(log_probs)}

def ctc_backward(logits,labels,blank=0):
    forward=ctc_forward(logits,labels,blank);log_probs=forward['log_probs'];expanded=forward['expanded'];times=len(log_probs);states=len(expanded)
    # Beta excludes this time's emission; alpha includes it. No division by a tiny current probability.
    beta=np.full((times,states),-np.inf);beta[-1,-1]=0.
    if states>1:beta[-1,-2]=0.
    skip=np.zeros(states,dtype=bool)
    if states>2:skip[:-2]=(expanded[2:]!=blank)&(expanded[2:]!=expanded[:-2])
    for time in range(times-2,-1,-1):
        outgoing=log_probs[time+1,expanded]+beta[time+1]
        value=np.logaddexp(outgoing,np.concatenate([outgoing[1:],[-np.inf]]))
        if states>2:
            jump=np.concatenate([outgoing[2:],[-np.inf,-np.inf]])
            value=np.where(skip,np.logaddexp(value,jump),value)
        beta[time]=value
    state_posterior=np.exp(forward['alpha']+beta-forward['log_probability']);class_posterior=np.zeros_like(log_probs)
    for state,label in enumerate(expanded):class_posterior[:,label]+=state_posterior[:,state]
    gradient=forward['probabilities']-class_posterior
    return {'beta':beta,'state_posterior':state_posterior,'class_posterior':class_posterior,
            'loss':-forward['log_probability'],'gradient':gradient}

def ctc_loss_and_grad(logits,labels,blank=0):
    result=ctc_backward(logits,labels,blank)
    return float(result['loss']),result['gradient'].copy()

def _sp_int(value,minimum,maximum):
    if type(value) is not int or not minimum<=value<=maximum:raise ValueError('Geheel getal buiten de oefengrens')
    return value

def _sp_array(value,dimensions,max_abs=2):
    raw=np.asarray(value)
    if raw.ndim!=dimensions or raw.dtype.kind not in 'iuf' or not raw.size or raw.size>65536:
        raise ValueError('Een niet-lege reële array met maximaal 65536 cellen vereist')
    result=raw.astype(float,copy=True)
    if not np.isfinite(result).all() or np.max(np.abs(result))>max_abs:raise ValueError('Eindige waarden binnen de oefengrens vereist')
    return result

def frame_audio(samples,size=128,hop=128,pad_end=False):
    samples=_sp_array(samples,1);size=_sp_int(size,4,512);hop=_sp_int(hop,1,size)
    if len(samples)>16384 or type(pad_end) is not bool:raise ValueError('Begrensde audio en een expliciete bool vereist')
    if len(samples)<size and not pad_end:raise ValueError('Audio is korter dan één volledig frame')
    remaining=max(0,len(samples)-size)
    count=1+(remaining+hop-1)//hop if pad_end else 1+remaining//hop
    if count*size>65536:raise ValueError('Te veel framecellen vóór allocatie')
    frames=np.zeros((count,size))
    for index in range(count):
        part=samples[index*hop:index*hop+size];frames[index,:len(part)]=part
    return frames

def windowed_power(frames,sample_rate=8000):
    frames=_sp_array(frames,2);sample_rate=_sp_int(sample_rate,8000,48000);count,size=frames.shape
    if not 4<=size<=512 or count>256:raise ValueError('1–256 frames van 4–512 samples vereist')
    window=np.hanning(size);transform=np.fft.rfft(frames*window,axis=1)
    power=np.abs(transform)**2/(size*float(np.sum(window**2)))
    if size%2==0:power[:,1:-1]*=2
    else:power[:,1:]*=2
    return {'frequencies':np.fft.rfftfreq(size,d=1/sample_rate),'power':power,'window':window}

def mel_filters(size=128,sample_rate=8000,bands=6):
    size=_sp_int(size,4,512);sample_rate=_sp_int(sample_rate,8000,48000);bands=_sp_int(bands,2,12)
    # Explicit HTK mel spacing, linear-Hz triangles, no area normalization.
    top=2595*np.log10(1+(sample_rate/2)/700);edges=700*(10**(np.linspace(0,top,bands+2)/2595)-1)
    frequencies=np.fft.rfftfreq(size,d=1/sample_rate);weights=np.zeros((bands,len(frequencies)))
    for index in range(bands):
        left,center,right=edges[index:index+3]
        weights[index]=np.maximum(0,np.minimum((frequencies-left)/(center-left),(right-frequencies)/(right-center)))
    if np.any(weights.sum(axis=1)==0):raise ValueError('Resolutie te laag: minstens één filter is leeg')
    return {'weights':weights,'edges_hz':edges,'frequencies':frequencies}

def spectral_features(samples,sample_rate=8000,size=128,hop=128,bands=6):
    frames=frame_audio(samples,size,hop,pad_end=True);spectrum=windowed_power(frames,sample_rate);filters=mel_filters(size,sample_rate,bands)
    mel=spectrum['power']@filters['weights'].T;energy=spectrum['power'].sum(axis=1,keepdims=True)
    # Relative digital-amplitude features, not calibrated acoustic dB, a PSD or MFCCs.
    return np.log1p(1000*np.concatenate([mel,energy],axis=1))

def tone_audio(labels,rng):
    labels=_ctc_ids(labels,3)
    if np.any(labels<1) or np.any(labels>2) or not isinstance(rng,np.random.Generator):raise ValueError('Toonlabels 1/2 en een opgegeven generator vereist')
    blocks=[rng.normal(0,.01,size=int(rng.integers(1,3))*128)]
    for label in labels:
        frames=int(rng.integers(2,6));frequency=(500 if label==1 else 1000)*rng.uniform(.95,1.05)
        amplitude=rng.uniform(.3,.6);phase=rng.uniform(0,2*np.pi);time=np.arange(frames*128)/8000
        block=amplitude*(np.sin(2*np.pi*frequency*time+phase)+.1*np.sin(4*np.pi*frequency*time+phase))
        blocks.extend([block+rng.normal(0,.01,size=len(block)),rng.normal(0,.01,size=int(rng.integers(1,3))*128)])
    return np.concatenate(blocks)

def initialize_ctc_head(seed=211):
    seed=_sp_int(seed,0,2**32-1);rng=np.random.default_rng(seed)
    return {'weights':rng.normal(0,.03,size=(7,3)),'bias':np.zeros(3)}

def _sh_parameters(parameters):
    if type(parameters) is not dict or set(parameters)!={'weights','bias'}:raise ValueError('Gewichten en bias vereist')
    weights=_sp_array(parameters['weights'],2,100);bias=_sp_array(parameters['bias'],1,100)
    if weights.shape!=(7,3) or bias.shape!=(3,):raise ValueError('Zeven kenmerken en drie CTC-klassen vereist')
    return {'weights':weights,'bias':bias}

example = acoustic_example(
    np.random.default_rng(5))
data_ready = isinstance(example,dict) and \
    isinstance(example.get('audio'),np.ndarray)
print(example['labels'].tolist()
      if data_ready else None)
print(len(example['audio'])//128
      if data_ready else None)
[2]

2. Train met echte CTC-gradiënten

Vul train_ctc_head in met nieuwe voorbeelden uit een eigen datagenerator. Bewaar beginparameters als onafhankelijke kopie en leer alleen uit audio en gehele transcripties.

De CTC-helper geeft G: neem features.T@G voor de gewichten en de rijsom voor de bias, gemiddeld over de minibatch. Gebruik Adam met beide biascorrecties en epsilon buiten de wortel; registreer echte verliezen op de afgesproken stappen.

De demo traint 600 updates met batch 4. Een schommelende curve is toegestaan; initial, parameters, curve en configuratie komen samen in trained.

Bekijk voorbeeld
def train_ctc_head(steps=600,batch=4,rate=.01,seed=211):
    steps=_sp_int(steps,1,800);batch=_sp_int(batch,1,8);seed=_sp_int(seed,0,2**32-1)
    if type(rate) not in (int,float) or not .00001<=rate<=.05:raise ValueError('Leersnelheid buiten de oefengrens')
    parameters=initialize_ctc_head(seed);initial={key:value.copy() for key,value in parameters.items()};rng=np.random.default_rng(seed+1)
    first={key:np.zeros_like(value) for key,value in parameters.items()};second={key:np.zeros_like(value) for key,value in parameters.items()};curve=[]
    for step in range(1,steps+1):
        gradients={key:np.zeros_like(value) for key,value in parameters.items()};losses=[]
        for _ in range(batch):
            example=acoustic_example(rng);features=spectral_features(example['audio']);logits=features@parameters['weights']+parameters['bias']
            loss,delta=ctc_loss_and_grad(logits,example['labels'])
            gradients['weights']+=features.T@delta/batch;gradients['bias']+=delta.sum(axis=0)/batch;losses.append(loss)
        for key in parameters:
            first[key]=.9*first[key]+.1*gradients[key];second[key]=.999*second[key]+.001*gradients[key]**2
            parameters[key]-=rate*(first[key]/(1-.9**step))/(np.sqrt(second[key]/(1-.999**step))+1e-8)
        if step==1 or step%20==0 or step==steps:curve.append({'step':step,'loss':float(np.mean(losses))})
    return {'initial':initial,'parameters':parameters,'curve':curve,'steps':steps,'batch':batch,'rate':rate,'seed':seed}

trained = None
if data_ready:
    trained = train_ctc_head()
print(len(trained['curve'])
      if isinstance(trained,dict)
      else None)
[3]

3. Voorspel vanuit audio

Vul decode_ctc_head in. Bereken zeven audiokenmerken, drie logits per frame, stabiele kansen en de greedy-transcriptie.

De functie krijgt uitsluitend modelparameters en audio. Blank is klasse 0; A en B zijn 1 en 2, en opeenvolgende gelijke labels worden vóór blankverwijdering samengevoegd.

De demo gebruikt de onafhankelijke voorbeeldaudio uit cel 1. Het juiste doel wordt niet als argument aan de decoder meegestuurd.

Bekijk voorbeeld
def decode_ctc_head(parameters,audio):
    parameters=_sh_parameters(parameters);features=spectral_features(audio);logits=_ctc_logits(features@parameters['weights']+parameters['bias'])
    shifted=logits-logits.max(axis=1,keepdims=True);probabilities=np.exp(shifted);probabilities/=probabilities.sum(axis=1,keepdims=True)
    return {'labels':ctc_greedy(logits),'features':features,'logits':logits,'probabilities':probabilities}

decoded = None
if data_ready and isinstance(trained,dict):
    decoded = decode_ctc_head(
        trained['parameters'],
        example['audio'])
print(decoded['labels']
      if isinstance(decoded,dict)
      else None)
[4]

4. Meet nieuwe volledige reeksen

Vul label_edit_distance en evaluate_ctc_head in. De bewerkingsafstand telt inserties, deleties en substituties, ook wanneer een doel leeg is.

Maak één nieuw corpus en vergelijk begin/eindmodellen op exact dezelfde audio. Bewaar werkelijke voorspellingen, verliezen, reeksaccuracy en bewerkingen; bij nul doellabels is de label error rate None.

De demo gebruikt 64 nieuwe voorbeelden met de vaste evaluatieseed. Configuratiekeuze hoort bij validatie; hertrain hier niet en gebruik geen eindtest om instellingen bij te stellen.

Bekijk voorbeeld
def label_edit_distance(predicted,target):
    predicted=_ctc_ids(predicted,64);target=_ctc_ids(target,16);previous=list(range(len(target)+1))
    for row,label in enumerate(predicted,1):
        current=[row]
        for column,expected in enumerate(target,1):current.append(min(current[-1]+1,previous[column]+1,previous[column-1]+int(label!=expected)))
        previous=current
    return int(previous[-1])

def evaluate_ctc_head(trained,count=64,seed=9901):
    if type(trained) is not dict or not {'initial','parameters','curve','steps','batch','rate','seed'}<=set(trained):raise ValueError('Een trainingsresultaat met configuratie vereist')
    steps=_sp_int(trained['steps'],1,800);_sp_int(trained['batch'],1,8);_sp_int(trained['seed'],0,2**32-1)
    if type(trained['rate']) not in (int,float) or not .00001<=trained['rate']<=.05:raise ValueError('Leersnelheid buiten de oefengrens')
    expected=sorted(set([1,steps,*range(20,steps+1,20)]))
    if type(trained['curve']) is not list or len(trained['curve'])!=len(expected):raise ValueError('Een volledige trainingscurve vereist')
    curve=[]
    for row,step in zip(trained['curve'],expected):
        if type(row) is not dict or set(row)!={'step','loss'} or type(row['step']) is not int or row['step']!=step:raise ValueError('Werkelijke geregistreerde stappen vereist')
        if type(row['loss']) not in (int,float) or not 0<=row['loss']<=1e6:raise ValueError('Eindig niet-negatief verlies vereist')
        curve.append({'step':step,'loss':float(row['loss'])})
    parameters={key:_sh_parameters(trained[field]) for key,field in [('initial','initial'),('trained','parameters')]};count=_sp_int(count,1,256);seed=_sp_int(seed,0,2**32-1)
    rng=np.random.default_rng(seed);examples=[acoustic_example(rng) for _ in range(count)];metrics={};illustration=None
    chosen=next((index for index,row in enumerate(examples) if len(row['labels'])),0)
    for key,model in parameters.items():
        correct=0;edits=0;labels=0;losses=[];rows=[]
        for index,example in enumerate(examples):
            decoded=decode_ctc_head(model,example['audio']);error=label_edit_distance(decoded['labels'],example['labels'])
            correct+=int(decoded['labels']==example['labels'].tolist());edits+=error;labels+=len(example['labels'])
            loss=ctc_loss_and_grad(decoded['logits'],example['labels'])[0];losses.append(loss)
            rows.append({'target':example['labels'].tolist(),'prediction':decoded['labels'],'edits':error})
            if index==chosen:
                if illustration is None:illustration={'audio':example['audio'].copy(),'target':example['labels'].tolist(),'features':decoded['features'].copy()}
                illustration[key]={'prediction':decoded['labels'],'probabilities':decoded['probabilities'].copy()}
        metrics[key]={'sequence_accuracy':correct/count,'label_error_rate':edits/labels if labels else None,'edits':edits,'target_labels':labels,'mean_loss':float(np.mean(losses)),'rows':rows}
    return {'metrics':metrics,'count':count,'example':illustration,'curve':curve}

report = None
if isinstance(trained,dict) and \
        isinstance(decoded,dict):
    distance = label_edit_distance([],[])
    integer_distance = isinstance(
        distance,(int,np.integer)) and \
        not isinstance(
            distance,(bool,np.bool_))
    if integer_distance:
        report = evaluate_ctc_head(trained)
print(report['metrics']['trained']
      ['sequence_accuracy']
      if isinstance(report,dict)
      else None)
[5]

5. Teken het werkelijke rapport

Vul plot_ctc_report in voor drie geopende figuren: curve en validatieaccuracy, echte audio en zeven kenmerken, en beide modellen met hun drie kanslijnen.

Gebruik uitsluitend report. Teken de werkelijke waarden met leesbare assen en legenda zonder opnieuw te trainen, audio te genereren of te evalueren.

Laat de figuren geopend en bewaar de lijst in figures. De controle vergelijkt de getekende data met het rapport; een lijst met alleen figuurnummers of gesloten figuren volstaat niet.

Bekijk voorbeeld
def plot_ctc_report(report):
    figures=[]
    figure,axes=plt.subplots(2,1,figsize=(4.2,6.5),layout='constrained')
    axes[0].plot([row['step'] for row in report['curve']],[row['loss'] for row in report['curve']],color='#236b4f')
    axes[0].set(title='Werkelijk CTC-minibatchverlies',xlabel='Update',ylabel='Gemiddelde NLL')
    axes[1].bar(['Begin','Getraind'],[100*report['metrics'][key]['sequence_accuracy'] for key in ['initial','trained']],color=['#a66a43','#236b4f'])
    axes[1].set(title='Nieuwe synthetische reeksen',ylabel='Volledige reeks goed (%)',ylim=(0,110));figures.append(figure)
    example=report['example'];audio=example['audio'];features=example['features']
    figure,axes=plt.subplots(2,1,figsize=(4.2,6.5),layout='constrained')
    axes[0].plot(np.arange(len(audio))/8000,audio,color='#3868a0',linewidth=1)
    axes[0].set(title='Eigen toonachtige audio',xlabel='Tijd (s)',ylabel='Digitale amplitude')
    heatmap=axes[1].imshow(features.T,origin='lower',aspect='auto',interpolation='nearest',
        extent=(0,len(audio)/8000,0,7),vmin=0,vmax=max(1,float(np.max(features))),cmap='viridis')
    axes[1].set(title='Werkelijke invoerkenmerken',xlabel='Tijd (s)',ylabel='Kenmerk',yticks=np.arange(7)+.5,yticklabels=['M1','M2','M3','M4','M5','M6','E'])
    colorbar=figure.colorbar(heatmap,ax=axes[1]);colorbar.set_label('log1p');figures.append(figure)
    figure,axes=plt.subplots(2,1,figsize=(4.2,6.5),layout='constrained')
    for axis,key,title in zip(axes,['initial','trained'],['Beginparameters','Getrainde parameters']):
        probabilities=example[key]['probabilities']
        for label,color,style,name in [(0,'#777','--','Blank'),(1,'#3868a0','-.','A'),(2,'#236b4f','-','B')]:
            axis.plot(np.arange(len(probabilities)),probabilities[:,label],label=name,color=color,linestyle=style)
        axis.set(title=title,xlabel='Framenummer',ylabel='CTC-kans',ylim=(0,1.05))
    handles,labels=axes[0].get_legend_handles_labels();figure.legend(handles,labels,loc='outside upper center',fontsize=11.5,ncols=3)
    figures.append(figure)
    for figure in figures:
        for axis in figure.axes:
            axis.tick_params(labelsize=11);axis.title.set_fontsize(12);axis.xaxis.label.set_size(11);axis.yaxis.label.set_size(11)
    return figures

import matplotlib.pyplot as plt

figures = None
if isinstance(report,dict):
    figures = plot_ctc_report(report)
print(len(figures)
      if isinstance(figures,list)
      else None)