HOW AI WORKS

Deep Speech 2는
어떻게 동작하는가

2015년 Baidu의 Deep Speech 2는 음성 인식 파이프라인에서 음향 모델·발음 사전·상태 정렬을 전부 걷어내고 스펙트로그램을 넣으면 글자가 나오는 신경망 하나로 바꿔 놓은 논문입니다. 그런데 논문의 절반은 모델 이야기가 아니라 데이터를 어떻게 모았는가와 GPU를 어떻게 굴렸는가입니다. 이 셋이 저자들이 말하는 세 기둥이에요. 이 문서는 논문을 앞에서부터 따라가되, 표와 수식이 나올 때마다 브라우저에서 그 값을 직접 계산해 보면서 읽습니다.

원 논문: Deep Speech 2: End-to-End Speech Recognition in English and Mandarin (Amodei et al., Baidu Research — arXiv:1512.02595, 2015년 12월 8일 · ICML 2016) · 저자 34명, 저자 순서는 알파벳순

차례

§1~§3이 뼈대(무엇이 들어가 무엇이 나오고 어떻게 배우는가), §4~§9가 논문의 아키텍처 실험, §10~§13이 데이터·계산·결과·배포입니다. 스펙트로그램이 처음이라면 §0부터 읽으세요.

  1. 시작하기 전에 — 스펙트로그램이란
  2. 전체 그림 — 소리가 글자가 되기까지
  3. 입력 — 스펙트로그램 한 장 만들기
  4. CTC — 정렬을 모른 채 배우는 법 (식 9)
  5. 깊이와 BatchNorm — 순환망 어디에 넣는가 (식 7 vs 8)
  6. SortaGrad — 길이가 곧 난이도다
  7. simple RNN vs GRU — 규모가 커지면 뒤집힌다
  8. 주파수 합성곱 — 화자가 바뀌면 스펙트럼이 늘어나고 줄어든다
  9. 스트라이드와 바이그램 — 영어는 초당 14.1글자다
  10. 단방향화와 디코딩 — 두 절은 별도 자료로
  11. 데이터 — 11,940시간과 거듭제곱 법칙
  12. 계산 — 동기 SGD·all-reduce·GPU CTC
  13. 결과 — 사람과 겨루기
  14. 배포 — Batch Dispatch와 67ms
  15. 정리 — 세 기둥
  16. 이해도 퀴즈
  17. 부록 · GMM-HMM이 뭐였나
이 시리즈에는 DS2의 특정 부품을 깊게 파는 자료가 이미 있습니다 — 행 합성곱(3.7절), CTC 빔 서치(3.8·7.3절), 그리고 축소판을 직접 학습시켜 보는 DS2 My Voice. 이 문서는 그 부품들이 어디에 어떻게 끼워지는지, 그리고 논문의 나머지 절반인 데이터·계산·배포를 다룹니다.

§0 시작하기 전에 — 스펙트로그램이란

이 문서는 처음부터 끝까지 스펙트로그램이라는 단어를 씁니다. DS2가 먹는 입력이 이것이고, 음성 인식을 공부할 때 가장 먼저 넘어야 하는 개념이라 여기서 먼저 잡고 갑니다. 이미 안다면 §1로 건너뛰어도 됩니다.

마이크가 기록하는 것 — 파형

소리는 공기의 떨림입니다. 마이크는 그 떨림(공기 압력)을 1초에 16,000번 재서 숫자로 적습니다. 이게 16 kHz 샘플링이고, 이 숫자들을 시간 순서대로 이어 그린 것이 파형(waveform)입니다. 7초짜리 발화면 숫자 112,000개예요.

문제는 파형을 봐서는 무슨 소리인지 거의 알 수 없다는 겁니다. 같은 "아"라도 사람마다, 말할 때마다 파형의 모양이 다 다릅니다. 귀가 "아"와 "이"를 구분하는 단서는 파형의 모양 자체가 아니라 어떤 높이의 소리가 얼마나 섞여 있는가입니다.

소리를 높이별로 쪼개기 — 스펙트럼

어떤 소리든 높이(주파수)가 다른 순수한 사인파 여러 개의 합으로 쪼갤 수 있습니다. 프리즘이 흰빛을 무지개색으로 나누는 것과 같아요. 이 쪼개기가 푸리에 변환이고, 컴퓨터에서 빠르게 계산하는 방법이 FFT입니다. 쪼갠 결과 — "각 주파수가 얼마나 센가"를 그린 그래프 — 를 스펙트럼이라고 부릅니다.

데모 0-A · 사인파 섞기 — 파형은 헷갈리고, 스펙트럼은 재료를 보여 준다
순수한 사인파 세 개를 섞습니다. 왼쪽은 섞인 결과의 파형(앞 30 ms만 확대), 오른쪽은 브라우저가 같은 소리 128 ms를 FFT로 쪼갠 스펙트럼입니다.
파형 — 가로축이 시간
스펙트럼 — 가로축이 주파수
내가 섞은 주파수—
스펙트럼이 찾아낸 주파수—
성분 1·2를 10 Hz 차이로 붙여 보세요(예: 550·560) — 봉우리가 하나로 합쳐집니다. 20 Hz만 벌려도 다시 둘로 갈라지죠. 128 ms 구간을 분석하면 약 15 Hz보다 가까운 두 소리는 구분이 안 됩니다 — §2의 "주파수 해상도"가 이것입니다.

