[논문리뷰]A Neural Probabilistic Language Model

2022. 2. 20. 20:56Artificial_Intelligence/Natural Language Processing

A Neural Probabilistic Language Model

Bengio, Yoshua, Réjean Ducharme, and Pascal Vincent. "A neural probabilistic language model." Advances in Neural Information Processing Systems 13 (2000).

 

NPLM은 단어를 임베딩하여 벡터로 바꾸는 과정에서 신경망 기반의 기법을 제시하여 향후 Word2Vec으로 가는 기반이 되었다고한다.

 

간단하게

  • 학습 데이터에 존재하지 않는 n-gram이 포함된 문장이 나타날 확률을 0으로 매긴다
  • n을 5이상으로 설정하기 어렵기 때문에 문장의 장기 의존성을 포착해내기 어렵다.
  • 단어/문장 간 유사도는 고려 하지 않는다.

 

neural net을 쓰기 이전에는 smoothing( 작은 상수를 더해서 0이 안나오도록) 또는 backoff를 사용해서 data sparcity를 해결했다. long-term dependencies 문제는 n개의 토큰만 검색하므로 다음 토큰은 추론할 수 없다는 문제인데 이것을 해결하기 위해 n을 늘리면 또다시 data sparcity와 마주하게 된다. n-gram으로는 long-term dependencies를 해결할 수 없다.

nplm은 이러한 문제점을 해결하는 과정에서 나타났다.

 

Abstract(Eng)

A goal of statistical language modeling is to learn the joint probability function of sequences of words in a language. This is intrinsically difficult because of the curse of dimensionality: a word sequence on which the model will be tested is likely to be different from all the word sequences seen during training. Traditional but very successful approaches based on n-grams obtain generalization by concatenating very short overlapping sequences seen in the training set. We propose to fight the curse of dimensionality by learning a distributed representation for words which allows each training sentence to inform the model about an exponential number of semantically neighboring sentences. The model learns simultaneously (1) a distributed representation for each word along with (2) the probability function for word sequences, expressed in terms of these representations. Generalization is obtained because a sequence of words that has never been seen before gets high probability if it is made of words that are similar (in the sense of having a nearby representation) to words forming an already seen sentence. Training such large models (with millions of parameters) within a reasonable time is itself a significant challenge. We report on experiments using neural networks for the probability function, showing on two text corpora that the proposed approach significantly improves on state-of-the-art n-gram models, and that the proposed approach allows to take advantage of longer contexts.

 

Abstract (Kor)

통계 언어 모델링의 목표는 언어 내 단어의 시퀀스의 공동 확률 함수를 배우는 것입니다. 이것은 차원성의 저주로 인해 본질적으로 어렵습니다. 모델을 테스트할 단어 시퀀스는 훈련 중에 볼 수 있는 모든 단어 시퀀스와 다를 수 있습니다. n-gram을 기반으로 하는 기존의 매우 성공적인 접근 방식은 교육 세트에서 볼 수 있는 매우 짧은 중첩 시퀀스를 연결함으로써 일반화됩니다. 우리는 각 훈련 문장이 의미론적으로 인접한 문장의 기하급수적인 수를 모델에 알릴 수 있는 단어의 분산 표현을 학습함으로써 차원성의 저주와 싸울 것을 제안합니다. 모델은 (1) 단어 시퀀스에 대한 확률 함수와 함께 각 단어에 대한 분산 표현을 동시에 학습하며, 이러한 표현으로 표현됩니다. 일반화는 이전에 볼 수 없었던 일련의 단어들이 이미 본 문장을 형성하는 단어와 (주변 표현을 갖는다는 의미에서) 유사한 단어로 만들어진다면 높은 확률을 얻기 때문에 얻어집니다. 합리적인 시간 내에 수백만 개의 매개 변수를 가진 대규모 모델을 교육하는 것 자체가 큰 과제입니다. 우리는 확률 함수에 신경망을 사용하는 실험에 대해 보고하며, 제안된 접근법이 최첨단 n-그램 모델을 크게 개선하고 제안된 접근법이 더 긴 맥락을 활용할 수 있음을 두 개의 텍스트 말뭉치에 보여줍니다.

 

OverView

Neural Probabilistic Language Model (NPLM)Distributed Representation을 사용하는 방식 중 하나이다.

Distributed Representation (분산표현) 분산 표현은 분포 가설을 이용하여 텍스트를 학습하고, 단어의 의미를 벡터의 여러 차원에 분산하여 표현함.

