단어 임베딩
신경망에 텍스트를 입력하려면 먼저 단어를 숫자로 바꿔야 합니다. 어떤 형태의 숫자로 단어를 표현할 것인지가 자연어 처리의 첫 번째 관문입니다. 가장 단순한 출발점은 단어를 범주형 변수로 보고 원-핫 인코딩으로 표현하는 것입니다. 그러나 이 표현은 단어 사이의 의미적 관계를 전혀 담지 못한다는 근본적인 한계를 가집니다. 이 장에서는 원-핫 인코딩의 문제를 짚고, 그 대안으로 단어를 저차원의 조밀한 벡터로 학습하는 임베딩(embedding) 을 살펴봅니다. 임베딩은 단어의 의미적 유사성을 벡터 공간의 기하학적 구조로 드러내며, 이후의 언어 모델이 텍스트를 다루는 방식의 토대가 됩니다.
1원-핫 인코딩과 그 한계¶
텍스트의 단어를 범주형 변수로 생각하면 원-핫 인코딩이 자연스러운 첫 선택지가 됩니다. 어휘 집합의 각 단어를 하나의 벡터로 표현하되, 어휘가 개라면 각 단어 벡터는 차원이 됩니다. 원-핫 인코딩의 특성상 차원 중 해당 단어의 위치에서만 값이 1이고 나머지는 모두 0으로 채워집니다.
어휘수가 1000이라면, 단어 벡터 전체는 단위 행렬로 표현됩니다. 각 행이 하나의 단어에 대응하는 원-핫 벡터입니다.
어휘수 = 1000
pd.DataFrame(np.eye(어휘수))#, index=list('가나다라마바사아자차'))표기가 다른 단어는 서로 완전히 다른 벡터로 표현됩니다. 문제는 여기서 발생합니다. '개’와 '고양이’는 표기도 다르고 개념도 다릅니다. 반면 '개’와 '강아지’는 완벽히 동일한 개념은 아니지만 매우 유사한 개념입니다. 그런데 원-핫 인코딩은 이 차이를 전혀 구분하지 못합니다.
두 벡터의 유사성은 코사인 유사도(cosine similarity) 로 측정할 수 있습니다.
두 벡터가 완벽히 같은 방향이면 유사도는 1, 완전히 반대 방향이면 -1입니다. 그런데 원-핫 벡터는 단 하나의 위치에서만 1을 가지므로, 서로 다른 두 단어 벡터의 내적은 항상 0입니다. 즉 어떤 두 단어를 골라도 상호 유사도가 모두 0으로 계산됩니다.
| 단어 쌍 | 실제 의미 관계 | 원-핫 코사인 유사도 |
|---|---|---|
| 개 - 고양이 | 다른 개념 | 0 |
| 개 - 강아지 | 유사한 개념 | 0 |
결국 '개’와 '고양이’가 다른 만큼이나 '개’와 '강아지’도 완전히 별개의 단어로 취급되어, 둘 사이에 아무런 유사성이 없다고 평가됩니다. 단어 사이의 의미 관계가 이렇게 전부 소거된 표현 위에서 기계학습을 성공적으로 수행하기는 어렵습니다. 여기서 원-핫 인코딩이 과연 단어를 표현하는 적절한 방법인가에 대한 의문이 제기됩니다.
2통계적 언어 모델과 N-Gram¶
통계적 언어 모델은 텍스트를 다음 단어의 조건부 확률로 표현합니다. 조건부 확률의 조건은 그 이전에 등장한 단어들입니다. 이상적으로는 이전의 모든 단어 순서를 조건으로 다음 단어의 확률을 계산하지만, 가능한 단어 조합의 수가 폭발적으로 늘어나기 때문에 이를 그대로 다루기는 어렵습니다.
N-Gram 모델은 이 부담을 덜기 위해, 이전의 전체 단어 순서 대신 직전 개의 단어만 조건으로 삼아도 통계적 언어 모델과 유사하다는 가정을 둡니다. 즉 다음 단어는 직전 개 단어 조합으로 이루어진 맥락에 대한 조건부 확률표로 근사됩니다.
이런 통계적 언어 모델은 오래전부터 자연어 처리의 중요한 근거로 활용되어 왔습니다. 자연어의 통계적 모델을 형성할 때 단어의 순서를 고려하는 것은 크게 도움이 되는데, 실제로 가까이 있는 단어들은 서로 통계적으로 더 강하게 연관되기 때문입니다. N-Gram 모델 역시 이 점을 핵심적으로 고려한 방법입니다. 다만 N-Gram은 여전히 단어를 개별 기호로 다루기 때문에, 원-핫 인코딩이 가진 의미 표현의 한계를 공유합니다.
3임베딩: 단어를 조밀한 벡터로¶
원-핫 인코딩의 한계를 넘는 핵심 아이디어는, 단어를 고차원의 희소한 벡터가 아니라 저차원의 조밀한 벡터로 표현하고 그 벡터를 데이터로부터 학습하는 것입니다. 이것이 임베딩입니다.
임베딩 층은 내부에 형태의 가중치 행렬 를 가집니다. 정수로 인코딩된 토큰 시퀀스가 입력되면, 각 토큰 ID는 에서 해당 행을 조회(lookup)하는 인덱스로 쓰여 그 행 벡터로 치환됩니다. 아래에서는 어휘수를 토크나이저의 어휘 크기로 두고 벡터차원을 128로 설정해, 하나의 정수 시퀀스를 임베딩에 통과시킵니다.
어휘수 = tokenizer.vocab_size()
벡터차원 = 128
임베딩 = layers.Embedding(어휘수, 벡터차원)
x = 임베딩(np.array([정수시퀀스]))
print(f'C={임베딩.get_weights()[0].shape}')
print(f'배치수, 토큰수, 벡터차원={tuple(x.shape)}')출력에서 가중치 행렬의 형태 는 이고, 임베딩을 통과한 결과는 형태가 됩니다. 즉 길이가 같은 정수 시퀀스의 각 토큰이 128차원 벡터로 펼쳐진 것입니다. 원-핫 인코딩이 어휘수만큼의 차원을 쓰던 것과 달리, 임베딩은 훨씬 작은 고정 차원으로 단어를 표현합니다.
이 가중치 행렬 가 바로 단어 벡터들의 사전입니다. 특정 단어의 벡터를 보려면, 토크나이저로 그 단어를 토큰 ID로 바꾼 뒤 에서 해당 행을 꺼내면 됩니다.
C = 임베딩.get_weights()[0]
단어 = '배'
토큰ID = tokenizer.encode(단어)
단어벡터 = C[토큰ID[0]]
print(f'{단어}->{단어벡터[:10].round(3)} ...')여기서 중요한 점은 이 벡터들이 고정된 값이 아니라 학습되는 파라미터라는 것입니다. 임베딩 행렬은 모델의 다른 가중치와 함께 역전파로 갱신되므로, 학습이 진행되면 비슷한 맥락에서 쓰이는 단어들의 벡터가 서로 가까워집니다. 그 결과 원-핫 인코딩에서는 표현할 수 없던 단어 사이의 의미적 유사성이 벡터 공간 위에 자연스럽게 자리잡습니다.
4단어 유사도와 벡터 공간의 기하학¶
임베딩의 진가는 단어 사이의 의미 관계가 벡터 공간의 기하학적 구조로 드러난다는 데 있습니다. 예로 네 개의 단어를 2차원 공간에 임베딩했다고 합시다. 이렇게 표현되면 각 단어 벡터 사이의 의미 관계를 벡터의 이동, 즉 기하학적 변환으로 해석할 수 있습니다.
예를 들어 '개’에서 '늑대’로 가는 벡터를 생각하면, 같은 벡터를 '고양이’에 적용했을 때 '호랑이’에 도달합니다. 이 벡터는 '애완동물에서 야생동물로’라는 의미 변환으로 읽을 수 있습니다.
다른 방향으로도 같은 원리가 성립합니다. '개’에서 '고양이’로 변환하는 벡터를 '늑대’에 적용하면 '호랑이’가 됩니다. 이 경우의 벡터는 '개과에서 고양이과로’라는 의미 변환으로 해석됩니다.
| 변환 벡터 | 의미 | 적용 예 |
|---|---|---|
| 늑대 − 개 | 애완동물 → 야생동물 | 고양이 + (늑대 − 개) ≈ 호랑이 |
| 고양이 − 개 | 개과 → 고양이과 | 늑대 + (고양이 − 개) ≈ 호랑이 |
이렇게 단어들의 의미 관계가 벡터의 덧셈과 뺄셈으로 표현된다는 사실은, 임베딩이 단순히 단어를 숫자로 바꾸는 것을 넘어 의미를 담은 표현을 학습한다는 점을 보여 줍니다. 이것이 원-핫 인코딩이 결코 줄 수 없었던 임베딩의 본질적 이점입니다.
5생각해 볼 문제¶
이미지 데이터에서 회전, 이동, 좌우 반전 같은 변형으로 데이터를 늘리는 데이터 증강이 효과적이었다면, 텍스트 데이터에서도 같은 발상이 가능할지 생각해 봅시다.
Q: 텍스트 데이터도 데이터 증강이 가능한가요?