brunch

You can make anything
by writing

C.S.Lewis

by 박성준 Jul 17. 2018

한국어를 위한 어휘 임베딩의 개발 -1-

한국어 자모의 FastText의 결합

이 글은 Subword-level Word Vector Representations for Korean (ACL 2018)을 다룹니다. 두 편에 걸친 포스팅에서는 이 프로젝트를 시작하게 된 계기, 배경, 개발 과정의 디테일을 다룹니다. 논문의 내용은 http://aclweb.org/anthology/P18-1226 에서 확인하실 수 있습니다.


한국어를 위한 어휘 임베딩의 개발 (1) : 이론적 배경



들어가며

오랫동안 자연어처리 분야에서 텍스트 데이터를 처리하는 가장 작은 단위(atomic unit)는 어휘였고, 이는 일반적으로 단위 벡터(one-hot vector)로 표현되었습니다. 간단하게 예를 들면, '나는 사과를 좋아한다'라는 문장에서 등장하는 어휘는 총 3개인데, 이를 각각 단위 벡터로 하면 ‘나는’, ‘사과를’, ‘좋아한다’라는 어휘는 각각 (1, 0, 0), (0, 1, 0), (0, 0, 1)으로 대응됩니다. 즉, 이 3차원 벡터들의 각 차원은 곧 어휘를 나타내는 인덱스가 됩니다. 이 방법은 단순하다는 장점이 있을 뿐만 아니라, 다양한 통계적 자연어 처리(statistical natural language processing)기법에 적용되어 좋은 성과를 보였습니다. 


그럼에도 불구하고, 어휘를 단위 벡터로 표현하는 방법만으로는 어휘 간 의미적, 구문적 유사도를 평가할 수 없었을 뿐만 아니라, 데이터의 규모가 매우 커서 코퍼스 상에서 등장하는 어휘의 종류가 기하급수적으로 증가할 경우, 단위 벡터의 크기가 지나치게 커진다는 단점이 있었습니다, 그래서 이에 대한 대안으로, 어떤 어휘를 벡터로 표현할 때 이를 N차원 공간 상의 연속된 벡터(continuous vector)로 표현하고자 하는 시도들이 있었습니다. 예를 들어, 어떤 코퍼스 상에서 등장하는 어휘의 종류가 10,000개 라면, 이를 각각 10,000차원의 단위 벡터로 표현하는 대신 (0.3, 0.4, 0.5, 0.4) 와 같은 저차원 공간 상의 연속된 벡터로 표현하는 것입니다. 


 

 



어휘의 분산표상 (Distributed Representations of Words) 

 

이렇게 어휘를 연속된 공간 상의 벡터로 표현하는 것은 흔히 “어휘의 분산 표상(distributed representation of word)”이라고 불리며, 줄여서 “어휘의 표상(representation of word)"이라고 하기도 합니다. 여기서 표상은 무엇일까요? 


표상(representation)은 현실에 존재하는 어떤 사물에 대해 우리가 마음 속에 갖고 있는 인지적 상징(cognitive symbol)을 의미합니다. 예를 들어, ‘사과’라는 단어를 보았을 때 우리는 일반적으로 ‘빨갛다’, ‘과일’, ‘꼭지’ 등의 특징을 함께 떠올리게 마련입니다. 이 특징들은 우리 마음 속에 있는 ‘사과’라는 개념을 구성하는 특징들입니다. 이를 어휘에도 적용해본다면, 어떤 어휘의 표상은 그것이 지닌 의미와 구문적인 특성들을 포괄하는 종합적인 것으로써, 많은 사람들이 그러한 특징을 동시에 떠올리고 공유할 수 있기 때문에 언어를 통한 의사소통이 가능해지는 것입니다. 


그래서, 어휘의 벡터 표상(vector representation)은 사람들이 어휘에 대해 갖고 있는 이러한 표상을 벡터의 형태로 나타낸 것입니다. 즉, 이는 어휘가 갖는 의미적(semantic), 구문적(syntactic) 특성을 연속된 벡터공간 상에 표현한 것입니다.