기존에는 대부분 one-hot-encoding을 사용했다. One-hot-encoding란, 전체 Vocabulary의 각 단어에 대해서 각각 고유한 인덱스를 부여한다. 각 단어의 word vector는 vocabulary 크기와 동일하게 생성되고, 해당 단어에게 부여된 인덱스의 값을 1로, 나머지 값들은 모두 0으로 표현하는 방식이다. 예를 들어, arsenal, chelsea, liverpool, manchaster로 구성된 volabulary가 존재한다고 했을 때, arsenal을 표현하기 위해서는 첫 번째 값을 1로 할당하고 나머지 값들은 모두 0으로 할당해서 표현하는 방식이다.

one-hot-encoding 의 단점

  • 전체 vocabulary의 크기가 커지는 경우, 한 단어를 나타내는 단어 벡터의 크기도 함께 커짐. 전체 단어 목록이 매우 커지는 경우, 한 단어를 표현하기 위한 벡터 또한 매우 커진다.
  • 두 단어 사이의 관계를 표현하지 못함. 서로 다른 자리가 1로 되어 있고, 나머지 자리는 모두 0이기 때문에, 두 벡터를 내적하는 경우 무조건 0이 나온다. 두 벡터가 직교한다는 뜻인데, 이러면 두 벡터는 벡터 공간에서 서로 독립적으로 존재한다고 할 수 있다. 그러므로, one-hot-vector를 사용하는 경우 두 벡터 사이의 관계를 나타낼 수 없다.

두 번째 문제가 가장 큰 문젠데, 실제 생활 속에서는 단어들 사이에 관계가 존재하고, 그 관계를 표현하는 것이 매우 중요하기 때문이다. 이러한 문제들을 해결하기 위해서 distributed representation을 사용하게 된다.

 

NPLM에서는 여태까지 주어진 현재 단어를 이전 (n-1)개의 단어를 이용해서 예측하게 된다. 즉, t번째 단어는 (t−1)번째 단어부터 (t−n+1)번째 단어를 기반으로 에측을 한다는 뜻이다. 이를 수식으로 쓰면 다음과 같다

NPLM은 위의 조건부 확률을 최대화하는 방식으로 학습을 하게 된다. 이전의 (n-1) 개의 단어를 이용해서 현재 단어를 예측하기 때문에, n-gram 모델을 사용한다고 볼 수 있다.

위의 조건부 확률을 최대화하기 위해서는 분자를 최대화하고 분모를 최소화해야 한다.

 

NPLM의 출력층에서는 V-차원의 점수 벡터(score vector)에 해당하는 y_wt 값을 softmax 함수를 통과시켜 나온 확률벡터를 제공한다. 확률 벡터에서 높은 확률값이 제공되는 인덱스의 단어가 (one-hot-vector를 사용하기 때문에) 실제 정답에 해당하는 단어와 일치하도록 학습을 진행한다.

 

NPLM의 입력층에서는 (n-1)개의 선행하는 단어들의 one-hot-vector가 입력으로 들어온다. One-hot-vector이기 때문에 각 벡터는 V-차원일 것이다. 각 벡터들은 와 연산을 통해서 지정된 크기인 m차원의 입력 벡터로 변환된다. 해당 변환의 과정은 다음과 같다.

입력 벡터 x_tm 차원이기 때문에, 해당 차원의 결과물을 만들어 내기 위해서 C는 m×|V|의 차원을 보유하게 된다. One-hot-vector로 구성된 w_t와 C가 연산을 한다는 것은, C행렬에서 t 번째의 열만 참조(lookup)하는 것과 동일한 연산이다.


Curse of Dimensionality with Distributed Representations

저자는 단어를 컴퓨터에게 입력시키는 과정에서 그 차원이 커지는 것이 학습을 어렵게 만드는 근본적인 문제라고 말하고 있다. 예를 들어, 10000개의 Vocabulary size가 있고 단어를 one-hot-encoding 으로 처리한다면 해당하는 단어의 element만 1이 될 것이고 나머지는 0이 될 것이다. 이러한 벡터는 sparse vector이며 여러 측면에서 효율성이 매우 떨어지고, Vocabulary size가 매우 큰 real problem에서는 계산복잡도가 크게 늘어날 것이기 때문에 학습을 방해할 것이다. 또한 저자는 단어간의 의미적, 문법적 유사성을 표현하는 것 역시 중요하나 이런 점도 잘 이뤄지지 않고 있다고 한다. 이러한 문제점들을 극복하기 위해 고안된 것이 분산표현(Distributed Representation) 이다.

분산표현은 단어를 기존의 원핫벡터처럼 차원수를 Vocabulary size 전체로 하는 것이 아니라 그보다 훨씬 작은 m차원 벡터로 표현하여 sparse vector가 아닌 Dense vector 로 나타낼 수 있게 한다. 이때 element들은 discrete한 variable이 아닌 continuous한 variable인데, 저자는 Continuous variable들을 modeling 할 때가 smoothness 측면에서 discrete variable보다 훨씬 쉽고 효율적이라며 Continuous variable의 중요성을 강조하고 있다. 차원수를 줄이는 것이 주 목적이지만 element들이 Continuous variable이기 때문에 벡터간 유사도와 거리 계산이 가능하고 이는 곧 앞에서 언급한 단어의 유사성을 표현하는 것이 가능하다는 뜻이다.

 


