728x90
반응형

은닉층 이후의 처리(행렬 곱과 Softmax 계층의 계산)병목 해소가 목표

네거티브 샘플링(부정적 샘플링)기법 사용해 해결

Softmax 대신 네거티브 샘플링을 이용하면 어휘가 아무리 많아져도 계산량을 낮은 수준에서 일정하게 억제할 수 있다.

 

 

 

 

 

은닉층 이후 계산의 문제점

  • 어휘가 100만 개, 은닉층 뉴런 100개 CBOW 모델 예

 

어휘가 100만 개 가정 word2vec : "you"와 "goodbye"가 맥락 "say"가 타깃(예측해야 할 단어)

[그림1] 어휘가 100만 개 가정 word2vec

 

은닉층 이후 계산이 오래 걸리는 부분

  • 은닉층의 뉴런과 가중치 행렬(Wout)의 곱
  • Softmax 계층의 계산

 

거대한 행렬은 곱하는 문제

  • 은닉층 벡터 크기 100, 가중치 행렬 크기 100 x 100 
  • 행렬 곱은 '가볍게' 만들어야 한다.

 

Softmax 문제

  • 어휘가 많아지면 Softmax의 계산량도 증가한다.

k번째 원소(단어)를 타깃으로 했을 때 Softmax 계산식

[그림2] k번째 원소(단어)를 타깃 Softmax 계산식 

 

  • 어휘 수를 100만개 가정 분모값을 얻으려면 exp 계산 100만 번, 
  • 어휘 수에 비례하므로 Softmax를 대신할 '가벼운'계산이 필요하다.

 

다중 분류에서 이진 분류로

  • 네거티브 샘플링 기법의 핵심 아이디어이진 분류(binary classification)에 있다.
    • 다중 분류(multi-class classification)(다중 클래스 분류)를 '이진 분류'로 근사하는 것이 네거티브 샘플링을 이해하는데 중요한 포인트
  • 이진 분류 방식으로 해결하는 것은 "Yes/No"로 답할 수 있는 질문을 생각하는 것이다.

 

 

타깃 단어만의 점수를 구하는 신경망

[그림3] 타깃 단어만의 점수를 구하는 신경망

 

"say"에 해당하는 열벡터와 은닉층 뉴런의 내적을 계산한다.('dot' 노드가 내적을 계산)

[그림4] "say"에 해당하는 열벡터와 은닉층 뉴런의 내적을 계산

  • 출력 측의 가중치 Wout에는 각 단어 ID의 단어 벡터가 각각의 열로 저장
    • 예) "say"에 해당하는 단어 벡터를 추출한다. 
    • 벡터와 은닉층 뉴런과의 내적을 구한다.
    • 이 내적이 최종 점수다.

 

 

 

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

  • 이진 분류 문제를 신경망으로 풀려면 점수에 시그모이드 함수를 적용해 확률로 변환하고, 손실을 구할 때는 손실 함수로 '교차 엔트로피 오차'를 사용한다.

 

시그모이드 함수

[그림5] 시그모이드 함수

  • 그래프는 S자 곡선 형태이며, 입력 값(x)는 0에서 1사이의 실수로 변환된다.
  • 시그모이드 함수의 출력(y)을 '확률'로 해석할 수 있다는 점이다.

 

Sigmoid 계층(왼쪽)과 시그모이드 함수의 그래프(오른쪽)

[그림6] Sigmoid 계층과 시그모이드 함수의 그래프

 

  • 시그모이드 함수를 적용해 확률 y를 얻으면, y로부터 손실을 구한다.
  • 시그모이드 함수에 사용되는 손실 함수는 다중 분류 때처럼 '교차 엔트로피 오차'다.

 

교차 엔트로피 오차 수식

[그림7] 교차 엔트로피 오차 수식

  • y는 시그모이드 함수 출력
  • t는 정답 레이블(레이블 값은 0 혹은 1이다.) 
  • t가 1이면 "Yes"이고 -logy가 출력
  • t가 0이면 "No"이고 -log(1-y)가 출력

 

Sigmoid 계층과 Cross Entropy 계층의 계산 그래프(오른쪽은 Sigmoid with Loss 계층으로 통합)

[그림8] Sigmoid 계층과 Cross Entropy 계층

  • y-t는 정답 레이블이 1이라면 y가 1(100%)에 가까워질수록 오차가 줄어든다는 뜻이다.
  • y가 1로부터 멀어지면 오차가 커진다.
  • 오차가 크면 '크게' 학습하고, 작으면 '작게' 학습한다.

 

다중 분류에서 이진 분류로(구현)

 

다중 분류를 수행하는 CBOW 모델의 전체 그림

[그림9] 다중 분류를 수행하는 CBOW 모델 전체 그림

 

신경망 이진 분류 신경망으로 변환

 

이진 분류를 수행하는 word2vec(CBOW 모델)의 전체 그림

[그림10] 이진 분류 수행하는 word2vec 전체 그림

  • 은닉층 뉴런 h와, 출력 측의 가중치 Wout에서 단어 "say"에 해당하는 단어 벡터와의 내적을 계산한다.
  • 그 출력을 Sigmoid with Loss 계층에 입력해 최종 손실을 얻는다.

 

Embedding Dot 계층 구현

# Embedding Dot 계층 구현
class EmbeddingDot:
    def __init__(self, W):
        self.embed = Embedding(W)
        self.params = self.embed.params
        self.grads = self.embed.grads
        self.cache = None
        
    def forward(self, h, idx):
        target_W = self.embed.forward(idx)
        out = np.sum(target_W * h, axis = 1)
        
        return out
        
    def backward(self, dout):
        h, target_W = self.cache
        dout = dout.reshape(dout, shape[0], 1)
        
        dtarget_W = dout * h
        self.embed.backward(dtarget_W)
        dh = dout * target_W

        return dh
  • params에는 매개변수 저장
  • grads에는 기울기 저장
  • embed는 Embedding 계층
  • cache는 순전파 시의 계산 결과를 잠시 유지하기 위한 변수

 

 

