Phase 06 - Lesson 06

Reconhecimento e Verificação de Locutor

This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.

O ASR pergunta "o que foi dito?". O reconhecimento de locutor pergunta "quem disse?". A matemática parece a mesma — embeddings mais cosseno — mas toda decisão de produção depende de um único número de EER.

Tipo: Build Linguagens: Python Pré-requisitos: Fase 6 · 02 (Espectrogramas e Mel), Fase 5 · 22 (Modelos de Embedding) Tempo: ~45 minutos

O Problema

Um usuário fala uma frase secreta. Você quer saber: esta é a pessoa que ela afirma ser (verificação, 1:1), ou é a primeira pessoa no seu banco de cadastro (identificação, 1:N)? Ou nenhuma das duas — este é um locutor desconhecido (open-set)?

Antes de 2018: GMM-UBM + i-vectors. EER razoável, mas frágil a mudança de canal (telefone vs laptop) e emoção. 2018–2022: x-vectors (backbone TDNN treinado com margem angular). 2022+: embeddings ECAPA-TDNN e WavLM-large. Em 2026 o campo é dominado por três modelos e uma métrica.

A métrica é o EER — Equal Error Rate (taxa de erro igual). Ajuste o limiar de decisão de modo que a Taxa de Falsa Aceitação = Taxa de Falsa Rejeição. O ponto de cruzamento é o EER. Usado em todo artigo, todo leaderboard, toda licitação.

O Conceito

Pipeline de cadastro + verificação com embedding + cosseno + EER

O pipeline. Cadastro: grave de 5 a 30 segundos do locutor-alvo; calcule um embedding de dimensão fixa (192-d para ECAPA-TDNN, 256-d para WavLM-large). Verificação: obtenha o embedding da fala de teste; calcule a similaridade de cosseno; compare com um limiar.

ECAPA-TDNN (2020, ainda dominante em 2026). Emphasized Channel Attention, Propagation and Aggregation - Time-Delay Neural Network. Blocos de convolução 1D com squeeze-excitation, pooling por atenção multi-cabeça, seguidos por uma camada linear para 192-d. Treinado no VoxCeleb 1+2 (2.700 locutores, 1,1 M de falas) com a perda Additive Angular Margin (AAM-softmax).

WavLM-SV (2022+). Faça fine-tuning de um backbone SSL WavLM-large pré-treinado com perda AAM. Qualidade maior, porém mais lento — 300+ MB vs 15 MB.

x-vector (baseline). TDNN + statistics pooling. Clássico; ainda útil em CPU / edge.

AAM-softmax. Softmax padrão com margem m adicionada no espaço angular: cos(θ + m) para a classe correta. Força a separação angular entre classes. m=0.2 típico, escala s=30.

Pontuação

  • Cosseno entre os embeddings de cadastro e de teste. Decisão baseada em limiar.
  • PLDA (LDA Probabilístico). Projeta os embeddings em um espaço latente onde mesmo-locutor vs locutor-diferente tem uma razão de verossimilhança em forma fechada. Adicionado sobre o cosseno para uma redução de EER de +10–20%. Padrão antes de 2020; agora usado apenas em configurações closed-set.
  • Normalização de score. S-norm ou AS-norm: normalize cada score contra um cohort de médias e desvios-padrão de impostores. Essencial para avaliação cross-domain.

Números que você deveria conhecer (2026)

Modelo VoxCeleb1-O EER Parâmetros Throughput (A100)
x-vector (clássico) 3.10% 5 M 400× RT
ECAPA-TDNN 0.87% 15 M 200× RT
WavLM-SV large 0.42% 316 M 20× RT
Pyannote 3.1 segmentação + embedding 0.65% 6 M 100× RT
ReDimNet (2024) 0.39% 24 M 100× RT

Diarização

"Quem falou quando" em um clipe com múltiplos locutores. Pipeline: VAD → segmentar → embedar cada segmento → clusterizar (aglomerativo ou espectral) → suavizar fronteiras. Stack moderno: pyannote.audio 3.1, que reúne segmentação de locutor + embedding + clustering por trás de uma única chamada. O DER SOTA de 2026 no AMI é ~15% (queda dos 23% de 2022).

Construa

Passo 1: embedding de brinquedo a partir de estatísticas de MFCC

def embed_mfcc_stats(signal, sr):
    frames = featurize_mfcc(signal, sr, n_mfcc=13)
    mean = [sum(f[i] for f in frames) / len(frames) for i in range(13)]
    std = [
        math.sqrt(sum((f[i] - mean[i]) ** 2 for f in frames) / len(frames))
        for i in range(13)
    ]
    return mean + std  # 26-d

