car
= auto
, automobile
, machine
, motocar
object
motor vehicle
car
, go-kart
, truck
def preprocess(text):
text = text.lower()
text = text.replace('.', ' .')
text = text.split(' ')
word_to_id = {}
id_to_word = {}
for word in words:
if word not in word_to_id:
new_id = len(word_to_id)
word_to_id[word] = new_id
id_to_word[new_id] = word
corpus = np.array([word_to_id[w] for w in words])
return corpus, word_to_id, id_to_word
text = 'You say goodbye and I say hello.'
corpus, word_to_id, id_to_word = preprocess(text)
동시 발생 행렬: 그 주변의 단어가 몇 번이나 등장하는지 세어 집계
you | say | goodbye | and | i | hello | . | |
---|---|---|---|---|---|---|---|
you | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
say | 1 | 0 | 1 | 0 | 1 | 1 | 0 |
goodbye | 0 | 1 | 0 | 1 | 0 | 0 | 0 |
and | 0 | 0 | 1 | 0 | 1 | 0 | 0 |
i | 0 | 1 | 0 | 1 | 0 | 0 | 0 |
hello | 0 | 1 | 0 | 0 | 0 | 0 | 1 |
. | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
실제로 관련이 없어도 많이 등장한다는 이유로 관련성 높다고 표현
단어의 관련성은 벡터 사이의 유사도를 계산
ex) 벡터의 내적, 유클리드 거리, 코사인 유사도
# 수동으로 만들기
C = np.array([
[0,1,0,0,0,0,0],
[1,0,1,0,1,1,0],
[0,1,0,1,0,0,0],
[0,0,1,0,1,0,0],
[0,1,0,1,0,0,0],
[0,1,0,0,0,0,1],
[0,0,0,0,0,1,0]
], dtype=np.int32)
# 자동으로 만드는 함수
def create_co_matrix(corpus, vocab_size, window_size=1):
corpus_size = len(corpus)
co_matrix = np.zeros((vocab_size, vocab_size), dtype=np.int32)
for idx, word_id in enumerate(corpus):
for i in range(1, window_size+1):
left_idx = idx-i
right_idx = idx+i
if left_idx >= 0:
left_word_id = corpus[left_idx]
co_matrix[word_id, left_word_id] += 1
if right_idx < corpus_size:
right_word_id = corpus[right_idx]
co_matrix[word_id, right_word_id] += 1
return co_matrix
the
, a
와 같은 단어가 빈도가 높다는 이유로 강한 관련성을 갖는다고 평가되는 문제 해결def ppmi(C, verbose=True, eps=1e-8):
M = np.zeros_like(C, dtype=np.float32)
N = np.sum(C)
S = np.sum(C, axis=0)
total = C.shape[0] * C.shape[1]
cnt = 0
for i in range(C.shape[0]):
for j in range(C.shape[1]):
pmi = np.log2(C[i,j]*N / (S[j]*S[i]) + eps)
M[i,j] = max(0, pmi)
if verbose:
cnt += 1
if cnt % (total//100) == 0:
print('%.1f%% 완료'%(100*cnt/total))
return M
text = 'You say goodbye and I say hello.'
corpus, word_to_id, id_to_word = preprocess(text)
vocab_size = len(word_to_id)
C = create_co_matrix(corpus, vocab_size)
W = ppmi(C)
U, S, V = np.linalg.svd(W)
U[:,:2] # 희소벡터 W를 밀집벡터 U로 변환한 후 2차원으로 감소
2. Truncated SVD: 대각 원소(S) 중 상위 몇 개만 추출해 차원 축소
빠르다!
from sklearn.utils.extmath import radomized_svd
U, S, V = randomized_svd(W, n_components=wordvec_size,
n_iter=5, random_state=None)
맥락이 주어졌을 때 해당 위치에 어떤 단어가 들어가는질 추측하는 문제를 반복적으로 풀어 출현패턴을 학습.
간결한 표현이 목적이므로 입력층 뉴런 수보다 은닉층 뉴런 수가 더 적어야 함
말뭉치가 다르면 분산 표현도 다름
, 모두 단어의 의미가 포함되어있지만 만 이용하는 것이 가장 대표적
위 방법을 Word2Vector의 CBOW 방법
Skip-gram은 CBOW를 역전시킨 형태(중앙 단어로 맥락 예측)
성능은 Skip-gram이, 학습속도는 CBOW가 우수
class SimpleCBOW:
def __init__(self, vocab_size, hidden_size):
V, H = vocab_size, hidden_size
# 가중치 초기화
W_in = 0.01 * np.random.randn(V,H).astype('f')
W_out = 0.01 * np.random.randn(V,H).astype('f')
# 계층 생성
self.in_layer0 = MatMul(W_in)
self.in_layer1 = MatMul(W_in)
self.out_layer = MatMul(W_out)
self.loss_layer = SoftmaxWithLoss()
# 모든 가중치와 기울기를 리스트에 모은다.
layers = [self.in_layer0, self.in_layer1, self.out_layer]
self.params, self.grads = [], []
for layer in layers:
self.params += layer.params
self.grads += layer.grads
# 인스턴스 변수에 단어의 분산 표현을 저장한다.
self.word_vecs = W_in
def forward(self, contexts, target):
h0 = self.in_layer0.forward(contexts[:,0])
h1 = self.in_layer1.forward(contexts[:,1])
h = (h0+h1)*0.5
score = self.out_layer.forward(h)
loss = self.loss_layer.forward(score, target)
return loss
def backward(self, dout=1):
ds = self.loss_layer.backward(dout)
da = self.out_layer.backward(ds)
da += 0.5
self.in_layer1.backward(da)
self.in_layer0.backward(da)
return None
그렇다고 해서 추론기반 기법이 우수한 것이 아니고 우열을 가릴 수 없다.
skip-gram은 동시발생행렬에 SVD적용한 것과 동일
Glove: 추론기반기법 + 통계기반기법(통계정보를 손실함수에 반영)
참고
밑바닥부터 시작하는 딥러닝2 (사이토 고키)