성분을 하나씩 켜고 꺼 보세요. 왼쪽 파형은 성분이 셋만 섞여도 금세 알아보기 어려운 모양이 되는데, 오른쪽 스펙트럼은 무엇을 섞었는지 봉우리로 바로 보여 줍니다. 봉우리의 위치가 주파수, 높이가 세기예요. "스펙트럼이 찾아낸 주파수"가 여러분이 섞은 값과 일치하는 것도 확인해 보세요 — 정답을 알려 준 게 아니라 파형 숫자만 보고 역산한 값입니다.

시간을 되살리기 — 스펙트로그램

그런데 스펙트럼 하나에는 시간 정보가 없습니다. 구간 전체를 통째로 쪼갰으니 "언제" 그 소리가 났는지가 사라지죠. 말소리는 1초에도 여러 번 바뀌니 이대로는 쓸 수 없습니다. 해법은 단순합니다.

① 소리를 아주 짧게 자른다 (수십 ms씩, 조금씩 겹치게)
② 조각마다 스펙트럼을 구한다
③ 각 스펙트럼을 세로로 세워서, 시간 순서대로 옆으로 이어 붙인다

이렇게 만든 그림이 스펙트로그램(spectrogram)입니다. 가로가 시간, 세로가 주파수, 밝기가 세기예요. 조각을 하나씩 넘기며 직접 만들어 봅시다. 소리는 "도-미-솔-도"를 치고 마지막에 음을 쭉 끌어올리는 것입니다.

데모 0-B · 스펙트로그램을 한 줄씩 만들기
파형 위의 주황색 상자가 지금 잘라 낸 조각(64 ms)입니다. 그 조각의 스펙트럼을 세로로 세운 것이 왼쪽, 그걸 한 줄로 칠해 옆으로 이어 붙인 것이 오른쪽입니다. 왼쪽 막대와 오른쪽 줄의 밝은 자리가 같은 높이에 있는지 보세요.
이 조각의 스펙트럼
스펙트로그램 (가로 = 시간)
이 조각의 구간—
가장 센 주파수—
이어 붙인 세로줄—

완성본을 보면 계단과 비탈이 보입니다. 가장 아래의 밝은 선이 음의 기본 높이이고, 그 위로 2배·3배 높이에 옅은 선이 따라다닙니다. 이게 배음이에요 — 같은 "도"라도 피아노와 사람 목소리가 다르게 들리는 이유가 이 배음들의 세기 비율입니다. 음을 끌어올린 마지막 구간에서는 선들이 비스듬히 올라가고, 높은 배음일수록 더 가파르게 올라갑니다(2배, 3배로 움직이니까요).

악보에 가깝다고 생각하면 쉽습니다. 악보도 가로가 시간, 세로가 음높이죠. 스펙트로그램은 사람이 적는 대신 소리에서 직접 뽑아낸 악보이고, 음표 사이의 모든 높이와 세기가 연속적으로 다 들어 있습니다. 훈련된 음성학자는 스펙트로그램만 보고 무슨 말을 했는지 상당 부분 읽어 냅니다.

그래서 왜 신경망에 이걸 넣나

기존 음성 인식은 여기서 멜 필터뱅크·MFCC까지 더 손으로 압축했지만, DS1/DS2는 선형 스펙트로그램에서 멈췄습니다. (DS1은 "더 정교한 특징은 평가하지 않았다"고만 적습니다.)

"2D라서 이미지처럼 쓴다"는 결과이지 이유가 아닙니다. 2D 행렬이 된 덕에 합성곱을 붙일 수 있게 됐을 뿐이고, 두 축의 성질도 이미지와 다릅니다. 시간 축은 "아"를 언제 말하든 같은 소리지만, 주파수 축은 옮기면 다른 소리가 됩니다 (§7).

이 문서에서 쓰는 용어

용어뜻이 문서에서
파형마이크가 잰 공기 압력을 시간순으로 적은 숫자열데모 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
데모 1-A · 논문 Figure 1 — 층수를 직접 바꿔 보기
논문이 실은 구조도를 이 페이지 스타일로 다시 그린 것입니다. 캡션이 말하는 합성곱 1~3층 · 순환층 1~7층의 범위를 직접 움직여 보세요.
그림에서 층 무리를 클릭하면 그 층이 무엇을 보는지 표시됩니다.
전체 층수—
논문 표기—
논문에 나오는 구성—

논문 Figure 1 캡션: "영어와 중국어 음성을 모두 학습시키는 데 쓴 DS2 시스템의 구조. 합성곱 층을 1~3개, 순환층 또는 GRU 층을 1~7개로 바꿔 가며 이 구조의 변형들을 탐색한다." 원본 그림은 논문 PDF 5쪽에 있습니다. Figure 1처럼 완전연결층은 1층으로 고정했습니다 — 논문 표기 M RNN, N total은 "합성곱 1층 + 순환 M층 + 나머지 전부 FC"라서, 표 1의 가장 얕은 구성(1 RNN, 5 total)만 FC가 3층이라 이 그림과 다릅니다.

