개요
지난 글에서 RNN이 남긴 숙제를 확인했다.
멀리 있는 단어를 기억하지 못한다.
원인은 구조에 있었다. RNN은 메모(hidden state) 하나에 모든 걸 담아 다음 단계로 넘긴다. 매 단계마다 새 단어와 섞이면서 예전 내용이 조금씩 덮어써지고, 거슬러 올라가며 배울 때는 신호가 곱해지며 사라진다.
이번 글은 그 숙제에 대한 답인 LSTM이다. 1997년에 나온, 꽤 오래된 구조다.
교육 자료는 대외비라 슬라이드 이미지나 예제 데이터는 싣지 않는다. 개념 흐름만 내가 이해한 방식으로 재구성했고, 그림은 전부 생성했다.
1. 핵심 아이디어 — 통로를 둘로 나눈다
LSTM의 이름은 Long Short-Term Memory, 우리말로 장단기 기억이다. 이름에 답이 다 들어 있다.
기억을 두 갈래로 나눠서 관리한다.
| 하는 일 | 성격 | |
|---|---|---|
| cell state | 오래 남길 기억 | 웬만하면 건드리지 않고 그대로 흘려보낸다 |
| hidden state | 지금 당장 쓸 기억 | 매 단계 새로 만들어 밖으로 내보낸다 |
RNN은 이 두 역할을 하나의 메모가 다 했다. 그러니 지금 쓸 정보를 갱신하다 보면 오래 간직해야 할 정보까지 같이 뭉개졌다.
LSTM은 이걸 분리했다. 위쪽 굵은 선(cell state)은 곱셈과 덧셈만 지나간다. 매 단계 통째로 새로 계산되는 게 아니라서, 값이 멀리까지 살아남는다.
더 쉬운 비유로
여행 중에 짐을 관리한다고 하자. RNN은 가방 하나로 버틴다. 새 물건이 생기면 가방을 통째로 다시 싸는데, 그러다 보면 예전 물건이 자꾸 밀려 나간다.
LSTM은 가방을 둘로 나눈다. 캐리어(cell state)에는 여행 내내 필요한 짐을 넣어두고 웬만하면 안 연다. 크로스백(hidden state)에는 지금 당장 쓸 것만 넣고 수시로 바꾼다.
새 물건이 생겼다고 캐리어를 다 뒤집지 않는다. 필요한 것만 넣고 빼면 된다.
이 구조 덕분에 기울기 소실도 완화된다. 지난 글에서 신호가 0.5배씩 곱해지며 사라지는 걸 봤는데, cell state는 거의 그대로 통과하는 길이 있어서 신호가 훨씬 멀리까지 전달된다.
2. 게이트 — 여닫는 정도를 스스로 정한다
“필요한 것만 넣고 뺀다”를 어떻게 구현할까? 여기서 게이트(gate)가 나온다.
게이트 = 정보를 얼마나 통과시킬지를 0과 1 사이 값으로 정하는 장치
값의 의미는 직관적이다.
| 게이트 값 | 뜻 |
|---|---|
| 0에 가까움 | 거의 막는다 (이 정보는 버린다) |
| 0.5쯤 | 절반만 통과시킨다 |
| 1에 가까움 | 거의 다 통과시킨다 (그대로 유지한다) |
여기서 제일 중요한 건 이 값을 사람이 정하지 않는다는 점이다.
게이트 값은 매 시점마다 새로 계산된다. 지금 들어온 단어와 직전 기억을 보고, 상황에 맞게 열림 정도를 스스로 정한다.
즉 “이럴 땐 열고 저럴 땐 닫아라”를 프로그래밍하는 게 아니라, 여닫는 요령 자체를 데이터에서 배운다.
수도꼭지를 떠올리면 쉽다. 잠그면 0, 활짝 열면 1, 반쯤 열면 0.5다. LSTM은 수도꼭지를 얼마나 돌릴지 스스로 판단하는 법을 배우는 셈이다.
0과 1 사이 값은 어떻게 만드나
지난 시리즈에서 나온 시그모이드가 여기서 다시 등장한다. 어떤 숫자를 넣어도 0과 1 사이로 눌러 담는 함수였다.
게이트는 전부 시그모이드로 만든다. 로지스틱회귀에서 확률을 만들던 그 함수가, 여기서는 밸브의 개폐 정도를 만든다. 같은 도구가 문맥에 따라 다른 역할을 하는 게 재밌는 지점이다.
3. 세 개의 게이트
LSTM에는 게이트가 세 개 있다. 각각 하는 일이 명확하다.
| 게이트 | 하는 일 | 한 단어로 |
|---|---|---|
| Forget gate | 이전 기억 중 무엇을 버리고 무엇을 유지할지 정한다 | 지우기 |
| Input gate | 새 정보 중 얼마나 기억에 써넣을지 정한다 | 쓰기 |
| Output gate | 기억 중 얼마나 밖으로 내보낼지 정한다 | 읽기 |
지우고, 쓰고, 읽는다. 파일을 다루는 동작과 똑같다. LSTM은 결국 기억이라는 저장소에 대한 읽기·쓰기 장치인 셈이다.
① Forget gate — 버릴 것을 고른다
가장 먼저 하는 일이 비우기다.
새 기억 후보 = 이전 기억 × forget gate 값
값이 0이면 그 기억은 사라지고, 1이면 그대로 남는다.
왜 지우는 게 먼저일까? 예를 들어 문장의 주어가 바뀌면, 앞 주어에 대한 정보는 더 이상 필요 없다. 그때 붙들고 있으면 오히려 방해가 된다.
책상 정리와 같다. 새 책을 펴기 전에 다 본 책을 먼저 치운다. 안 치우면 새 책 놓을 자리가 없다.
② Input gate — 새로 쓸 것을 고른다
이번에 들어온 단어에서 기억할 만한 내용을 만들고, 그중 얼마나 반영할지를 정한다.
기억 = (지운 기억) + (새 내용 × input gate 값)
두 단계로 나뉘어 있다는 게 포인트다.
- 무엇을 쓸까 — 이번 단어로부터 새 후보 내용을 만든다
- 얼마나 쓸까 — input gate가 그 비중을 정한다
관사나 조사처럼 별 정보가 없는 단어가 들어오면 게이트를 거의 닫아버리면 된다. 모든 단어를 똑같은 무게로 받아 적지 않는다는 것이 RNN과의 큰 차이다.
③ Output gate — 꺼내 쓸 것을 고른다
갱신된 기억 전체를 매번 내보내지는 않는다. 지금 필요한 부분만 꺼낸다.
내보낼 값 = 기억 × output gate 값
이 부분이 처음엔 신기했었다. 기억이 있는데 왜 다 안 쓰지?
생각해보면 당연하다. 시험 볼 때 머릿속에 아는 게 아무리 많아도 지금 문제에 필요한 것만 꺼내야 답을 쓸 수 있다. 아는 걸 전부 쏟아내면 오히려 답이 산으로 간다.
이 덕분에 cell state에는 정보를 넉넉히 쌓아두고, hidden state로는 필요한 것만 흘려보내는 분업이 완성된다.
4. 그래서 뭐가 좋아졌나
| RNN | LSTM | |
|---|---|---|
| 기억 통로 | 하나 | 둘 (cell state + hidden state) |
| 정보 갱신 | 매 단계 통째로 새로 | 필요한 부분만 지우고 쓴다 |
| 긴 문장 | 앞부분을 잊는다 | 훨씬 오래 유지한다 |
| 계산량 | 적다 | 게이트 세 개만큼 더 많다 |
공짜는 없다. LSTM은 RNN보다 계산이 몇 배 무겁다. 하지만 긴 문장을 다뤄야 하는 작업에서는 그만한 값을 한다.
한동안 번역, 음성 인식, 문장 생성의 표준이 LSTM이었다. Transformer가 나오기 전까지 자연어처리의 주력이었다고 봐도 된다.
그래도 남는 한계
LSTM이 기억 문제를 완화한 건 맞지만 해결한 건 아니다.
- 아무리 게이트를 잘 써도 정보를 한 줄로 흘려보내는 구조 자체는 그대로다
- 100단어, 1000단어로 길어지면 여전히 앞부분이 희미해진다
- 그리고 순서대로 처리해야 하니 느리다. 앞 단어를 계산해야 다음 단어를 계산할 수 있다
마지막 항목이 나중에 아주 큰 문제가 된다. 지난 시리즈에서 신경망이 빠른 이유가 서로 독립인 계산을 한꺼번에 하기 때문이라고 정리했는데, RNN 계열은 구조상 그걸 못 한다. 1번 단어를 끝내야 2번 단어를 시작할 수 있다.
이 두 가지 — 여전히 남은 거리 문제와 병렬 처리 불가 — 가 나중에 Transformer를 부르게 된다.
정리
-
LSTM = 기억 통로를 둘로 나눈 RNN. 장기 기억과 단기 기억을 분리했다
- cell state — 오래 남길 기억. 곱셈·덧셈만 지나서 멀리까지 살아남는다
- hidden state — 지금 쓸 기억. 매 단계 새로 만들어 내보낸다
- 게이트 = 정보를 얼마나 통과시킬지 정하는 0~1 밸브. 값은 매번 스스로 계산된다
- 게이트 세 개 = 지우기 / 쓰기 / 읽기
- Forget gate — 필요 없어진 기억을 비운다
- Input gate — 새 내용을 얼마나 써넣을지 정한다
- Output gate — 기억 중 지금 쓸 부분만 꺼낸다
- RNN보다 훨씬 오래 기억하지만 계산은 더 무겁다
- 남은 한계 — 정보를 한 줄로 흘려보내는 구조는 그대로, 그리고 병렬 처리가 안 된다
다음 글에서
다음 글에서는 시선을 조금 넓혀 언어 모델이 무엇인지 정리하고, 문장을 받아 문장을 만들어내는 Seq2Seq 구조를 다룬다. 번역기가 실제로 어떻게 동작하는지가 여기서 나온다.
한줄 평
- 다시 정리하는걸 검토해보니 나름 쉬운거 같기도 하네요?