Werkplaats / De werkplaats
In jouw eigen tempo
← Multimodaal & generatief
MODULE 03 · LES 10

Maak vermogens- en melkenmerken

Bereken Hann-venstervermogen, expliciete mel-driehoeken en zeven log-vermogenskenmerken.

90 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je normaliseert een eenzijdig spectrum correct voor even en oneven framelengtes.
  • Je onderscheidt frequenties, melgewichten en log-vermogenskenmerken zonder dB of MFCCs te claimen.

Een lage toon en een hoge toon kunnen ongeveer even luid zijn, terwijl hun golven heel anders bewegen. Een model dat alleen gemiddelde amplitude ziet, mist dat verschil. We zetten daarom ieder frame om naar kenmerken die de frequentie-inhoud beschrijven.

Denk aan een akkoord dat je uitsplitst in bijdragen van verschillende tonen. De Fouriertransformatie doet iets vergelijkbaars met een kort digitaal signaal. We gebruiken NumPy voor deze transformatie en bouwen zelf de venstering, vermogensnormalisatie en filterstappen eromheen.

1. Maak de randen zachter en verdeel vermogen correct

Een frame afknippen kan aan de randen een sprong maken. Het symmetrische Hann-venster geeft de randen minder gewicht en het midden meer. Voor vier samples zijn de twee buitenste gewichten nul.

window = np.hanning(4)
print(window.round(3).tolist())
# [0.0, 0.75, 0.75, 0.0]

Dat heeft een echte consequentie: een signaal dat uitsluitend op zo'n eindpunt niet nul is, verdwijnt na venstering. Het venster herkent geen geluid en kan geen informatie terughalen. Het verandert welke samples bijdragen aan de frequentieberekening.

Noem de FFT-uitvoer X_k en het venster w_n, bij framelengte M. Voor ons eenzijdige vermogen P_k gebruiken we de volgende normalisatie. De factor c_k is één bij de nulfrequentie en bij de Nyquistbin van een even M; bij overige positieve frequenties is hij twee.

De Nyquistfrequentie ligt op f_s/2: bij 8000 samples per seconde is dat 4000 Hz. Een even rfft heeft daar precies een laatste bin. Een oneven framelengte heeft geen bin precies op die frequentie.

Pk=ck|Xk|2M∑wn2

De verdubbeling neemt de bijdrage van de weggelaten negatieve frequenties mee. Bij een oneven framelengte is er geen afzonderlijke Nyquistbin, dus verdubbelt ook de laatste positieve bin. De som van P is hierdoor het venstergewogen gemiddelde van de gekwadrateerde digitale amplitude.

spectrum = windowed_power(
    np.ones((1,4)),8000)
print(spectrum['frequencies']
      .tolist())
print(spectrum['power']
      .round(3).tolist())
# [0.0, 2000.0, 4000.0]
# [[0.5, 0.5, 0.0]]

Bij vier constante enen is die som één. Door venstering wordt de getransformeerde rij [0;0,75;0,75;0], met ook een positieve frequentiecomponent. De berekening gebruikt dus een andere rij dan de oorspronkelijke constante enen.

Maak windowed_power met de juiste frequentieas en drie onafhankelijke uitvoerarrays. Het resultaat is vermogen per frequentiebin, geen gekalibreerde akoestische luidheid of vermogensdichtheid per hertz.

01
CHECKPOINTBereken eenzijdig venstervermogen

2. Combineer naburige frequenties met mel-filters

Een hele rij FFT-bins is niet altijd een handige modelinvoer. We combineren buurfrequenties met driehoekige gewichten en kiezen de randen op een mel-schaal. Onze expliciete HTK-conventie zet hertz f om naar melwaarde m.

m=2595log10(1+f700)

Verdeel bands+2 punten gelijkmatig op die schaal en reken ze terug naar hertz. Elk filter stijgt tussen zijn linker- en middenrand en daalt daarna tot de rechterrand. De theoretische piek is één; een FFT-bin hoeft niet precies op die piek te vallen.

Wij gebruiken lineaire Hz-driehoeken zonder oppervlaktenormalisatie. Andere conventies kunnen dezelfde frequenties anders wegen. Vermeld daarom de gekozen instelling wanneer je kenmerken vergelijkt.

De inverse omzetting maakt van de gelijk verdeelde melpunten weer hertz. Eerst deel je m door 2595, daarna neem je de macht van tien. Zo krijg je de concrete linker-, midden- en rechterrand voor iedere driehoek.

f=700(10m2595−1)
bank = mel_filters(128,8000,6)
print(bank['weights'].shape)
print(bank['edges_hz'][:3]
      .round(1).tolist())
# (6, 65)
# [0.0, 218.8, 506.1]

Er zijn 65 rfft-bins en zes filterrijen. Bij een te kleine FFT kunnen sommige driehoeken geen enkele positieve bin raken. Maak mel_filters zodat die configuratie een ValueError geeft, in plaats van ongemerkt een altijd leeg modelkenmerk te produceren.

02
CHECKPOINTMaak niet-lege mel-driehoeken
VERKEN HET ZELFVan trillende samples naar bruikbare kenmerken

Vergelijk twee vensterlengtes en toonhoogtes. Bekijk het venstervermogen of één mel-driehoek op precies dezelfde frequentieas.

00,0480,09501234Frequentie (kHz)Digitaal vermogen
Som venstervermogen
0,125
Breedte van een FFT-bin
62,5 Hz