n-gram

Statistical Language Model(통계적 언어모델) 은 이전 단어들이 주어졌을 때에 대한 다음 단어의 조건부 확률들의 곱으로 표현된다.

여기서 단어 순서를 고려하는 이점을 살리고 Word Sequence에서 가까운 단어들은 통계적으로 더 dependent하다는 사실을 적용, 이 방법을 개선하여 이전 n-1개만의 단어들 혹은 맥락(context)에 대한 조건부 확률을 활용하는 것이 바로 n-gram model이다.

그러나, 만약 어떤 n개의 word sequence가 Training corpus(말뭉치)에 없다면 어떻게 할까? 확률을 0으로는 할 수 없을것이다. 저자는 새로운 word sequence는 얼마든지 등장할 수 있고 context가 커질 수록 더욱 이런 상황이 빈번할 것이라고 얘기한다. 이에 대한 대표적인 해결책으로 더 작은 context를 사용해 살펴보는 Back-off 방법(Katz, 1987)과, 특정한 값을 더해 확률을 0으로 만들지 않는 Smoothing 방법이 있다. 저자는 그러나, 이 n-gram 모델은 context 범위를 벗어난 단어와의 연관성을 고려하지 않으며 단어간 유사성을 고려하지 않는다고 말하고 있다.

NPLM은 n-gram모델을 본질로 하되, 이러한 단점들을 극복하기 위해 앞에서 언급한 분산표현(Distributed Representation)을 이용하는 것이다.


what is NPLM?

NPLM (Neural Probabilistic Language Model)은 2003년에 개발된 임베딩 기법으로, n-1개 단어 순서 기반으로 다음 n번째에 등장할 단어를 맞추는 n-gram 언어 모델이다.

 

이는 기존의 언어 모델이 가지고 있던 문제점을 보완한 모델이다.

  • 존재하지 않는 n-gram에 대한 확률 0으로 부여하는 문제점
  • 차원의 저주 : n을 크게 설정하면 위와 같이 확률이 0이 되는 경우가 빈번하게 발생하는 문제점
  • 단어 간 유사도를 계산할 수 없는 문제점

w_t는 문장에서 t번째에 등장하는 단어.

index~ for~ w_t는 t번째임을 가리키는 one-hot vector이다.

행렬 C는 각 단어에 대한 벡터가 행으로 쌓여 있는 행렬이며, 초기값은 랜덤으로 부여된다.

t는 예측할 단어가 등장하는 위치이고, n은 입력되는 단어 개수.

t-(n-1)번째부터 t-1번째 단어에 대한 one-hot vector값이 입력으로 들어감.

 

입력층 (input layer)

t-(n-1) 부터 t-1번째 단어 벡터 위치를 나타내는 one-hot vector와 행렬 C가 내적으로 곱해지며 해당 단어의 벡터값이 나온다. 이 나오는 벡터값들을 각각 x_k라고 하며 옆으로 concatenate하여 x로 나타낸다.

 

은닉층 (hidden layer)

tanh 함수를 이용하여 score vector 값을 구하며, 공식은 다음과 같다.

y = b + Wx + U*tanh(d+Hx) (b, W, U, d, H는 매개변수)

(b와 d는 각각 bias term  H는 hidden layer의 weights 

W는 input layer와 output layer의 direct connection을 만들 경우의 weights를 의미)

 

출력층 (output layer)

y값에 softmax 함수를 적용시키며, 정답 one-hot vector와 값을 비교하여 역전파(back-propagation)를 통해 학습된다.

 

단어들을 ∣V∣보다 작은 차원의 벡터로 표현할 수 있다는 시도 덕분에 이후 Word2vec으로 발전 할 수 있었다고 한다. 그러나 update 해야할 파라미터가 분산표현을 담당하는 C외에도 HU등이 있어 여전히 계산복잡성이 높다는 것이 문제이다. 실제로 Word2Vec은 이러한 문제점을 고치기 위해 학습해야할 파라미터를 줄여냈다고 한다.

 

코드로 구현하면 다음과 같다.

import torch
import torch.nn as nn
import torch.optim as optim

device = "CUDA" if torch.cuda.is_available() else "cpu"
print(f"Using {device} device")

def make_batch():
    input_batch = []
    target_batch = []

    for sen in sentences:
        word = sen.split()#자르고
        input = [word_dict[n] for n in word[:-1]] #인풋은 1~ n-1까지
        target = word_dict[word[-1]] # 아웃풋은 n

        input_batch.append(input)
        target_batch.append(target)

    return input_batch, target_batch #배치사이즈 만들어서 반환

# NPLM 모델 제작
class NNLM(nn.Module):
    def __init__(self): #계층(Layer)
        super(NNLM, self).__init__()
        #임베딩층
        self.C = nn.Embedding(n_class, m) #임베딩할 단어들의 갯수(룩업테이블), 임베딩할 벡터의 차원
        #레이어 층 추가
        self.H = nn.Linear(n_step * m, n_hidden, bias=False) #인풋 = 스텝수*차원 , 아웃풋 = 히든사이즈
        #파라미터 추가
        self.d = nn.Parameter(torch.ones(n_hidden)) #Bias Term
        #레이어 층 추가
        self.U = nn.Linear(n_hidden, n_class, bias=False) #인풋 = 히든사이즈, 아웃풋 = 룩업 테이블
        #레이어 층 추가
        self.W = nn.Linear(n_step * m, n_class, bias=False) #인풋 = 스텝수*차원, 아웃풋 = 룩업테이블
        #Bias 추가
        self.b = nn.Parameter(torch.ones(n_class))   #Bias Term

    def forward(self, X): #Output 반환
        X = self.C(X) #임베팅 레이어에 룩업테이블 넣음
        X = X.view(-1, n_step * m) # X를 [batch_size, n_step*m]로 형태 변경
        tanh = torch.tanh(self.d + self.H(X)) # [batch_size, n_hidden]
        output = self.b + self.W(X) + self.U(tanh) # [batch_size, n_class]
        # NPLM의 스코어 벡터 y = b + Wx + U*tanh(d+Hx)
        return output


if __name__ == '__main__':
    n_step = 2 # 스텝의 수, 논문에서는 n-1로 표현
    n_hidden = 2 # 히든사이즈 수, 논문에서의 h
    m = 2 # 임베딩사이즈, 논문에서의 m

    sentences = ["i like dog", "i love coffee", "i hate milk"]

    word_list = " ".join(sentences).split()  #join으로 한줄로 만든 다음에 split으로 자름
    word_list = list(set(word_list)) #['like', 'coffee', 'love', 'milk', 'hate', 'dog', 'i']

    word_dict = {w: i for i, w in enumerate(word_list)} #{'like': 0, 'coffee': 1, 'love': 2, 'milk': 3, 'hate': 4, 'dog': 5, 'i': 6}
    number_dict = {i: w for i, w in enumerate(word_list)} #{0: 'like', 1: 'coffee', 2: 'love', 3: 'milk', 4: 'hate', 5: 'dog', 6: 'i'}
    n_class = len(word_dict)  # 7

    model = NNLM()

    criterion = nn.CrossEntropyLoss() #다중분류
    #optim을 사용해서 가중치 갱신 방법을 구현함.
    optimizer = optim.Adam(model.parameters(), lr=0.001) #모델의 파라미터 = <generator object Module.parameters at 0x00000221A1AE2EB0>

    input_batch, target_batch = make_batch()
    input_batch = torch.LongTensor(input_batch) # type int형인 텐서로 바꾸고
    target_batch = torch.LongTensor(target_batch)

    # 학습
    for epoch in range(5000):
        #Pytorch에서는 gradients값들을 추후에 backward를 해줄때 계속 더해주기 때문"에
        #항상 backpropagation을 하기전에 gradients를 zero로 만들어주고 시작을 해야함
        optimizer.zero_grad()#변화도 버퍼를 0으로 만듬. 중요

        output = model(input_batch)

        # output : [batch_size, n_class], target_batch : [batch_size]
        #손실 함수는 (output, target)을 한 쌍(pair)의 입력으로 받아,
        #출력(output)이 정답(target)으로부터 얼마나 멀리 떨어져있는지 추정하는 값을 계산함.
        loss = criterion(output, target_batch)

        if (epoch + 1) % 1000 == 0:
            print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss))

        loss.backward() #역전파하여 가중치 변화 업데이트
        optimizer.step() #업데이트 진행

    # 예측
    predict = model(input_batch).data.max(1, keepdim=True)[1]

    # 테스트
    print([sen.split()[:2] for sen in sentences], '->', [number_dict[n.item()] for n in predict.squeeze()])

 

A neural probabilistic language model, The Journal of Machine Learning ResearchVolume 33/1/2003 pp 1137–1155
https://misconstructed.tistory.com/35
https://heehehe-ds.tistory.com/entry/NLP-NPLMNeural-Probabilistic-Language-Model
https://velog.io/@donggunseo/NPLM
https://nobase2dev.tistory.com/24
728x90