word2vec은 단어를 숫자로 변환하는 방법 중 하나입니다. word2vec 이전에 썼던 방법은 one-hot encoding이 있습니다
One-hot encoding
다음과 같은 문장을 봅시다
Laughter echoed through the hall as the children played.
각 단어 당 차원 1개를 할당하여 매핑합니다
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |
Laughter | 1 | ||||||||
echoed | 1 | ||||||||
through | 1 | ||||||||
the | 1 | ||||||||
hall | 1 | ||||||||
as | 1 | ||||||||
the | 1 | ||||||||
children | 1 | ||||||||
played | 1 |
예를들어 echoed를 vector로 나타내면 [0,1,0,0,0,0,0,0] 입니다
다음과 같은 단점이 있습니다
1. 단어 1개당 차원 1개가 필요해 차원이 크게 늘어난다. (그마저도 벡터의 대부분의 값은 0이다)
2. Orthogonality : 각 단어의 내적이 0임. 단어간의 의미를 파악할 수 없다
Word2Vec
"비슷한 문맥에 있는 단어들은 비슷한 뜻을 가지고 있다"
...President Moon said yesterday...
...President Trump said yesterday...
...President Putin said yesterday...
각 3단어들은 비슷한 문맥에서 쓰였으므로 비슷한 단어라는 것을 알 수 있다.
=> 주변단어와 중심단어로부터 임베딩을 학습하자!
주변단어 : President, said, yesterday
중심단어 : Moon
주변단어로부터 중심단어 예측하는 방법 => CBOW(Continuous Bag of Words)
중심단어로부터 주변단어 예측 하는 방법 => Skip-gram
Window : 주변단어 앞뒤로 몇개의 글자를 주변단어로 가져올 것인가
CBOW
Vfat은 fat을 word2vec로 구한 결과임
projection layer를 구하기 위한 weight, output layer를 구하기위한 weight, 총 2개의 weight가 필요함
loss는 크로스 엔트로피 함수를 쓰면 됨
Bias는 없다
Skip-gram
CBOW : 학습이 빠름
Skip-gram : 적게 등장하는 단어들도 학습에 여러번 쓰이기 때문에 잘 임베딩 됨
Skip-gram이 일반적으로 성능이 더 좋다고 한다.
실제로 쓰이지 않음 => Negative sampling
코드
딥러닝 라이브러리 없이 skip gram을 구현해보겠습니다
데이터는 책 데이터 뒤져보다가 txt로만 된 것이 있어서 gutenberg에서 가져왔습니다.
import requests
URL = 'https://www.gutenberg.org/cache/epub/73510/pg73510.txt'
response = requests.get(URL)
response.status_code
txt = response.text
전처리는 그냥 nltk를 썼습니다, 저는 통으로 학습했는데, 문장 단위로 끊어서 학습하는 것이 더 좋습니다.
import nltk
nltk.download('punkt')
from nltk.tokenize import word_tokenize
def parse_text(txt):
ascii_letters = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
txts = txt.split('\n')
START = False
END = False
words = []
for t in txts:
t = t.replace('\r','')
t = t.replace('_',' ')
t = t.replace('-',' ')
if t == 'THE CASTAWAYS':
START = True
if t == 'THE END':
END = True
if START and not END:
for word in word_tokenize(t):
if set(word) & set(ascii_letters):
words.append(word)
words = list(map(lambda x: x.lower(), words))
return words
def make_one_hot_encoder(words):
one_hot_encoder = {}
v = len(set(words))
for word in words:
if word not in one_hot_encoder:
one_hot_encoder[word] = np.zeros(v)
one_hot_encoder[word][len(one_hot_encoder)-1] = 1
return one_hot_encoder
words = parse_text(txt)
one_hot_encoder = make_one_hot_encoder(words)
back propagation은 softmax + cross entropy loss를 같이 썼을 때 계산을 쉽게 할 수 있어 간단하게 구현할 수 있습니다
import numpy as np
def forward(Wvm, Wmv, idx) : # idx: 중심단어
P = Wvm @ one_hot_encoder[words[idx]].T # projection layer
O = Wmv @ P # output layer
S = np.exp(O) / np.sum(np.exp(O))
return P, O, S
def backward(Wvm, Wmv, P,O,S, idx):
dO = np.zeros(len(one_hot_encoder))
for i in range(idx-window_size,idx+window_size+1,1):
if i == idx:
continue
if i < 0 or i >= len(words):
continue
dO += S - one_hot_encoder[words[i]]
dWmv = np.outer(dO, P)
dP = dO @ Wmv
dWvm = np.outer(dP, one_hot_encoder[words[idx]])
Wvm -= learning_rate * dWvm
Wmv -= learning_rate * dWmv
return Wvm, Wmv
def train():
global Wvm, Wmv
for e in range(epoch):
print(f"epoch:{e}")
for i in range(len(words)):
P,O,S = forward(Wvm, Wmv, i)
Wvm, Wmv = backward(Wvm, Wmv, P,O,S, i)
# hyper-parameters
window_size = 2
vector_size = 100
learning_rate = 0.01
epoch = 3
Wvm = np.random.rand(vector_size, len(one_hot_encoder))
Wmv = np.random.rand(len(one_hot_encoder), vector_size)
train()
학습이 잘 되었는지 확인해봅시다
유사도가 가장 높은 단어들을 살펴봅시다
word2vec = {}
word_set = set()
for word in one_hot_encoder:
word2vec[word] = Wvm @ one_hot_encoder[word]
word_set.add(word)
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
def find_top_two_similar_words(word2vec):
word_list = list(word_set)
sim_list = []
for i in range(len(word_list)):
for j in range(i+1, len(word_list)):
word1 = word[i]
word2 = word[j]
sim_list.append((word1, word2, cosine_similarity(word2vec[word1], word2vec[word2])))
return sorted(sim_list, key=lambda x: -x[2])
sim_list = find_top_two_similar_words(word2vec)
print(sim_list[:10])
[('ye', 'you', 0.934766170577427),
('he', 'they', 0.9267597479993857),
('for', 'with', 0.9233073761163063),
('was', 'is', 0.9221814438976493),
('him', 'them', 0.9183951146136528),
('and', 'but', 0.9139173115845995),
('in', 'into', 0.9131892792371982),
('we', 'you', 0.9118327913774454),
('i', 'we', 0.9108868958041368),
('by', 'in', 0.9099312696120511)]
최적화를 하려면
1. negative sampling
2. hierarchical softmax
와 같은 기법이 있습니다.
'인공지능' 카테고리의 다른 글
모델 경량화 프루닝 (Pruning) - Unstructured (0) | 2025.03.23 |
---|---|
A Discriminative Feature Learning Approach for Deep Face Recognition (0) | 2024.12.15 |
Entropy, Cross Entropy, KL divergence (0) | 2024.07.07 |
Vessl GPU 서버 사용 (1) | 2024.04.26 |
연세대학교 공학(야간)대학원 인공지능학과 2024 전기 합격 후기 (23) | 2024.04.12 |