그리고 이러한 표상이 분산(distributed)되어 있다는 말은, 어휘의 표상을 구성하는 이러한 특징들이 벡터의 각 차원마다 독립적으로 할당된 것이 아니라, 모든 차원에 분산되어 있다는 것을 의미합니다. 예를 들어, `사과`에 대응되는 어휘 벡터가 10차원 벡터로 표현되었다면, 각 차원은 ‘빨갛다’, ‘과일’ 등으로 각각 해석가능한 의미로 대응되는 것이라기 보다는, ’빨갛다’ 는 정보는 10차원에 걸쳐서 분산되어 저장되었다는 의미입니다. 이는 곧 어휘의 분산표상의 각 차원은 해석이 어렵지만, 어쨌든 이 모든 속성이 벡터에 저장되었다는 것입니다. 


그렇다면 어휘의 벡터 표상(vector representation)에 포함된 각각의 숫자를 해석하기 어려움에도 불구하고, 벡터에 이러한 정보가 잘 저장되었다는 것은 어떻게 알 수 있을까요? 그것은 다른 벡터와의 관계를 통해 알 수 있을 것입니다. 만약 어떤 어휘를 N차원 공간 상의 벡터로 나타냈을 때, 이 공간 상에서 서로 의미가 유사하거나 구문적 특성이 유사한 어휘들은 이 공간 상에서 서로 가깝게 위치하도록, 그렇지 않은 경우에는 어휘들 간의 거리를 더욱 멀게 할 수 있다면, 아마도 우리는 어휘 벡터들이 잘 학습 되었는지를 평가할 수 있을 것입니다. 


 



어휘의 분산표상의 학습 : Word2Vec (2012) 

 

어휘의 벡터 표상을 학습하는 방법으로 널리 알려진 것은 Word2Vec(2012)이지만, 이를 학습하고자 하는 시도는 Word2Vec 이전부터 있어왔습니다. Word2Vec이 소개되기 10년 전에 이미 제프리 힌튼(Geoffrey Hinton)은 언어 모델링(language modelling)을 위한 인공신경망 모형을 학습하는 과정에서 어휘의 벡터 표현법을 보인 바 있습니다. 뿐만 아니라, 잠재의미분석(Latent Semantic Analysis)과 잠재 디리클레 할당(Latent Dirichlet Allocation)이라는 방법을 이용해 어휘나 문서의 표상 벡터를 구하는 방법이 널리 사용되고 있었습니다. 


그럼에도 불구하고  Word2Vec이 주목을 받게 된 이유는 효율성 때문이었는데, 간단한 인공신경망 모형을 기반으로 하는 Word2Vec은 학습 데이터의 규모가 10억 단어 이상으로 커져도 요구되는 계산량을 낮은 수준으로 유지(computationally cheap)할 수 있었습니다. 또한, 학습 과정을 쉽게 병렬화하여 짧은 시간 안에 양질의 어휘의 벡터 표상을 얻을 수 있다는 장점이 크게 부각되었습니다. 


사실, Word2vec은 두 가지 방법을 통칭하여 이르는 말입니다. 첫 번째 방법은 CBOW(continuous bag-of-words), 두 번째 방법은 Skip-Gram이라고 하는데, 둘의 이름은 매우 다르지만 원리는 크게 다르지 않습니다. 예를 들면, 어떤 문장에서 기준이 되는 단어(target word)가 있고, 그와 가까운 위치에서 함께 등장한 단어를 문맥 단어(context word)라고 해봅시다. “나는 맥주를 매일 마십니다.” 라는 문장에서 “맥주를” 이라는 단어를 기준 단어로 삼으면, 문맥 단어는 “나는”, “매일”이 될 것 입니다. (편의상 문맥 단어는 기준 단어 바로 옆에 있는 단어라고 정의합니다.) 


이렇게 기준 단어와 문맥 단어를 정의하면, CBOW는 문맥 단어를 보고 기준 단어가 무엇인지를 예측하는 모델이고, Skip-Gram은 기준 단어를 보고 어떤 문맥 단어가 등장했는지를 맞추는 모델로서, 각각은 매우 간단한 단일 레이어 인공신경망 모형을 사용합니다. 즉, CBOW와 Skip-Gram은 매우 유사한 구조를 갖지만 입력(input)과 출력(output)이 서로 반대인 모델입니다. 두 모델은 입력으로 주어진 단어를 N차원의 벡터로 투영(projection)한 뒤, 이 벡터를 다시 소프트맥스(softmax) 함수를 이용해 출력 단어를 맞추도록 학습됩니다. 


