Hoppa till innehållet
AI-grafen
E· Universitetljud-och-tal· ca 60 min· utvecklande· verifierad 2026-09-20

Ljudklassificering

Kunna träna en klassificerare på spektrogram.

Förkunskaper

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:

  1. 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.
  2. Augmenteringen är ljudspecifik. Tidssträckning, tonhöjdsändring, bakgrundsbrus, och SpecAugment — att maskera slumpmässiga tids- och frekvensband direkt i spektrogrammet.
  3. 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:

KontrollVad 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

Alla källor och licenser