Melband M1 loopt van 0 tot 506,101 Hz en heeft haar theoretische piek bij 218,842 Hz. Een FFT-bin hoeft niet precies op die piek te liggen.

Zeven log-vermogenskenmerken van hetzelfde frame
KenmerkWaarde
M12,055
M24,754
M31,427
M40
M50
M60
E4,836

Verdubbeling van de amplitude verviervoudigt het vermogen vóór de logtransformatie. De melgewichten veranderen daardoor niet.

Dit zijn vaste toonvoorbeelden bij 8 kHz met een symmetrisch Hann-venster. De zeven kenmerken zijn ln(1+1000×vermogen): zes melbanden en totale energie. Het zijn geen dB, PSD of MFCCs.

3. Maak een begrensde logrepresentatie

Vermenigvuldig het vermogenspectrum met de getransponeerde filtermatrix. Voeg de totale binenergie als laatste kolom toe en pas log1p(1000×waarde) toe. De vaste factor maakt onze kleine digitale waarden leesbaar; dit is geen decibelschaal en geen MFCC-berekening.

Hier is u zo'n gewogen vermogenswaarde en z het modelkenmerk. Ln is de natuurlijke logaritme; log1p berekent dezelfde uitdrukking nauwkeurig bij kleine invoerwaarden.

z=ln(1+1000u)

Twee vermogenswaarden 0,001 en 0,003 met gewichten één en een half geven samen 0,0025. De logbewerking levert vervolgens ongeveer 1,253. Eerst wegen en optellen, daarna pas de logaritme nemen.

powers = np.array([.001,.003])
weights = np.array([1.,.5])
value = np.log1p(1000*(powers@weights))
print(round(float(value),3))
# 1.253

Maak spectral_features met de eerder gebouwde functies. Een volledig nulframe geeft zeven nulkenmerken: zes melbanden en de energie. Log1p maakt een nul invoer geldig; gewoon log(0) zou hier geen eindige waarde geven.

03
CHECKPOINTCombineer melvermogen en energie

Wat neem je mee?

  • Venstering verandert de bijdrage van de samples.
  • Eenzijdig vermogen vraagt aandacht voor nulfrequentie, pariteit en Nyquist.
  • Log-melkenmerken zijn modelinvoer; ze zijn nog geen herkende transcriptie.

Hierna onderzoek je hoe framevoorspellingen in een kortere labelreeks veranderen.

DE VOLGENDE STAPSom transcriptiekansen over CTC-paden
JOUW EXPERIMENT

Probeer het zelf

Python
main.py

Python start zodra jij code uitvoert.

UITVOER

Je uitvoer verschijnt hier.
Alles rekent in je eigen browser.

Jouw checkpoints0 / 3
01
Bereken eenzijdig venstervermogen

Schrijf windowed_power(frames,sample_rate=8000). Gebruik een symmetrisch Hann-venster np.hanning(size), rfft per rij en power=abs(FFT)**2/(size*sum(window**2)). Verdubbel positieve frequenties behalve Nyquist bij even size; bij oneven size is ook de laatste bin te verdubbelen. Geef frequencies,power,window als zelfstandige arrays; frequencies=np.fft.rfftfreq(size,d=1/sample_rate). Geldige frames hebben 1–256 rijen, 4–512 kolommen en voldoen aan de gegeven reële arraygrenzen/65536 cellen. Sample_rate is Python-int 8000–48000. Behoud invoer; ongeldige invoer geeft ValueError.

Een hint

Werk eerst het handvoorbeeld uit; controleer daarna de vormen, grenzen en het behoud van de invoer.

02
Maak niet-lege mel-driehoeken

Schrijf mel_filters(size=128,sample_rate=8000,bands=6). Kies bands+2 gelijk verdeelde punten van 0 tot mel(sample_rate/2), met mel(f)=2595*log10(1+f/700) en inverse f(m)=700*(10**(m/2595)-1). Maak op de rfft-frequenties lineaire Hz-driehoeken met piek 1 en ondergrens 0; geen oppervlaktenormalisatie. Geef weights(bands,size//2+1),edges_hz(bands+2),frequencies terug. Size is Python-int 4–512, sample_rate Python-int 8000–48000, bands Python-int 2–12. Een filter zonder positieve gewichten geeft ValueError; overige ongeldige configuratie ook. Geef zelfstandige floatarrays.

Een hint

Werk eerst het handvoorbeeld uit; controleer daarna de vormen, grenzen en het behoud van de invoer.

03
Combineer melvermogen en energie

Schrijf spectral_features(samples,sample_rate=8000,size=128,hop=128,bands=6). Gebruik frame_audio met pad_end=True, windowed_power en mel_filters. Bereken mel=power@weights.T en energy=sum(power,axis=1,keepdims=True). Geef log1p(1000*concatenate([mel,energy],axis=1)) als zelfstandige floatmatrix: eerst melbanden, dan energie. Gebruik de gegeven oefengrenzen; behoud de invoer. Dit zijn relatieve digitale vermogenskenmerken, geen gekalibreerde dB/PSD/MFCCs. Ongeldige invoer geeft ValueError.

Een hint

Werk eerst het handvoorbeeld uit; controleer daarna de vormen, grenzen en het behoud van de invoer.

Vastgelopen? Bekijk de voorbeeldoplossing
import numpy as np

# Jouw functies

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))

# Gegeven helpers — eerdere methoden en invoergrenzen

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

Voer de code uit en verklaar voor jezelf waarom elke stap werkt.