일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
- study book
- ARM
- BOF
- C++
- Stream
- system hacking
- Widget
- Got
- Dreamhack
- fastapi를 사용한 파이썬 웹 개발
- DART
- ML
- Kaggle
- Computer Architecture
- bloc
- pytorch
- llm을 활용 단어장 앱 개발일지
- Image Processing
- 백준
- Flutter
- PCA
- FastAPI
- 영상처리
- MDP
- BFS
- Algorithm
- BAEKJOON
- rao
- 파이토치 트랜스포머를 활용한 자연어 처리와 컴퓨터비전 심층학습
- MATLAB
- Today
- Total
Bull
[AI::NLP] Word2Vec에 대해 알아보자: 단어의 비밀, 사실은 숫자의 배열이었다! (CBOW, Skip-gram) 본문
[AI::NLP] Word2Vec에 대해 알아보자: 단어의 비밀, 사실은 숫자의 배열이었다! (CBOW, Skip-gram)
Bull_ 2024. 7. 21. 21:26Word2Vec 모델
Word2Vec는 자연어 처리에서 단어를 벡터로 표현하기 도구입니다. 벡터를 처음 마주한다면 어떤 뜻인지 궁금할 수 있을텐데요. 쉽게 생각하면 배열을 상상하면 됩니다. 벡터의 본래 개념은 방향과 크기를 가진 물리량입니다.
만약 2차원 좌표공간에서 우리는 1,2
를 x=1, y=2
이라는 좌표를 떠올릴 수 있습니다. 이를 2차원이 아닌 n차원에서 벡터를 표현하게 된다면 [X1, X2, X3, ..., Xn]으로 벡터 물리량을 표현할 수 있습니다. 우리는 단어 혹은 말뭉치(코퍼스)를 n개 라 할 때 단어를 차원으로 표현할 것입니다.
예를 들어, "fat cat on the mat"인 단어는 5차원 벡터로 ["fat", "cat", "on", the", "mat"]로 표현할 수 있겠네요. 즉, 이 모델은 단어의 의미를 수치화하여, 단어 간의 유사성을 계산할 수 있게 합니다. Word2Vec 모델은 CBOW (Continuous Bag of Words)와 Skip-gram로 크게 두 가지 학습 방식으로 나눌 수 있습니다.
만약 하늘과 풍선을 떠올리면 어떤 단어가 관련이 깊을까요? 저는 구름을 생각했습니다. 사실 구름을 보고 하늘과 풍선을 생각했죠. 이처럼 단어의 유사도를 검색하게 해주는 사이트도 존재합니다. 여기서 여러 단어의 조합을 하며 Word2Vec이 어떤 개념인지 조금 더 직관적인 확인이 가능합니다. 아래 사이트에서 예문을 연습할 수 있습니다.
Korean Word2Vec
ABOUT 이곳은 단어의 효율적인 의미 추정 기법(Word2Vec 알고리즘)을 우리말에 적용해 본 실험 공간입니다. Word2Vec 알고리즘은 인공 신경망을 생성해 각각의 한국어 형태소를 1,000차원의 벡터 스페이
word2vec.kr
CBOW (Continuous Bag of Words)
CBOW 모델은 주어진 문맥 단어들로부터 중심 단어를 예측하는 방식입니다. 예를 들어 제가 "fat"과 "cat"을 주어진 문장에서 어떤 코퍼스가 제일 관련이 깊은 지 보고 싶으면 CBOW 모델을 통해 "mat"이 라는 답을 예측합니다. 여러 개의 코퍼스를 입력을 통해 원하는 코퍼스를 예측하는 것입니다.
우리는 이제 중심 단어
와 주변 단어
라는 개념과 Window
라는 개념을 알아야합니다.
예문인 "fat cat on the mat"를 보며 중심 단어
주변 단어
Window
가 뭔지 확인해 봅시다.
임베딩
첫 단어 부터 끝 단어 까지 마치 슬라이딩 하며 미끄러지는 모습을 연상시킵니다. 이제 여기서 Window
란 무엇일까요? 그림에서 보셨듯이 주변 단어를 양 옆을 짝으로 했을 때 몇 개인지를 나타냅니다. 위 그림은 양 옆으로 주변단어가 1개니까 Window
를 1개라고 할 수 있겠네요.
우선 각각의 코퍼스를 희소 행렬로 원-핫 벡터로 표현할 수 있습니다. 희소행렬이란 행렬의 원소가 대부분 0인 원소를 나타냅니다. 여기서 원-핫 벡터로 표현한다는 목표가 되는 값(옳은 것)을 1로 표현하고 그렇지 않은 것을 0으로 표현합니다.
중심 단어[1 0 0 0 0]은 처음 등장하는 단어인 cat을 중심으로 목표가 되는 값을 설정하겠죠? 그럼 주변 단어의 벡터는 말 그대로 주변의 단어가 목표가 된다는 의미입니다. 그래서 cat인 중심 단어인 벡터를 [1 0 0 0 0]으로 표현하고 그 주변 단어의 벡터는 윈도우가 1이고 오른쪽에 목표가 되는 단어가 있으므로 [0 1 0 0 0]이 됩니다.
학습
코퍼스 임베딩을 마쳤으면 학습을 해야합니다. "cat"의 주변 단어는 "fat"과 "on"입니다. 임베딩된 벡터 만으로 "fat"과 "on"의 입력으로 "cat"이 중심단어인지 확인하는 것은 어려운 일입니다. 이제 딥러닝 기법을 통해 주변단어를 통해 중심단어를 예측하는 가중치는 어떻게 이루어져있는 지 알아봅시다.
"fat" = [1 0 0 0 0], "on" = [0 1 0 0 0] 입니니다. 이제 가중치 W을 랜덤한 숫자로 생성합니다. 이때 크기는 (코퍼스에 있는 단어의 총 수) X (사용자가 설정한 임베딩 차원의 크기 수) 입니다.
임베딩 차원이란?
원-핫 벡터는 단어 집합의 크기만큼의 고차원을 가지지만, 이 고차원을 그대로 사용하는 것은 메모리와 계산 효율성 측면에서 비효율적입니다. 임베딩 차원을 설정하는 것은 이러한 고차원의 데이터를 더 낮은 차원으로 변환하여 의미적 유사성을 유지하면서도 계산을 효율적으로 하기 위함입니다. 예를 들어, 임베딩 차원을 3으로 설정하면, 모든 단어는 3차원 벡터로 표현되며, 단어 간의 관계를 3개의 축을 통해 설명할 수 있게 됩니다.임베딩 차원을 설정할 때는 데이터의 복잡성, 도메인 특성, 사용 가능한 계산 자원 등을 고려해야 합니다. 데이터가 복잡하고 다양한 의미를 포함하고 있다면 더 높은 차원이 필요할 수 있습니다. 반대로, 단순한 데이터나 제한된 계산 자원을 가진 경우에는 더 낮은 차원이 적합할 수 있습니다. 임베딩 차원을 적절하게 설정하면, 모델이 단어 간의 의미적 관계를 잘 학습할 수 있으며, 모델의 성능을 최적화할 수 있습니다. 최적의 임베딩 차원을 찾기 위해 다양한 차원을 실험해보고 성능을 평가하는 과정이 필요합니다.
이제 학습을 위한 연산이 어떻게 진행 되는 지 살펴보겠습니다.
주변 단어 "fat" 벡터인 v1, "on" 벡터인 v2를 입력합니다.
여기서 W는 임의의 원소를 생성하고 임베딩 차원은 3으로 정했습니다.
행렬곱을 수행하고 나온 결과는 빨간 네모박스 부분이 나오게 됩니다. 왜냐하면 단위벡터를 행렬곱하는 경우, 1행 n열인지 n행 1열인지, 그리고 W의 앞에 있는가 뒤에 있는 가에 따라 정해진 위치의 행이나 열이 나오게 되는데 위의 같은 경우에서는 1행 5열의 벡터에서 W의 앞의 위치에서 곱해게 되면 단위벡터의 위치가 1번째인 경우 1행의 원소들이 결과가 나오고 3번째 위치인 경우 3행의 결과가 나오게 됩니다.
위 개념이 어렵다면 넘어가도 좋지만 선형대수학을 공부하다보면 자주 등장하고 기억해 놓으면 도움될 때가 종종 있습니다.
지금은 V1, V2만 임베딩 되었습니다. 하지만 Window
의 크기가 커서 임베딩 벡터가 더욱 커질 수 있죠. 이를 이제 평균으로 나눠줍니다. 이때 평균으로 나누는 크기는 2 x window_size
가 됩니다. 하지만 가장 자리 부분은 양 옆의 개수가 존재하지 않기 때문에 점점 적어져서 2 x window_size
로 나타낼 수 없습니다. 이때는 2 x window_size
로 나타낼 수 없어도 임베딩 벡터의 개수로 나눠주어야 합니다.
크기는 가중치 행렬 W의 전치된 크기이지만 이는 W의 전치행렬이 아니라 새로운 가중치 W여야 합니다. 그러면 다시 원래의 단어 집합 크기를 원-핫 인코딩 했던 벡터의 크기대로 나옵니다.
이제 각각의 원소를 0에서 1로 분포할 수 있도록 SoftMax
함수를 적용합니다. 여기서 SoftMax
를 적용하기 전 z의 원소가 0에서 1은 가중치 행렬의 수치를 작게 잡아서 0~1로 나왔지만 실제로 그 범위가 나오지 않을 수 있습니다.
마지막으로 예측했던 중심 단어와 실제 중심 단어의 loss를 계산하고 epoch
에 따라 최적의 가중치 W와 W'을 찾습니다.
그렇게 찾은 가중치 행렬은?
학습을 완료한 W와 W'이 나왔습니다. 그렇다면 결론을 무엇을까요? 바로 이 가중치 행렬 W에서 각각 단어에 대한 임베딩 벡터를 가져옵니다.
학습이 완료된 가중치 행렬에서 임베딩 벡터를 위의 그림처럼 가져오게 됩니다. 우리는 임베딩 차원을 3으로 정했습니다. 그러면 "fat" = [1.6852628 -0.95475525 -2.2619936]과 같이 3차원에 표현이 가능합니다.
이제 단어사이의 관계는 코사인 유사도를 통하여 -1은 반대의 방향, 1은 유사한 방향을 나타내게 됩니다. 코사인 유사도는 고등학교 때 배웠던 cos 공식과 동일합니다. 이를 내적과 크기로 나타낼 수 있는데 차원만 커지게 된 것이고 크기는 노름(Norm)으로 표현하게 되는 차이만 발생합니다. 벡터 공간에서는 크기를 Norm으로 표시합니다. 크기라고 단정짓기는 애매하지만 좌표공간에서 크기의 개념을 Norm이라고 표현하는 것입니다.
CODE
다음은 이론의 내용을 코드로 구현하였습니다. 차원이 낮고 적은 단어의 수와 매번 변하는 가중치의 랜덤시드 때문에 결과가 실제로 유사한 관계를 나타낸다고 단정짓기 어렵습니다. 이론으로만 봐주시길 바랍니다.
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
# 데이터 준비
corpus = [['fat', 'cat', 'on', 'the', 'mat']]
vocab = set(sum(corpus, []))
word_to_idx = {word: i for i, word in enumerate(vocab)}
idx_to_word = {i: word for i, word in enumerate(vocab)}
# 하이퍼파라미터 설정
embedding_dim = 3
context_size = 1 # 윈도우 크기 설정
learning_rate = 0.01
num_epochs = 1000
# CBOW를 위한 데이터셋 준비
data = []
for sentence in corpus:
for i in range(len(sentence)):
context = []
for j in range(1, context_size + 1):
if i - j >= 0:
context.append(sentence[i - j])
if i + j < len(sentence):
context.append(sentence[i + j])
target = sentence[i]
data.append((context, target))
print(f"data: {data}\n")
# Word2Vec 모델 정의
class Word2VecCBOW(nn.Module):
def __init__(self, vocab_size, embedding_dim):
super(Word2VecCBOW, self).__init__()
self.embeddings = nn.Embedding(vocab_size, embedding_dim)
self.linear1 = nn.Linear(embedding_dim, vocab_size)
self.init_weights()
def init_weights(self):
initrange = 0.5
self.embeddings.weight.data.uniform_(-initrange, initrange)
self.linear1.weight.data.uniform_(-initrange, initrange)
self.linear1.bias.data.zero_()
def forward(self, inputs):
embeds = self.embeddings(inputs).mean(dim=1) # 주변 단어 벡터의 평균
out = self.linear1(embeds) # 예측
log_probs = torch.log_softmax(out, dim=1) # 소프트맥스 적용 후 로그
return log_probs
# 모델 초기화
model = Word2VecCBOW(len(vocab), embedding_dim)
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=learning_rate)
# 데이터 전처리
def make_context_vector(context, word_to_idx):
idxs = [word_to_idx[w] for w in context]
return torch.tensor(idxs, dtype=torch.long)
# 모델 학습
for epoch in range(num_epochs):
total_loss = 0
for context, target in data:
context_vector = make_context_vector(context, word_to_idx)
target_vector = torch.tensor([word_to_idx[target]], dtype=torch.long)
optimizer.zero_grad()
log_probs = model(context_vector.unsqueeze(0))
loss = criterion(log_probs, target_vector)
loss.backward()
optimizer.step()
total_loss += loss.item()
if (epoch + 1) % 100 == 0:
print(f'Epoch: {epoch + 1}, Loss: {total_loss:.4f}')
print()
# 단어 임베딩 확인
for word, idx in word_to_idx.items():
print(f'단어: {word}, 벡터: {model.embeddings.weight[idx].detach().numpy()}')
# 단어 간 유사도 계산 (코사인 유사도)
def cosine_similarity(vec1, vec2):
return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))
cat_vector = model.embeddings.weight[word_to_idx['cat']].detach().numpy()
mat_vector = model.embeddings.weight[word_to_idx['mat']].detach().numpy()
similarity = cosine_similarity(cat_vector, mat_vector)
print(f"\n단어 'cat'과 'mat'의 유사도: {similarity:.4f}")
data: [(['cat'], 'fat'), (['fat', 'on'], 'cat'), (['cat', 'the'], 'on'), (['on', 'mat'], 'the'), (['the'], 'mat')]
Epoch: 100, Loss: 6.8404
Epoch: 200, Loss: 5.2586
Epoch: 300, Loss: 3.6457
Epoch: 400, Loss: 2.4320
Epoch: 500, Loss: 1.7228
Epoch: 600, Loss: 1.2089
Epoch: 700, Loss: 0.8251
Epoch: 800, Loss: 0.5722
Epoch: 900, Loss: 0.4157
Epoch: 1000, Loss: 0.3171
단어: cat, 벡터: [-0.03888694 -3.0729935 0.45441577]
단어: mat, 벡터: [-1.3624699 -1.2569458 0.52239734]
단어: on, 벡터: [-1.1695201 1.016864 0.9518821]
단어: fat, 벡터: [0.48233137 1.3976113 1.2771398 ]
단어: the, 벡터: [ 1.5251784 0.9244947 -2.648047 ]
단어 'cat'과 'mat'의 유사도: 0.6941
시각화
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 임베딩 벡터 3D 시각화
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for word, idx in word_to_idx.items():
vec = model.embeddings.weight[idx].detach().numpy()
ax.scatter(vec[0], vec[1], vec[2])
ax.text(vec[0], vec[1], vec[2], word)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.title('Word Embeddings in 3D Space')
plt.show()
이를 Gensim
의 Word2Vec
을 사용하여 Code를 바꿔보겠습니다.
Gensim
import gensim
from gensim.models import Word2Vec
# 예제 문장
corpus = [['fat', 'cat', 'on', 'the', 'mat']]
# Word2Vec 모델 학습
model = Word2Vec(sentences=corpus, vector_size=3, window=1, min_count=1, sg=0, epochs=1000)
# 단어 벡터 확인
word_vectors = model.wv
# 각 단어의 벡터 출력
for word in word_to_idx.keys():
print(f"단어: {word}, 벡터: {word_vectors[word]}")
# 단어 간 유사도 확인 (코사인 유사도)
similarity = word_vectors.similarity('cat', 'mat')
print(f"\n단어 'cat'과 'mat'의 유사도: {similarity:.4f}")
# 3D 시각화
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for word in word_to_idx.keys():
vec = word_vectors[word]
ax.scatter(vec[0], vec[1], vec[2])
ax.text(vec[0], vec[1], vec[2], word)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.title('Word Embeddings in 3D Space')
plt.show()
단어: cat, 벡터: [-0.1226346 0.25150684 -0.05610979]
단어: mat, 벡터: [-0.01437103 0.01732101 0.16506158]
단어: on, 벡터: [ 0.21749914 0.3027327 -0.16991712]
단어: fat, 벡터: [-0.14649029 0.22340672 -0.17063576]
단어: the, 벡터: [ 0.30471483 -0.3044758 -0.24490714]
단어 'cat'과 'mat'의 유사도: -0.0661
직접 구현한 코드는 매번 엉뚱하게 나오는데 Gensim
은 최적화가 잘 되어 있어서 그런지는 잘 모르겠습니다만, 랜덤시드로 하여도 "fat" "cat" "mat"은 항상 붙어서 출력됩니다. 혹시 이론으로 짠 제 코드가 잘못되어 있을 수도 있으니 혹시 발견하게 된다면 알려주시면 감사하겠습니다.
요약
- 목표: 주어진 주변 단어들(문맥)로 중심 단어를 예측하는 것.
- 입력: 주변 단어들의 원-핫 인코딩 벡터.
- 출력: 중심 단어의 원-핫 인코딩 벡터.
학습 과정
- 입력층: 주변 단어들의 원-핫 벡터를 입력으로 받습니다.
- 임베딩 층: 입력 벡터를 임베딩 벡터로 변환합니다.
- 투사층: 주변 단어들의 임베딩 벡터의 평균을 계산합니다.
- 출력층: 평균 벡터를 통해 중심 단어를 예측합니다.
- 소프트맥스: 예측된 중심 단어의 확률 분포를 생성합니다.
- 손실 계산: 실제 중심 단어와 예측된 확률 분포 간의 손실을 계산하여 모델을 학습합니다.
Skip-gram
Skip-gram
모델은 중심 단어로부터 주변 단어들을 예측하는 방식입니다. CBOW
와 반대되는 개념이므로 여기서 크게 설명하지는 않겠습니다. 나중에 기회가 된다면 따로 설명해보겠습니다. 아래는 요약입니다.
- 목표: 주어진 중심 단어로부터 주변 단어들을 예측하는 것.
- 입력: 중심 단어의 원-핫 인코딩 벡터.
- 출력: 주변 단어들의 원-핫 인코딩 벡터.
학습 과정
- 입력층: 중심 단어의 원-핫 벡터를 입력으로 받습니다.
- 임베딩 층: 입력 벡터를 임베딩 벡터로 변환합니다.
- 투사층: 중심 단어의 임베딩 벡터를 계산합니다.
- 출력층: 중심 단어의 임베딩 벡터로 주변 단어들을 예측합니다.
- 소프트맥스: 예측된 주변 단어들의 확률 분포를 생성합니다.
- 손실 계산: 실제 주변 단어들과 예측된 확률 분포 간의 손실을 계산하여 모델을 학습합니다.
CBOW와 Skip-gram의 차이점
- CBOW: 다수의 주변 단어들로부터 하나의 중심 단어를 예측. 주로 작은 데이터셋에서 잘 작동.
- Skip-gram: 하나의 중심 단어로부터 여러 주변 단어들을 예측. 큰 데이터셋에서 잘 작동하며, 드문 단어들에 대해 더 잘 학습.
참고자료
09-02 워드투벡터(Word2Vec)
앞서 원-핫 벡터는 단어 벡터 간 유의미한 유사도를 계산할 수 없다는 단점이 있음을 언급한 적이 있습니다. 그래서 단어 벡터 간 유의미한 유사도를 반영할 수 있도록 단어의 의미를…
wikidocs.net
https://m.blog.naver.com/j7youngh/222886766883
[ Word2Vec ] 파이썬 python 자연어 처리 NLP( 임베딩 embedding, Word2Vec 로 빅데이터 분석 마스터 )
파이썬(python), 자연어 처리 NLP Word2Vec 으로 임베딩 embedding 해 단어 간 유사성을 파악해 보자....
blog.naver.com
'Artificial Intelligence > NLP' 카테고리의 다른 글
[NLP] TF-IDF(Term Frequency-Inverse Document Frequency) | study book (0) | 2024.09.22 |
---|---|
[NLP] FastText | study book (0) | 2024.09.21 |
[NLP] N-gram | study book (0) | 2024.09.21 |
[NLP] 토큰화 | study book (1) | 2024.09.08 |
[AI::NLP] Bag-of-Words (BoW) 모델 (0) | 2024.07.20 |