Longe de SOTA — apenas para fins didáticos. code/main.py usa isto como prova de conceito em dados sintéticos de locutores.

Passo 2: similaridade de cosseno + limiar

def cosine(a, b):
    dot = sum(x * y for x, y in zip(a, b))
    na = math.sqrt(sum(x * x for x in a))
    nb = math.sqrt(sum(x * x for x in b))
    return dot / (na * nb) if na and nb else 0.0

def verify(enroll, test, threshold=0.75):
    return cosine(enroll, test) >= threshold

Passo 3: EER a partir de pares de similaridade

def eer(same_scores, diff_scores):
    thresholds = sorted(set(same_scores + diff_scores))
    best = (1.0, 1.0, 0.0)  # (fa, fr, threshold)
    for t in thresholds:
        fr = sum(1 for s in same_scores if s < t) / len(same_scores)
        fa = sum(1 for s in diff_scores if s >= t) / len(diff_scores)
        if abs(fa - fr) < abs(best[0] - best[1]):
            best = (fa, fr, t)
    return (best[0] + best[1]) / 2, best[2]

Retorna (eer, threshold_at_eer). Reporte ambos.

Passo 4: produção com SpeechBrain

from speechbrain.pretrained import EncoderClassifier

clf = EncoderClassifier.from_hparams(source="speechbrain/spkrec-ecapa-voxceleb")

# enroll: average the embeddings of 3-5 clean samples
enroll = torch.stack([clf.encode_batch(load(x)) for x in enrollment_clips]).mean(0)
# verify
score = clf.similarity(enroll, clf.encode_batch(load("test.wav"))).item()
verdict = score > 0.25   # ECAPA typical threshold; tune on your data

Passo 5: diarize com pyannote

from pyannote.audio import Pipeline

pipe = Pipeline.from_pretrained("pyannote/speaker-diarization-3.1")
diarization = pipe("meeting.wav", num_speakers=None)
for turn, _, speaker in diarization.itertracks(yield_label=True):
    print(f"{turn.start:.1f}–{turn.end:.1f}  {speaker}")

Use

O stack de 2026:

Situação Escolha
Verificação 1:1 closed-set, edge ECAPA-TDNN + limiar de cosseno
Verificação open-set, nuvem WavLM-SV + AS-norm
Diarização (reuniões, podcasts) pyannote/speaker-diarization-3.1
Anti-spoofing (detecção de replay / deepfake) AASIST ou RawNet2
Embarcado minúsculo (KWS + cadastro) Titanet-Small (NeMo)

Armadilhas

  • Incompatibilidade de canal. Modelo treinado no VoxCeleb (vídeo da web) ≠ áudio de chamada telefônica. Sempre avalie no canal-alvo.
  • Falas curtas. O EER degrada bruscamente abaixo de 3 segundos de áudio de teste.
  • Cadastro com ruído. Um único cadastro ruidoso envenena a âncora. Use ≥3 amostras limpas e faça a média.
  • Limiar fixo entre condições. Sempre ajuste o limiar em um conjunto de dev separado do domínio-alvo.
  • Cosseno sobre embeddings não normalizados. Normalize por L2 primeiro; caso contrário, a magnitude domina.

Entregue

Salve como outputs/skill-speaker-verifier.md. Escolha o modelo, o protocolo de cadastro, o plano de ajuste de limiar e as salvaguardas contra fraude.

Exercícios

  1. Fácil. Execute code/main.py. Constrói "locutores" sintéticos (perfis de tom diferentes), faz o cadastro, calcula o EER em uma lista de teste de 100 pares.
  2. Médio. Use o ECAPA do SpeechBrain em 30 falas do VoxCeleb1 (5 locutores × 6 cada). Calcule o EER com cosseno vs PLDA.
  3. Difícil. Construa o pipeline completo cadastro → diarização → verificação com pyannote.audio. Avalie o DER no conjunto de dev do AMI.

Termos-chave

Termo O que as pessoas dizem O que realmente significa
EER A métrica principal Limiar onde Falsa Aceitação = Falsa Rejeição.
Verificação 1:1 "Esta é a Alice?"
Identificação 1:N "Quem está falando?"
Open-set Desconhecido possível O conjunto de teste pode conter locutores não cadastrados.
Cadastro Registrar Calcular o embedding de referência de um locutor.
AAM-softmax A perda Softmax com margem angular aditiva; força a separação de clusters.
PLDA Pontuação clássica LDA Probabilístico; pontuação por razão de verossimilhança sobre os embeddings.
DER Métrica de diarização Diarization Error Rate — miss + falso alarme + confusão.

Leitura Complementar

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).