본문 바로가기
----------책----------/밑바닥부터 시작하는 딥러닝2

CHAPTER 4 word2vec 속도 개선

by 탶선 2020. 2. 6.
반응형

 

 

3장에서의 word2vec 문제점

  • input layer의 one-hot 표현과 가중치 행렬 $W_{in}$의 곱 계산
    • 어휘 수가 많아지면 one-hot vector의 size도 커짐(상당한 memory차지)
  • hidden layer의 가중치 행렬 $W_{out} $의 곱
  • Softmax layer 계산

위 2개의 계산이 병목되며 많은 계산시간이 소요하는 문제 발생

#해결방법
-> Embedding Layer

-> Negative Sampling(loss function) 

 

 

 

Embedding Layer

가중치 parameter로부터 '단어 ID에 해당하는 vector'를 추출하는 layer

  • 기존 one-hot encoder와 matmul계층의 행렬 곱 계산(행렬의 특정 행 추출) 대신 사용

 

 

 

Embedding layer 구현

import numpy as np

#행렬 생성
W = np.arange(21).reshape(7,3)
W
W[2]
W[5]

#가중치 W로부터 여러 행을 한번에 추출
idx = np.array([1,0,3,0])
W[idx]

class Embedding:
    def __init__(self,W):
        self.params = [W]
        self.grads = [np.zeros_like(W)]
        self.idx = None

    def forward(self, idx):
        W, = self.params
        self.idx = idx
        out = W[idx]
        return out

    def backward(self, dout):
        dW, = self.grads
        dW[...] = 0

        #for i, word_id in enumerate(self.idx):
        #   dW[word_id] += dout[i]

        # np.add.at(A,idx,B) - B를 A의 idx번째 행에 더하기
        # 파이썬의 for문 보다 numpy의 내장 메서드가 속도와 효율면에서 더 최적화 

        np.add.at(dW, self.idx,dout)
        return None

 

 

 

Negative Sampling(loss function)

hidden layer의 가중치 행렬 $W_{out} $의 곱의 문제점을 해결하기 위해 Softmax 대신 negative sampling을 사용

k번째 단어를 target으로 했을 때의 Softmax 계산식 - 계산량이 너무 많음

  • multi-class classification(다중 클래스 분류) 를 binary classification(이진 분류)로 근사하는 방법
  • 어휘가 많아져도 계산량을 낮은 수준에서 일정하게 억제 가능
  • 지금까지 긍정적 정답에 대해서만 학습
  • 부정적 정답에 대해서 sigmoid layer의 출력을 0 에 가깝게 만드는 것
  1. 정답을 target으로 한 경우의 loss 구함
  2. 동시에 오답의 예를 몇 개 샘플링하여 loss 구함
  3. 각각의 데이터의 loss를 더한 값을 최종 loss로 한다.

negative sampling의 예

 

 

 

다중 분류에서 이진 분류로

정답과 오답 각각에 대해 바르게 분류해야 한다.(정답/오답 모두 대상으로 문제 생각)

  1. hidden layer와 output의 weight 행렬의 내적으로 target에 해당하는 vetor만을 추출
  2. 추출된 vector와 hidden layer neuron의 inner product 계산

target만의 score를 구하는 신경망
target에 해당하는 열벡터와 hidden layer 뉴런의 내적 계산

이전까진 모든 단어를 대상으로 계산 수행(출력층)

Negative sampling 후 target 단어 하나에 주목하여 해당 점수만을 계산

 

 

 

시그모이드 함수 & 교차 엔트로피 오차

# 흔히 사용되는 방식

이진 분류 - 시그모이드 적용(확률 변환) , loss - cross entropy error

다중 분류 - 소프트맥스 적용(확률 변환) , loss - cross entropy error

 

 

 

Negative sampling의 sampling(선별) 기법

말뭉치의 통계 데이터를 기초로 sampling

말뭉치에서 자주 등장하는 단어를 많이 추출/ 드물게 등장하는 단어를 적게 추출

각 단어의 출현 횟수를 구해 '확률분포'로 표현, 확률분포대로 단어 sampling

# 빈도가 잦은 단어를 처리하는 편이 좋은 결과로 이어진다.