새로운 블록은 하나도 없습니다. 전부 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의 설계 판단 상당수가 이 숫자 하나에서 나옵니다.

데모 1 · 모양 추적기 — 7초를 넣으면 무엇이 몇 개가 되는가
발화 길이와 시간 스트라이드를 바꾸면 각 단계의 텐서 모양이 다시 계산됩니다. 맨 아래에서 CTC가 성립하는지까지 확인합니다.
순환층의 시간 스텝 T—
한 스텝이 담는 시간—
필요한 출력 토큰 수—
토큰 하나당 스텝—
CTC 성립 여부—
발화 속도는 논문 각주 2의 영어 14.1 char/s · 중국어 3.3 char/s를 씁니다.
DS2 논문은 샘플링 레이트도, 스펙트로그램의 창 길이·프레임 간격도 명시하지 않습니다. 위 데모는 전작 Deep Speech 1 논문에 적힌 설정 — 16 kHz로 리샘플링, 20 ms 창 · 10 ms 간격, 선형 간격 로그 필터뱅크 160개 + 에너지 항 1개(= 161차원) — 을 그대로 가정했습니다 (그래서 스트라이드 2면 한 스텝 = 20 ms, 스트라이드 4면 40 ms — 논문 7.3절의 "40 ms stride of speech data"와 맞아떨어집니다).

토큰 하나당 스텝 칸을 보면서 스트라이드를 올려 보세요. 영어 유니그램 기준으로 스트라이드 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—유니그램, 스트라이드 26.1절
DS2 영어 (최종)11층 · 2D conv 3층 + 양방향 RNN 7층 + FC 1층100 M바이그램, 스트라이드 36.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, so x⁽ⁱ⁾t,p denotes 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를 직접 돌립니다.

데모 2 · 파형에서 스펙트로그램까지
합성한 발화(모음 3개 + 마찰음 + 묵음)를 창 길이/간격을 바꿔 가며 스펙트로그램으로 변환합니다. 위가 파형, 아래가 x[t,p]입니다. 색 눈금은 고정이라 밝기 변화가 그대로 보입니다.
입력 행렬 모양—
주파수 해상도—
시간 해상도—
한 발화의 입력 숫자 개수—
신경망이 보는 평균 파워—
가로 = 시간(프레임), 세로 = 주파수(아래가 저주파) 밝을수록 그 주파수에 에너지가 많음 (dB 스케일)

FFT를 쓰려고 창을 2의 거듭제곱으로 0-패딩합니다 — 20 ms(320 샘플)는 512점 FFT가 되어 빈이 257개입니다. 320점 FFT를 그대로 쓰면 0~8 kHz를 50 Hz 간격으로 나눈 빈 161개가 나오고, DS1이 쓴 "선형 간격 필터뱅크 160개"도 같은 50 Hz 간격입니다. 빈 개수는 이렇게 달라져도 실제 주파수 해상도는 창 길이가 정합니다 (20 ms → 약 50 Hz).

창 길이를 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절은 이걸 "실시간 배포를 막는 세 가지 이유" 중 하나로 꼽고, 학습셋에서 뽑은 통계로 온라인 적응 정규화를 하는 방식으로 해결했다고 적습니다. 양방향 순환층, 넓은 빔 서치와 함께 정확도를 위한 선택이 배포를 막는 전형적인 사례입니다.

그리고 이 그림을 잘 봐 두세요. 세로축이 주파수라는 사실이 §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 tc a tc a tcat
_ c _ a a _ t_ c _ a _ tc a tcat
c c _ c a t tc _ c a tc c a tccat

위의 두 줄은 전혀 다르게 생겼는데 똑같이 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   ✓
데모 3-0 · 축약기 — 직접 넣어 보기
프레임마다 낸 글자를 이어서 쓰세요. 밑줄 _ 가 blank입니다.
신경망이 낸 글자
① 연달아 같은 기호 합치기
② blank 지우기
결과—
프레임 수 → 글자 수—

그러면 길이 T짜리 출력 중 "cat"으로 축약되는 것들이 여러 개 생깁니다. 그 집합이 Align(x,y)이고, 손실은 그 전부의 확률을 더한 값의 음의 로그입니다 (식 9).

L(x, y; θ) = − log  Σ        Π  p_ctc(ℓ_t | x; θ)
                  ℓ∈Align(x,y)  t

경우의 수가 지수적으로 많지만 전부 나열할 필요는 없습니다. 전진 변수 α를 왼쪽에서 오른쪽으로 채워 나가면 O(T·S)에 합이 구해져요. 직접 채워 봅시다.

데모 3 · CTC 전진 계산 — 모든 정렬을 한 번에 더하기
행은 확장 라벨 ℓ' = [_, c, _, a, _, t, _], 열은 시간입니다. 각 칸이 α[t][s] — "t 시점까지 와서 s 위치에 있을 확률의 총합"입니다.
정답으로 축약되는 정렬의 개수—
p(y | x) = α의 마지막 두 칸 합—
손실 −ln p—
가장 확률 높은 정렬 1개만 썼다면—
0이 아닌 칸 (도달 가능) 마지막에 더해지는 두 칸 흐린 칸 = 도달 불가능 (0)

