개요
여기까지 단어를 벡터로 바꾸고(임베딩), 순서를 처리하는 구조(RNN·LSTM)를 봤다. 도구는 갖춰졌다.
이번 글에서는 시선을 조금 넓힌다. 그 도구들로 무엇을 만들 것인가에 대한 이야기다.
- 언어 모델 — 자연어를 다루는 거의 모든 것의 뿌리
- Seq2Seq — 문장을 받아 문장을 만들어내는 구조
두 번째가 특히 중요하다. 번역기, 요약, 챗봇이 전부 이 구조에서 출발했다. 지금 우리가 쓰는 대화형 AI의 조상 격이다.
교육 자료는 대외비라 슬라이드 이미지나 예제 데이터는 싣지 않는다. 개념 흐름만 내가 이해한 방식으로 재구성했고, 그림은 전부 직접 그렸다.
1. 언어 모델이란
이름은 거창한데 하는 일은 하나다.
언어 모델(Language Model) = 앞의 말을 보고 다음에 올 단어의 확률을 매기는 모델
“오늘 저녁에 친구랑 영화 보러” 다음에 올 단어를 생각해보자. 사람은 “갈까”, “간다” 정도를 떠올리지 “파랗다”는 떠올리지 않는다. 언어 모델이 하는 게 정확히 이 판단이고, 그걸 확률이라는 숫자로 내놓는다.
이미 매일 쓰고 있다
거창한 연구실 얘기가 아니다.
- 스마트폰 자동완성 — “만나서 반갑” 다음에 “습니다”를 띄워준다
- 검색어 추천 — “날씨”를 치면 “날씨 서울”, “날씨 내일”이 나온다
- 문법 검사기 — 확률이 지나치게 낮은 조합을 이상하다고 짚어준다
전부 “다음에 뭐가 올 확률이 높은가”를 계산하고 있는 것이다.
문장 전체의 확률
여기서 조금 더 나간다. 단어 하나가 아니라 문장 전체가 얼마나 자연스러운지도 잴 수 있다.
방법은 단순하다. 앞에서부터 하나씩 예측한 확률을 전부 곱한다.
"고양이가 밥을 먹는다"의 확률
= P(고양이가) × P(밥을 | 고양이가) × P(먹는다 | 고양이가 밥을)
P(밥을 | 고양이가)는 “고양이가”가 나온 상황에서 다음이 “밥을”일 확률이라고 읽는다. 세로줄 |은 “~인 상황에서”라는 뜻이다. 조건부 확률이라고 부른다.
자연스러운 문장은 각 단계의 확률이 다 높아서 곱한 값도 크고, 이상한 문장은 어딘가에서 확률이 확 떨어져 전체 값이 작아진다. 문장의 자연스러움에 점수를 매기는 방법인 셈이다.
지난 시리즈에서 우도를 다룰 때 곱셈이 언더플로를 일으킨다고 했는데, 여기서도 똑같은 문제가 생긴다. 그래서 실제로는 로그를 씌워 더한다.
2. N-gram 언어 모델
신경망 이전에 쓰던 고전적인 방법이다. 발상이 아주 소박하다.
직전 N−1개 단어만 보고, 말뭉치에서 세어서 확률을 구한다.
n-gram은 연속된 n개의 단어 묶음을 말한다.
| 이름 | 묶음 크기 | 예 |
|---|---|---|
| unigram | 1개 | “점심으로” |
| bigram | 2개 | “오늘 점심으로” |
| trigram | 3개 | “먹어서 오늘 점심으로” |
3-gram 모델이라면 직전 2단어만 보고 다음 단어를 예측한다. 계산은 그냥 나눗셈이다.
P(떡볶이 | 오늘 점심으로) = "오늘 점심으로 떡볶이"가 나온 횟수
÷ "오늘 점심으로"가 나온 횟수
학습이랄 것도 없이 그냥 세면 된다. 그래서 빠르고 단순하다.
한계 — 잘라낸 앞부분에 답이 있었다면
그림의 예를 보자. 전체 문장은 이랬다.
"나는 매운 걸 전혀 못 먹어서 오늘 점심으로 ___"
3-gram 모델이 보는 건 “오늘 점심으로” 두 단어뿐이다. 그 앞은 통째로 버린다. 그리고 말뭉치를 세어보면 이런 순위가 나온다.
| 후보 | 확률 | |
|---|---|---|
| 떡볶이 | 0.42 | 말뭉치에서 제일 흔한 조합 |
| 라면 | 0.20 | |
| 김밥 | 0.11 | 확률로는 3위 |
모델은 당연히 “떡볶이”를 고른다. 통계적으로는 가장 그럴듯한 답이다.
그런데 잘려나간 앞부분에는 “매운 걸 전혀 못 먹어서”가 있었다. 이걸 봤다면 떡볶이는 후보에서 아예 빠진다. 확률 3위였던 김밥이 정답이 된다.
1등과 정답이 뒤바뀐다. 그것도 모델이 틀린 계산을 해서가 아니라, 계산 자체는 정확한데 볼 범위를 잘못 잡아서 벌어지는 일이다.
시험지의 지문 앞 절반을 가리고 문제를 푸는 것과 같다. 남은 절반만 보면 그럴듯한 선택지가 답처럼 보이지만, 가려진 쪽에 결정적인 조건이 있었던 것이다.
이게 장기 의존성 문제가 N-gram에서 드러나는 방식이다. RNN은 적어도 앞을 보려고 시도라도 했지만, N-gram은 아예 보지 않기로 정해놓고 시작한다.
또 하나 — 못 본 조합은 확률이 0이다
말뭉치에 “오늘 점심으로 유부초밥”이 한 번도 없었다면 확률이 정확히 0이 된다. 충분히 가능한 문장인데 불가능하다고 판정한다.
그럼 n을 키우면 되지 않을까? 그런데 여기서 딜레마가 생긴다.
- n을 키우면 — 문맥을 더 보지만, 그 긴 조합이 말뭉치에 있을 확률이 급격히 떨어진다
- n을 줄이면 — 조합은 흔해지지만, 앞의 예처럼 결정적인 문맥을 놓친다
늘려도 문제, 줄여도 문제다. 세는 방식으로는 이 딜레마를 벗어날 수가 없다.
이 한계 때문에 언어 모델은 신경망 쪽으로 넘어가게 된다. 세는 대신 배우는 방식으로 말이다.
3. 번역이라는 어려운 문제
이제 응용으로 넘어간다. 기계 번역은 오랫동안 자연어처리의 대표 난제였다.
1990년대부터 2010년대까지는 통계적 기계 번역(SMT)이 주류였다. 확률 계산을 여러 조각으로 쪼개고, 언어쌍마다 규칙과 사전을 사람이 만들어 붙이는 방식이었다.
문제는 유지가 안 된다는 것이었다.
- 구조가 복잡해서 손대기 어렵다
- 사람 손이 너무 많이 든다
- 언어쌍마다 처음부터 다시 만들어야 한다
한국어→영어를 만들었다고 한국어→프랑스어가 거저 나오지 않는다. 언어 조합마다 새 프로젝트인 셈이다.
그리고 길이 문제
번역에는 구조적인 난점이 하나 더 있다.
영어 : the black cat drank milk (5단어)
프랑스어: le chat noir a bu du lait (7단어)
입력과 출력의 길이가 다르다. 게다가 얼마나 다를지 미리 알 수도 없다. 단어를 일대일로 대응시키는 방식으로는 감당이 안 된다.
4. Seq2Seq — 읽는 담당과 쓰는 담당
2014년에 나온 해법이 Seq2Seq(sequence-to-sequence)다. 아이디어가 시원할 만큼 단순하다.
RNN 계열 모델을 두 개 쓴다. 하나는 읽고, 하나는 쓴다.
| 역할 | |
|---|---|
| 인코더(Encoder) | 입력 문장을 끝까지 읽어서 하나의 벡터로 압축한다 |
| 디코더(Decoder) | 그 벡터를 받아 출력 문장을 한 단어씩 만들어낸다 |
인코더가 마지막까지 읽고 남긴 요약 벡터가 “이 문장이 무슨 뜻인지”를 담고 있다는 발상이다. 디코더는 그 요약만 보고 다른 언어로 다시 풀어쓴다.
더 쉬운 비유로
통역사가 하는 일을 떠올려 보자. 문장을 단어 단위로 바꿔치기하지 않는다. 상대의 말을 끝까지 듣고 머릿속에 뜻을 담은 다음, 그 뜻을 다른 언어로 처음부터 다시 말한다.
듣는 과정이 인코더, 머릿속에 남은 뜻이 요약 벡터, 다시 말하는 과정이 디코더다.
이 구조 덕분에 길이 문제가 자연스럽게 풀린다. 입력이 5단어든 출력이 7단어든 상관없다. 중간의 요약 벡터를 거치기 때문이다.
번역만이 아니다
Seq2Seq는 “문장 → 문장” 형태의 작업이면 뭐든 할 수 있다.
| 작업 | 입력 | 출력 |
|---|---|---|
| 번역 | 한국어 문장 | 영어 문장 |
| 요약 | 긴 문서 | 짧은 요약문 |
| 대화 | 사용자 발화 | 응답 |
| 코드 생성 | 자연어 설명 | 프로그래밍 코드 |
“입력 문장에 대응하는 출력 문장을 만든다”는 틀 하나로 이 모든 게 묶인다. 지금의 대화형 AI가 하는 일도 큰 틀에서는 여기서 벗어나지 않는다.
5. 학습할 때의 요령 — Teacher Forcing
Seq2Seq는 인코더와 디코더가 하나의 네트워크로 연결되어 있다. 디코더에서 생긴 오차가 역전파를 타고 인코더까지 흘러가서, 양쪽이 동시에 학습된다.
그런데 학습 초반에 곤란한 일이 생긴다.
디코더는 자기가 방금 뱉은 단어를 다음 입력으로 쓴다. 그런데 초반에는 실력이 형편없어서 첫 단어부터 틀린다. 틀린 단어를 입력으로 받아 또 틀리고, 그게 계속 이어진다.
첫 단추를 잘못 끼우면 나머지가 전부 어긋나는 상황이다. 이러면 학습이 좀처럼 안정되지 않는다.
그래서 쓰는 게 Teacher Forcing이다.
학습할 때는 모델이 예측한 단어 대신 정답 단어를 다음 입력으로 넣어준다.
앞에서 틀렸더라도 다음 단계는 올바른 출발점에서 시작하게 만드는 것이다. 훨씬 빠르고 안정적으로 배운다.
받아쓰기 연습에 비유하면 이렇다. 한 문장을 틀렸다고 그 뒤를 전부 틀린 채로 이어 쓰게 두지 않고, 선생님이 정답을 알려준 뒤 다음 문장을 이어 쓰게 하는 것이다.
다만 실전에서는 알려줄 사람이 없다. 그래서 학습할 때와 실제로 쓸 때의 조건이 달라지는 문제가 따라오는데, 이건 이 방식이 안고 가는 대가다.
6. 출력할 때의 요령 — Greedy와 Beam Search
학습이 끝나고 실제로 문장을 만들 때도 선택지가 있다. 디코더는 매 단계 모든 단어에 대한 확률을 내놓는데, 그중 무엇을 고를 것인가?
Greedy — 매 순간 1등만 집기
가장 단순한 방법이다. 매 단계에서 확률이 가장 높은 단어 하나를 고른다.
빠르고 간단한데 치명적인 약점이 있다. 되돌릴 수 없다.
앞에서 고른 단어가 나중에 보니 잘못된 선택이었어도, 이미 지나간 일이다. 그 단어를 전제로 나머지를 계속 만들어야 하니 문장 전체가 어긋난다.
지난 시리즈의 경사 하강법에서 웅덩이에 빠지던 문제와 결이 같다. 매 순간의 최선이 전체의 최선을 보장하지 않는다.
Beam Search — 후보를 몇 개 들고 가기
그래서 나온 게 빔 서치다.
- 매 단계에서 가장 유망한 후보 k개를 살려둔다
- 각 후보마다 다음 단어를 이어 붙이고, 다시 상위 k개만 남긴다
- 문장이 끝나면 완성된 문장 목록에 넣는다
- 충분히 모이면, 문장 전체 점수가 가장 높은 것을 고른다
핵심은 한 번에 하나만 붙들지 않는 것이다. 지금은 2등이지만 뒤에 가서 더 좋아질 수 있는 길을 열어둔다.
길 찾기와 비슷하다. 갈림길마다 무조건 넓은 길로만 가면 막다른 골목에 도착할 수 있다. 후보 경로를 두세 개 들고 가다가 끝에서 비교하면 훨씬 나은 선택을 할 수 있다.
k를 키우면 품질이 좋아지지만 계산이 그만큼 늘어난다. 여기서도 품질과 비용의 거래가 나온다.
7. 그런데 큰 문제가 하나 남아 있다
Seq2Seq는 분명 대단한 진전이었다. 하지만 구조를 다시 보면 이상한 지점이 있다.
인코더가 문장 전체를 딱 하나의 벡터에 밀어 넣는다.
10단어짜리 문장도, 100단어짜리 문단도 같은 크기의 벡터 하나로 압축된다. 그리고 디코더는 오직 그 벡터만 보고 전체를 다시 써야 한다.
문장이 길어질수록 이게 감당이 안 된다. 정보가 뭉개지고, 앞부분이 흐려진다.
이 지점을 병목(bottleneck) 문제라고 부른다. 그리고 이걸 뚫기 위해 등장한 것이 다음 글의 주제, Attention이다.
정리
- 언어 모델 = 앞말을 보고 다음 단어의 확률을 매기는 모델. 자동완성·검색어 추천이 전부 이것
- 문장 전체의 확률 = 각 단계 확률의 곱. 실제로는 로그를 씌워 더한다
-
N-gram = 직전 N−1단어만 보고 세어서 확률을 구한다
- 한계 — 그 앞은 못 보고, 못 본 조합은 확률이 0이 된다
- 번역이 어려운 이유 — 입력과 출력의 길이가 다르고, 언어쌍마다 새로 만들어야 했다
- Seq2Seq = 인코더(읽고 요약) + 디코더(요약을 보고 생성). 길이가 달라도 된다
- Teacher Forcing — 학습할 때 정답 단어를 넣어줘 안정적으로 배우게 한다
- Greedy는 매 순간 1등만 집어 되돌릴 수 없고, Beam Search는 후보 k개를 들고 가 끝에서 고른다
- 남은 문제 — 문장 전체를 벡터 하나에 압축하는 병목
다음 글에서
다음 글에서는 Attention을 다룬다. 요약 벡터 하나에 모든 걸 밀어 넣는 대신, 필요할 때마다 원문을 다시 들여다보는 방식이 어떻게 병목을 뚫었는지 정리한다.
한줄 평
- 인코더가 아무리 많은 일을 해도 디코더가 받는 입력은 1개라는게 처음엔 이해하기 어려웠으나, 그림으로 시각화되니 많이 쉬워졌다!