확률분포에 따른 샘플링 예

import numpy as np

#0~9 숫자 중 하나 무작위 샘플링
np.random.choice(10)
np.random.choice(10)

# words에서 하나만 무작위로 샘플링
words = ['you','say','goodbye','I','hello','.']
np.random.choice(words)

# 5개만 무작위로 샘플링(중복 O)
np.random.choice(words, size=5)

# 5개만 무작위로 샘플링(중복 X)
np.random.choice(words, size=5, replace=False)

# 확률분포에 따른 샘플링
p = [0.5, 0.1, 0.05, 0.2, 0.05, 0.1]
np.random.choice(words, p=p)

확률분포 수정식

$ p(w_i) $ - i 번째 단어의 확률

낮은 확률의 단어가 조금 더 쉽게 샘플링 되도록 각 요소에 0.75 제곱

 

Negative sampling 구현

 

class NegativeSamplingLoss:
    def __init__(self, W, corpus, power=0.75, sample_size=5): #초기화 메서드
        self.sample_size = sample_size
        self.sampler = UnigramSampler(corpus, power, sample_size)
        self.loss_layers = [SigmoidWithLoss() for _ in range(sample_size + 1)]
        self.embed_dot_layers = [EmbeddingDot(W) for _ in range(sample_size + 1)]

        self.params, self.grads = [], []
        for layer in self.embed_dot_layers:
            self.params += layer.params
            self.grads += layer.grads

    def forward(self, h, target):
        batch_size = target.shape[0]
        negative_sample = self.sampler.get_negative_sample(target)

        # 긍정적 예 순전파
        score = self.embed_dot_layers[0].forward(h, target)
        correct_label = np.ones(batch_size, dtype=np.int32)
        loss = self.loss_layers[0].forward(score, correct_label)

        # 부정적 예 순전파
        negative_label = np.zeros(batch_size, dtype=np.int32)
        for i in range(self.sample_size):
            negative_target = negative_sample[:, i]
            score = self.embed_dot_layers[1 + i].forward(h, negative_target)
            loss += self.loss_layers[1 + i].forward(score, negative_label)

        return loss

    def backward(self, dout=1):
        dh = 0
        for l0, l1 in zip(self.loss_layers, self.embed_dot_layers):
            dscore = l0.backward(dout)
            dh += l1.backward(dscore)

        return dh

loss_layers, embed_dot_layers - 원하는 계층을 리스트로 보관, sample_size(오답) + 1(정답) 개의 계층 생성

  • W - 출력 측 가중치
  • corpus - 말뭉치(단어 ID 리스트)
  • power - 확률분포에 제곱할 값
  • sample - 오답 샘플링 횟수

 

word2vec으로 얻은 단어의 분산 표현

  • 비슷한 단어를 가까이 모음
  • 유추(비유) 문제 해결 가능
    • word2vec의 단어의 분산 표현 사용시 유추 문제를 벡터의 덧셈, 뺄셈으로 풀이 가능
    • ex) king - man + woman = queen

  • 단어를 고정 길이 벡터로 변환해줌
    • 자연어를 벡터로 변환하여 일반적인 머신러닝 기법을 적용 가능

단어의 분산 표현을 이용한 시스템의 처리 흐름

 

분산 표현의 평가 방법

  • 유사성, 유추 문제를 활용한 평가
  • 단어의 유사성 평가 - 사람이 작성한 단어 유사도를 검증 세트를 사용해 평가
  • 유추 문제 - 단어의 의미나 문법적인 문제를 어느정도 이해하고 있는지 측정
    • 모델에 따라 정확도 다름
    • 일반적으로 말뭉치(데이터)가 클수록 결과 좋음
    • 단어 벡터 차원 수는 적당한 크기가 좋음(너무 크면 정확도 하락)

 

word2vec을 사용한 애플리케이션의 예

전이 학습(transfer learning) - 한 분야에서 배운 지식을 다른 분야에 적용하는 기법

 

 

 

### 본 게시물은 밑바닥부터 시작하는 딥러닝 2를 읽고 정리한 노트입니다. ###

 

반응형

댓글