네거티브 샘플링

 

CBOW 모델의 은닉층 이후의 처리 예

[그림11] CBOW 모델의 은닉층 이후의 처리 예

  • 긍정적 예인 "say"에 대해서만 학습한다.
  • 부정적 예("say"이외의 단어)에 대해서는 아무런 지식도 얻지 못한다.
  • 원하는 것
    • 긍정적 예에 대해서는 Sigmoid 출력을 1에 가깝게 만들고
    • 부정적 예에 대해서는 Sigmoid 출력을 0에 가깝게 만드는 것이다.

[그림12] 부정적 예 

 

  • 적은 수의 부정적 예를 샘플링하는 것이 '네거티브 샘플링'기법이 의미하는 바다.

 

네거티브 샘플링 기법

  • 긍정적 예를 타깃으로 한 경우 손실을 구한다.
  • 동시에 부정적 예를 몇 개 샘플링(선별)해, 그 부정적 예에 대해서도 마찬가지로 손실을 구한다.
  • 각각의 데이터(긍정적 예와 부정적 예)의 손실을 더한 값을 최종 손실로 한다.

 

 

네거티브 샘플링의 예(은닉층 이후의 처리에 주목해 그린 계산 그래프)

[그림13] 네거티브 샘플링의 예

 

네거티브 샘플링의 샘플링 기법

  • 부정적 예를 샘플링하는 방법
    • 말뭉치의 통계 데이터를 기초로 샘플링하는 방법
    • 말뭉치에서 자주 등장하는 단어를 많이 추출하고 드물게 등장하는 단어를 적게 추출하는 방법
    • 각 단어의 출현 횟수를 '확률분포;로 나타내고 확률분포대로 샘플링하면 된다.

 

확률분포에 따라 샘플링

[그림14] 확률분포에 따라 샘플링

 

np.random.choice()

  • 무작위로 샘플링 용도로 이용가능
  • size를 지정하면 샘플링을 size만큼 수행한다.
  • 인수에 replace=False를 지정하면 샘플링 시 중복을 없애준다.
  • 인수 p에 확률분포를 담은 리스트를 지정하면 그 확률분포대로 샘플링한다.

 

word2vec 네거티브 샘플링

[그림15] word2vec 네거티브 샘플링 수식

 

  • 0.75제곱을 하는 이유는 출현 확률이 낮은 단어를 '버리지 않기' 위해서다.
  • 0.75제곱을 함으로써, 원래 확률이 낮은 단어의 확률을 살짝 높일 수 있다.

 

 

네거티브 샘플링 구현

import sys
sys.path.append('..')
from np import *  # import numpy as np
from layers import Embedding, SigmoidWithLoss
import collections


class EmbeddingDot:
    def __init__(self, W):
        self.embed = Embedding(W)
        self.params = self.embed.params
        self.grads = self.embed.grads
        self.cache = None

    def forward(self, h, idx):
        target_W = self.embed.forward(idx)
        out = np.sum(target_W * h, axis=1)

        self.cache = (h, target_W)
        return out

    def backward(self, dout):
        h, target_W = self.cache
        dout = dout.reshape(dout.shape[0], 1)

        dtarget_W = dout * h
        self.embed.backward(dtarget_W)
        dh = dout * target_W
        return dh


class UnigramSampler:
    def __init__(self, corpus, power, sample_size):
        self.sample_size = sample_size
        self.vocab_size = None
        self.word_p = None

        counts = collections.Counter()
        for word_id in corpus:
            counts[word_id] += 1

        vocab_size = len(counts)
        self.vocab_size = vocab_size

        self.word_p = np.zeros(vocab_size)
        for i in range(vocab_size):
            self.word_p[i] = counts[i]

        self.word_p = np.power(self.word_p, power)
        self.word_p /= np.sum(self.word_p)

    def get_negative_sample(self, target):
        batch_size = target.shape[0]

        if not GPU:
            negative_sample = np.zeros((batch_size, self.sample_size), dtype=np.int32)

            for i in range(batch_size):
                p = self.word_p.copy()
                target_idx = target[i]
                p[target_idx] = 0
                p /= p.sum()
                negative_sample[i, :] = np.random.choice(self.vocab_size, size=self.sample_size, replace=False, p=p)
        else:
            # GPU(cupy)로 계산할 때는 속도를 우선한다.
            # 부정적 예에 타깃이 포함될 수 있다.
            negative_sample = np.random.choice(self.vocab_size, size=(batch_size, self.sample_size),
                                               replace=True, p=self.word_p)

        return negative_sample


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

 

ID 샘플링 (부정적 예를 골라줌)

corpus = np.array([0,1,2,3,4,1,2,3])
power = 0.75
sample_size = 2

sampler = UnigramSampler(corpus, power, sample_size)
target = np.array([1,3,0]) # 긍정적 예 미니배치 
negative_sample = sampler.get_negative_sample(target)
print(negative_sample)

[그림16] ID 샘플링

 

 

출처 : 밑바닥부터 시작하는 딥러닝2

https://www.hanbit.co.kr/store/books/look.php?p_code=B8950212853

 

밑바닥부터 시작하는 딥러닝 2

이 책은 『밑바닥부터 시작하는 딥러닝』에서 다루지 못했던 순환 신경망(RNN)을 자연어 처리와 시계열 데이터 처리에 사용하는 딥러닝 기술에 초점을 맞춰 살펴본다. 8장 구성으로 전체를 하나

www.hanbit.co.kr

 

728x90
반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기