T를 왼쪽 끝까지 내려 보세요. T가 라벨 길이보다 짧아지면 정렬 개수가 0이 되고 확률도 0이 됩니다 — 한 글자당 최소 한 스텝이 필요하다는 CTC의 하드 제약이 여기서 나옵니다. §1의 모양 추적기에서 빨간 칸이 떴던 게 바로 이 조건이에요.

반대로 정답을 aa처럼 같은 글자가 붙은 것으로 바꿔 보세요. 필요한 최소 T가 2가 아니라 3이 됩니다. a a는 규칙 ①에 걸려 a 하나로 합쳐지니 가운데에 blank를 반드시 하나 끼워야 하거든요. 대각선 건너뛰기(α[t−1][s−2])가 이 경우에만 막히는 이유입니다.

그리고 여기서 SortaGrad가 나온다

식 9의 안쪽은 T개의 확률을 곱한 것입니다. 확률은 전부 1보다 작죠. 그러니 같은 품질의 모델이라도 발화가 길면 손실이 큽니다. 내용이 어려워서가 아니라 길어서요.

데모 3-B · 발화가 길어지면 손실이 커진다
실제 상황처럼 발화가 길어지면 글자 수도 함께 늘립니다(프레임당 0.3글자 고정). 확신도는 위 슬라이더 값을 그대로 씁니다.
T = 5일 때 손실—
T = 30일 때 손실—
길이 6배 → 손실—
In equation 9, the inner term is a product over time-steps of the sequence, which shrinks with the length of the sequence since p_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가 나옵니다.

CTC는 정렬을 정확히 맞추라고 요구한 적이 없습니다 — 식 9는 모든 정렬에 대해 적분(합)해 버리니까요. 그런데 논문 5.1절은 "양방향 RNN으로 학습하면 실제로 정렬이 정확하게 나온다"는 경험적 사실을 이용해 수천 시간짜리 긴 오디오를 자동으로 잘라 학습 데이터를 만듭니다 (§10). 부산물이 데이터 파이프라인의 핵심 부품이 된 셈입니다.

§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을 씁니다. 차이는 통계를 어느 범위에서 재느냐입니다.

데모 4 · 평균과 분산을 어디서 재는가
은닉 유닛 하나에 대한 미니배치 활성값입니다. 세로 = 배치의 발화 8개, 가로 = 시간 스텝(발화마다 길이가 다릅니다). 파란 칸이 통계에 쓰이는 값.
통계에 쓰인 값의 개수 m—
표준오차 ∝ 1/√m—
역전파 난이도—

식 7은 한 시점의 배치만 볼 수 있습니다. 시간축으로 평균을 낼 수가 없어요 — h_t를 계산하려면 h_{t−1}이 이미 있어야 하니 시간 방향은 순차적이라 "모든 시점을 모아 평균"이 성립하지 않습니다. 그래서 m = 배치 크기뿐이고, 슬라이더를 오른쪽으로 밀어 보면 긴 발화만 남는 뒤쪽 시점에서는 m이 더 줄어듭니다.

식 8은 W·h^{l−1}만 정규화합니다. 이 항은 시간 의존성이 없어서 배치 × 전체 시간에 걸쳐 한꺼번에 계산할 수 있고, m이 수십 배로 커집니다. 순환항 U·→h_{t−1}은 정규화 밖에 두어 반복 곱해지는 경로를 건드리지 않고요.

"뒤쪽 시점에서 m이 줄어든다"는 관찰은 논문에 직접 적혀 있지 않습니다. 논문이 말하는 것은 "시간축 의존성 때문에 모든 시점에 대한 평균이 불가능하다"와 "누적 평균도 시도했으나 효과가 없었고 역전파가 크게 복잡해졌다"까지입니다.

표 1 — 깊을수록 격차가 벌어진다

데모 4-B · 논문 표 1을 상대 개선율로 다시 보기
파라미터 3,800만 개 고정. 개발셋 WER, 값이 작을수록 좋습니다.
BatchNorm 없음 BatchNorm (식 8)

가장 얕은 순환층 1개짜리 모델에서는 BatchNorm이 오히려 해롭습니다 (개발셋 13.55 → 14.40). 논문도 이걸 숨기지 않고 "harms generalization error for the shallowest network"라고 적어요. 그런데 순환층 7개에서는 10.83 → 9.52, 상대 12% 개선입니다. BatchNorm은 깊이를 쓸 수 있게 해 주는 도구이지 그 자체가 만능 개선책이 아니라는 뜻입니다.

