ai papers

Speaker Recognition with Deep Learning (2) - 화자 인식의 어려움 극복을 위한 접근법 정리 및 개요

Junyoung Park · 2025-12-23 · 14 min

바로 이전에 작성한 글인 Speaker Recognition with Deep Learning (1) - 화자 인식의 개념 정리 및 태스크 분류에 이어 작성한다. 이 글에서부터 기본적인 신호와 시스템에서 사용하는 input/output/state 함수에 대한 notation 및 푸리에 변환/Convolution의 수식을 설명 없이 바로 사용할 예정이다.

발화 특징을 뽑기 위한 노력

결국 Speaker Recognition을 구성하는 여러 Task인 Speaker Verification/Identification 등을 잘 수행하기 위해서는 다음의 공통 목표를 가지게 된다.

발화의 “특징”을 어떻게 잘 정의하느냐?

결국 모든 형태의 입력 음성은 적절한 전처리 과정을 거친 뒤에 특징을 추출하는 과정을 거치며, 추출된 특징을 기반으로 여러 Downstream task를 수행하게 될 것이다. 여기서, 우리는 다음과 같은 접근 방식을 생각해볼 수 있다.

Speaker Recognition을 위한 첫걸음

이전 글에서도 간략하게나마 정리했지만 Speaker Recognition은 굉장히 어려운 작업이다. 그 이유에 대해서 간단하게는 마이크 특성, 배경의 노이즈, 다른 사람 발화와의 겹침 등등으로 대충만 언급했었다. 우리가 관측하는 음성 xx는 화자 고유 정보만 담고 있는 게 아니라, 각종 nuisance(방해 요인)의 불순물이 섞인 혼합물이라 볼 수 있다. 흙탕물이라면 필터링도 쉽겠지만, 다소 그렇게 간단하지는 않다.

가장 단순한 관측 모델만 써도 이런 느낌이다:

x(t)=(sy(t)h(t))+n(t)x(t) = \big(s_y(t) * h(t)\big) + n(t)
  • sy(t):s_y(t): 화자 yy의 원래 발화(그 사람의 음색/발성 특성이 들어있는 부분)
  • h(t):h(t): 마이크/채널/공간(잔향) 같은 전달 경로의 임펄스 응답
  • n(t):n(t): 배경 소음/음악/겹침 발화 등 환경 잡음

우리가 하고 싶은 것은 다음과 같다.

z=fθ(x)z = f_\theta(x)
  • fθ:f_\theta: feature extractor / embedding model
  • z:z: 우리가 얻고 싶은 speaker embedding(=latent)

근데 이 zz가 좋은 latent가 되려면 조건이 붙는다.

  • 같은 화자면 환경/채널/언어/세션이 달라도 zz가 최대한 비슷해야 하고
  • 다른 화자면 내용이 비슷해도 zz가 확실히 달라야 한다.

이걸 목표로 쓰면 이런 형태가 된다:

(same speaker)z(xy(a))z(xy(b))(different speaker)z(xy(a))≉z(xy(b))\begin{aligned} \text{(same speaker)}\quad z(x_{y}^{(a)}) \approx z(x_{y}^{(b)}) \newline \text{(different speaker)}\quad z(x_{y}^{(a)}) \not\approx z(x_{y'}^{(b)}) \end{aligned}

음성 인식이 왜 어려운가

여기서부터 위에서 나열한 challenge들이 왜 치명적인지가 더 명확해진다. 이를 보다 상세하게 분류하면 다음과 같이 정리할 수 있다:

Environment (환경)

실제 음성은 화자 목소리만 들어오는 경우가 거의 없다. 주변 노이즈, 음악, 잔향 같은 게 항상 같이 섞이고, 이게 화자 고유 특징까지 같이 흔들어서 학습/테스트 전 단계에서 성능을 깎아먹는다. 환경 잡음/음악/잔향은 위 식에서 n(t)n(t)h(t)h(t)로 들어오는데 화자 특징을 직접 망가뜨리거나 가려버린다. 특히 잔향은 그냥 노이즈 추가가 아니라 convolution으로 스펙트럼을 변형시켜서,

Sx(ω)Ss(ω)H(ω)2+Sn(ω)S_x(\omega) \approx S_s(\omega)\cdot |H(\omega)|^2 + S_n(\omega)

처럼 주파수 대역별로 성질이 바뀌어버리는 느낌이 된다. 그래서 모델이 사람을 보라는 건지, 공간을 보라는 건지 헷갈리는 상황이 자주 나온다.

Channel (채널)

똑같은 사람이 말해도 어떤 장비나 경로로 녹음됐는지가 음색을 바꿔버린다. 유선/무선, 마이크 종류, 전송 과정에서 들어오는 잡신호 때문에 채널이 달라지면 임베딩 분포가 쉽게 벌어지고 이 차이를 완전히 없애기도 어렵다. 결국 같은 화자 yy라도

x1(t)=sy(t)h1(t),x2(t)=sy(t)h2(t)x_1(t) = s_y(t) * h_1(t),\qquad x_2(t) = s_y(t) * h_2(t)

입력 분포 자체가 달라지니까 임베딩 분포도 쉽게 갈라진다. “같은 사람인데 다른 사람처럼 보이는” 대표 원인이다.

Speaker characteristics (발화자별 특징)

같은 사람도 컨디션, 건강, 나이, 감정, 악센트 같은 요인으로 목소리가 계속 변한다. 즉,

sy(t)sy,state(t)s_y(t) \rightarrow s_{y,\,\text{state}}(t)

처럼 “한 화자 = 하나의 점”이 아니라 “한 화자 = 상태에 따른 분포”가 된다. 결국 모델 입장에선 intra-class variance(같은 사람 내 분산) 가 커져서 구분이 더 어려워진다. 또한 상태 함수(h(t)h(t))가 명시되지 않는 문제를 풀기 때문에 다른 system에 비해 그 솔루션을 찾는 것이 사실상 불가능에 가까운 상황이다.

Speech variability (발화의 다양성)

발화는 매번 다르고, 심지어 같은 문장을 말해도 속도/에너지/억양이 바뀐다. 그래서 실전 데이터는 종종 이런 형태에 가깝다:

x=g(y,  c,  r,  n,  session)x = g\big(y,\; c,\; r,\; n,\; \text{session}\big)
  • y:y: speaker identity
  • c:c: linguistic content(무슨 말을 했는지)
  • r:r: room/channel response
  • n:n: noise
  • session:\text{session}: 그날그날의 녹음 상황

여기서 우리는 yy 요인만 남기고 나머지는 최대한 지우고 싶은데, 실제로는 ccsession\text{session} 변화가 너무 커서 모델이 쉽게 끌려간다.

Language characteristics (언어적 특징)

언어가 바뀌면 발음 체계(음운), 리듬, 억양 패턴이 달라지고, cc쪽 변동을 크게 만든다. 즉, 동일 화자라도

p(xy,Korean)p(xy,English)p(x\mid y, \text{Korean}) \neq p(x\mid y, \text{English})

가 될 수 있고, 이러면 모델이 speaker가 아니라 언어/발화 내용에 더 민감해지는 문제가 생긴다. 다국어 환경에서 성능이 무너지는 이유가 여기랑 직결된다.

Transducing characteristics (음성 신호 전달 매개체의 특징)

마이크/헤드셋이 다르면 주파수 응답이 달라서 H(ω)H(\omega)가 바뀐다. 대역폭이 좁으면 정보가 잘리기도 한다:

X(ω)=H(ω)S(ω),H(ω)0 for some ωX(\omega)=H(\omega)S(\omega),\quad H(\omega)\approx 0 \text{ for some } \omega

같은 사람이 말해도 기기가 바뀌면 다른 사람처럼 보이는 현상이 수학적으로도 자연스럽게 나온다.

그래서, 우리가 진짜로 원하는 건 뭐냐?

speaker identity yy 에 대해선 민감하고, 환경/채널/언어/세션 같은 nuisance에는 둔감한 latent zz를 만드는 것. 정보 관점으로 쓰면,

maxI(z;y)&&minI(z;nuisance).\max I(z; y)\quad \&\&\quad \min I(z; \text{nuisance}).

zz는 발화 정보를 최대한 많이 담고, 환경/장비/언어/그날 컨디션 정보는 최대한 덜 담아야 한다. 내가 기존에 disentanglement와 관련된 글을 다룬 이유도 사실 이런 문제에 대한 개인적인 고민 때문이었다.

그런데 여기서 한 번 더 냉정하게 보면, 라벨이나 explicit한 분리 신호가 없는 상황에서 이걸 모델이 알아서 해주길 기대하는 건 생각보다 쉽지 않다. 우리가 관측하는 데이터 xx는 대충 이런 생성 과정으로 생각할 수 있다:

xp(xy,u)x \sim p(x \mid y, u)
  • y:y: speaker identity (우리가 남기고 싶은 요인)
  • u:u: nuisance (환경/채널/언어/세션/컨디션 등 우리가 지우고 싶은 요인)

그리고 우리는 어떤 인코더(표현 학습기) fθf_\theta

z=fθ(x)z = f_\theta(x)

를 만들고 싶은데, 목표는 직관적으로 I(z;y) 는 크게,I(z;u) 는 작게'I(z; y)\ \text{는 크게}, \quad I(z; u)\ \text{는 작게}'와 같은 형태다. 문제는 xx만 가지고는 yyuu를 분리하는 해가 유일하지 않다는 데 있다.

분리 가능한 표현은 기본적으로 비식별(Non-identifiable) 문제가 된다

극단적으로는 이런 상황이 가능하다.

z=g(y,u)z = g(y, u)

라고 했을 때, gg가 어떤 혼합 함수든 가능하다. 심지어 gg가 bijection(일대일 대응 관계의 함수)이면,

(y,u)z(y,u) \leftrightarrow z

가 되니까, zz에 모든 정보가 섞인 채로 들어가도 손실을 충분히 줄이는 해가 존재한다. 예를 들어 reconstruction 기반 목적(오토인코더)만 있다고 치면,

minθ,ϕ E[xdϕ(fθ(x))2]\min_{\theta,\phi}\ \mathbb{E}\left[\|x - d_\phi(f_\theta(x))\|^2\right]

이걸 최소화하는 가장 쉬운 방법 중 하나는 그냥 zzxx를 최대한 잘 복사하는 방향이다. 그러면 자연히 zz에는 yy뿐 아니라 uu도 같이 들어간다. 즉, 특별한 제약이 없으면 disentangle이 아니라 entangle이 훨씬 자연스러운 해가 된다. 결국 우리가 추구하는 일반적인 학습법으로 화자의 목소리(yy)만 방해하는 요인(uu)을 분리하라는 것이 쉽지 않다는 것을 알 수 있다.

RBM/DBM 같은 generative 모델도 알아서 분리는 잘 안 된다

RBM/DBM류는 기본적으로 에너지 기반 모델이다.

pθ(x,h)=1Zexp(Eθ(x,h))p_\theta(x,h) = \frac{1}{Z}\exp(-E_\theta(x,h))
  • h:h: latent(hidden unit)
  • Eθ:E_\theta: 에너지 함수

학습은 보통 likelihood를 키우는 방향이다.

maxθ Exdata[logpθ(x)]\max_\theta\ \mathbb{E}_{x \sim \text{data}}[\log p_\theta(x)]

근데 likelihood 최대화는 요인을 잘 분리하라는 목적 함수가 아니다.

hh의 유닛을 아래처럼 나누고 싶을 수 있다.

  • 어떤 유닛은 speaker를 잡고, 어떤 유닛은 채널을 잡고, 어떤 유닛은 언어/내용을 잡고

이렇게 깔끔하게 역할이 갈릴 필요가 없다. 오히려 학습 관점에서는 speaker + channel + content가 섞인 패턴을 하나의 hidden factor가 먹어버리고 나머지 factor는 더미 노드처럼 놀 수가 있다. 결국 RBM/DBM이 잘 하는 건 분포 근사지, 의미 단위로 요인 분해가 아니다.

DNN도 편한 shortcut을 잡는다

DNN으로도 z=fθ(x)z = f_\theta(x)를 학습할 수 있지만, objective가 예를 들어 classification이라 해도(화자 분류)

minθ L(softmax(Wz),y)\min_\theta\ \mathcal{L}(\text{softmax}(W z), y)

이건 화자를 구분하기 좋은 특징을 만들라는 뜻이지, nuisance를 버리라는 뜻은 아니다.

실제로 데이터에 채널/언어/환경이 label과 우연히라도 상관이 생기는 순간 (아무리 대용량 데이터라고 하더라도 정해진 수의 사람에 대해, 정해진 환경에서 녹음을 하기 때문에 entanglement가 발생할 수밖에 없다),

I(u;y)>0I(u; y) > 0

모델은 yy를 잘 맞추기 위해 uu를 적극적으로 사용할 수 있다(일종의 shortcut). 그러면 zz는 자연스럽게

z[speaker info]+[nuisance cue]z \approx [\text{speaker info}] + [\text{nuisance cue}]

처럼 섞여버리고, 우리가 원하던 I(z;u)0I(z; u) \approx 0와는 멀어진다.

제약/신호가 없으면 분리는 그냥 잘 안 된다

지금까지의 내용을 식으로 묶으면,

  • likelihood 기반 학습(RBM/DBM/AE)은 “복원/모사”가 목표라 uu를 버릴 이유가 없고
  • 분류 기반 학습(DNN)도 “정답 맞추기”가 목표라 uu가 도움이 되면 오히려 끌어다 쓴다

Explicit한 정보(조건, 제약, augmentation 설계, adversarial/regularization 같은 유도)가 없으면, 우리가 원하는 disentanglement는 생각보다 자연스럽게 나오지 않는다.

이 관점에서 다음 섹션에서 다룰 GMM→ii-vector→xx-vector→ECAPA/SSL 흐름은, 사실상 각 시대가

“어떤 제약/구조/학습 신호로 uu를 덜 보게 만들었는가” 를 계속 바꿔온 역사라고도 볼 수 있다.

제대로 된 Deep/Deeper Feature 추출을 위한 설계

화자 인식에서 특징(feature)이라는 말은 시대별로 의미가 조금씩 바뀌었다. 초반엔 Hand-crafted 음성 특징(MFCC/PLP 등)을 넣고 직접 “화자 인식 모델”을 만들었고, 시간이 지나면서 고정 길이 latent(=embedding)를 뽑아내는 방향으로 점점 정리되기 시작했다. 그러나 사실 deep learning 쪽에서도 시작은 똑같다. 입력은 MFCC/Fbank 같은 hand-crafted feature일 수도 있고, 아예 raw waveform에서부터 convolution으로 뽑아낼 수도 있다. 결국 중요한 것은 어떻게 이런 기본적인 shallow feature로부터 시작하여 우리가 진정으로 원하는 ‘화자 정보’에 대한 deep feature를 뽑아내느냐이다.

GMM-UBM: 화자 모델을 확률로 잡던 시대

가장 클래식한 접근은 GMM(혼합 가우시안)으로, 화자의 발화 특징을 다변수+혼합 가우시안 분포로 모델링하는 방식이다. (대표적으로 GMM-UBM speaker verification 계열이 있다)

GMM은 샘플들의 분포가 여러 평균/표준편차를 가지는 가우시안 분포들의 weighted summation으로 구성되어 있다고 가정한다.

(1) 입력 특징

보통 프레임 단위로 MFCC/PLP 같은 특징을 만든다. 각 프레임 특징을 xtRDx_t \in \mathbb{R}^D라고 하면, GMM은 다음과 같이 정의된다. 이때 KK는 가우시안 분포의 개수에 해당된다. MFCC/PLP 같은 음성 신호의 경우 시간축을 기점으로 연속으로 들어오는 음성 신호를 특정 윈도우 크기 및 슬라이딩 크기에 따라 “프레임”으로 구분하고(이를 시간축으로 생각한다) 각각의 슬라이딩 윈도우 상에서 주파수 분석을 통해 각 지점에서 주파수들이 대체적으로 어떻게 분포가 되어 있는지를 표현한 특징이다. 사실 더 디테일하게 설명하자면 (STFT(Spectrogram) → Mel Filterbank(Mel-Spectrogram) → Cepstral Coefficient(MFCC)) 이 순서가 맞는데 PLP의 경우에는 프로세스가 좀 달라지기도 하고 이거 하나하나 다 설명하면 한나절 걸리니까 그냥 주파수의 주된 성분을 뽑아낸다고만 생각하면 된다 (아래는 MFCC의 예시).

아무튼 이렇게 특징들을 쭉 뽑아내고 나면 보이는 것이 각 시간축마다 보이는 “Index”의 나열이다. 인덱스는 결국 특정 주파수의 대역을 의미하며, 세로로 펼쳐진 값의 나열(그림에서는 타일 형태로 보이지만, 각각은 결국 숫자이므로)을 일종의 feature embedding/vector로 간주할 수 있다. 실제로 GMM을 통해 모델링하고자 하는 다변수가 바로 이 부분이다. GMM-UBM에서는 8080차원을 사용했다고 하므로 Index는 총 80개로 구성되어 있다.

p(xtλ)=k=1KwkN(xt;μk,Σk)p(x_t \mid \lambda)=\sum_{k=1}^{K} w_k \,\mathcal{N}(x_t;\mu_k,\Sigma_k)

여기서 λ={wk,μk,Σk}k=1K\lambda=\{w_k,\mu_k,\Sigma_k\}_{k=1}^{K}. 이렇게 구성된 GMM은 단일 화자의 음성에 대한 MFCC feature를 모델링하므로, 다변적인 화자의 발화에 대한 일반화 능력이 떨어진다. 이를 보조하기 위한 수단이 바로 UBM이다.

(2) UBM(Universal Background Model)

UBM은 모든 화자들의 평균 같은 배경 분포다. 먼저 UBM λubm\lambda_{\text{ubm}}을 화자의 발화를 포함한 많은 발화 데이터로 학습해두고, 각 화자에 대해 MAP adaptation으로 화자 모델 λspk\lambda_{\text{spk}}를 만든다.

(3) Baum–Welch 통계 (ii-vector로 이어지는 핵심 재료)

UBM 기준 posterior로 다음과 같이 원래의 화자 정보에 대한 GMM을 생성한다.

γt(k)=p(kxt,λubm)Nk=tγt(k),Fk=tγt(k)xt\begin{aligned} &\gamma_t(k) = p(k\mid x_t, \lambda_{\text{ubm}}) \newline &N_k=\sum_t \gamma_t(k), \quad F_k=\sum_t \gamma_t(k)\,x_t \end{aligned}

일반화된 화자 분포 hypothesis(λ\lambda)로 다시 모델링하겠다는 간단한 원리다.

이 통계들이 이후 ii-vector/PLDA로 그대로 이어진다.

(4) 스코어링(Verification)

가장 기본은 log-likelihood ratio:

score(X)=logp(Xλspk)logp(Xλubm)\text{score}(X)=\log p(X\mid \lambda_{\text{spk}})-\log p(X\mid \lambda_{\text{ubm}})

문제점은 채널/환경/세션이 조금만 바뀌어도 λspk\lambda_{\text{spk}} 의 MAP adaptation 자체가 흔들린다. 그리고 MFCC feature 자체가 애초에 노이즈 등에 크게 robust한 특징이 아니라는 문제가 있다.

ii-vector: 고정 길이 latent로 압축해버리기

GMM-UBM이 화자마다 GMM 분포를 생성한다고 가정하면, ii-vector는 이를 좀 더 개선해서

화자 발화 하나(가변 길이)를 고정 길이 벡터 하나로 압축한다. (total variability / ii-vector)

(1) 핵심 아이디어: supervector를 저차원으로

GMM의 mean들을 쭉 이어붙인 “supervector”를 MM이라 하면,

M=m+TwM = m + T\cdot w

  • m:m: UBM mean supervector(전역 평균)
  • T:T: total variability matrix
  • w:w: 우리가 뽑고 싶은 저차원 latent = ii-vector

보통 prior는 가우시안으로 지정한다: wN(0,I)w \sim \mathcal{N}(0,I). 이 구조의 핵심은 speaker와 channel 변동을 한 공간에 같이 모델링한다는 점이다. (그래서 “total variability”라는 이름이 붙는다) 즉, ww 자체가 원래부터 “사람+채널”이 섞여 있을 가능성이 높다는 관점에서 접근함.

(2) 추정은 통계 기반(UBM posterior → ww posterior)

위에서 만든 Nk,FkN_k, F_k 같은 통계를 이용해서 ww의 posterior를 구하고, 보통 그 평균 w^\hat{w}ii-vector로 쓴다.

그냥 프로토타입 하나를 잘 구하겠다는 느낌이라고 생각하면 될 것 같다.

Back-end: Cosine vs PLDA, 그리고 length normalization

ii-vector를 뽑았으면 이제 같은 사람인지를 비교해야 한다.

가장 단순하게는 다음과 같이 cosine similarity 방식을 사용하게 되고,

cos(w^1,w^2)=w^1w^2w^1w^2\cos(\hat{w}_1,\hat{w}_2)=\frac{\hat{w}_1^\top \hat{w}_2}{\|\hat{w}_1\|\|\hat{w}_2\|}

더 정교하게는 PLDA로 ‘speaker vs session’ 분해를 backend에서 한 번 더 한다. PLDA는 임베딩 zz화자 요인 + 세션(채널/환경) 요인의 합으로 생성된다고 가정하는 확률 모델이다. 가장 흔한 형태는 다음과 같다:

z=μ+Fy+ϵ,yN(0,I), ϵN(0,Σw)z = \mu + F y + \epsilon,\quad y\sim\mathcal N(0,I),\ \epsilon\sim\mathcal N(0,\Sigma_w)
  • y:y: 화자(speaker) 잠재변수(같은 화자는 공유)

  • ϵ:\epsilon: 화자 내 변동(세션/채널/노이즈 등)

  • 학습은 라벨이 있는 임베딩들 {zs,i}\{z_{s,i}\}F,ΣwF,\Sigma_w를 추정하고, 실전에선 전처리를 같이 둬서 가우시안 assumption과 맞도록 해준다.

  • Scoring은 두 임베딩 z1z_1, z2z_2에 대해 H1:H1: 같은 화자(같은 yy 공유) vs H0:H0: 다른 화자(서로 다른 yy)

    의 log-likelihood ratio로 점수화한다. 말은 어렵게 했지만 같은 화자라고 가정했을 때가 두 분포가 잘 맞는지, 다른 화자라고 가정했을 때가 두 분포가 서로 잘 맞는지를 비교하는 과정이라고 이해할 수 있다.

DNN이 들어오면서: feature를 모델이 직접 뽑게 하자

여기서부터는 분위기가 바뀐다. 확률 모델로 간접적으로 압축하던 걸 DNN이 직접 embedding을 뽑도록 한다는 관점이다. MFCC 등에 의존하던 방식은 피상적인 주파수 특징을 활용하는 방식이고, DNN은 Black-box인 Neural Network로 하여금 더 ‘추상적’인 특징을 뽑아서 활용하겠다는 관점이다.

dd-vector: 마지막 hidden을 평균내서 embedding으로

초기 딥러닝 임베딩의 전형적인 형태가 dd-vector다.

  • DNN으로 프레임을 처리하고
  • 마지막 hidden activation을 뽑아서
  • utterance 단위로 평균내서 embedding으로 씀
ht=fθ(xt),z=1Tt=1Thth_t = f_\theta(x_t),\qquad z=\frac{1}{T}\sum_{t=1}^{T} h_t

그리고 verification은 cosine으로 가는 게 기본이다.

jj-vector: speaker + text(내용) 같이 보는 멀티태스크

화자만 보겠다는 게 실제로는 어려우니까, 아예 speaker id와 text 정보를 같이 학습하는 멀티태스크 형태(jj-vector)도 등장한다. 이건 관점이 꽤 솔직하다. 내용(content)을 무시하기가 어려우면, 차라리 내용도 같이 학습 구조에 넣어서 통제해보자는 쪽. 근데 그렇게 Fancy한 접근법은 아닌 것 같다.

xx-vector: TDNN + statistics pooling = 사실상 표준 템플릿

x-vector는 지금도 기본 베이스라인 템플릿처럼 쓰이는 구조다. 요지는 간단하다:

  • 프레임 레벨은 TDNN(Time-Delay Neural Network)로 넓은 시간 문맥을 보게 만들고
  • utterance 레벨로 넘어갈 때 statistics pooling(평균/표준편차 등)으로 가변 길이를 고정 길이로 바꾼다
  • speaker classification으로 학습하고, 중간 레이어 출력을 embedding으로 쓴다

statistics pooling 수식

프레임 임베딩이 hth_t

μ=1Ttht,σ=1Tt(htμ)2,s=[μ;σ]\mu = \frac{1}{T}\sum_t h_t,\quad \sigma = \sqrt{\frac{1}{T}\sum_t (h_t-\mu)^2}, \quad s = [\mu;\sigma]

ss가 utterance-level representation의 재료가 된다. 그래서 xx-vector 계열은 마지막에 pooling이 진짜 중요하다는 말이 항상 따라다닌다. 그리고 xx-vector는 보통 임베딩 뽑고 나서도 cosine/PLDA를 backend로 붙이거나, 더 강하게는 margin loss류로 “임베딩을 더 분리되게” 학습시킨다(AM/AAM-Softmax 등은 여기서 자주 등장).

DNN / ii-vector hybrid: alignments만 DNN으로 바꾸자

재밌는 중간 단계도 있다. ii-vector 파이프라인은 그대로 두고, UBM-GMM이 하던 frame alignment(=posterior 계산)만 DNN으로 대체하는 것이다.

ECAPA-TDNN: xx-vector를 더 잘 만든 버전

최근엔 xx-vector 계열을 더 강화한 구조들이 강하게 자리잡았고, 그 중 가장 유명한 축이 ECAPA-TDNN이다.

원 논문도 “xx-vector의 TDNN + statistics pooling 형태의 기본적 구성틀은 그대로 유지하되, attention/aggregation을 넣어서 강화”라는 메시지가 명확하다.

  • TDNN backbone은 유지
  • 채널(attention) 쪽을 더 잘 보게 만들고(SE류)
  • multi-layer feature를 더 잘 섞고(aggregation)
  • pooling도 더 표현력 있게(channel-dependent stats pooling 같은 변형)

Raw waveform: SincNet 같은 “프론트부터 학습” 계열

MFCC/Fbank 같은 hand-crafted feature를 안 쓰고, 아예 waveform에서 바로 들어가서 필터뱅크를 학습시키는 흐름도 있다. 대표적으로 SincNet. Sinc 기반 convolution 이후 CNN 파이프라인으로 speaker classification을 하는 구조를 언급하고, mutual information 관점으로 unsupervised embedding까지 확장이 가능하다. 이 계열의 메시지는 깔끔하다:

사람을 구분하는 데 중요한 건 좁은 대역의 특징(피치/포먼트 등)인데, 그걸 굳이 앞단에서 먼저 뽑지(사람이 설계하지) 말고 네트워크가 직접 학습하게 설명력 있는 제약을 걸자. 물론, 이때 제약을 어떻게 걸어줄 수 있느냐가 키포인트가 된다.

SSL(wav2vec2/HuBERT/WavLM): Representation을 먼저 키우기

마지막으로 요즘 이런저런 모달리티의 가장 강한 흐름인 SSL 기반이다. 대표적으로는 Vision/Image/Video 등에 많이 적용되어 있지만, 여전히 Audio는 SSL의 흐름이 많이 넘어오지는 않아보인다.

  • wav2vec 2.0: latent space masking + contrastive 학습
  • HuBERT: clustering 기반 hidden unit을 target으로 masked prediction
  • WavLM: masked prediction + denoising 등을 결합해서 non-ASR task까지 잘 먹히게 설계

여기서 speaker recognition에 SSL을 붙이는 방식은 보통 둘 중 하나다.

feature extractor로만 사용하거나,

h1:T=SSL(x),z=pool(h1:T)h_{1:T} = \text{SSL}(x),\quad z=\text{pool}(h_{1:T})

(평균/attentive stats pooling 등)

speaker loss로 fine-tune하거나,

minθ Lspk(z,y)\min_\theta \ \mathcal{L}_{\text{spk}}(z, y)

이때 loss는 softmax도 가능하지만, 요즘은 margin 계열(AAM/AM-softmax 등)이 더 많은 듯하다. 뭐 이건 downstream을 어떤 식으로 수행하냐의 관점 차이. 아무래도 음성은 아직 SSL 연구를 많이 안 한 것처럼 보인다(왜지?). 솔직히 더 잘 만들어볼 수 있을 것 같다는 생각도 든다.

결론

화자 인식은 모델이 화자를 알아보게 만든다기보단, 모델이 볼 수밖에 없는 것들 중에서 무엇을 버리고 무엇을 남길지 선택하게 만드는 문제에 가깝다. 우리가 관측하는 음성 xx는 항상 yy만 담고 있지 않고, uu(환경/채널/언어/세션/컨디션)가 섞여 들어온 혼합물이다. 처음부터 끝까지 목표는 같다. 임베딩 zz는 사람 정보는 최대한 살리고 방해 요인은 최대한 덜 담아야 한다.

이 관점에서 GMM→i-vector→x-vector→ECAPA/SSL 흐름을 보면, 각 시대가 uu를 덜 보게 만드는 구조와 학습 신호와 데이터를 어떻게 설계했는지가 드러난다.

  • GMM-UBM은 MFCC/PLP 같은 shallow feature 위에서 분포를 모델링하며 화자 차이를 잡아보려 했고, 채널/환경 변동에는 구조적으로 취약했다.
  • ii-vector는 발화를 고정 길이 ww로 압축하면서 비교 가능한 latent를 만들었지만, total variability라는 이름 그대로 ww 안에 speaker와 nuisance가 원천적으로 섞이기 쉬웠다. 그래서 backend(PLDA, length norm 등)에서 한 번 더 분해하려는 시도가 붙는다.
  • DNN 임베딩(d/x/ECAPA)은 아예 feature 자체를 네트워크가 학습하게 만들면서 표현력을 끌어올렸고, pooling/attention/margin loss 같은 장치로 화자 중심 표현을 더 강하게 밀어붙였다.
  • SSL은 큰 데이터에서 representation 자체를 먼저 키워두고, downstream에서 speaker 방향으로 정렬시키는 방식인데, 결국 이것도 zz가 무엇을 불변(invariant)으로 보고 무엇을 구분(discriminative)으로 볼 것인가의 싸움이다.

그래서 결론은 꽤 단순하다.

화자 인식에서 좋은 모델은 결국 좋은 임베딩을 만드는 모델, 좋은 임베딩은 좋은 제약(학습 신호 + 구조 + 데이터 설계) 없이는 거의 나오지 않는다.

아키텍처가 GMM이든 DNN이든 SSL이든, 진짜로 중요한 건 파라미터 수보다도 (1) 어떤 nuisance를 현실적으로 상정했고, (2) 그 nuisance를 모델이 shortcut으로 쓰지 못하게 어떤 제약을 걸었고, (3) 그 제약이 실제 데이터 분포에서 얼마나 성립하느냐 이 세 가지다.

다음 글에서는 이 흐름을 한 단계 더 현실로 끌어오려고 한다. 즉, 논문에서 잘 되는 구조를 나열하는 게 아니라, 방송 음성이라는 도메인에서 uu가 어떤 형태로 들어오고, 그때 어떤 학습/추론 설계가 실제로 I(z;y)I(z;y)를 키우고 I(z;u)I(z;u)를 줄이는지—조금 더 실전적인 관점으로 정리해보려고 한다.