Fourier och spektrogram
Kunna beräkna ett spektrogram och förklara varför det är indata till de flesta ljudmodeller.
Förkunskaper
Intuition
En vågform är tryck över tid: 16 000 tal per sekund som säger hur högtalarmembranet ska röra sig. Det säger nästan ingenting om vad ljudet är.
En Fouriertransform svarar på en annan fråga: vilka frekvenser består ljudet av? Den delar upp signalen i rena toner och talar om hur mycket det finns av varje.
Men ett helt ljudklipp på en gång är för trubbigt — musik och tal ändras hela tiden. Därför gör man en STFT (short-time Fourier transform):
- Klipp signalen i korta, överlappande bitar (t.ex. 25 ms med 10 ms hopp).
- Fouriertransformera varje bit för sig.
- Lägg resultaten sida vid sida.
Resultatet är ett spektrogram: en bild där x-axeln är tid, y-axeln är frekvens och ljusstyrkan är energi. Det är den bilden nästan alla ljudmodeller faktiskt får se.
Formellt
Diskret Fouriertransform av en ram :
är amplituden vid frekvensen hertz. För reell insignal är spektrumet symmetriskt, så bara värden bär information — det är vad rfft returnerar.
Tid–frekvens-avvägningen. Med samplingsfrekvens och fönsterlängd blir
- frekvensupplösningen ,
- tidsupplösningen sekunder.
De går åt motsatt håll. Vid 16 kHz:
| Fönster | Δf | Tidsupplösning | Passar |
|---|---|---|---|
| 128 (8 ms) | 125 Hz | 8 ms | transienter, trumslag |
| 400 (25 ms) | 40 Hz | 25 ms | tal (standardvalet) |
| 2048 (128 ms) | 7,8 Hz | 128 ms | tonhöjd i musik |
Fönsterfunktion. Att bara klippa ut en bit är att multiplicera med en rektangel, vilket sprider energi över hela spektrumet (spektralt läckage). Därför multipliceras varje ram med en mjukt avtagande funktion — Hann är standardvalet.
Nyquist: frekvenser över går inte att representera. 16 kHz sampling ger information upp till 8 kHz, vilket räcker gott för tal.
Kod
import numpy as np
fs = 16000
t = np.arange(fs) / fs
# Två toner som byter plats efter en halv sekund
x = np.concatenate([np.sin(2 * np.pi * 440 * t[:fs // 2]),
np.sin(2 * np.pi * 880 * t[fs // 2:])])
def stft(x, N=400, hopp=160):
w = np.hanning(N)
ramar = 1 + (len(x) - N) // hopp
return np.stack([np.abs(np.fft.rfft(x[i * hopp:i * hopp + N] * w)) for i in range(ramar)])
S = stft(x)
print(S.shape) # (98, 201)
frek = np.fft.rfftfreq(400, 1 / fs) # 0 … 8000 Hz i 201 steg
print(round(frek[np.argmax(S[10])]), round(frek[np.argmax(S[80])])) # 440 880
# dB-skala — nästan alltid det man vill se och mata in
S_db = 20 * np.log10(S + 1e-10)
print(round(S_db.max() - S_db.min())) # dynamikomfång i dB
Ramen på index 10 ligger i första halvan (440 Hz), ramen på index 80 i andra (880 Hz) — spektrogrammet visar bytet, vilket en enda Fouriertransform över hela klippet inte hade gjort.
dB-skalan är inte kosmetik. Hörseln är ungefär logaritmisk, och linjär amplitud gör att allt utom de starkaste partierna blir svart. Modeller tränar också avsevärt bättre på log-skala.
Interaktivt
Pröva tid–frekvens-avvägningen själv på ett klipp där någon säger en kort mening:
- Kör STFT med
N=128,N=512ochN=2048(hopp = N/4). - Rita alla tre som bilder med samma axlar.
Du bör se:
- N=128: varje konsonant syns skarpt i tid, men frekvensbanden flyter ihop till suddiga block.
- N=512: balans — man ser både stavelsegränser och de vågräta strimmorna (formanter) som skiljer vokaler åt.
- N=2048: vackra skarpa frekvenslinjer, men stavelserna smetas ut och du kan inte längre se var ett ord slutar.
Frågan att svara på: vilken av bilderna skulle du mata in i en modell som ska avgöra vilket ord som sägs, och vilken i en modell som ska avgöra vilken ton en sångare tar? Svaren skiljer sig, och det är hela poängen.
Behärskning innebär
- Förklarar vad en Fouriertransform gör med en signal
- Beräknar ett spektrogram med STFT
- Väljer fönsterlängd utifrån tid–frekvens-avvägningen
Logga in för att göra övningarna och bygga upp din behärskning.
Källor
- librosa — dokumentation (ISC) — ISC
- Dive into Deep Learning (CC BY-SA 4.0) — CC BY-SA 4.0
- Wikipedia — Short-time Fourier transform (CC BY-SA 4.0) — CC BY-SA 4.0