Ljudklassificering
Kunna träna en klassificerare på spektrogram.
Förkunskaper
- EFaltningsnät (CNN)krävs
- EFourier och spektrogramkrävs
Intuition
Ett mel-spektrogram är en bild. Därför fungerar ett vanligt CNN förvånansvärt bra på ljud — hela verktygslådan från bildklassificering går att återanvända.
Men tre saker skiljer sig från bilder:
- Axlarna betyder olika saker. Att flytta ett mönster i tid ändrar ingenting (samma ljud, senare). Att flytta det i frekvens ändrar allt (annan tonhöjd). Translationsinvarians är önskvärd i den ena riktningen men inte i den andra.
- Augmenteringen är ljudspecifik. Tidssträckning, tonhöjdsändring, bakgrundsbrus, och SpecAugment — att maskera slumpmässiga tids- och frekvensband direkt i spektrogrammet.
- Läckage är lättare att råka ut för. Klipper du ett långt ljudklipp i bitar och slumpar ut dem över tränings- och testmängd, har du samma inspelning på båda sidor. Modellen lär sig rummet, mikrofonen eller talaren — inte klassen.
Kod
import torch, torch.nn as nn, torchaudio
mel = torchaudio.transforms.MelSpectrogram(sample_rate=16000, n_fft=400,
hop_length=160, n_mels=64)
till_db = torchaudio.transforms.AmplitudeToDB()
# SpecAugment: maskera band i tid och frekvens
aug = nn.Sequential(torchaudio.transforms.FrequencyMasking(freq_mask_param=12),
torchaudio.transforms.TimeMasking(time_mask_param=25))
class LjudCNN(nn.Module):
def __init__(self, n_klasser):
super().__init__()
def block(i, o):
return nn.Sequential(nn.Conv2d(i, o, 3, padding=1), nn.BatchNorm2d(o),
nn.ReLU(), nn.MaxPool2d(2))
self.f = nn.Sequential(block(1, 32), block(32, 64), block(64, 128),
nn.AdaptiveAvgPool2d(1), nn.Flatten())
self.h = nn.Linear(128, n_klasser)
def forward(self, vagform):
x = till_db(mel(vagform)).unsqueeze(1) # (B, 1, mel, tid)
x = (x - x.mean()) / (x.std() + 1e-5)
if self.training:
x = aug(x)
return self.h(self.f(x))
Dela per inspelning, inte per klipp. Gör uppdelningen på inspelningsnivå (eller talarnivå) innan du klipper:
import numpy as np
inspelningar = sorted({k["inspelning_id"] for k in klipp})
rng = np.random.default_rng(0); rng.shuffle(inspelningar)
n = int(0.8 * len(inspelningar))
train_ids, test_ids = set(inspelningar[:n]), set(inspelningar[n:])
train = [k for k in klipp if k["inspelning_id"] in train_ids]
test = [k for k in klipp if k["inspelning_id"] in test_ids]
Skillnaden mellan rätt och fel uppdelning är ofta 15–25 procentenheter i rapporterad träffsäkerhet — helt och hållet illusion.
Interaktivt
Ett verkligt felmönster. En modell som klassificerar fågelläten når 94 % på valideringen och 61 % i fält. Felsökningen brukar gå så här:
| Kontroll | Vad du letar efter |
|---|---|
| Delades data per inspelning? | samma bakgrundsljud i träning och test |
| Korrelerar klass med inspelningsplats? | modellen lär sig platsen, inte fågeln |
| Skiljer sig mikrofon/samplingsfrekvens? | domänskifte mellan labb och fält |
| Vad ger en modell som bara ser bakgrundsbruset? | om den når 70 % finns genvägen där |
Den sista kontrollen är den mest avslöjande och den som oftast hoppas över: träna en modell på klipp där du maskerat bort själva lätet och behållit bakgrunden. Är den bättre än slumpen har du en genväg i datan, inte en klassificerare.
Samma logik gäller allt ljud: en «hostdetektor» som egentligen känner igen sjukhusrum, en «motorfelsdetektor» som känner igen verkstaden.
Behärskning innebär
- Tränar ett CNN på mel-spektrogram
- Tillämpar ljudspecifik augmentering
- Undviker läckage mellan inspelningar
Logga in för att göra övningarna och bygga upp din behärskning.
Källor
- torchaudio — dokumentation (BSD-2) — BSD-2-Clause
- arXiv — SpecAugment — arXiv (öppen åtkomst; licens per artikel)
- librosa — dokumentation (ISC) — ISC