우리가 흔히 알고 있는 어휘 임베딩은 CBOW 혹은 Skip-Gram 모델이 학습하는 파라미터 중의 일부입니다. CBOW의 경우 주어진 문맥 단어를 사이즈 N의 잠재 레이어로 투영(projection)해야 하고, Skip-Gram은 주어진 기준 단어를 역시 사이즈 N의 잠재 레이어로 투영해야 하는데, 이 때 사용되는 파라미터 (일반적으로 U로 지칭됩니다)가 곧 어휘 임베딩입니다. 즉, 이 파리미터 행렬 U는 단위 벡터 표현된 입력 어휘를 N차원의 연속된 어휘 임베딩 벡터로 변환해주는 역할을 합니다. 


 




Word2Vec 최적화: Negative Sampling (2013) 


그런데 위에서 언급한 두 방법을 구현할 때, 모델의 출력 부분에서 Softmax 함수를 사용하면 학습 속도가 매우 느려진다는 단점이 있었습니다. 이 함수는 정확한 계산을 위해 코퍼스 내에 등장한 모든 종류의 단어를 한꺼번에 고려해야하기 때문에, 이 과정에서 요구되는 계산량이 매우 커집니다. 그래서 부담스러운 Softmax 함수의 계산량을 줄이기 위한 방법으로 두 가지가 제안되었습니다. 첫 번째 방법은 Hierarchical Softmax, 두 번째는 Negative Sampling입니다. 


Hierarchical Softmax는 순수하게 Softmax 함수를 빠르게 계산하기 위해 고안된 방법입니다. 먼저, 모든 어휘가 코퍼스 상에서 등장한 횟수를 센 다음, 그것을 바탕으로 이진 트리(binary tree)를 구성합니다. 트리의 루트(root)에는 코퍼스 상에서 가장 높은 빈도로 등장한 단어를, 그 자식 노드에는 2, 3번째로 등장한 단어를, 각각의 다음 자식 노트에은 그 다음으로 많이 등장한 단어를 저장하는 방식으로 구성합니다. 


이 트리는 Softmax 함수를 이용해 어떤 단어가 출현할 확률을 계산할 때 일종의 대체재로 사용되는데, Softmax 함수의 산출값을 구할 때, 루트에서부터 내가 확률값을 구하고자 하는 단어가 저장된 리프(leaf) 노드까지 가는 길에 저장된 확률을 곱해나가는 식으로 이를 계산할 수 있도록 도와줍니다. 즉, 이 트리를 사용하면 임의의 어휘 벡터에 대한 Softmax 산출값을 구하기 위해 어휘의 종류 N개를 모두 탐색하지 않고, 최대 logN 번만 계산하면 될 뿐만 아니라, 자주 출현하는 단어의 경우 이 트리의 윗 부분에 저장되어 있으므로, 많은 경우 logN 번 보다 덜 계산을 해도 Softmax 값을 구할 수 있다는 장점이 있습니다.  


또 다른 방법은 현재 널리 사용되고 있는 Negative Sampling이라는 방법입니다. 이 방법은 Softmax 함수를 계산하는 것이 아닌, 다른 방식을 통해 어휘 임베딩을 학습하고자 하는 것입니다. 기본적인 아이디어의 출발은 이렇습니다. 어떤 코퍼스 상에서 등장한 어휘의 종류가 N개라면, Softmax 함수는 이 N개 (혹은 log N번) 를 모두 고려하여 특정 단어가 등장할 확률값을 계산하는데, 이렇게 하는 대신 관점을 바뀌서, “특정 맥락에서 나와야 하는 단어 몇 개, 나오면 안 되는 단어 몇 개만 추려서” 계산을 해보자는 것이 Negative Sampling의 접근법입니다. 