배포에서의 BatchNorm. 서비스에서는 발화 하나씩 처리하니 미니배치가 없습니다. "그 발화 안에서만 평균/분산을 내면 성능이 나빠진다"고 논문은 적고, 대신 학습 중에 모아 둔 이동 평균을 씁니다. 그러면 발화 하나씩 처리해도 큰 배치로 평가하는 것보다 오히려 결과가 좋았다고 해요. (이 이동 평균 이야기는 ResNet의 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으로 내립니다.

데모 5 · 첫 에포크의 미니배치 구성
길이가 제각각인 발화 96개를 배치 8개짜리 미니배치로 묶습니다. 세로축은 그 미니배치 안의 최장 발화 길이 — 기울기 크기의 대리 지표입니다.
1번째 미니배치의 최장 발화—
학습 첫 10스텝 중 최악—
전체 미니배치 수—

랜덤 순서를 눌러 보세요. 1번 미니배치부터 12초짜리 발화가 튀어나옵니다. 가중치가 아직 난수인 상태에서 말이죠. §3에서 봤듯 손실은 발화가 길수록 커지고, 학습률은 길이와 무관하게 고정입니다 — 첫 스텝부터 큰 기울기를 맞는 겁니다. 논문은 실제로 이 상황에서 기울기 노름이 400을 넘으면 400으로 재스케일하는 클리핑을 겁니다 (6절).

순서학습셋 WER개발셋 WER
BatchNorm 없음BatchNormBatchNorm 없음BatchNorm
정렬 안 함10.718.0411.969.78
SortaGrad8.767.6810.839.52
상대 개선18.2%4.5%9.4%2.7%

논문 표 2 (9층 · 순환층 7개 모델). 마지막 줄은 이 페이지에서 계산한 값입니다.

표를 세로로 읽으면 재미있는 게 보입니다. BatchNorm이 없을 때 SortaGrad의 효과가 훨씬 큽니다 (18.2% vs 4.5%). 논문의 표현대로 "두 기법이 어느 정도 서로를 대체(substitute for one another)"하는 거죠. 둘 다 깊은 순환망의 초기 수치 안정성이라는 같은 문제를 다른 쪽에서 건드립니다.

SortaGrad의 부수 효과 하나가 논문 부록에서 다시 등장합니다. CTC를 CPU로 계산할 때와 GPU로 계산할 때 초월함수 구현이 달라 비트 단위로 같은 값이 안 나오는데, 발화를 길이순으로 정렬해 학습하면 두 구현이 "동등하게 잘" 학습된다고 적혀 있어요. 재현성이 목표였던 팀에게는 정확도 못지않게 중요한 성질이었을 겁니다.

§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 state h_{t−1} by U_h prior to scaling by the reset gate. This allows for all operations on h_{t−1} to be computed in a single matrix multiplication. — 3.4절 우리는 표준 GRU와 조금 다르게, 리셋 게이트로 스케일링하기 전에 은닉 상태 h_(t−1)에 U_h를 곱한다. 이렇게 하면 h_(t−1)에 가해지는 모든 연산을 행렬 곱 하나로 계산할 수 있다.
데모 6 · 왜 순서를 바꿨나 — 행렬 곱 횟수 세기
한 시간 스텝에서 h_(t−1)에 가해지는 연산만 봅니다. 은닉 차원 d는 조절 가능.
곱셈-덧셈 횟수 (둘이 같음)—
GEMM 호출 횟수—
행렬 크기—

연산량(FLOP)은 완전히 같습니다. 바뀌는 건 세 개의 작은 행렬 곱이 하나의 큰 행렬 곱이 된다는 것뿐이에요. 그런데 GPU에서는 이게 큰 차이입니다 — 행렬이 클수록 연산 강도(arithmetic intensity)가 높아져 메모리 대역폭 병목에서 벗어나거든요. 수식을 하드웨어에 맞춰 다시 쓴 것이고, 이런 판단이 논문 전체에 깔려 있습니다 (§11, §13).

그래서 뭐가 이겼나

데모 6-B · 표 3과 표 11 — 깊이에서는 GRU, 규모에서는 RNN
두 버튼을 번갈아 눌러 보세요. 같은 비교가 파라미터 규모를 바꾸면 뒤집힙니다.
simple RNN GRU 낮을수록 좋음 (WER %)

표 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 층을 쓴다.

게이트의 이득이 데이터와 모델이 커지면 사라진다는 관찰입니다. "어떤 순환 유닛이 제일 좋은가"라는 질문에는 "어느 규모에서?"가 늘 따라붙어야 한다는 것을, 이 논문은 표 두 개로 보여 줍니다.

논문 표 11의 캡션에는 "All models contain approximately 35 million parameters"라고 적혀 있는데 같은 표의 첫 열이 1,800만~1억 파라미터입니다. 캡션 쪽이 표 3·4에서 복사되며 남은 오기로 보입니다. 본문(6.1.1절)은 명확히 "모델 크기를 1억까지 키웠다"고 서술합니다.

§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)은 간격 자체가 다르죠. 그러니 기대할 수 있는 건 완전한 불변성이 아니라 작은 변화에 대한 국소적 강건성입니다.

