자연어 처리 · 단어 임베딩
“king − man + woman ≈ queen”이 어떻게 가능한가. 분포 가설에서 출발해 CBOW와 Skip-gram, 소프트맥스를 대체한 두 가지 기법, 서브샘플링과 구(phrase) 학습까지 따라가고, 마지막에는 브라우저에서 실제로 임베딩을 학습시켜 지도 위에 단어가 모이는 것을 직접 확인합니다.
컴퓨터에게 단어를 주려면 숫자로 바꿔야 합니다. 가장 단순한 방법은 어휘에 번호를 붙이고, 해당 자리만 1인 긴 벡터를 쓰는 것입니다. 이를 원-핫 인코딩이라 합니다.
이 방식에는 치명적인 결함이 있습니다. 어떤 두 단어를 골라도 내적이 0이라는 점입니다. ‘개’와 ‘고양이’, ‘개’와 ‘행정’이 컴퓨터에게는 똑같이 무관합니다. 게다가 어휘가 100만 개면 벡터 하나가 100만 차원입니다.
위는 원-핫, 아래는 학습된 밀집 벡터(개념 예시)입니다. 오른쪽 표의 코사인 유사도를 보세요. 원-핫에서는 모든 쌍이 정확히 0이지만, 밀집 벡터에서는 관계가 숫자로 나타납니다.
↔ 그림이 잘려 보이면 가로로 밀어서 볼 수 있습니다.
그렇다면 밀집 벡터의 숫자들은 어디서 오는 걸까요? 사람이 “동물성 0.8, 인공성 0.1”처럼 채워 넣는 것은 불가능합니다. 여기서 언어학의 오래된 통찰이 등장합니다.
단어의 의미는 그 단어가 함께 나타나는 이웃들이 결정한다. Zellig Harris(1954)가 정식화하고 J. R. Firth(1957)가 “단어는 함께 다니는 친구를 보면 안다”는 문장으로 요약한 생각입니다. ‘커피’와 ‘차’가 비슷하다는 걸 우리가 아는 이유는, 둘 다 마시다·뜨겁다·잔 같은 단어 옆에 나타나기 때문입니다.
word2vec의 전략은 여기서 곧장 따라 나옵니다. “주변 단어를 잘 맞히도록 벡터를 학습시키면, 비슷한 이웃을 갖는 단어끼리 비슷한 벡터를 갖게 된다.” 의미를 직접 정의하지 않고, 예측 과제를 잘 푸는 부산물로 얻어 내는 것입니다.
레이블이 붙은 데이터는 필요 없습니다. 텍스트 자체가 정답을 갖고 있기 때문입니다. 문장 위로 윈도우를 밀면서, 가운데 단어(중심어)와 그 주변 단어(문맥어)의 쌍을 만들어 내면 그것이 학습 데이터가 됩니다. 이런 방식을 자기 지도 학습(self-supervised)이라 부릅니다.
여기서 방향을 어느 쪽으로 잡느냐에 따라 두 가지 모델이 갈립니다.
| 모델 | 과제 | 특징 |
|---|---|---|
| CBOW (Continuous Bag-of-Words) | 문맥어들 → 중심어 맞히기 | 문맥 벡터를 평균내어 한 번에 예측. 순서를 무시하므로 “bag of words”. 빠르고, 흔한 단어에서 유리합니다. |
| Skip-gram | 중심어 → 문맥어 각각 맞히기 | 한 중심어로 여러 개의 예측을 만들어 학습 신호가 많습니다. 느리지만 드문 단어와 작은 코퍼스에서 강합니다. |
실무에서 자주 언급되는 구현 세부 하나. 윈도우 크기 C는 고정이 아니라 매번 1부터 C 사이에서 무작위로 뽑습니다. 이렇게 하면 중심어에 가까운 단어일수록 뽑힐 확률이 높아져, 가중치를 따로 주지 않고도 “가까울수록 중요하다”가 자연스럽게 반영됩니다.
문장을 직접 고쳐 보세요. 주황색이 중심어, 파란색이 윈도우 안의 문맥어입니다. 아래에 이 위치에서 생성되는 학습 쌍이 표시됩니다.
“신경망”이라는 말에 속기 쉽지만, word2vec에는 은닉층의 비선형 활성화가 없습니다. 있는 것은 행렬 두 개뿐입니다.
원-핫 벡터에 W를 곱하는 것은 사실상 행 하나를 꺼내 오는 조회(lookup)일 뿐입니다. 그래서 이 부분에는 곱셈 비용이 거의 없습니다. 예측은 두 벡터의 내적에 소프트맥스를 씌워 만듭니다.
단어 하나를 예측할 때마다 어휘 전체를 훑어야 한다는 뜻입니다. 어휘가 100만이면 학습 예제 하나에 100만 번의 내적입니다. 코퍼스에 단어가 수십억 개라면 계산이 불가능합니다.
이전 세대의 신경망 언어 모델(NNLM)은 투사층과 은닉층을 모두 갖고 있었고, 비용의 대부분이 은닉층에서 나왔습니다. word2vec은 “좋은 단어 벡터를 얻는 것”만이 목표라면 은닉층이 필요 없다고 보고 이를 제거했습니다. 학습 예제 하나당 비용을 비교하면 이렇습니다.
| 모델 | 예제 하나당 계산량 Q | 병목 |
|---|---|---|
| NNLM | N×D + N×D×H + H×V | 은닉층(N×D×H)과 출력층(H×V) |
| RNNLM | H×H + H×V | 순환 연결과 출력층 |
| CBOW | N×D + D×log₂(V) | 거의 없음 — 조회 + 트리 경로 |
| Skip-gram | C×(D + D×log₂(V)) | 중심어 하나당 C개의 예측 |
여기서 N은 문맥 단어 수, D는 벡터 차원, H는 은닉층 크기, V는 어휘 크기입니다. 핵심은 V가 log₂V로 바뀌었다는 점인데, 그것이 바로 다음 절의 이야기입니다.
어휘 크기와 벡터 차원을 바꿔 보세요. 저장해야 할 파라미터는 2VN개이고, 학습 예제 하나를 처리하는 비용은 어떤 출력층을 쓰느냐에 따라 수만 배 차이가 납니다.
분모의 합을 없애기 위해 word2vec은 두 가지 해법을 제시합니다. 둘 중 하나를 골라 쓰며, 목적은 같지만 접근이 전혀 다릅니다.
어휘를 이진 트리의 잎에 배치합니다. 그러면 “어떤 단어인가”를 한 번에 고르는 대신, 뿌리에서 잎까지 내려가며 왼쪽이냐 오른쪽이냐를 연달아 결정하는 문제로 바뀝니다. 깊이가 log₂V이므로 결정도 그만큼만 하면 됩니다.
벡터는 단어가 아니라 내부 노드가 갖습니다. 잎이 V개면 내부 노드는 V−1개이므로 파라미터 총량은 그대로지만, 한 번의 갱신에 건드리는 것은 경로 위의 노드들뿐입니다. 트리는 허프만 코딩으로 만듭니다. 자주 나오는 단어일수록 뿌리에 가깝게 배치되어 경로가 짧아지고, 그만큼 평균 계산량이 더 줄어듭니다.
단어를 클릭하면 뿌리에서 그 단어까지의 경로가 표시됩니다. 빈도가 높은 단어일수록 경로가 짧습니다. 확률은 경로 위 시그모이드 값들의 곱입니다.
↔ 트리가 잘려 보이면 가로로 밀어서 볼 수 있습니다.
이쪽은 접근이 훨씬 대담합니다. 확률 분포를 만드는 일을 아예 포기합니다. 대신 문제를 이렇게 바꿉니다.
“이 문맥에서 나올 단어를 모든 후보 중에 고르시오”가 아니라, “(중심어, 문맥어) 이 쌍은 실제 코퍼스에서 나온 것인가, 아니면 내가 무작위로 지어낸 것인가?”라는 참/거짓 문제로.
진짜 쌍 하나마다 가짜 쌍 k개를 무작위로 만들어 함께 학습시킵니다. 목적 함수는 다음과 같습니다.
한 번의 갱신에 손대는 벡터는 k+1개뿐입니다. V가 100만이든 1000만이든 상관없습니다. 논문이 권하는 값은 작은 데이터셋에서 k = 5~20, 큰 데이터셋에서 k = 2~5입니다.
여기에 word2vec에서 가장 유명한 “실험으로 찾은 수치”가 등장합니다. 가짜 단어를 그냥 빈도대로 뽑으면 the, of 같은 단어만 계속 뽑히고, 균등하게 뽑으면 희귀어가 지나치게 자주 뽑힙니다. word2vec은 그 중간을 택합니다 — 빈도의 3/4 제곱에 비례해 뽑습니다.
지수 α를 움직여 보세요. α=1은 원래 빈도 그대로, α=0은 완전 균등입니다. 0.75에서는 흔한 단어의 비중이 줄고 드문 단어가 뽑힐 기회가 늘어납니다.
↔ 그래프가 잘려 보이면 가로로 밀어서 볼 수 있습니다.
코퍼스에서 the는 수억 번 등장합니다. 그런데 “the가 France 옆에 나온다”는 사실은 거의 아무것도 알려 주지 않습니다. 반면 “Paris가 France 옆에 나온다”는 정보량이 큽니다. 흔한 단어는 학습 시간을 잡아먹으면서 정보는 적게 주는 셈입니다.
그래서 word2vec은 학습 전에 각 단어를 빈도에 따라 확률적으로 버립니다.
부수 효과가 하나 더 있습니다. 흔한 단어가 빠지면 남은 단어들 사이의 거리가 가까워져서 윈도우가 실질적으로 넓어집니다. 계산은 줄고 신호는 좋아지는, 흔치 않은 조합입니다.
임계값 t를 조절해 보세요. 가로축은 코퍼스 내 빈도 비율(로그 스케일)이고 세로축은 버릴 확률입니다. 점은 실제 영어 코퍼스에서의 대략적인 빈도로 배치한 예시 단어들입니다.
↔ 그래프가 잘려 보이면 가로로 밀어서 볼 수 있습니다.
이제 실제로 돌려 봅시다. 아래 데모는 설명용 애니메이션이 아니라 브라우저에서 진짜로 SGNS를 학습합니다. 윈도우로 쌍을 만들고, 3/4 제곱 분포에서 네거티브를 뽑고, 확률적 경사 하강법으로 두 행렬을 갱신합니다.
코퍼스는 규칙성이 뚜렷한 작은 문장 모음입니다. 실제 코퍼스는 수십억 단어지만, 원리를 눈으로 보기에는 이 정도가 오히려 낫습니다.
▶ 학습 시작을 누르면 손실이 떨어지면서 단어들이 자리를 잡습니다. 화면의 2D 좌표는 학습된 고차원 벡터를 주성분 2개로 투영한 것입니다. 단어를 클릭하면 코사인 유사도 기준 가장 가까운 이웃이 표시됩니다.
↔ 단어 지도가 잘려 보이면 가로로 밀어서 볼 수 있습니다.
학습이 끝나면 king·queen·prince·princess가 한쪽에, man·woman·boy·girl이 다른 쪽에 모입니다. 아무도 “왕족”이나 “성별”이라는 범주를 알려 주지 않았는데도 그렇습니다. 오직 “어떤 단어 옆에 나타났는가”만으로 얻은 결과입니다.
word2vec을 유명하게 만든 것은 유사도가 아니라 덧셈과 뺄셈이 의미를 갖는다는 발견이었습니다.
왜 이런 일이 벌어질까요? 학습이 끝난 공간에서 “성별을 바꾼다”는 조작이 하나의 고정된 방향 벡터로 표현되기 때문입니다. king과 queen을 가르는 차이, man과 woman을 가르는 차이가 거의 같은 방향·같은 크기라면, 그 차이를 빼고 더하는 것으로 다른 단어쌍에도 같은 조작을 적용할 수 있습니다. 이런 규칙성은 의미 관계(나라–수도)뿐 아니라 문법 관계(원형–과거형, 단수–복수, 형용사–비교급)에서도 나타납니다.
데모 7에서 방금 학습한 벡터로 계산합니다(먼저 학습을 돌려 주세요). 오른쪽 그림은 네 단어가 이루는 평행사변형입니다 — 두 화살표가 나란할수록 유추가 잘 작동합니다.
Mikolov 등은 이 능력을 재기 위해 약 2만 문항의 평가 집합을 함께 공개했습니다. 구성은 크게 둘입니다.
채점은 엄격합니다. 가장 가까운 단어 하나가 정확히 정답일 때만 맞은 것으로 칩니다. 이 기준에서 word2vec은 이전 세대 모델들을 큰 폭으로 앞섰고, 특히 훈련 데이터와 벡터 차원을 함께 키울 때 정확도가 계속 올라간다는 점을 보였습니다. 둘 중 하나만 키우면 곧 정체됩니다.
New York은 new와 york의 의미를 합친 것이 아니고, Air Canada도 마찬가지입니다. 이런 표현은 하나의 토큰으로 다뤄야 합니다. word2vec은 여기에도 아주 단순한 통계적 방법을 씁니다.
분자는 “같이 나온 횟수”, 분모는 “각자 얼마나 흔한가”입니다. 그래서 this is처럼 둘 다 흔한 조합은 자주 붙어 나와도 점수가 낮고, New York처럼 따로는 덜 쓰이는데 붙어서 자주 나오는 조합이 높은 점수를 받습니다.
이 과정을 2~4번 반복하면 세 단어, 네 단어짜리 표현도 잡힙니다. 매 회차마다 문턱값을 낮추면서 더 긴 구를 허용하는 식입니다. 구까지 포함해 학습하면 “나라 : 수도”처럼 여러 단어로 된 대상에 대해서도 유추가 가능해집니다.
미니 코퍼스에서 계산한 바이그램 점수입니다. 할인 계수 δ와 문턱값을 조절해 보세요. 초록색 행이 하나의 토큰으로 합쳐질 조합입니다.
학습된 벡터에는 예상 밖의 성질이 하나 더 있습니다. 단순히 더하기만 해도 의미가 결합됩니다. “Czech + currency”에 가장 가까운 단어가 체코의 화폐 단위이고, “Vietnam + capital”이 하노이를 가리키는 식입니다.
이유는 목적 함수의 형태에 있습니다. 단어 벡터는 문맥 분포의 로그 확률과 선형적으로 관계 맺도록 학습됩니다. 로그 공간에서의 덧셈은 확률 공간에서의 곱이므로, 두 벡터를 더하는 것은 두 문맥 분포를 곱하는 것(AND)에 해당합니다. 두 단어 모두와 자주 어울리는 단어가 가장 높은 점수를 받게 되는 것입니다.
그럼에도 word2vec을 지금 배우는 이유는, “예측 과제를 잘 풀게 하면 표현이 따라온다”는 발상 자체가 현대 언어 모델의 출발점이기 때문입니다. 오늘날의 거대 모델도 결국은 훨씬 큰 규모로 같은 일을 합니다 — 다음 토큰을 맞히게 하고, 그 부산물로 얻은 표현을 사용하는 것입니다.
New York 같은 표현을 하나의 토큰으로 만든다.