Negative Sampling을 간단하게 설명하면 다음과 같습니다. 앞서 예로 들었던 문장 “나는 맥주를 매일 마십니다”에서, “맥주를”이라는 기준 단어를 보고 “나는”, “매일”이라는 문맥 단어를 맞추는 Skip-Gram 모델을 학습하는 상황을 가정해봅시다. 이 경우, “나는”, “매일”이라는 두 단어는 기준 단어 주위에서 실제로 관찰된 긍정적인(positive) 예시가 되고, “마십니다”는 해당 문맥에서 관찰되지 않은 “부정적인(negative)” 예시에 해당합니다. 다만, 실제 코퍼스 상에서는 특정 문맥에서 나타나지 않은 부정적인 예시들의 수가 매우 클 것이므로, 가능한 부정적인 예시들 중에서 몇 개를 뽑아서(sampling) 실제 부정적 예시로 사용하게 됩니다. 이렇게 기준 단어마다 실제로 등장했던(positive), 그리고 등장하지 않았던(negative) 예시를 구성하여 간단한 이진 로지스틱 분류기(binary logistic classifier)를 학습하는 것이 Negative Sampling의 골자입니다. 


학습속도의 향상을 위해 제시되었던 두 가지 방법을 각각 적용하여 어휘 임베딩의 성능을 평가한 결과, Hierarchical Softmax보다 Negative Sampling을 적용하였을 때 성능이 더 좋고, CBOW와 Skip-Gram 중에서 Skip-Gram으로 학습된 어휘 임베딩의 성능이 더 좋다는 것이 알려지게 되었습니다. 따라서, 이후 Word2Vec은 곧 Skip-Gram모델을 Negative Sampling 방법을 적용해 학습하는 것이 당연한 방법으로 굳어지게 되었습니다. 그래서 2013~4년 이후로 등장한 논문들부터는 이를 줄여서 SGNS (Skip-Gram with Negative Sampling)이라는 말로 Word2vec을 대신하기도 합니다. 2014년 발표된 Glove의 등장과 함께, SGNS는 이제 어휘 임베딩 학습 방법의 양대 산맥으로써 자리잡았습니다. 





Fasttext 등장 (2016) 

 

다양한 자연어처리 과제를 위해서 SGNS로 학습된 어휘 임베딩을 적용한 결과, 성능이 눈에 띄게 향상되었다는 결과가 일관적으로 보고되면서, 이 방식을 적용한 엄청나게 많은 논문들이 쏟아져 나오게 되었습니다. 이 과정에서 새롭게 알려지게 된 Word2Vec의 단점은, 이 방법이 애초에 영어만을 위한 임베딩 학습 방법이 아니었음에도 불구하고, 영어가 아닌 언어에 대해서 이를 적용하면 여러 가지 문제가 생긴다는 것이었습니다. 


중국어처럼 어휘의 경계가 명확하지 않은 경우, 이를 어떻게 적용해야 하는지 난감했습니다. 또한, 한국어와 같은 교착어는 동일한 어휘가 문맥 속에서 복잡한 문법적 규칙에 의해 다양하게 변합니다(morphologically rich). 이런 경우에는 의미상 유사하지만 문법적으로 조금만 다른 어휘들이 모두 서로 전혀 무관한 개별 어휘(unique word)로 처리되어, 웬만해서는 이러한 특징들이 제대로 학습이 안 되는 문제가 발생했습니다. 또, 학습 셋에 없었던 어휘가 갑자기 등장할 경우, 새로 등장한 어휘에 대해서 임베딩을 어떻게 구성해야하는지 뾰족한 방법이 없었는데, 이러한 문제를 당시에 유행하기 시작한 접근법을 활용해서 개선하고자 한 시도가 FastText입니다. 


2015년에 들어서면서, 텍스트 자료에서 무언가를 학습하는 인공신경망 기반 기계학습 모델을 구성할 때, 어휘보다 낮은 단위(subword-level)의 입력(input)을 투입하면 다양한 자연어처리 과제에서 더 좋은 결과를 낸다는 논문(NIPS 2015, AAAI 2016)들이 나오기 시작했습니다. 특히, 어휘를 글자 수준(character-level)으로 쪼개서 학습하면 감성 분석(sentiment analysis), 문서 분류(document classification), 언어 모델링(language modelling)등의 과제에서 더 좋은 성능을 보인다는 것을 알려졌는데, 이러한 접근 방법을 어휘 임베딩에 적용한 것이 FastText입니다.