데모 7 · 화자를 바꾸면 무슨 일이 생기나
같은 발화의 주파수축을 비율로 늘이거나 줄여 목소리 높이가 다른 화자를 흉내 내고, 두 종류의 첫 층 출력이 얼마나 유지되는지 봅니다.
1D 불변 합성곱 — 필터가 주파수 전체를 덮음
원래 출력과의 코사인 유사도—
2D 불변 합성곱 — 필터가 주파수축으로도 미끄러짐
위치 그대로 비교—
주파수 ±3칸(≈ ±94 Hz) 여유를 주면—
1.03× 정도의 작은 변화에서 세 숫자를 비교해 보세요
발화는 §2의 합성 신호이고 필터는 설명용입니다(논문은 41×11 같은 모양만 공개). 둘 다 프레임별로 표준화한 스펙트럼에 적용했고, 2D의 주파수 스트라이드는 1로 두었습니다(논문 첫 층은 2×2). 화자 차이는 음높이와 포먼트를 같은 비율로 옮기는 단순화이고, "±3칸 여유"는 주파수 방향 최대값 풀링 후 비교한 이 페이지의 측정 방식입니다.

한 가지 구분할 것 — 합성곱에 스트라이드를 줘 순환층의 시간 스텝을 줄이는 이득(§8)은 1D 합성곱도 똑같이 얻습니다. 2D를 고른 근거는 아래 표 4입니다.

표 4 — 잡음에서 특히 벌어진다

데모 7-B · 합성곱 구성별 WER
모두 뒤에 순환층 7개 + FC 1층, 파라미터 약 3,500만 개. 낮을수록 좋습니다.

깨끗한 데이터에서는 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/s4.9 bits/char약 58 bit/s69.1
중국어3.3 char/s12.6 bits/char약 41 bit/s41.6

논문 각주 2. 마지막 열은 이 페이지의 계산입니다 — 중국어는 맞아떨어지는데 영어는 69.1이 나와 논문의 58과 어긋납니다. 공백을 어느 쪽에 포함시켰는지 등 계산 기준이 본문에 없어 여기서는 차이를 그대로 적어 둡니다.

핵심은 마지막 열이 아니라 두 번째 열입니다. 중국어는 초당 3.3자, 영어는 초당 14.1자. 10 ms 프레임에 스트라이드 4를 걸면 초당 25스텝인데, 영어는 글자 사이에 blank를 넣을 자리가 거의 안 남습니다.

해법 — 알파벳을 늘린다

DS2는 출력 단위를 글자에서 겹치지 않는 바이그램으로 바꿉니다. 단어 안에서 두 글자씩 묶고, 홀수면 마지막 글자는 유니그램으로, 공백도 유니그램으로 둡니다. 같은 단어는 항상 같은 토큰열이 되는 단순 동형사상이에요.

데모 8 · 바이그램 분해기
문장을 넣으면 논문 3.6절의 규칙대로 토큰으로 쪼갭니다. 필요한 최소 시간 스텝이 얼마나 줄어드는지 보세요.
유니그램 (글자)
바이그램 (논문 3.6절)
유니그램 토큰 수—
바이그램 토큰 수—
필요한 최소 스텝 감소—
CTC에 필요한 최소 시간 스텝—

the cat sat은 [th, e, ␣, ca, t, ␣, sa, t] 8토큰이 됩니다. 글자로는 11개니까 27% 감소죠. 절반이 아닌 이유는 공백이 유니그램으로 남고 홀수 길이 단어의 꼬리가 유니그램이 되기 때문입니다.

표 5 — 바이그램이 스트라이드를 사 준다

데모 8-B · 스트라이드 × 출력 단위
칸을 클릭하면 같은 조건의 유니그램/바이그램 차이를 계산합니다. 5-gram 언어 모델 사용 여부도 바꿔 보세요.
선택한 칸—
같은 스트라이드에서 바이그램의 이득—

언어 모델을 켠 상태에서 스트라이드 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). 이유는 셋입니다.

한 줄로 줄이면 — 최고 성능을 보여줄 때는 양방향, 실제 사람이 쓰는 서비스에는 단방향 + 행 합성곱입니다. 행 합성곱이라는 장치 자체가 바로 이 배포 문제를 풀려고 제안된 것이라고 보면 됩니다.

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
데모 9 · 표 6 — 모델이 깊어질수록 언어 모델의 몫은 줄어든다
막대는 언어 모델을 붙였을 때의 상대 개선율입니다 (이 페이지에서 계산).

영어는 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년 기준으로 공개 데이터셋의 자릿수가 달랐습니다.

데모 10 · 영어 학습 데이터의 구성 (표 9)
막대를 클릭하면 그 데이터셋을 뺐을 때의 총량이 계산됩니다.
합계11,940 시간
공개 데이터만—
Baidu 내부 데이터—

합계의 72%가 Baidu 내부 데이터입니다. 그중 3,600시간은 "수 분에서 한 시간이 넘는" 긴 원본 오디오에 부정확한 자막이 붙은 것이었어요. 그대로는 RNN에 펼칠 수가 없습니다. 그래서 만든 게 정렬 → 분할 → 필터링 파이프라인입니다.

1정렬이미 학습된 양방향 CTC 모델로 비터비 정렬 (식 13) — 어느 프레임이 어느 글자인지 찾는다§3의 부산물
2분할blank가 길게 연속되는 구간 = 묵음. 그 지점에서 자른다. 영어는 그 안에 공백 토큰이 있을 때만 잘라 단어 경계를 지킨다평균 7초로 조정
3필터링CTC 비용·길이비·단어수 등으로 선형 분류기를 학습해 잘못 정렬된 예제를 버린다WER 17% → 5%

마지막 줄이 이 파이프라인의 성적표입니다. 전사 오류율 17%짜리 원본에서 5%짜리 학습셋을 뽑아내면서 예제의 절반 이상을 살렸습니다. 정답 라벨을 사람이 새로 만든 게 아니라 기존 모델로 라벨의 품질을 추정해 걸러낸 것이고, 여기에 쓰인 특징이 전부 CTC 비용에서 파생된 값이라는 점이 깔끔합니다.

여기에 잡음 증강이 더해집니다. 무작위로 고른 40%의 발화에만 잡음을 섞어요. "너무 많이 섞으면 최적화가 어려워지고, 너무 적게 섞으면 강건성이 떨어진다"는 균형점입니다.

표 10 — 데이터를 10배 늘리면 오류가 얼마나 줄어드는가

데모 10-B · 거듭제곱 법칙 맞추기
논문 표 10의 5개 점을 로그-로그 평면에 찍고 최소제곱으로 직선을 맞춥니다. 직선이면 거듭제곱 법칙입니다.
맞춘 지수 (기울기)—
10배마다 상대 감소—
그 WER에 필요한 시간—
잡음/깨끗한 데이터 격차—
깨끗한 개발셋 잡음 개발셋
논문은 "학습셋을 10배 늘릴 때마다 WER이 약 40% 상대적으로 감소한다"고 적습니다. 구간별로 실제로 계산해 보면 120 → 1,200시간이 52.8%, 1,200 → 12,000시간이 38.7%이고, 5개 점 전체에 직선을 맞추면 47%가 나옵니다. 논문의 "약 40%"는 데이터가 많은 쪽 구간에 해당합니다.

두 선이 거의 평행합니다. 논문의 표현대로 "두 경우 모두 데이터의 이득을 똑같이 본다"는 뜻이고, 잡음 데이터가 특별히 데이터를 더 요구하지도 덜 요구하지도 않는다는 얘기죠. 목표 WER 슬라이더를 낮춰 보면 필요 시간이 무섭게 튀어 오릅니다 — 거듭제곱 법칙의 다른 얼굴입니다. 절반의 오류율을 원하면 데이터를 10배가 아니라 수십 배 늘려야 합니다.

논문은 여기에 검증하지 못한 가설 하나를 덧붙입니다 — 중요한 건 시간 수가 아니라 "음성 맥락(speech context)"의 개수일 것이라고요. 화자, 배경 잡음, 환경, 마이크 하드웨어 같은 것들이죠. "화자 수를 축으로 WER을 재면 단순 랜덤 샘플링보다 훨씬 큰 상대 이득이 보일 것"이라고 적고, 라벨이 없어 검증하지 못했다고 솔직하게 씁니다.

§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로 돌립니다.

데모 11 · 직접 만든 all-reduce는 얼마나 빨랐나 (표 7)
한 에포크 동안 all-reduce에 쓴 총 시간(초). 링 알고리즘 직접 구현 vs OpenMPI 1.8.5.
실제로 쓴 구성(8·16 GPU)에서—
노드 안(≤8 GPU) 평균 배속—
노드 간(≥32 GPU) 평균 배속—

GPU가 8장 이하일 때 20배까지 벌어지다가 128장에서 3.8배로 좁혀집니다. 한 노드 안에서는 GPUDirect로 CPU 메모리를 거치지 않고 GPU끼리 직접 주고받을 수 있어서인데, 노드를 넘어가면 InfiniBand 대역폭이 병목이라 직접 구현의 여지가 줄어듭니다. 저자들이 한 노드에 GPU 8장을 같은 PCI 루트 컴플렉스에 몰아넣은 이유이기도 합니다 (부록 A.1).

CTC를 GPU로 옮기기

언어CPU CTC (초/에포크)GPU CTC (초/에포크)배속에포크당 절약
영어5,888.12203.5628.9×약 95분
중국어1,688.01135.0512.5×약 25분

논문 표 8. 전체 학습 시간의 10~20%를 줄였습니다.

원래는 softmax 출력을 CPU로 복사해 OpenMP로 CTC를 계산하고 기울기를 다시 GPU로 올렸습니다. 문제가 두 개였어요 — 중국어처럼 문자 집합이 크면 그 행렬이 수억 개의 원소고, 그 복사에 쓰는 인터커넥트 대역폭은 데이터 병렬용 기울기 전송에서 빼앗아 온 것입니다. 부록 A.2의 구현 요령이 인상적인데, 유효하지 않은 칸까지 전부 계산해 버리고 α와 β를 더할 때 −∞가 쓰레기 값을 자동으로 지우게 만듭니다. 분기를 없애 GPU에 맞춘 거죠.

메모리는 가중치가 아니라 활성값이 먹는다

데모 11-B · 12 GB GPU에 무엇이 들어가는가
논문 4.3절의 두 숫자(가중치 280 MB vs 활성값 1.5 GB)를 재구성해 봅니다. Titan X의 메모리는 12 GB입니다.
파라미터 수—
가중치 메모리—
활성값 메모리—
12 GB에 들어가나—

기본값(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을 냅니다.

데모 12 · 11개 테스트셋에서의 DS1 · DS2 · 사람
낮을수록 좋습니다. 사람보다 잘한 칸은 초록색으로 표시됩니다.
DS1 (2014) DS2 (2015) 사람 (Mechanical Turk)
DS2가 사람을 이긴 테스트셋—
DS1 대비 평균 상대 개선—
사람과 가장 크게 벌어진 곳—

읽은 음성에서는 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 발화

논문 6.2절. 짧은 음성 검색 질의 형태의 발화입니다.

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 — 이전 배치가 끝나는 순간, 그때까지 모인 것만 들고 바로 출발합니다. 배치 크기를 최대화하려 들지 않아요.

데모 13 · Batch Dispatch 시뮬레이터
동시 스트림 수를 바꿔 가며 eager batching을 이산 사건 시뮬레이션으로 돌립니다. 요청 4,000개 기준.
지연 중앙값—
98 백분위 지연—
평균 배치 크기—
배치 크기 ≥ 2인 작업 비율—
막대 = 그 크기의 배치에서 처리된 요청의 비율 (논문 그림 5에 대응)
서비스 시간 모델(배치 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.816.10
순환층양방향 7층단방향 5층 (2560) + 행 합성곱 τ=19
수치 정밀도32비트16비트
빔 서치전체 문자 탐색누적 p=0.99 · 최대 40자 → 150배
지연발화 길이에 비례중앙값 44 ms · 98백분위 67 ms

정확도 손실은 5% 상대뿐입니다. 이 표가 논문의 마지막 메시지예요 — "연구실 최고 기록"과 "서비스에 올릴 수 있는 것" 사이의 거리를 5%로 좁혔다.

초록은 98백분위 지연을 67 ms로 적고, 본문 7.1절의 그림 6 설명은 70 ms로 적습니다. 같은 실험(10 동시 스트림)을 가리키는 것으로 보이며 논문 안에서 두 값이 엇갈립니다.

§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~§9WER 40%↓
데이터11,940 h · 정렬-분할-필터링 파이프라인 · 40% 잡음 증강§1010배당 약 40%↓
계산동기 SGD · 직접 만든 all-reduce · GPU CTC · 메모리 할당자§117배 · 3~5일
(+ 배포)단방향화 · 16비트 · Batch Dispatch · 빔 가지치기§1367 ms · CER +5%

10년 뒤에 다시 읽으면

다음에 볼 것. 부품을 더 파고 싶다면 행 합성곱과 CTC 빔 서치를, 직접 돌려 보고 싶다면 DS2 My Voice로 가세요 — PyTorch로 만든 축소판 DS2를 한국어 51.7시간으로 학습시키고 내 목소리로 파인튜닝하는 실습입니다. 순환층의 기초가 필요하면 How RNN Works, How LSTM Works가 있습니다.

§15 이해도 퀴즈

부록 · GMM-HMM이 뭐였나

§3에서 "기존 파이프라인은 GMM-HMM으로 프레임마다 정답 라벨을 붙여 놓고 학습했다"고 지나갔는데, CTC가 무엇을 대체했는지 알려면 이게 뭔지 알아야 해서 개념만 짧게 정리합니다. 2010년대 초까지 음성 인식의 표준이었던 구조입니다.

이름이 곧 역할 분담

부품맡은 질문하는 일
HMM
은닉 마르코프 모델
"지금 발음의 어디쯤인가?"말소리를 상태들의 시간 전이로 봅니다. 음소 하나를 보통 시작·중간·끝 3개 상태로 쪼개고, 프레임마다 어느 상태에 있는지와 다음 상태로 넘어갈 확률을 다룹니다. 시간 정렬 담당.
GMM
가우시안 혼합 모델
"이 상태라면 이런 소리가 날 확률은?"상태가 주어졌을 때 그 프레임의 특징 벡터(보통 MFCC)가 나올 확률 p(x|상태)를 여러 개의 정규분포를 섞어 표현합니다. 관측 확률 담당.

둘을 붙이면 이런 파이프라인이 됩니다. 부품마다 따로 만들고 따로 학습시켜야 합니다.

1특징 추출파형 → MFCC 등 (사람이 설계한 압축)신호 처리
2음향 모델GMM이 프레임별 상태 확률, HMM이 상태 전이 확률GMM-HMM
3발음 사전음소열 → 단어. 단어마다 발음을 사람이 적어 둔 표lexicon
4언어 모델단어열의 그럴듯함 (n-gram)LM
5디코더위 넷을 한꺼번에 놓고 가장 그럴듯한 문장 탐색WFST 등

왜 "프레임마다 정답 라벨"이 필요했나

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절이 "중국어로 옮길 때 발음 모델을 만들 필요가 없었다"를 장점으로 꼽을 수 있었던 거죠.