지난 포스팅에서는 Kiwi에 내장된 통계 기반 언어 모델이 가지는 한계를 살펴보고 신경망 모델 도입 시 얻을 수 있는 혜택이 무엇인지 다뤘습니다. 그리고 어떤 신경망 구조를 사용할지 결정하기 위해서 다음과 같은 조건을 설정했다고 했었죠.
또 신경망 모델 중 가장 정교한 것은 Transformer Decoder를 사용한 GPT 계열 생성 모델이고, 가장 가벼운 것은 Word2Vec CBOW라고 언급했었습니다. 당연히 GPT 모델을 사용할 수 있으면 최고겠지만, 위의 두 조건을 달성할 수 없으니 현실과 타협하면서 모델 구조를 경량화해야했는데요, 오늘 포스팅에서는 경량화를 통해 도달하게 된 CoNg 모델의 구조와 그 학습 방법을 소개하고자 합니다.
Kiwi CoNg 포스팅 시리즈
무엇이든 탐색을 시작할 때는 베이스라인을 잘 잡고 가는 것이 중요합니다. 오늘날 신경망 기반 언어 모델에서 사실상 표준은 GPT이므로 일단 GPT모델을 베이스라인으로 잡고 시작해봅시다. GPT 모델은 1부터 시작해서 현재 4.5까지 다양한 버전이 나왔는데, 사실 오픈소스로 공개된 것은 GPT-1, 2밖에 없고 나머지는 그 실체나 내부구조를 정확히 알 수 없습니다. 따라서 GPT-2를 베이스라인으로 삼았습니다. GPT-2는 작은 크기인 Base부터 가장 큰 크기인 XL까지 총 4가지 규모로 공개가 되었습니다. 각 크기별로 하이퍼 파라미터의 값을 정리해보면 다음 표와 같습니다.
모델 크기 | Vocab Size | Hidden Size | Num Heads | Num Layers |
---|---|---|---|---|
(Small) | 50256 | 512 | 8 | 6 |
Base | 50256 | 768 | 12 | 12 |
Medium | 50256 | 1024 | 16 | 24 |
Large | 50256 | 1280 | 20 | 36 |
XL | 50256 | 1600 | 25 | 48 |
Small사이즈는 OpenAI에서 공개한 바는 없지만 개인적으로 판단하기에 Base조차도 CPU에서 돌리기에는 다소 부담이 있다고 생각하여 더 작은 크기인 Small을 임의로 비교군에 추가하였습니다. 이 모델들을 가지고 $P(x|y)$, 즉 문맥 $y$ 다음에 형태소 $x$가 등장할 확률이 얼마인지 추정할때 얼마나 많은 연산이 필요한지 FLOPs단위로 연산량을 추정해보았습니다.
총 연산량은 문맥 길이에 크게 좌우됩니다. 그래서 문맥 길이가 4인 경우, 16인 경우, 256인 경우를 나누어 보았습니다. Intel이 공개한 자료에 의하면 최신 CPU들의 일반적인 부동소수점 연산능력은 100~1000 GFLOPs정도(초당 100~1000 GFLOP을 연산 가능)가 된다고 합니다. 위에서 추산한 것은 형태소 1개의 확률을 계산하는데에 드는 연산량입니다. 문장 하나를 형태소 분석한다고 하면 이와 같은 형태소 확률 계산을 문장 길이에 따라 다르겠지만 최소 몇 백 개에 몇 천 개까지 해야할 수 있습니다. 대략 1000번 이 연산을 수행한다고 가정해봅시다. 아주 작은 (Small) 모델에 문맥길이도 아주 짧게 4를 사용한다고 쳐도, 1회에 0.1GFLOP의 연산이 필요합니다. 1000회 한다고 치면 총 100GFLOP이 필요하구요, 연산성능이 1000GFLOPs인 CPU가 아주 이상적으로 작동하면 이걸 0.1초만에 처리할 수 있을 것입니다(형태소 분석에 들어가는 다른 연산들은 일단 무시하구요). 그럼 1초에 10문장 정도를 처리할 수 있겠네요. 참고로 현재 KnLM 기반의 형태소 분석기는 초당 1000~2000개 정도의 문장을 처리할 수 있습니다. 즉 아주 작은 Small 모델을 쓴다해도 KnLM보다 100배 이상 느려진다는 것입니다. 즉 GPT 구조를 Kiwi내에 그대로 넣을 생각은 꿈도 꾸지 말라는 결론이 되겠네요~
그래도 GPT가 잘하니깐 포기하기는 아쉽습니다. 모델 크기를 더 줄이거나 문맥을 더 줄이면 어떻게 안될까요? 극단적으로 문맥 길이를 1로 줄인다고 하면 연산량을 약 1/4로 줄일 수 있습니다. 근데 문맥 길이가 1이라고 하는 것은 바로 이전 단어만 보고 다음 단어를 예측한다는 건데 이럴거면 GPT모델을 쓸 필요가 있느냐는 근본적인 물음에 직면하게 됩니다. 이 물음에 대해서는 이따가 더 고민하는 것으로 하고 일단 대척점에 서 있는 제일 가벼운 모델인 Word2Vec으로 가봅시다.
Word2Vec CBOW(Continuous Bag Of Words)은 사실 다음 단어를 예측하는 모델은 아닙니다. 주변의 단어들을 이용해서 중심 단어를 예측하는 모델입니다. 예를 들자면 다음과 같습니다.
오늘 점심은 ___을 먹자
여기서 빈 칸에 들어갈 내용을 찾는데, 주변의 N개의 단어를 이용하겠다는게 Word2Vec CBOW의 접근법이고, 이 때 이 N개의 단어는 순서를 전혀 고려하지 않습니다. 즉 순서 상관없이 (오늘, 점심, 은, 을, 먹자)가 주변에 있을 때 어떤 단어 $x$가 등장할 확률을 구하는게 목적인 것이지요. 하지만 우리는 다음 형태소를 예측하는데에 이걸 쓰고 싶은 것이므로 CBOW의 정의에서 "주변의 단어"를 "왼쪽에 있는 단어"로 바꾸어서 사용하도록 하겠습니다. 따라서 (오늘, 점심, 은)이 순서 상관없이 왼쪽에 있을 때 어떤 단어 $x$가 등장할 확률을 구하는게 변경된 목적이 됩니다. 이러면 다음 형태소의 확률을 예측하는데에 충분히 쓸 수 있습니다. 여기서 $P(x | y_1, y_2, \cdots , y_L)$는 길이가 $L$인 문맥 $y$ 다음에 형태소 $x$가 등장할 확률을 계산하는 함수입니다. $\mathbf H$는 입력 문맥의 임베딩을 평균내는 함수(->문맥을 임베딩으로 변환), $\mathbf D$는 출력 형태소의 임베딩을 구하는 함수(->다음 형태소를 임베딩으로 변환)이 됩니다.
$$ P(x | y_1, y_2, \cdots , y_L) = \mathbf{Softmax}( \mathbf{H}(y_1, y_2, \cdots , y_L) \cdot \mathbf{D}(x) ) $$
Word2Vec CBOW의 연산량을 관장하는 하이퍼파라미터는 딱 3가지 밖에 없습니다. 전체 Vocab Size와 문맥의 길이, 그리고 Embedding의 크기입니다. 위의 GPT와 직접 비교가 가능하도록 Small 모델과 마찬가지로 Vocab Size는 50256, Embedding 크기는 512라고 해봅시다. CBOW의 연산량은 다음과 같이 간단하게 계산할 수 있습니다.
$$\mathbf{CBOW\_FLOP} = \mathbf{ContextLength} \cdot \mathbf{Dim} + \mathbf{VocabSize} \cdot \mathbf{Dim} $$
2개의 항으로 이뤄져있는데 왼쪽은 입력 문맥 임베딩들의 평균을 구하는 부분이고 오른쪽은 출력 임베딩들의 확률을 계산하는 부분입니다. 입력 문맥 길이가 4라면 왼쪽 항은 4 * 512가 되어 매우 작은 값이고, 사실상 오른쪽 항(50256 * 512)이 전체 연산량을 좌우하는 부분이됩니다. 실제로 계산해보면 약 0.026 GFLOP이 나오네요. GPT Small보다 4배 정도밖에 연산량이 안 줄었습니다. 가장 가볍다는 녀석도 이렇게 연산량이 많아서야 도저히 Kiwi 내장 모델로 사용할수가 없겠습니다.
병목은 위에서 언급했다시피 출력 임베딩들의 확률 계산하는 부분에 있습니다. 전체 형태소가 50256가지라면 50256가지에 대해 그 확률을 모두 계산해야하기 때문에 연산량이 늘어나늑 것이죠. 근데 Kiwi에 내장된 언어 모델이 해야할 것은 다음 형태소를 예측하는게 아니라 다음 형태소가 주어졌을때 그 확률을 계산하는 것이라고 했죠? 그런데도 꼭 전체 형태소들에 대해 계산을 해야할까요? 답부터 말하자면 예, 계산해야 합니다. 어쩔수가 없습니다. 그 이유는 출력부분에 붙은 Softmax 때문입니다. Softmax연산을 풀어 적으면 다음과 같습니다.
$$P(a) = \frac{e^{s_a}}{\sum_i{e^{s_i}}}$$
예를 들어 위와 같이 "오늘 점심은"이라는 왼쪽 문맥이 주어졌고, 우리가 가지고 있는 후보 형태소는 "짜장면, 제육, 냉면, 돈까스, 국밥" 5가지가 있다고 가정해봅시다. $\mathbf H$의 결과와 $\mathbf D$의 결과를 가져와 내적을 구했더니 다음과 같은 값이 나왔네요.
$$s_{짜장면} = 1.2 \\
s_{제육} = 5.1 \\
s_{냉면} = -0.1 \\
s_{돈까스} = 2.7 \\
s_{국밥} = -0.6 $$
이 값들은 확률의 범위가 아니기에 Softmax 연산을 통해서 0~1사이 값으로 정규화해줘야 제대로된 확률값이 나오게됩니다.
$$P(짜장면) \approx 0.018 \\
P(제육) \approx 0.892 \\
P(냉면) \approx 0.004 \\
P(돈까스) \approx 0.081 \\
P(국밥) \approx 0.002 $$
그래서 만약 다음 형태소가 "짜장면"일 확률만 알고싶다고 하더라도, 짜장면부터 국밥까지 모든 $s_i$값을 구해야 짜장면의 확률을 제대로 알 수 있습니다. 이게 출력항에서 모든 형태소에 대한 임베딩을 다 계산하는 이유입니다. 그 중 1개 형태소 값만 알고싶은데 전체를 다 계산해야한다니 너무 낭비가 심하다고 볼수도 있죠. 만약 우리가 $\sum_i{e^{s_i}}$를 알고 있다면 $s_{짜장면}$만 구해도 $P(짜장면)$을 구할 수 있을텐데요. 이게 가능하다고 하면 전체 CBOW의 연산량은 다음과 같이 줄어들 겁니다.
$$\mathbf{CBOW\_FLOP'} = \mathbf{ContextLength} \cdot \mathbf{Dim} + \mathbf{Dim} $$
이 경우 총 연산량은 약 0.000003 GFLOP이 되어 전보다 만 분의 일 정도로 적은 연산만으로도 확률을 추정할 수 있게 됩니다! 근데 과연 모든 $s_i$를 구하지 않고도 분모항인 $\sum_i{e^{s_i}}$를 구할 수 있는 방법이 있을까요? $s_i$값은 입력 문맥 임베딩의 평균에 좌우됩니다. 즉 특정한 입력 문맥 임베딩의 평균에 대해 분모항 $\sum_i{e^{s_i}}$를 미리 계산해두고, 실제로 이 값이 필요할때는 미리 계산된 값을 가져다가 쓴다면 분모항 $\sum_i{e^{s_i}}$ 계산을 우회할 수 있습니다! 그런데 가능한 입력 임베딩의 평균은 몇 가지나 될까요? 문맥 길이를 $L$이라고 하고, 알고 있는 형태소의 개수가 $V$개라고 하면 총 L개의 형태소가 문맥에 들어갈 것이고, 각 형태소는 $V$개 중에 1개일 것이므로 $V^L$이 가능한 입력의 가짓수일 것입니다. $L=4$, $V=50256$이라고 하면 $6.38 \times 10^{18}$ 정도가 되네요. 이 걸 미리 다 계산해서 저장해두는 건 말이 안됩니다. 만약 문맥 길이를 줄여서 $L=1$ ~ $2$ 정도로 한다면 충분히 미리 계산해둘만한 크기가 되긴 하겠네요. 근데 지금 KnLM이 사용하는 문맥 길이가 3~4이므로 왼쪽 형태소 1개 혹은 2개만을 보는 신경망 모델은 크게 메리트가 없습니다.
가볍고 빠른 신경망 모델을 향한 이렇게 여정은 실패로 끝나고 마는 것일까요? 다행히도 기존 Kiwi가 사용하던 KnLM에 돌파구가 있었습니다.
문맥 길이가 4인 형태소들을 모두 찾아서 분모항을 미리 계산해두자는게 위의 최적화 아이디어였는데, 굳이 모든 형태소 조합을 고려해야할까요? "오늘, 점심, 메뉴, 는"이라는 형태소 조합과 "알고리즘, 초콜렛, 뛰, 는"이라는 형태소 조합이 있다고 했을때 둘을 동등하게 저장해두는게 맞을까요? 사실 전자는 충분히 접할 가능성이 있는 반면 후자는 형태소 분석기가 거의 접할 일이 없는 형태소 조합입니다. 물론 후자와 같은 표현도 언젠가 자연스레 나타날 가능성이 있겠지만, 현대 한국어에서는 그럴 일이 없을것 같습니다. 따라서 문맥 길이가 4인 형태소 조합들의 빈도수를 조사하여 자주 쓰이는 것들만 미리 계산해둔다면 저장해둬야할 분모항의 개수도 줄어들면서 연산량도 크게 줄일 수 있을 것입니다. 사실 이렇게 자주 쓰이는 표현들만 골라서 사용하는 것은 이미 KnLM에서 쓰이는 아이디어입니다. 실제로 KnLM에서도 문맥 길이를 최대 4까지 확장하여 쓰지만, 실제로 다루게 되는 문맥은 1000만 종류 안쪽입니다. 1000만 개 이내라면 충분히 계산해서 저장해둘만한 크기이지요.
그런데 자주 쓰이는 문맥 표현만 선택해서 계산해둔다는 방식이 꼭 CBOW에만 적용될 필요는 없습니다. 동일하게 GPT에도 적용할 수 있습니다.
$$ P(x | y_1, y_2, \cdots , y_L) = \mathbf{Softmax}( \mathbf{H}(y_1, y_2, \cdots , y_L) \cdot \mathbf{D}(x) ) $$
GPT모델이 다음 토큰의 확률을 계산하는 과정을 크게 멀리서 보면 결국 Word2Vec CBOW과 다를게 없습니다. 다만 $\mathbf H$가 단순히 입력 문맥의 임베딩 평균을 계산하는게 아니고, 문맥 내의 토큰들에 대해서 Self-attention과 Feed Forward Network처럼 좀 더 복잡한 연산을 수행한다는 차이만 있을 뿐이지요. 어쨌든 $y$가 이미 결정되어 있다면 $ \mathbf{H}(y_1, y_2, \cdots , y_L) $도 항상 값이 결정되므로 CBOW와 마찬가지로 미리 계산해둘 수 있습니다. 그러면 우리는 실제 모델 추론 시에는 그냥 두 임베딩 간의 내적 연산 한번만 수행해주면 GPT와 동일한 결과를 얻을 수 있습니다.
지금까지의 아이디어를 통해 등장한 것이 바로 Contextual N-gram embedding(줄여서 CoNg)입니다. n-gram 자체가 의미를 반영한 문맥 임베딩으로 바로 대응된다는 점에서 이렇게 이름 붙였습니다. (사실 Context2Vec, Ngram2Vec 같은 이름을 쓰고 싶었는데 이미 다른 연구에서 쓰이고 있어서...) 바로 위에서 설명한 것처럼 자주 쓰이는 문맥에 대해서는 미리 계산을 하는 방식을 사용하고, 여기에 추가로 저장할 문맥의 숫자를 줄이기 위해 유사한 문맥들은 묶어서 하나의 클러스터를 구성하도록 한 것입니다. 예를 들어 "오늘 점심 메뉴는", "내일 점심 메뉴는", "이번 중간 고사는", "이번 기말 고사는"이라는 문맥이 있다고 생각해볼까요? 해당 문맥 다음에 등장할 형태소들을 생각해 볼 때 "오늘 점심 메뉴는"과 "내일 점심 메뉴는"은 서로 굉장히 유사할 것이고, "이번 중간 고사는"과 "이번 기말 고사는"도 역시 서로 굉장히 유사할 것입니다. 따라서 다음과 같이 유사한 문맥들은 동일한 문맥 클러스터에 할당합니다.
문맥 클러스터 | 문맥들 |
---|---|
$c_0$ | 오늘 점심 메뉴는, 내일 점심 메뉴는, ... |
$c_1$ | 이번 중간 고사는, 이번 기말 고사는, ... |
$c_2$ | ... |
... | ... |
이렇게 길이가 $L$이하인 자주 쓰이는 문맥들에 대해서 유사한 것끼리 동일한 클러스터로 할당하고, 실제로 $\mathbf H$를 계산할 때도 문맥 클러스터를 인자로 넣어줍니다. 어차피 $\mathbf H$의 결과는 입력 $c_k$에 의해 결정되는 것이므로 내부에 복잡한 연산을 둘 필요도 없이 Embedding Layer에서 $k$에 해당하는 임베딩 값을 읽어오는 식으로 구현해도 상관없습니다.
$$ P(x | c_k) = \mathbf{Softmax}( \mathbf{H}(c_k) \cdot \mathbf{D}(x) ) $$
이제 주어진 문맥을 대응하는 문맥 클러스터로 적절하게 바꿔서 넣어주기만 하면 다음 형태소의 확률을 계산할 수 있게 된 것입니다. 근데 우리는 길이가 $L$인 모든 종류의 형태소 조합을 다루지는 않을 것이므로 만약 어느 문맥 클러스터에도 속하지 않는 문맥이 들어왔을 경우의 대책을 마련해놓아야합니다. 백업 플랜으로 어느 문맥 클러스터에도 속하지 않는 문맥이 입력되면 문맥에서 제일 왼쪽의 형태소를 잘라버려서 문맥을 줄이도록 합니다. 또한 문맥 클러스터를 구성할때 길이가 $L$인 것만을 포함하지 말고, 1~$L$까지 모두를 포함하는 것으로 합시다. 그리고 길이가 1인 문맥은 저빈도여도 버리지 않고 항상 대응되는 문맥 클러스터가 존재하도록 배정하겠습니다. 이렇게 하면 우리는 어떤 문맥이 들어오더라도 항상 대응되는 문맥 클러스터를 찾을 수 있습니다.
예를 들어 "어제 점심 메뉴는"이라는 문맥이 입력되었는데, 대응되는 문맥 클러스터가 없다고 가정해봅시다. 그럼 여기에서 왼쪽 형태소 하나를 잘라버려서 "점심 메뉴는"이라는 문맥을 찾는 것으로 계획을 변경합니다. 만약 "점심 메뉴는"에 대응되는 클러스터를 찾았다면 여기서 마무리하고, 그렇지 못했다면 형태소를 한 번 더 잘라서 "메뉴는"을 찾아봅니다. 만약 "메뉴는"도 없다면 마지막으로 한 번 더 형태소를 잘라서 "는"을 찾아봅니다. 길이가 1인 문맥은 저빈도여도 모두 포함하기로 했으므로 이 지점에서 항상 대응되는 클러스터를 찾을 수 있습니다. 왼쪽 형태소가 하나씩 잘려나갈때마다 원 문맥과의 유사성이 조금씩 낮아지긴 하겠지만, 그래도 모르는 문맥이라고 아예 오동작하는 것보다는 훨씬 낫겠죠?
자 이제 문맥 클러스터를 잘 구성하기만 하면 CoNg 모델이 최종적으로 완성됩니다. 당연히 실제로 의미가 유사한 문맥들을 같은 클러스터에 모아두어야지 모델이 GPT 모델과 유사한 품질에 도달할 수 있겠죠? 수많은 문맥들 중 의미가 유사한 것들을 어떻게 묶어낼 수 있을까요? GPT모델의 $\mathbf H$함수를 생각해봅시다. 이 함수의 역할은 결국 주어진 문맥을 그 의미가 잘 반영되는 임베딩으로 바꾸어서 $\mathbf D$와 내적을 구하면 다음 형태소가 적절하게 나오게 하는 것입니다. 즉 $\mathbf H$ 함수의 출력값이 그 자체로 의미를 잘 반영하는 임베딩 값이라고 볼 수 있다는 것이죠. 따라서 먼저 일반적인 GPT 모델을 학습시키고, 이 $\mathbf H$의 출력들을 모아서 클러스터링을 수행하면 유사한 문맥 클러스터를 얻을 수 있을 겁니다.
위 과정을 통해 우리는 GPT모델을 CoNg 모델로 변환할 수 있습니다. 복잡한 Transformer레이어들로 구성된 GPT 모델의 $\mathbf H$는 선택된 문맥 클러스터들에 대해서 미리 계산되어 CoNg에서는 단순한 Embedding lookup 연산으로 바뀌게 됩니다. 속도는 빨라지면서 정확도 손실은 최소화하는 것이죠. 실제로 효과가 있는지 간단한 파일럿 실험을 해보았습니다.
Vocab Size | 70128 |
---|---|
Hidden Size | 384 |
Num Heads | 6 |
Num Layers | 4 |
학습데이터 | [모두의 말뭉치] 형태 분석 말뭉치 |
모두의 말뭉치에 있는 형태 분석 말뭉치를 대상으로 형태소들을 뽑아 Vocab을 구성한뒤 이를 통해서 GPT 모델을 학습시켰습니다. 그리고 위에서 설명한 절차대로 CoNg 모델을 학습시켰습니다. 문맥의 최소 빈도수는 5, 최대 길이는 4로 고정했고, 문맥 클러스터의 개수를 바꿔가면서 성능이 어떻게 바뀌는지 측정해보았습니다.
평가 척도는 Next Token Prediction의 Loss, 즉 Negative Log-likelihood입니다. 낮을수록 다음 형태소를 더 정확하게 예측한다는 뜻이죠. 다음과 같이 3가지 데이터셋에 대해 평가해보았습니다.
가장 기본적인 베이스라인은 통계 언어 모델인 KnLM입니다. KnLM은 학습 때 배운것과 동일한 데이터셋(In-Domain)에서는 아주 높은 예측력을 보이지만 배우지 못한 입력(Out-of-Domain)에서는 매우 취약한 모습을 보입니다(주의: 실제 loss값은 5.6정도였는데 차트 내에 끼워넣고자 억지로 5.0으로 끌고 내려왔음).
반면 GPT를 사용할 경우 ID에서는 KnLM보다 살짝 예측력이 떨어지지만 OOD에서 예측력이 크게 개선됩니다. 즉 모델이 배우지 않은 것에 대해서도 제법 잘 예측할 수 있다는 것이지요. 그리고 실제로 CoNg의 경우 문맥 클러스터 개수가 작을 때는 예측력이 많이 낮았지만, 클러스터 개수를 증가시킬수록 점차 GPT모델과 유사한 성능에 다가간다는 것을 확인할 수 있습니다. 실제로 실험하는 것은 불가능하지만 길이가 4일때 가능한 모든 문맥의 개수($70128^4 \approx 2.379 \cdot 10^{16} k$)로 클러스터 개수를 늘리면 이론상 GPT와 완전히 동일한 성능이 되겠죠. 물론 저만한 크기의 문맥 클러스터를 전부 메모리에 담는것은 불가능하기 때문에 적당한 위치에서 타협하는게 필요합니다.
ID 성능이 KnLM보다는 많이 떨어지는게 아쉽긴 하지만, 사실 현재 학습에 쓸 수 있는 형태 분석 말뭉치의 규모가 작다는 것을 생각해볼때 ID은 굉장히 좁고, 실제로 분석 대상이 될 텍스트들은 대부분 OOD일 것이 자명하기 때문에 ID에서의 예측력을 살짝 잃더라도 OOD에서 예측력을 높이는게 중요하다고 판단했습니다.
모델 성능은 그럭저럭 나오는것 같으니 모델 크기가 어떻게 될지 계산해봅시다. CoNg 모델의 파라미터는 문맥 클러스터 개수 $s$, Vocab Size $v$, 그리고 Hidden Size $d$ 세 가지 하이퍼파라미터에 의해 결정됩니다.
$$\mathbf {Num\:of\:Param\:of\:CoNg} = sd + vd$$
GPT-Baseline | 61.03M |
---|---|
CoNg(32k) | 39.56M |
CoNg(64k) | 52.14M |
CoNg(128k) | 77.30M |
CoNg(256k) | 127.64M |
CoNg(512k) | 228.30M |
문맥 컨텍스트 개수 $s$를 늘릴수록 모델의 파라미터가 팍팍 늘어나는 것을 확인할 수 있습니다. 8비트 양자화를 한다고 가정해도 CoNg(256k)면 벌써 모델 크기가 100MB를 넘어갑니다. 모델 크기는 아쉽지만 그렇다고 $s$를 작게 잡으면 성능이 낮아지니 일단은 넉넉하게 $s$를 잡고 다양한 경량화 및 최적화 기법을 적용하는게 좋아보입니다.
CoNg 구조를 떠올린 다음 유사한 연구가 있나 찾아보니 다음과 같이 Scone이라는 모델에 대한 연구가 있더라구요.
Scone과 비교해보면 CoNg는 Frequent n-gram을 사용한다는 점에서는 동일하지만 Frequent n-gram을 그대로 모델의 입력으로 사용하는 대신 문맥 클러스터를 만들어서 클러스터ID를 입력으로 사용했다는 점, 모델의 최종 구조가 Transformer 레이어가 전혀 포함되지 않고 클러스터ID가 들어가는 임베딩 레이어 하나와 출력 형태소의 LM Head 하나만 존재하는 단순한 구조라는 결정적인 차이점이 있습니다.
위에서 살펴보았다시피 CoNg 모델은 입력 문맥의 길이를 극도로 짧게 잡은 GPT 모델에서 미리 계산 가능한 부분은 다 계산해두어 연산량을 극적으로 줄인 버전에 가깝습니다. 간단한 글에서는 바로 이전의 3~4개의 형태소만 봐도 문맥을 파악할수 있으므로 입력 문맥의 길이가 짧은 것이 문제가 되지 않으나, 실제로 글이 좀만 복잡해져도 긴 문맥을 다루지 못한다는 것은 치명적인 문제로 다가옵니다. 그래서 KnLM에 Skip-Bigram 모델을 도입하여 먼 거리의 형태소 간의 관계를 모델링한 것처럼 CoNg 모델에도 비슷한 아이디어를 적용하기로 했습니다.
주로 먼 거리에서 영향을 주고 받는 형태소들은 조사나 어미 같은 형식 형태소(문법적인 역할만 수행할뿐 실제 가리키는 대상은 없음)가 아니라 명사, 형용사, 동사 등의 실질 형태소(가리키는 대상이 존재)일 것이므로 다음 형태소를 예측할때 바로 이전에 등장했던 실질 형태소 $m$개를 고려하도록 한 것이죠. (이 각각의 실질 형태소들을 $t_i$로 표현하도록 하겠음) 그리고 모델 구조를 단순화하기 위해 각각의 실질 형태소들은 다음 형태소 예측에 독립적 & 선형적으로 영향을 준다고 가정하겠습니다. 다소 과감한 가정이긴 하지만, 여러 실질 형태소들이 동시에 등장하며 영향을 주고 받는 것을 모델링하기 위해서는 다시 Transformer와 같은 복잡한 모델 구조로 회귀해야하기 때문에 정밀성 대신에 단순성을 택한 것입니다. 이렇게 이전의 실질 형태소들이 선형적으로 다음 형태소 예측에 영향을 준다고 가정하고 이들에 대한 항을 추가한 것이 CoNg Global 모델입니다. 수식으로 표현하면 다음과 같겠죠~
$$ P(x | c_k, t_1, t_2, \cdots , t_m) = w_0 \mathbf{Softmax}\left ( \mathbf{H}(c_k) \cdot \mathbf{D}(x) \right) + \sum_{i=1}^m w_i \mathbf{Softmax}\left (\mathbf E (t_i) \cdot \mathbf D x\right ) $$
여기에서 $w_0$ CoNg 모델의 확률 가중치, $w_{i\neq 0}$는 $i$번째 실질형태소의 확률 가중치이고 $\sum_{i=0}^m w_i = 1$ 입니다. $w_0$이 1에 가까워지고 $w_{i\neq 0}$가 0에 가까워질수록 바로 이전 문맥만 고려하게 되고, 반대로 $w_{i \neq 0}$들이 커질수록 먼 거리의 실질 형태소를 더 많이 고려하게 되는 식입니다. 먼 거리의 형태소가 다음 형태소에 미치는 영향을 모형화하기 위해 $\mathbf E$라는 실질 형태소 각각에 대한 Embedding Layer를 추가했습니다. 이 확률은 CoNg에서와 마찬가지로 다음 형태소의 임베딩과 내적을 구한뒤 Softmax를 취해 실제 확률을 계산하게 됩니다. 마찬가지로 $E(t_i)$는 $t_i$가 결정되면 알 수 있으므로 Softmax의 분모항을 미리 계산해두는 것이 여기에서도 가능합니다.
CoNg Global 모델이 잘 동작하려면 확률 가중치 $w_i$를 잘 조정하는게 필수입니다. 이를 사람이 조정하는 것보다는 모델이 학습을 통해 스스로 판단하는게 좋겠다고 생각하여 다음과 같이 계산하도록 모델을 구성했습니다.
$$w_0, w_1, \cdots , w_m = \mathbf{Softmax}(v_0, v_1, \cdots , v_m)$$
$$v_0 = \mathbf V (c_k)$$
$$v_{i \neq 0} = \mathbf U (t_i)$$
즉 각각의 가중치 $w_i$는 Softmax를 통해서 계산을 하는데 $v_0$은 이전 문맥의 중요성을 추정하는 함수 $\mathbf V$로부터 가져오고 $v_{i \neq 0}$는 이전 실질 형태소의 중요성을 추정하는 함수 $\mathbf U$로부터 가져오도록 했습니다. 실제로 $\mathbf V(\cdot)$, $\mathbf U(\cdot)$는 그저 학습가능한 실수 파라미터로 두어 모델이 적절한 값을 추정하도록 했구요.
CoNg Global 모델을 학습하는 것은 CoNg을 학습하는것과 거의 동일합니다. 5번 과정에서 CoNg 자리에 CoNg Global 모델이 들어가고 새로 추가된 $\mathbf E$ 임베딩은 랜덤으로 초기화한다는 점만 다를뿐입니다. CoNg Global도 실제로 잘 작동하는지 확인하기 위해 위와 동일한 세팅으로 실험을 진행해보았습니다. CoNg Global에서 중요한 하이퍼파라미터는 고려할 실질 형태소의 개수 $m$입니다. 아래 실험에서는 $m=7$로 고정하였습니다. 7을 선택한 이유는 적당히 높으면서 효율적인 숫자이기 때문으로 7로 잡을 경우 $w_0$부터 $w_7$까지 총 8개의 실수를 연산하게 되는데, 이 개수가 요즘 대부분의 CPU에서 동시에 처리할수 있는 최대 실수의 개수이기 때문입니다.
이전과 동일하게 값이 낮을수록 예측력이 높은것이고, CoNg Global의 효과를 비교하기 위해 동일한 세팅의 CoNg 모델을 같이 차트에 그려놓았습니다. 모든 경우에서 전반적으로 Loss가 내려간 것을 확인할 수 있죠? 먼 거리의 실질형태소를 보는 것이 실제로 도움이 되었다고 할 수 있겠습니다.
그럼 CoNg Global의 모델 크기는 어떻게 될까요? 실질 형태소에 대한 임베딩인 $\mathbf E$, 가중치 $w_i$를 계산하는데 필요한 $\mathbf V$와 $\mathbf U$가 추가되었으니 CoNg모델의 파라미터 개수에 이 파라미터 크기를 추가해주면 되겠죠. 실질 형태소 개수는 전체 Vocab Size보다 작겠지만, 사실 형식 형태소의 비중은 낮으므로 실질 형태소의 개수도 사실상 Vocab Size와 동일하다고 가정하면 $\mathbf E$에는 $vd$개의 파라미터, $\mathbf V$에는 $s$개의 파라미터, $\mathbf U$에는 $v$개의 파라미터가 들어갑니다. 따라서 추가되는 파라미터 양은 $vd + s + v$입니다. 이를 바탕으로 파라미터 개수를 다시 한번 계산해보면 아래와 같습니다.
CoNg | CoNg Global | |
---|---|---|
GPT-Baseline | 61.03M | |
32k | 39.56M | 66.64M |
64k | 52.14M | 79.25M |
128k | 77.30M | 104.48M |
256k | 127.64M | 154.95M |
512k | 228.30M | 255.87M |
전반적으로 30M정도씩 늘어났습니다. 좀 뚱뚱한 것 같긴하네요. 경량화 및 최적화 쪽에서 해야할 일이 쉽지 않아보이지만 일단 그 전에 이 방식이 실제로 얼마나 효과가 있는지 검증해보는게 먼저겠죠? 다음 포스팅에서는 CoNg 및 CoNg Global 모델의 실제 형태소 분석 성능에 대해서 분석해보도록 하겠습니다.
형태소 분석기 Kiwi CoNg (1/4): 신경망 모델 도입기 (0) | 2025.05.04 |
---|---|
한국어 말뭉치를 통한 사이시옷 사용 실태 조사 (1) | 2024.11.04 |
LLM으로 게임 텍스트 번역해보기 (2) | 2024.08.31 |
형태소 분석기의 모호성 해소 성능을 평가해보자 (4) | 2022.03.27 |
[Kiwi] 문장 같은 고유명사 잘 추출해내기 (2) | 2022.03.20 |
Kiwi로 한국어 문장 분리하기 (10) | 2021.12.23 |
댓글 영역