FastText는 위에서 언급한 SGNS 방법에 기반을 두고 있습니다. 그리고 SGNS와 유일하게 달라지는 점은 텍스트의 최소 단위를 ‘어휘’가 아닌 ‘어휘를 구성하는 글자 n-gram’ 단위로 한 수준 내렸다는 것입니다. 즉, 기존에는 임베딩 벡터를 어휘마다 하나씩 할당하고 이를 학습했다면, 이제 어휘를 구성하고 있는 n-gram마다 하나씩 이를 할당하고, 어휘를 구성하는 모든 n-gram 벡터의 평균 벡터를 어휘 임베딩으로 본 것입니다. 예를 들면, ‘where’라는 어휘가 있으면 이를 기존의 Word2Vec에서는 이 어휘에 대해 임의의 벡터를 하나 할당하고 이 어휘가 등장할 때마다 이를 학습했지만, Fasttext는 where를 아래와 같이 5개의 n-gram으로 쪼갭니다. (n=3이라고 가정합니다(trigram)) 


       <wh, whe, her, ere, re> 

 

구체적으로, 어휘의 시작과 끝을 뜻하는 기호 <, > 를 더하고, 글자의 배열을 처음부터 순서대로 3개씩 잘라서 각각의 trigram에 임베딩 벡터를 할당합니다. 그러면 ‘where’라는 어휘의 임베딩 벡터는 이 5개의 trigram에 대한 임베딩 벡터의 평균 벡터로 계산되는 것입니다. 논문에서는 이러한 접근방식에 대해서 Subword-Information Skip-Gram(SISG) 라는 이름을 붙였습니다.  


이러한 방식을 적용하면, 어휘 안에 포함된 다양한 요소를 n-gram수준에서 학습하기 때문에, 동일한 의미를 갖는 어휘가 문법적인 규칙에 따라서 변화하는 패턴을 학습하기 용이해집니다. 문법적으로 변화하는 부분이 아닌 의미를 나타내는 부분에 해당하는 n-gram 벡터가 별도로 존재하기 때문입니다. 뿐만 아니라, 이 방법은 동일한 양의 텍스트 데이터에서 더 많은 정보를 활용하기 때문에, 더 적은 양의 학습 데이터로도 높은 성능을 낼 수 있다는 것을 보였습니다. 


또, Word2Vec으로는 처리할 수 없었던, 학습 시 존재하지 않았던 어휘(OOV, out of vocabulary)에 대한 임베딩까지 만들어낼 수 있게 되었습니다. 예를 들면, 학습 시 ‘interconnect’라는 단어를 학습하지 못했어도, FastText의 경우 inter와 connect라는 n-gram을 학습한 적이 있다면 두 임베딩 벡터를 조합하여 임베딩 벡터를 만들어 낼 수 있었기 때문에, 자주 등장하지 않는 생소한 어휘(rare word)에 대해서도 Word2Vec에 비해 월등한 성능을 보인다는 것이 증명되었습니다. 


정리하면, FastText는 매우 간단한 아이디어를 통해 기존 어휘 임베딩의 많은 문제점을 혁신적으로 해결한 매우 좋은 연구였습니다. Word2Vec에 비해 좀 더 다양한 언어에 보편적으로 적용했을 때 어휘의 구문적(syntactic) 변화 규칙을 잘 잡아낼 수 있게 되었을 뿐만 아니라, 더 적은 양의 텍스트 데이터 상에서도 효과적으로 작동하고, 학습 시 관찰하지 못한 어휘에 대해서도 양질의 임베딩 벡터를 만들어낼 수 있게 된 것입니다. 그리고 연구진들은 FastText로 학습된 다양한 언어의 임베딩 벡터와 코드를 공개하여, 이를 이용하고 싶어하는 많은 자연어처리 연구자들을 수월하게 만들어 주었습니다. 


한국어 임베딩의 경우에도, Word2Vec과 비교했을 때 FastText로 학습한 어휘 임베딩의 성능이 더 좋다는 것이 확인되었기 때문에, 한국어를 위한 임베딩 기법 개발은 FastText를 기반으로 출발했습니다. 


 

(다음 포스팅에서 이어집니다.) 

매거진의 이전글 한국어를 위한 어휘 임베딩의 개발 -2-
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari