Deep Speech 2는
어떻게 동작하는가
2015년 Baidu의 Deep Speech 2는 음성 인식 파이프라인에서 음향 모델·발음 사전·상태 정렬을 전부 걷어내고 스펙트로그램을 넣으면 글자가 나오는 신경망 하나로 바꿔 놓은 논문입니다. 그런데 논문의 절반은 모델 이야기가 아니라 데이터를 어떻게 모았는가와 GPU를 어떻게 굴렸는가입니다. 이 셋이 저자들이 말하는 세 기둥이에요. 이 문서는 논문을 앞에서부터 따라가되, 표와 수식이 나올 때마다 브라우저에서 그 값을 직접 계산해 보면서 읽습니다.
- 시작하기 전에 — 스펙트로그램이란
- 전체 그림 — 소리가 글자가 되기까지
- 입력 — 스펙트로그램 한 장 만들기
- CTC — 정렬을 모른 채 배우는 법 (식 9)
- 깊이와 BatchNorm — 순환망 어디에 넣는가 (식 7 vs 8)
- SortaGrad — 길이가 곧 난이도다
- simple RNN vs GRU — 규모가 커지면 뒤집힌다
- 주파수 합성곱 — 화자가 바뀌면 스펙트럼이 늘어나고 줄어든다
- 스트라이드와 바이그램 — 영어는 초당 14.1글자다
- 단방향화와 디코딩 — 두 절은 별도 자료로
- 데이터 — 11,940시간과 거듭제곱 법칙
- 계산 — 동기 SGD·all-reduce·GPU CTC
- 결과 — 사람과 겨루기
- 배포 — Batch Dispatch와 67ms
- 정리 — 세 기둥
- 이해도 퀴즈
- 부록 · GMM-HMM이 뭐였나
§0 시작하기 전에 — 스펙트로그램이란
이 문서는 처음부터 끝까지 스펙트로그램이라는 단어를 씁니다. DS2가 먹는 입력이 이것이고, 음성 인식을 공부할 때 가장 먼저 넘어야 하는 개념이라 여기서 먼저 잡고 갑니다. 이미 안다면 §1로 건너뛰어도 됩니다.
마이크가 기록하는 것 — 파형
소리는 공기의 떨림입니다. 마이크는 그 떨림(공기 압력)을 1초에 16,000번 재서 숫자로 적습니다. 이게 16 kHz 샘플링이고, 이 숫자들을 시간 순서대로 이어 그린 것이 파형(waveform)입니다. 7초짜리 발화면 숫자 112,000개예요.
문제는 파형을 봐서는 무슨 소리인지 거의 알 수 없다는 겁니다. 같은 "아"라도 사람마다, 말할 때마다 파형의 모양이 다 다릅니다. 귀가 "아"와 "이"를 구분하는 단서는 파형의 모양 자체가 아니라 어떤 높이의 소리가 얼마나 섞여 있는가입니다.
소리를 높이별로 쪼개기 — 스펙트럼
어떤 소리든 높이(주파수)가 다른 순수한 사인파 여러 개의 합으로 쪼갤 수 있습니다. 프리즘이 흰빛을 무지개색으로 나누는 것과 같아요. 이 쪼개기가 푸리에 변환이고, 컴퓨터에서 빠르게 계산하는 방법이 FFT입니다. 쪼갠 결과 — "각 주파수가 얼마나 센가"를 그린 그래프 — 를 스펙트럼이라고 부릅니다.
성분을 하나씩 켜고 꺼 보세요. 왼쪽 파형은 성분이 셋만 섞여도 금세 알아보기 어려운 모양이 되는데, 오른쪽 스펙트럼은 무엇을 섞었는지 봉우리로 바로 보여 줍니다. 봉우리의 위치가 주파수, 높이가 세기예요. "스펙트럼이 찾아낸 주파수"가 여러분이 섞은 값과 일치하는 것도 확인해 보세요 — 정답을 알려 준 게 아니라 파형 숫자만 보고 역산한 값입니다.
시간을 되살리기 — 스펙트로그램
그런데 스펙트럼 하나에는 시간 정보가 없습니다. 구간 전체를 통째로 쪼갰으니 "언제" 그 소리가 났는지가 사라지죠. 말소리는 1초에도 여러 번 바뀌니 이대로는 쓸 수 없습니다. 해법은 단순합니다.
① 소리를 아주 짧게 자른다 (수십 ms씩, 조금씩 겹치게)
② 조각마다 스펙트럼을 구한다
③ 각 스펙트럼을 세로로 세워서, 시간 순서대로 옆으로 이어 붙인다
이렇게 만든 그림이 스펙트로그램(spectrogram)입니다. 가로가 시간, 세로가 주파수, 밝기가 세기예요. 조각을 하나씩 넘기며 직접 만들어 봅시다. 소리는 "도-미-솔-도"를 치고 마지막에 음을 쭉 끌어올리는 것입니다.
완성본을 보면 계단과 비탈이 보입니다. 가장 아래의 밝은 선이 음의 기본 높이이고, 그 위로 2배·3배 높이에 옅은 선이 따라다닙니다. 이게 배음이에요 — 같은 "도"라도 피아노와 사람 목소리가 다르게 들리는 이유가 이 배음들의 세기 비율입니다. 음을 끌어올린 마지막 구간에서는 선들이 비스듬히 올라가고, 높은 배음일수록 더 가파르게 올라갑니다(2배, 3배로 움직이니까요).
그래서 왜 신경망에 이걸 넣나
- 소리를 구분하는 정보가 주파수 구조에 있습니다. "아"와 "이"의 차이는 포먼트·배음의 위치인데, 파형에서는 위상과 섞여 흩어져 있어 신경망이 푸리에 변환부터 배워야 합니다. 스펙트로그램에서는 밝은 띠로 바로 보이죠 (§2).
- 귀도 같은 일을 합니다. 달팽이관이 소리를 주파수별로 나눠 신경에 전달합니다. 그래서 음성 인식은 딥러닝 훨씬 이전부터 이 표현을 기본으로 써 왔습니다.
- 시간 방향으로 훨씬 짧습니다. 7초 파형은 가로로 112,000개지만 10 ms 간격 스펙트로그램은 약 700칸 — 순환층이 펼칠 스텝이 160분의 1입니다(세로로 161개씩이라 전체 숫자 개수는 비슷합니다). 논문 3.6절도 FFT를 스트라이드와 나란히 다운샘플링 수단으로 적습니다.
기존 음성 인식은 여기서 멜 필터뱅크·MFCC까지 더 손으로 압축했지만, DS1/DS2는 선형 스펙트로그램에서 멈췄습니다. (DS1은 "더 정교한 특징은 평가하지 않았다"고만 적습니다.)
이 문서에서 쓰는 용어
| 용어 | 뜻 | 이 문서에서 |
|---|---|---|
| 파형 | 마이크가 잰 공기 압력을 시간순으로 적은 숫자열 | 데모 0-B 위쪽 그림 |
| 샘플링 레이트 | 1초에 몇 번 재는가 | 16 kHz — DS1 논문의 설정 (DS2 논문에는 없음) |
| 주파수 (Hz) | 1초에 몇 번 진동하는가 = 소리의 높이 | 가온 "도" ≈ 262 Hz |
| 푸리에 변환 · FFT | 소리를 주파수별 성분으로 쪼개는 계산 · 그걸 빠르게 하는 알고리즘 | 데모 0-A, §2 |
| 스펙트럼 | 한 조각 안의 "주파수별 세기" | 데모 0-B 왼쪽 |
| 창(window) · 프레임 | 잘라 낸 짧은 조각 하나 | 20 ms 창 · 10 ms 간격 — DS1 논문의 설정 |
| 주파수 빈 (bin) | 스펙트럼 세로축의 한 칸 | x[t, p]의 p |
| 파워 | 세기(진폭)의 제곱 | x[t, p]의 값 |
| 데시벨 (dB) | 세기를 로그로 잰 단위 — 파워 10배 = +10 dB | 스펙트로그램의 색 |
이제 이 그림 한 장이 신경망 안에서 어떻게 글자가 되는지 따라가 봅시다.
§1 전체 그림 — 소리가 글자가 되기까지
DS2의 구조는 한 줄로 쓸 수 있습니다 (논문 Figure 1).
스펙트로그램 → [합성곱 1~3층] → [양방향 순환층 1~7층] → [완전연결층] → softmax → CTC
새로운 블록은 하나도 없습니다. 전부 2015년 시점에 이미 있던 부품이에요. 논문의 기여는 어느 부품을 몇 개 쌓으면 어떤 일이 벌어지는지를 11개 층까지 전수 조사한 것, 그리고 그 조사를 몇 주가 아니라 며칠 만에 끝낼 수 있게 시스템을 만든 것입니다.
각 층은 무엇을 보는가
세 종류의 층을 가르는 기준은 "가중치를 어떻게 쓰느냐"가 아닙니다. 그건 셋 다 같아요. 갈리는 것은 출력 한 칸이 무엇을 볼 수 있느냐입니다.
여기서 한 스텝이 몇 초인지 먼저 정해 둡시다. 순환층의 한 스텝이 소화하는 시간은 스펙트로그램 프레임 간격 × 합성곱 시간 스트라이드입니다. 10 ms 간격이면 스트라이드 2는 20 ms, 3은 30 ms(최종 영어 모델), 4는 40 ms — 논문 7.3절이 배포 모델을 두고 "per 40 ms stride of speech data"라고 적는 게 이 값입니다. 7초 발화라면 스텝이 각각 350개, 233개, 175개가 되죠.
| 층 | 출력 한 칸이 보는 범위 | 시간을 섞나 | 파라미터 몫 |
|---|---|---|---|
| 합성곱 (41×11) | 주파수 41빈 × 시간 11프레임 (≈ 110 ms) — 고정된 작은 창 | 좁게 | 작음 |
| 양방향 순환층 | 발화 전체 — 과거 전부와 미래 전부 | 무제한 | 대부분 |
| 완전연결층 | 그 시각 한 프레임만 | 안 섞음 | 작음 |
합성곱은 가까운 것을 싸게 봅니다. 하는 일이 셋인데, 특징 추출만큼이나 중요한 게 두 번째입니다 — ①국소 시간–주파수 무늬(포먼트의 움직임, 자음의 파열 같은 것)를 잡고, ②스트라이드로 시간 스텝을 줄이고, ③주파수가 조금 밀려도 버팁니다 (§7). 논문 3.5절은 "sub-sampling is essential to make recurrent neural networks computationally tractable"이라고 못 박습니다. 스텝을 350개에서 175개로 줄이면 그 위 순환층 7개의 계산이 통째로 절반이 되니까요. 합성곱을 1~3층까지만 쌓고 마는 것도, 파라미터를 거의 안 쓰는 것도("convolutional layers add a small fraction of parameters") 이 때문입니다.
순환층은 시간 축으로 정보를 나르는 유일한 부품입니다. 합성곱은 110 ms 창 밖을 못 보고 완전연결층은 아예 한 프레임만 보니, 발화 전체를 문맥으로 쓰는 건 순환층뿐이에요. 그래서 이 스택이 실질적으로 언어 모델 역할을 합니다. 3.8절은 학습 데이터만으로 철자를 익히고 동음이의어를 문맥으로 구분한 예를 들고, 표 6(§9)은 외부 n-gram을 붙였을 때의 개선폭이 순환층 1개면 48.2%인데 7개면 36.2%로 줄어드는 것을 보여 줍니다 — 순환층을 쌓을수록 외부 언어 모델이 할 일을 자기가 가져간다는 뜻입니다. 3.7절도 행 합성곱을 순환층 위에 두는 이유로 "순환층이 이미 좋은 특징 표현을 학습했다"를 듭니다.
완전연결층은 시간을 전혀 섞지 않습니다. 각 시각의 벡터를 받아 글자 확률로 바꾸는 일만 하죠(식 4 → 식 5). 판단은 순환층에서 이미 끝났고, 이 층은 그 결과를 글자 알파벳으로 옮기는 자리입니다.
먼저 모양(shape)부터 감을 잡읍시다. 음성 인식에서 제일 헷갈리는 게 "7초짜리 발화가 신경망 안에서 몇 개의 시간 스텝이 되는가"이고, DS2의 설계 판단 상당수가 이 숫자 하나에서 나옵니다.
토큰 하나당 스텝 칸을 보면서 스트라이드를 올려 보세요. 영어 유니그램 기준으로
스트라이드 2에서는 3.54였다가 스트라이드 4에서 1.77까지 떨어집니다.
CTC가 요구하는 하한은 1이지만, 1에 가까워질수록 글자 사이에 blank를 끼울 자리가 없어집니다 —
같은 글자가 연달아 나오면(ll, ee) 사이에 blank가 반드시 하나 들어가야 하고(§3),
14.1 char/s는 어디까지나 평균이라 빨리 말하는 구간은 더 빡빡하죠.
출력 단위를 바이그램으로 바꾸면 같은 스트라이드 4에서 2.82로 회복됩니다. 이게 논문 3.6절이 바이그램 출력을 도입한 이유이고, §8에서 표 5의 숫자로 다시 확인합니다. 중국어 문자로 바꾸면 7.6까지 올라갑니다 — 초당 3.3자니까요. 논문이 "Mandarin에서는 striding을 straightforward하게 적용했다"고 한 문장이 이 여유를 말합니다.
논문이 실제로 학습시킨 구성
| 모델 | 층 구성 | 파라미터 | 출력 | 출처 |
|---|---|---|---|---|
| DS1 (2014) | 5층 · 단방향 RNN 1층 · 1D conv | — | 유니그램, 스트라이드 2 | 6.1절 |
| DS2 영어 (최종) | 11층 · 2D conv 3층 + 양방향 RNN 7층 + FC 1층 | 100 M | 바이그램, 스트라이드 3 | 6.1절 · 6.1.1절 |
| DS2 중국어 (연구용) | 9층 · 2D conv + 양방향 RNN 7층 + BatchNorm | 약 80 M | 문자 약 6,000자 | 6.2절 (표 16) |
| DS2 중국어 (배포) | 단방향 RNN 5층(2560) + 행 합성곱 τ=19 + FC 1층 | — | 문자, 16비트 추론 | 7.4절 |
마지막 줄이 중요합니다. 논문이 자랑하는 최고 정확도 모델과 실제로 서비스에 올린 모델은 다릅니다. 양방향 순환층은 발화가 끝나야 첫 글자를 낼 수 있어서 실시간에 못 씁니다 (§9·§13).
§2 입력 — 스펙트로그램 한 장 만들기
논문의 입력 정의는 짧습니다.
We use a spectrogram of power normalized audio clips as the features to the system, sox⁽ⁱ⁾t,pdenotes the power of the p'th frequency bin in the audio frame at time t. — 3.1절 우리는 파워를 정규화한 오디오 클립의 스펙트로그램을 시스템의 특징으로 쓴다. 따라서x⁽ⁱ⁾t,p는 시각 t의 오디오 프레임에서 p번째 주파수 빈이 갖는 파워를 뜻한다.
MFCC도, 필터뱅크도, 델타 특징도 아닙니다. 그냥 파워 스펙트로그램이에요. "손으로 만든 특징 추출을 걷어낸다"는 논문의 주장은 여기서부터 시작합니다. 만드는 절차는 세 줄입니다.
1. 파형을 20 ms짜리 조각으로 자른다 (10 ms씩 겹쳐 가며) ← 창(window)
2. 각 조각을 FFT 한다 ← 주파수 성분
3. 크기를 제곱한다 → 그 프레임의 파워 스펙트럼 ← x[t, :]
결과는 시간 × 주파수 행렬 한 장입니다. 이게 신경망이 보는 전부예요. 아래에서 실제로 만들어 봅시다 — 브라우저가 DFT를 직접 돌립니다.
x[t,p]입니다. 색 눈금은 고정이라 밝기 변화가 그대로 보입니다.창 길이를 10 ms로 줄여 보세요. 세로줄(가로 방향 번짐)이 선명해지는 대신 가로 줄무늬(배음 구조)가 뭉개집니다. 80 ms로 늘리면 반대가 됩니다 — 배음이 선명해지는 대신 자음이 시간축으로 번져요. 하나를 좋게 하면 다른 하나가 나빠지는 이 맞교환은 푸리에 변환의 성질이라 피할 방법이 없고, 그래서 음성 인식은 보통 20~25 ms 언저리에서 타협합니다.
그런데 "power normalized"가 정확히 무슨 연산인지는 DS2 논문에 없습니다. 전작 DS1 논문에 한 줄로 적혀 있어요.
We normalize training examples on a per utterance basis in order to make the total power of each example consistent. — Deep Speech 1 (Hannun et al., 2014) 학습 예제를 발화 단위로 정규화해, 예제마다 총 파워가 일정하도록 맞춘다.
이번엔 녹음 음량을 움직여 보세요. 정규화를 끄면 같은 말인데도 그림 전체가 통째로 밝아지고 어두워집니다 — 마이크가 다르거나 화자가 조금 멀리 있기만 해도 신경망이 보는 숫자가 몇 배씩 달라진다는 뜻이죠. 아래 "신경망이 보는 평균 파워" 칸이 0.06배에서 16배까지 움직입니다.
정규화를 켜면 음량을 0.25배로 줄이든 4배로 키우든 그림이 한 픽셀도 바뀌지 않습니다. 발화 전체의 파워를 기준으로 나눠 버리니 음량이라는 변수가 통째로 사라지거든요. 첫 층이 배우지 않아도 되는 변동을 하나 줄여 주는 셈입니다.
그리고 이 그림을 잘 봐 두세요. 세로축이 주파수라는 사실이 §7에서 다시 등장합니다 — 화자가 바뀌면 배음과 포먼트가 비율로 늘어나고 줄어드는데, 그걸 어떻게 다루느냐가 2D 합성곱의 존재 이유입니다.
§3 CTC — 정렬을 모른 채 배우는 법 (식 9)
문제는 이겁니다. 7초 발화는 스텝 350개인데 정답은 "cat" 세 글자입니다.
어느 스텝이 c인지 아무도 안 알려줍니다.
기존 파이프라인은 GMM-HMM으로 프레임마다 정답 라벨을 붙여 놓고(강제 정렬) 학습했어요
(부록에 개념만 따로 정리해 뒀습니다).
DS2는 그 단계를 통째로 없앱니다.
CTC(Connectionist Temporal Classification)의 아이디어는 "정렬을 고르지 말고 전부 더하자"입니다.
먼저 상황을 정확히 하죠. 신경망은 프레임마다 글자 하나씩을 찍습니다.
7프레임이면 글자가 7개 나와요. 정답은 세 글자인데 말이죠.
그래서 7글자를 3글자로 줄이는 규칙이 필요합니다. 기호가 하나 더 있는데,
"여기선 아무 글자도 안 냈다"는 뜻의 blank(_)입니다.
줄이는 방법은 순서가 정해진 두 단계입니다. 순서가 핵심이에요.
① 연달아 같은 기호가 반복되면 하나로 합친다 (blank도 포함해서)
② 그러고 나서 남은 blank를 전부 지운다
| 신경망이 낸 7글자 | ① 연속 합치기 | ② blank 지우기 | 결과 |
|---|---|---|---|
| c c a a a t t | c a t | c a t | cat |
| _ c _ a a _ t | _ c _ a _ t | c a t | cat |
| c c _ c a t t | c _ c a t | c c a t | ccat |
위의 두 줄은 전혀 다르게 생겼는데 똑같이 cat이 됩니다. 이게 핵심입니다 —
c를 몇 프레임 동안 끌든, 글자 사이에 blank를 몇 개 끼우든 결과는 같아요.
그래서 "어느 프레임이 c인지" 정할 필요가 없습니다.
셋째 줄은 blank가 하는 일을 보여 줍니다. c _ c에서 가운데 blank가
두 c가 합쳐지는 것을 막는 벽 역할을 해서 cc가 살아남았습니다.
이 벽이 없으면 같은 글자를 연달아 두 번 낼 방법이 아예 없습니다 — hello의 ll처럼요.
h e l l o → ① 합치기 → h e l o ✗ l이 하나로 합쳐져 버린다
h e l _ l o → ① 합치기 → h e l _ l o → ② blank 제거 → h e l l o ✓
_ 가 blank입니다.
그러면 길이 T짜리 출력 중 "cat"으로 축약되는 것들이 여러 개 생깁니다.
그 집합이 Align(x,y)이고, 손실은 그 전부의 확률을 더한 값의 음의 로그입니다 (식 9).
L(x, y; θ) = − log Σ Π p_ctc(ℓ_t | x; θ)
ℓ∈Align(x,y) t
경우의 수가 지수적으로 많지만 전부 나열할 필요는 없습니다.
전진 변수 α를 왼쪽에서 오른쪽으로 채워 나가면 O(T·S)에 합이 구해져요.
직접 채워 봅시다.
ℓ' = [_, c, _, a, _, t, _], 열은 시간입니다. 각 칸이 α[t][s] — "t 시점까지 와서 s 위치에 있을 확률의 총합"입니다.
T를 왼쪽 끝까지 내려 보세요. T가 라벨 길이보다 짧아지면 정렬 개수가 0이 되고
확률도 0이 됩니다 — 한 글자당 최소 한 스텝이 필요하다는 CTC의 하드 제약이 여기서 나옵니다.
§1의 모양 추적기에서 빨간 칸이 떴던 게 바로 이 조건이에요.
반대로 정답을 aa처럼 같은 글자가 붙은 것으로 바꿔 보세요.
필요한 최소 T가 2가 아니라 3이 됩니다. a a는 규칙 ①에 걸려 a 하나로 합쳐지니
가운데에 blank를 반드시 하나 끼워야 하거든요. 대각선 건너뛰기(α[t−1][s−2])가 이 경우에만 막히는 이유입니다.
그리고 여기서 SortaGrad가 나온다
식 9의 안쪽은 T개의 확률을 곱한 것입니다. 확률은 전부 1보다 작죠.
그러니 같은 품질의 모델이라도 발화가 길면 손실이 큽니다. 내용이 어려워서가 아니라 길어서요.
In equation 9, the inner term is a product over time-steps of the sequence, which shrinks with the length of the sequence sincep_ctc(ℓ_t|x;θ) < 1. This motivates a curriculum learning strategy we title SortaGrad. — 3.3절 식 9의 안쪽 항은 시퀀스의 시간 스텝에 대한 곱이고,p_ctc(ℓ_t|x;θ) < 1이므로 시퀀스가 길수록 그 값은 작아진다. 여기서 우리가 SortaGrad라 이름 붙인 커리큘럼 학습 전략의 동기가 나온다.
"긴 발화 = 큰 손실 = 큰 기울기"인데 학습률은 길이와 무관하게 고정입니다. 학습 초반에 8초짜리 발화가 미니배치에 들어오면 RNN 내부 상태가 폭주하기 쉽죠. 이 관찰 하나에서 §5의 SortaGrad가 나옵니다.
§4 깊이와 BatchNorm — 순환망 어디에 넣는가 (식 7 vs 식 8)
논문 3절의 첫 문장이 문제의식 전부입니다.
A simple multi-layer model with a single recurrent layer cannot exploit thousands of hours of labelled speech. In order to learn from datasets this large, we increase the model capacity via depth. — 3절 순환층이 하나뿐인 단순한 다층 모델로는 수천 시간의 라벨링된 음성을 활용할 수 없다. 이만큼 큰 데이터셋에서 배우기 위해 우리는 깊이로 모델 용량을 늘린다.
여기서 저자들이 한 실험 설계가 깔끔합니다. 파라미터 총수를 3,800만 개로 고정해 놓고 층수만 늘립니다 — 층이 깊어지면 층당 은닉 유닛은 2400 → 1880 → 1510 → 1280으로 줄어들죠. 그래야 "깊어서 좋아진 것"과 "커서 좋아진 것"이 섞이지 않습니다.
그런데 그냥 쌓으면 잘 학습이 안 됩니다. 그래서 BatchNorm(식 6)을 넣는데, 순환층에 넣는 방법이 두 가지이고 이 선택이 결과를 가릅니다.
식 7 (프레임 단위) →h_t = f( B( W·h_t^(l−1) + U·→h_(t−1) ) ) ← 순환항까지 정규화
식 8 (시퀀스 단위) →h_t = f( B( W·h_t^(l−1) ) + U·→h_(t−1) ) ← 입력항만 정규화 ✔ 채택
괄호 위치 하나 차이인데 논문은 식 7을 "개선이 없었다(does not lead to improvements)"고 버리고 식 8을 씁니다. 차이는 통계를 어느 범위에서 재느냐입니다.
식 7은 한 시점의 배치만 볼 수 있습니다. 시간축으로 평균을 낼 수가 없어요 —
h_t를 계산하려면 h_{t−1}이 이미 있어야 하니 시간 방향은 순차적이라
"모든 시점을 모아 평균"이 성립하지 않습니다.
그래서 m = 배치 크기뿐이고, 슬라이더를 오른쪽으로 밀어 보면
긴 발화만 남는 뒤쪽 시점에서는 m이 더 줄어듭니다.
식 8은 W·h^{l−1}만 정규화합니다. 이 항은 시간 의존성이 없어서
배치 × 전체 시간에 걸쳐 한꺼번에 계산할 수 있고, m이 수십 배로 커집니다.
순환항 U·→h_{t−1}은 정규화 밖에 두어 반복 곱해지는 경로를 건드리지 않고요.
표 1 — 깊을수록 격차가 벌어진다
가장 얕은 순환층 1개짜리 모델에서는 BatchNorm이 오히려 해롭습니다 (개발셋 13.55 → 14.40). 논문도 이걸 숨기지 않고 "harms generalization error for the shallowest network"라고 적어요. 그런데 순환층 7개에서는 10.83 → 9.52, 상대 12% 개선입니다. BatchNorm은 깊이를 쓸 수 있게 해 주는 도구이지 그 자체가 만능 개선책이 아니라는 뜻입니다.
§5 SortaGrad — 길이가 곧 난이도다
규칙은 어이없을 만큼 간단합니다.
In the first training epoch, we iterate through the training set in increasing order of the length of the longest utterance in the minibatch. After the first epoch, training reverts back to a random order over minibatches. — 3.3절 첫 번째 학습 에포크에서는 미니배치 안의 가장 긴 발화 길이를 기준으로 오름차순으로 학습셋을 훑는다. 첫 에포크가 끝나면 다시 미니배치를 무작위 순서로 돌린다.
첫 에포크만 짧은 것부터, 그 뒤로는 그냥 랜덤. 커리큘럼 학습이라 부르기에도 민망할 만큼 얇은 장치인데 개발셋 WER을 11.96 → 10.83으로 내립니다.
랜덤 순서를 눌러 보세요. 1번 미니배치부터 12초짜리 발화가 튀어나옵니다. 가중치가 아직 난수인 상태에서 말이죠. §3에서 봤듯 손실은 발화가 길수록 커지고, 학습률은 길이와 무관하게 고정입니다 — 첫 스텝부터 큰 기울기를 맞는 겁니다. 논문은 실제로 이 상황에서 기울기 노름이 400을 넘으면 400으로 재스케일하는 클리핑을 겁니다 (6절).
| 순서 | 학습셋 WER | 개발셋 WER | ||
|---|---|---|---|---|
| BatchNorm 없음 | BatchNorm | BatchNorm 없음 | BatchNorm | |
| 정렬 안 함 | 10.71 | 8.04 | 11.96 | 9.78 |
| SortaGrad | 8.76 | 7.68 | 10.83 | 9.52 |
| 상대 개선 | 18.2% | 4.5% | 9.4% | 2.7% |
표를 세로로 읽으면 재미있는 게 보입니다. BatchNorm이 없을 때 SortaGrad의 효과가 훨씬 큽니다 (18.2% vs 4.5%). 논문의 표현대로 "두 기법이 어느 정도 서로를 대체(substitute for one another)"하는 거죠. 둘 다 깊은 순환망의 초기 수치 안정성이라는 같은 문제를 다른 쪽에서 건드립니다.
§6 simple RNN vs GRU — 규모가 커지면 뒤집힌다
DS2의 기본 순환층은 바닐라 RNN입니다. 식 3 그대로, 게이트가 없어요.
비선형은 σ(x) = min{max{x,0}, 20} — 클리핑된 ReLU입니다.
순환망에 ReLU를 쓰면 발산하기 쉬운데, 20에서 잘라 버려 그걸 막습니다.
논문은 GRU도 시험합니다 (식 10). 그런데 표준 GRU에서 한 군데를 바꿔요.
표준 GRU: h̃_t = f( W_h x_t + U_h ( r_t ⊙ h_(t−1) ) ) ← 리셋을 곱한 다음 U_h
DS2 GRU: h̃_t = f( W_h x_t + r_t ⊙ ( U_h h_(t−1) ) ) ← U_h를 먼저, 그다음 리셋
We differ slightly from the standard GRU in that we multiply the hidden stateh_{t−1}byU_hprior to scaling by the reset gate. This allows for all operations onh_{t−1}to be computed in a single matrix multiplication. — 3.4절 우리는 표준 GRU와 조금 다르게, 리셋 게이트로 스케일링하기 전에 은닉 상태h_(t−1)에U_h를 곱한다. 이렇게 하면h_(t−1)에 가해지는 모든 연산을 행렬 곱 하나로 계산할 수 있다.
h_(t−1)에 가해지는 연산만 봅니다. 은닉 차원 d는 조절 가능.연산량(FLOP)은 완전히 같습니다. 바뀌는 건 세 개의 작은 행렬 곱이 하나의 큰 행렬 곱이 된다는 것뿐이에요. 그런데 GPU에서는 이게 큰 차이입니다 — 행렬이 클수록 연산 강도(arithmetic intensity)가 높아져 메모리 대역폭 병목에서 벗어나거든요. 수식을 하드웨어에 맞춰 다시 쓴 것이고, 이런 판단이 논문 전체에 깔려 있습니다 (§11, §13).
그래서 뭐가 이겼나
표 3에서는 모든 깊이에서 GRU가 이깁니다 (7층 기준 9.78 vs 7.79). 그런데 표 11에서 모델을 1억 파라미터까지 키우면 바닐라 RNN이 7.73으로 GRU 7.78을 앞섭니다. 게다가 합성곱층을 2개 더 달고도 학습이 더 빠릅니다.
In fact, for the 100 million parameter networks the simple RNN performs better than the GRU network and is faster to train despite the 2 extra layers of convolution. … most of the remaining experiments use the simple RNN layers rather than the GRUs. — 3.4절 · 6.1.1절 실제로 파라미터 1억 개 규모에서는 단순 RNN이 GRU 망보다 성능이 좋고, 합성곱층이 2개 더 많은데도 학습이 더 빠르다. … 이후 실험 대부분은 GRU 대신 단순 RNN 층을 쓴다.
게이트의 이득이 데이터와 모델이 커지면 사라진다는 관찰입니다. "어떤 순환 유닛이 제일 좋은가"라는 질문에는 "어느 규모에서?"가 늘 따라붙어야 한다는 것을, 이 논문은 표 두 개로 보여 줍니다.
§7 주파수 합성곱 — 화자가 바뀌면 스펙트럼이 늘어나고 줄어든다
DS1의 첫 층은 시간축 1D 합성곱이었습니다. 필터가 모든 주파수 빈을 한꺼번에 덮고 시간 방향으로만 미끄러지죠. DS2는 여기에 주파수 방향 이동을 추가합니다 — 필터가 41×11(주파수×시간)처럼 작아져서 스펙트로그램 위를 2차원으로 훑습니다.
Convolution in frequency attempts to model spectral variance due to speaker variability more concisely than what is possible with large fully connected networks. — 3.5절 주파수 방향 합성곱은 화자 차이로 생기는 스펙트럼 변동을 큰 완전연결망보다 간결하게 다루려는 것이다.
논문의 근거는 이 한 줄이고, "이미지처럼 보여서"가 아닙니다. 첫 층에 둬야 하는 이유도 함께 적습니다 — 완전연결층·순환층을 지나면 주파수 순서가 사라지기 때문입니다.
여기서 이미지와 다른 점을 짚어야 합니다. 이미지에서 고양이는 위에 있든 아래에 있든 고양이지만, 스펙트럼을 통째로 위로 옮기면 다른 소리입니다. 게다가 목소리가 높은 사람의 스펙트럼은 평행이동이 아니라 비율로 늘어난 모양입니다 — 200 Hz 배음(200·400·600)과 250 Hz 배음(250·500·750)은 간격 자체가 다르죠. 그러니 기대할 수 있는 건 완전한 불변성이 아니라 작은 변화에 대한 국소적 강건성입니다.
- 2D 층 하나는 불변이 아닙니다. 위치 그대로 비교하면 3%만 달라져도 0.35 근처로 떨어집니다. 특징이 사라지는 게 아니라 근처 행으로 옮겨 갈 뿐이에요.
- 그래서 몇 칸 여유를 주면 버팁니다. ±3칸 여유에서 ±3%는 0.93, ±20%는 0.8 안팎으로 서서히 무너집니다. 이 여유는 뒤따르는 층(과 주파수 스트라이드)이 맡습니다.
- 1D 층에는 옮겨 갈 축이 없습니다. 주파수 전체를 한 번에 덮으니 여유를 줄 방법이 없죠.
한 가지 구분할 것 — 합성곱에 스트라이드를 줘 순환층의 시간 스텝을 줄이는 이득(§8)은 1D 합성곱도 똑같이 얻습니다. 2D를 고른 근거는 아래 표 4입니다.
표 4 — 잡음에서 특히 벌어진다
깨끗한 데이터에서는 9.52 → 8.61, 겨우 9.6% 개선입니다. 안 써도 그만인 수준이죠. 그런데 잡음 데이터에서는 19.36 → 14.74, 23.9%입니다. 논문이 굳이 "Noisy Dev" 열을 따로 만든 이유예요 — 깨끗한 벤치마크만 봤다면 2D 합성곱은 채택되지 않았을 것입니다.
1D 쪽 표도 같이 보세요. 1D 합성곱은 층을 1개에서 3개로 늘려도 잡음 개발셋이 19.36 → 20.22로 오히려 나빠집니다. "층을 쌓으면 좋아진다"가 아니라 맞는 종류의 층을 쌓아야 좋아진다는 것을 보여주는 대조군입니다.
§8 스트라이드와 바이그램 — 영어는 초당 14.1글자다
합성곱층에 스트라이드를 주면 순환층이 처리할 시간 스텝이 줄어듭니다. 스텝이 절반이면 학습도 추론도 대략 절반이죠. 공짜로 보이는 최적화인데, 영어에서는 곧바로 벽에 부딪힙니다.
in English, striding can reduce accuracy simply because the output of our network requires at least one time-step per output character, and the number of characters in English speech per time-step is high enough to cause problems when striding. — 3.6절 영어에서는 스트라이드가 그것만으로 정확도를 떨어뜨릴 수 있다. 우리 신경망의 출력은 출력 글자 하나당 최소 한 개의 시간 스텝을 필요로 하는데, 영어 음성은 시간 스텝당 글자 수가 충분히 많아서 스트라이드를 주면 문제가 되기 때문이다.
§3에서 확인한 CTC의 하드 제약입니다. 논문 각주 2에 실제 숫자가 있습니다.
| 언어 | 초당 글자 수 | 글자당 엔트로피 | 논문이 적은 초당 비트 | = 앞 두 열의 곱 |
|---|---|---|---|---|
| 영어 | 14.1 char/s | 4.9 bits/char | 약 58 bit/s | 69.1 |
| 중국어 | 3.3 char/s | 12.6 bits/char | 약 41 bit/s | 41.6 |
핵심은 마지막 열이 아니라 두 번째 열입니다. 중국어는 초당 3.3자, 영어는 초당 14.1자. 10 ms 프레임에 스트라이드 4를 걸면 초당 25스텝인데, 영어는 글자 사이에 blank를 넣을 자리가 거의 안 남습니다.
해법 — 알파벳을 늘린다
DS2는 출력 단위를 글자에서 겹치지 않는 바이그램으로 바꿉니다. 단어 안에서 두 글자씩 묶고, 홀수면 마지막 글자는 유니그램으로, 공백도 유니그램으로 둡니다. 같은 단어는 항상 같은 토큰열이 되는 단순 동형사상이에요.
the cat sat은 [th, e, ␣, ca, t, ␣, sa, t] 8토큰이 됩니다.
글자로는 11개니까 27% 감소죠. 절반이 아닌 이유는 공백이 유니그램으로 남고
홀수 길이 단어의 꼬리가 유니그램이 되기 때문입니다.
표 5 — 바이그램이 스트라이드를 사 준다
언어 모델을 켠 상태에서 스트라이드 4 줄을 보세요. 유니그램은 11.92로 무너지는데 바이그램은 9.93 — 스트라이드 2일 때의 9.52와 크게 다르지 않습니다. 순환층이 처리할 스텝 수를 절반으로 줄이면서 정확도는 거의 유지한 거죠. 최종 영어 모델이 "스트라이드 3 + 바이그램"인 이유입니다.
§9 단방향화와 디코딩 — 두 절은 별도 자료로
3.7절과 3.8절은 이 시리즈에 이미 각각 한 편씩 있습니다. 여기서는 전체 그림에서의 위치만 잡고 넘어갑니다.
3.7절 · 행 합성곱 — 양방향성을 상수 지연으로 바꾸기
r[t,i] = Σ_j W[i,j] · h[t+j−1, i] (식 11)
양방향 순환층은 역방향 패스가 마지막 프레임에서 시작하므로 발화가 끝나야 첫 글자가 나옵니다. DS2 배포 모델은 순환층을 전부 단방향으로 바꾸고, 맨 위에 미래 τ 스텝만 훔쳐보는 층 하나를 얹습니다. 지연이 발화 길이에 비례에서 τ·스텝의 상수로 바뀌고, 배포 모델은 τ=19 · 40 ms = 760 ms입니다. 그런데 중국어에서는 이 근사가 최고의 양방향 모델보다 CER이 낮았습니다.
→ How Row Convolution Works에서
채널 비혼합, τ와 지연 예산, 파라미터가 d배 싼 이유까지 직접 계산해 볼 수 있습니다.
그래서 Baidu는 실제로 어느 쪽을 썼나 — 양방향? 단방향?
둘 다 썼습니다. 정확히는 목적에 따라 나눠 썼다고 보는 게 맞습니다.
| 용도 | 순환층 | 왜 |
|---|---|---|
| 연구 · 벤치마크 | 양방향 | 오프라인 평가는 발화 전체가 이미 주어진다 — 미래 정보를 전부 쓰면 WER·CER이 가장 좋다 |
| 실서비스 (배포) | 단방향 + 행 합성곱 | 말하는 도중에 결과가 나와야 한다 — 지연을 발화 길이 비례에서 상수로 바꾼다 |
논문에 실린 영어·중국어 벤치마크의 주요 수치는 대부분 양방향 모델에서 나온 것입니다 (§12). 성능의 한계를 보여주는 것이 목적이니 양방향이 자연스러운 선택이죠. 반면 실제 사용자 요청을 받는 중국어 음성 인식 서버에 올라간 것은 단방향 RNN 5층(2560) + 행 합성곱 τ=19였습니다 (7.4절 · §13). 이유는 셋입니다.
- 지연 시간 — 양방향은 사용자가 말을 다 끝내야 역방향 패스를 시작할 수 있습니다. 음성 검색이나 받아쓰기처럼 말하는 중에 화면이 채워져야 하는 서비스에서는 치명적이에요. 단방향은 오디오가 들어오는 대로 처리하니 말이 끝나는 순간 거의 바로 결과가 나옵니다.
- 정확도 손실이 작음 — 행 합성곱이 짧은 미래 문맥을 되돌려 주기 때문입니다. 단방향화 · 16비트 · 빔 가지치기를 전부 합쳐도 배포 모델의 손해는 CER 상대 5%였고 (§14), 단방향화만 놓고 보면 중국어에서는 위에서 본 대로 오히려 최고의 양방향 모델을 앞섰습니다. 약간의 정확도를 내주고 지연을 크게 줄이는 거래였고, 그마저 거의 공짜였던 셈입니다.
- 서빙 효율 — 스트리밍 처리는 여러 사용자의 요청을 묶어 GPU에서 한 번에 돌리는 Batch Dispatch (§13)와 잘 맞습니다. 동시 사용자가 많아도 지연을 낮게 유지할 수 있었죠.
3.8절 · 언어 모델과 빔 서치
Q(y) = log p_ctc(y|x) + α·log p_lm(y) + β·word_count(y) (식 12)
신경망은 학습 데이터만으로도 꽤 훌륭한 암묵적 언어 모델을 갖습니다.
논문이 드는 예가 재밌어요 — "he expects the Japanese agent to sell it for two hundred seventy five thousand dollars"처럼
동음이의를 문맥으로 구분해 냅니다. 그래도 외부 텍스트로 학습한 n-gram을 붙이면 더 좋아집니다.
| 언어 | 언어 모델 | 학습 텍스트 | n-gram 개수 | 빔 폭 |
|---|---|---|---|---|
| 영어 | Kneser-Ney 5-gram (KenLM) | Common Crawl 2.5억 줄 · 어휘 40만 단어 | 약 8.5억 | 500 |
| 중국어 | 문자 단위 KN 5-gram | 내부 코퍼스 80억 줄 | 약 20억 | 200 |
영어는 48.2% → 36.2%, 중국어는 27.2% → 23.0%. 순환층이 깊어질수록 신경망이 스스로 더 강한 언어 모델을 갖게 되어 외부 LM이 보탤 몫이 줄어듭니다. 그리고 두 언어를 비교하면 영어 쪽 개선폭이 훨씬 큰데, 논문은 "한자 하나가 담는 정보량이 영어 글자보다 커서"라고 설명합니다 — 영어 모델은 철자를 틀릴 여지가 그만큼 많다는 뜻이죠.
→ How CTC Beam Search Works에서 접두사 빔 서치를 프레임 단위로 실행하고 α·β를 직접 돌려 볼 수 있습니다.
§10 데이터 — 11,940시간과 거듭제곱 법칙
논문의 두 번째 기둥입니다. 영어 11,940시간 · 800만 발화, 중국어 9,400시간 · 1,100만 발화. 2015년 기준으로 공개 데이터셋의 자릿수가 달랐습니다.
합계의 72%가 Baidu 내부 데이터입니다. 그중 3,600시간은 "수 분에서 한 시간이 넘는" 긴 원본 오디오에 부정확한 자막이 붙은 것이었어요. 그대로는 RNN에 펼칠 수가 없습니다. 그래서 만든 게 정렬 → 분할 → 필터링 파이프라인입니다.
마지막 줄이 이 파이프라인의 성적표입니다. 전사 오류율 17%짜리 원본에서 5%짜리 학습셋을 뽑아내면서 예제의 절반 이상을 살렸습니다. 정답 라벨을 사람이 새로 만든 게 아니라 기존 모델로 라벨의 품질을 추정해 걸러낸 것이고, 여기에 쓰인 특징이 전부 CTC 비용에서 파생된 값이라는 점이 깔끔합니다.
여기에 잡음 증강이 더해집니다. 무작위로 고른 40%의 발화에만 잡음을 섞어요. "너무 많이 섞으면 최적화가 어려워지고, 너무 적게 섞으면 강건성이 떨어진다"는 균형점입니다.
표 10 — 데이터를 10배 늘리면 오류가 얼마나 줄어드는가
두 선이 거의 평행합니다. 논문의 표현대로 "두 경우 모두 데이터의 이득을 똑같이 본다"는 뜻이고, 잡음 데이터가 특별히 데이터를 더 요구하지도 덜 요구하지도 않는다는 얘기죠. 목표 WER 슬라이더를 낮춰 보면 필요 시간이 무섭게 튀어 오릅니다 — 거듭제곱 법칙의 다른 얼굴입니다. 절반의 오류율을 원하면 데이터를 10배가 아니라 수십 배 늘려야 합니다.
§11 계산 — 동기 SGD·all-reduce·GPU CTC
세 번째 기둥입니다. 그리고 이 논문을 다른 아키텍처 논문과 구별 짓는 부분이기도 하고요. 초록의 첫 자랑이 정확도가 아니라 "7배 빨라졌다"입니다.
Key to our approach is our application of HPC techniques, resulting in a 7x speedup over our previous system. Because of this efficiency, experiments that previously took weeks now run in days. — 초록 우리 접근의 핵심은 HPC 기법의 적용이고, 그 결과 이전 시스템보다 7배 빨라졌다. 이 효율 덕분에 예전에는 몇 주가 걸리던 실험이 이제 며칠이면 끝난다.
속도 자체가 목적이 아닙니다. 실험 회전율이 목적이에요. §4~§8에서 본 표들 — 깊이 4종, 합성곱 6종, 스트라이드 6종, 모델 크기 6종 — 이걸 전부 돌리려면 학습 한 번이 3주가 아니라 3일이어야 합니다.
동기 SGD를 고른 이유
2015년의 주류는 파라미터 서버 + 비동기 SGD였습니다. DS2는 정반대로 갑니다.
We find synchronous SGD useful because it is reproducible and deterministic. We have found that the appearance of non-determinism in our system often signals a serious bug. — 4.1절 우리는 동기 SGD가 재현 가능하고 결정론적이라는 점에서 유용하다고 본다. 우리 시스템에서 비결정성이 나타나면 대개 심각한 버그의 신호였다.
정확도가 아니라 디버깅 가능성을 근거로 분산 전략을 고릅니다. GPU당 미니배치 64를 고정하고 GPU 8~16장, 알고리즘 미니배치 512~1024로 돌립니다.
GPU가 8장 이하일 때 20배까지 벌어지다가 128장에서 3.8배로 좁혀집니다. 한 노드 안에서는 GPUDirect로 CPU 메모리를 거치지 않고 GPU끼리 직접 주고받을 수 있어서인데, 노드를 넘어가면 InfiniBand 대역폭이 병목이라 직접 구현의 여지가 줄어듭니다. 저자들이 한 노드에 GPU 8장을 같은 PCI 루트 컴플렉스에 몰아넣은 이유이기도 합니다 (부록 A.1).
CTC를 GPU로 옮기기
| 언어 | CPU CTC (초/에포크) | GPU CTC (초/에포크) | 배속 | 에포크당 절약 |
|---|---|---|---|---|
| 영어 | 5,888.12 | 203.56 | 28.9× | 약 95분 |
| 중국어 | 1,688.01 | 135.05 | 12.5× | 약 25분 |
원래는 softmax 출력을 CPU로 복사해 OpenMP로 CTC를 계산하고 기울기를 다시 GPU로 올렸습니다.
문제가 두 개였어요 — 중국어처럼 문자 집합이 크면 그 행렬이 수억 개의 원소고,
그 복사에 쓰는 인터커넥트 대역폭은 데이터 병렬용 기울기 전송에서 빼앗아 온 것입니다.
부록 A.2의 구현 요령이 인상적인데, 유효하지 않은 칸까지 전부 계산해 버리고
α와 β를 더할 때 −∞가 쓰레기 값을 자동으로 지우게 만듭니다.
분기를 없애 GPU에 맞춘 거죠.
메모리는 가중치가 아니라 활성값이 먹는다
기본값(1760 유닛 · 9층 · 배치 64 · 7초)에서 활성값이 1.4 GB 근처로 나옵니다 — 논문이 적은 1.5 GB와 거의 같습니다. 가중치는 그 10분의 1도 안 되고요. 역전파를 위해 모든 층·모든 시간 스텝의 활성값을 들고 있어야 한다는 순환망의 성질이 그대로 드러납니다.
이제 은닉 2560 · 배치 128 · 발화 20초로 밀어 보세요. 12 GB를 넘깁니다.
논문이 말하는 상황이 정확히 이겁니다 — 발화 길이 분포에 이상치가 섞여 있으면
예측 불가능하게 메모리가 터지죠. 대처가 실용적입니다.
죽이지 않고 cudaMallocHost로 GPU가 PCIe 너머로 직접 읽는
CPU 메모리를 할당해 느리게라도 진행시킵니다.
"긴 발화 하나 때문에 며칠짜리 학습이 죽는 것"을 막는 장치예요.
§12 결과 — 사람과 겨루기
DS2의 평가 방식에서 눈여겨볼 점은 사람을 벤치마크에 올렸다는 것입니다. Amazon Mechanical Turk 작업자 두 명에게 같은 5초짜리 클립을 받아 적게 하고 둘 중 나은 쪽을 씁니다. 원하는 만큼 다시 들을 수 있고, 평균 27초를 씁니다. 그 결과를 기존 정답과 비교해 WER을 냅니다.
읽은 음성에서는 4개 중 3개에서 사람을 이깁니다. WSJ eval'93은 4.98 vs 8.08로 압도적이죠. 논문도 여기서 선을 긋습니다 — "깨끗한 읽은 음성에서 범용 시스템이 더 나아질 여지는 별로 없어 보인다".
그런데 버튼을 "잡음"으로 바꿔 보세요. CHiME eval real에서 DS2는 21.79, 사람은 11.84입니다. DS1의 67.94에서 엄청나게 개선됐지만 여전히 사람의 두 배죠. 더 흥미로운 건 진짜 잡음(real)과 합성 잡음(sim)의 차이입니다 — 합성 잡음에서는 DS2가 45.05로 더 나쁜데, 사람도 31.33으로 나쁩니다. 합성 잡음이 진짜 잡음보다 어려운, 다소 부자연스러운 상황이에요.
"억양"을 보면 인도 억양에서만 사람과 비슷하고(22.44 vs 22.15) 나머지는 확연히 뒤집니다. 논문은 이 결과를 모델의 한계가 아니라 학습 데이터의 폭과 질에 대한 측정으로 읽습니다.
중국어 — 짧은 발화에서는 사람보다 낫다
| 비교 | 사람 | DS2 | 표본 |
|---|---|---|---|
| 5명이 함께 전사 | 4.0% | 3.7% | 100 발화 |
| 1명이 전사 | 9.7% | 5.7% | 250 발화 |
5명이 머리를 맞대야 겨우 비슷해집니다. 다만 표본이 100~250개로 작고, "짧은 음성 검색 질의"라는 특정 조건이라는 점을 논문도 명시합니다. 중국어 아키텍처 실험(표 16)도 영어와 같은 흐름을 보입니다 — 5층 1 RNN 7.13에서 9층 7 RNN + BatchNorm + 2D conv 5.81로, 상대 48% 개선. 언어가 완전히 달라도 같은 처방이 통한다는 게 논문의 핵심 주장 중 하나입니다.
§13 배포 — Batch Dispatch와 67ms
7절은 톤이 확 바뀝니다. 지금까지 쌓아 온 것들이 전부 배포를 막는 이유로 다시 나열돼요.
| 정확도를 위해 넣은 것 | 배포에서 생기는 문제 | 해법 |
|---|---|---|
| 양방향 순환층 | 발화 전체가 있어야 첫 글자가 나옴 | 단방향 + 행 합성곱 (§9) |
| 넓은 빔 서치 (폭 500) | 중국어는 후보 문자가 6,000개 — 40 ms마다 100만 번 조회 | 누적 확률 p=0.99까지만, 최대 40자 |
| 발화 전체 파워 정규화 | 발화가 끝나야 정규화 가능 | 학습셋 통계 기반 적응 정규화 |
| BatchNorm | 배포에는 미니배치가 없음 | 학습 중 수집한 이동 평균 (§4) |
| 큰 행렬곱 | 요청 하나씩 처리하면 대역폭 병목 | Batch Dispatch + 16비트 커널 |
이 표가 논문 7절의 전부라고 해도 됩니다. 그중 Batch Dispatch가 가장 흥미롭습니다.
왜 요청을 하나씩 처리하면 안 되는가
요청 하나를 처리할 때도 네트워크의 모든 가중치를 메모리에서 읽어야 합니다. 읽어온 가중치로 하는 일이 행렬-벡터 곱 하나뿐이면 연산 강도가 바닥이고, GPU는 계산이 아니라 메모리 대역폭에서 놉니다. 요청 4개를 묶으면 같은 가중치 읽기로 4배의 일을 하죠.
그래서 스케줄러를 하나 만듭니다. 그런데 배치를 키우려고 기다리면 지연이 늘어납니다. 논문의 선택은 eager batching — 이전 배치가 끝나는 순간, 그때까지 모인 것만 들고 바로 출발합니다. 배치 크기를 최대화하려 들지 않아요.
b에 대해 25 + 2b ms)과 스트림당 요청 간격은
논문이 보고한 동작점(10 스트림에서 중앙값 44 ms · 98백분위 70 ms)에 맞춰 고른 합성 값입니다.
논문은 이 커널 시간 모델을 공개하지 않았습니다. 재현하려는 것은 수치가 아니라 경향입니다.
스트림 수를 늘려 보세요. 부하가 늘어도 지연이 폭발하지 않습니다. 부하가 늘면 자연히 배치가 커지고, 배치가 커지면 처리 효율이 올라가 대기열이 소화되거든요. 스케줄러가 부하에 따라 알아서 효율-지연 지점을 옮기는 겁니다. "배치 8이 찰 때까지 기다리기"를 켜면 반대 현상이 보입니다 — 부하가 낮을 때 배치를 채우느라 기다리며 지연이 치솟습니다.
This scheduling algorithm has proved to be the best at reducing end-user latency, despite the fact that it is less efficient computationally, since it does not attempt to maximize batch size. — 7.1절 이 스케줄링 알고리즘은 배치 크기를 최대화하려 들지 않아 계산 효율은 떨어지지만, 최종 사용자 지연을 줄이는 데는 가장 좋은 것으로 드러났다.
최종 성적
| 항목 | 연구용 시스템 | 배포 시스템 |
|---|---|---|
| 중국어 CER (2,000 발화) | 5.81 | 6.10 |
| 순환층 | 양방향 7층 | 단방향 5층 (2560) + 행 합성곱 τ=19 |
| 수치 정밀도 | 32비트 | 16비트 |
| 빔 서치 | 전체 문자 탐색 | 누적 p=0.99 · 최대 40자 → 150배 |
| 지연 | 발화 길이에 비례 | 중앙값 44 ms · 98백분위 67 ms |
정확도 손실은 5% 상대뿐입니다. 이 표가 논문의 마지막 메시지예요 — "연구실 최고 기록"과 "서비스에 올릴 수 있는 것" 사이의 거리를 5%로 좁혔다.
§14 정리 — 세 기둥
논문 2절의 한 문장이 전체 구조를 요약합니다.
Since Deep Speech 2 is an end-to-end deep learning system, we can achieve performance gains by focusing on three crucial components: the model architecture, large labeled training datasets, and computational scale. — 2절 Deep Speech 2는 끝단(end-to-end) 딥러닝 시스템이므로, 세 가지 핵심 요소에 집중해 성능 향상을 얻을 수 있다 — 모델 아키텍처, 대규모 라벨링 학습 데이터셋, 그리고 계산 규모.
| 기둥 | 구체적으로 무엇 | 이 문서의 절 | 대표 숫자 |
|---|---|---|---|
| 아키텍처 | 깊이 + 시퀀스 단위 BatchNorm + SortaGrad + 2D 합성곱 + 바이그램 + 행 합성곱 | §4~§9 | WER 40%↓ |
| 데이터 | 11,940 h · 정렬-분할-필터링 파이프라인 · 40% 잡음 증강 | §10 | 10배당 약 40%↓ |
| 계산 | 동기 SGD · 직접 만든 all-reduce · GPU CTC · 메모리 할당자 | §11 | 7배 · 3~5일 |
| (+ 배포) | 단방향화 · 16비트 · Batch Dispatch · 빔 가지치기 | §13 | 67 ms · CER +5% |
10년 뒤에 다시 읽으면
- 살아남은 것 — 끝단 학습, CTC, 데이터 스케일 법칙, 합성 데이터 증강, 그리고 "하드웨어에 맞춰 수식을 다시 쓴다"는 태도. §6의 GEMM 합치기는 오늘날 모든 추론 엔진이 하는 일입니다.
- 대체된 것 — 양방향 RNN 스택은 Conformer·Transformer 인코더로, 행 합성곱은 청크 단위 어텐션이나 RNN-T의 right context로, n-gram 재순위는 신경망 LM 융합으로 옮겨 갔습니다.
- 더 중요해진 것 — Batch Dispatch는 지금 LLM 서빙에서 continuous batching이라는 이름으로 표준입니다. "가중치 읽기를 여러 요청이 나눠 쓴다"는 논리가 그대로고, eager 스케줄링이 배치 최대화보다 낫다는 결론도 그대로예요.
- 논문이 틀리지 않았던 예언 — "데이터와 계산이 늘면 계속 좋아질 것"이라는 마지막 문단. 이 주장을 음성으로 처음 정량화한 것이 표 10입니다.
§15 이해도 퀴즈
부록 · GMM-HMM이 뭐였나
§3에서 "기존 파이프라인은 GMM-HMM으로 프레임마다 정답 라벨을 붙여 놓고 학습했다"고 지나갔는데, CTC가 무엇을 대체했는지 알려면 이게 뭔지 알아야 해서 개념만 짧게 정리합니다. 2010년대 초까지 음성 인식의 표준이었던 구조입니다.
이름이 곧 역할 분담
| 부품 | 맡은 질문 | 하는 일 |
|---|---|---|
| HMM | "지금 발음의 어디쯤인가?" | 말소리를 상태들의 시간 전이로 봅니다. 음소 하나를 보통 시작·중간·끝 3개 상태로 쪼개고, 프레임마다 어느 상태에 있는지와 다음 상태로 넘어갈 확률을 다룹니다. 시간 정렬 담당. |
| GMM | "이 상태라면 이런 소리가 날 확률은?" | 상태가 주어졌을 때 그 프레임의 특징 벡터(보통 MFCC)가 나올 확률 p(x|상태)를 여러 개의 정규분포를 섞어 표현합니다. 관측 확률 담당. |
둘을 붙이면 이런 파이프라인이 됩니다. 부품마다 따로 만들고 따로 학습시켜야 합니다.
왜 "프레임마다 정답 라벨"이 필요했나
GMM을 학습시키려면 p(x|상태)를 배워야 하는데, 그러려면
각 프레임이 어느 상태인지를 알아야 합니다. 그런데 학습 데이터에는 문장 텍스트만 있고
프레임별 라벨은 없죠. 그래서 EM 알고리즘으로 "대충 정렬 → 모델 갱신 → 다시 정렬"을 반복합니다.
이렇게 만들어 낸 프레임별 라벨이 강제 정렬(forced alignment)입니다.
나중에는 GMM 자리를 신경망이 대신하는 DNN-HMM 하이브리드가 표준이 되는데, 그 신경망도 결국 프레임별 라벨을 정답으로 놓고 학습하므로 강제 정렬은 그대로 필요했습니다. 논문 2절이 가리키는 상황이 이것입니다.
Furthermore it has been necessary to pre-train the CTC-RNN network with a DNN cross-entropy network that is fed frame-wise alignments from a GMM-HMM system. In contrast, we train the CTC-RNN networks from scratch without the need of frame-wise alignments for pre-training. — 2절 게다가 CTC-RNN을 학습시킬 때 GMM-HMM이 만들어 준 프레임별 정렬로 사전학습하는 것이 필요했다. 이와 달리 우리는 프레임별 정렬 없이 처음부터 CTC-RNN을 학습시킨다.
그래서 CTC가 없앤 것
| 항목 | GMM-HMM 파이프라인 | DS2 |
|---|---|---|
| 입력 특징 | MFCC 등 (사람이 설계) | 파워 스펙트로그램 |
| 시간 정렬 | 강제 정렬로 미리 만들어 둠 | 식 9가 모든 정렬을 더해 없앰 |
| 기본 단위 | 음소 → 상태 3개 | 글자 (또는 바이그램·한자) |
| 발음 사전 | 필요 (언어마다 사람이 작성) | 필요 없음 |
| 학습 | 부품별로 따로 | 하나의 손실로 통째로 |
| 언어 모델 | 디코딩에 통합 | 디코딩에 통합 (§9) |
마지막 줄이 중요합니다. DS2도 언어 모델은 버리지 않았습니다. "끝단 학습"이 없앤 것은 음향 모델 쪽의 손으로 만든 부품들(발음 사전, 음소 상태, 강제 정렬)이지 n-gram 언어 모델이 아닙니다. 그래서 논문 3.9절이 "중국어로 옮길 때 발음 모델을 만들 필요가 없었다"를 장점으로 꼽을 수 있었던 거죠.