본문 바로가기

AI/NLP

[NLP] 룩업 테이블(lookup table)이란? NNLM, 워드 임베딩(word embedding)

룩업 테이블이란?

특정 단어와 맵핑되는 정수를 인덱스로 가지는 테이블로부터 임베딩 벡터 값을 가져오는 것

= 정수를 임베딩 벡터로 맵핑하는 것

 


피드 포워드 신경망 언어 모델(NNLM) 학습 과정 예시

NNLM이 언어 모델링을 학습하는 과정

 

예문 : "what will the fat cat sit on"

 

예를 들어 위와 같은 문장이 있다. 언어 모델은 주어진 단어 시퀀스로부터 다음 단어를 예측.

훈련 과정에서는 'what will the fat cat'이라는 단어 시퀀스가 입력으로 주어지면, 다음 단어 'sit'을 예측하는 방식으로 훈련.

훈련 코퍼스에 7개의 단어만 존재한다고 가정했을 때 다음과 같이 원-핫 인코딩 할 수 있음.

what = [1, 0, 0, 0, 0, 0, 0]
will = [0, 1, 0, 0, 0, 0, 0]
the = [0, 0, 1, 0, 0, 0, 0]
fat = [0, 0, 0, 1, 0, 0, 0]
cat = [0, 0, 0, 0, 1, 0, 0]
sit = [0, 0, 0, 0, 0, 1, 0]
on = [0, 0, 0, 0, 0, 0, 1]

이 원-핫 벡터들이 훈련을 위한 NNLM의 입력이면서 예측을 위한 레이블이 됨.

'what will the fat cat'를 입력을 받아서 'sit'을 예측하는 일은, 기계에게 what, will, the, fat, cat의 원-핫 벡터를 입력받아 sit의 원-핫 벡터를 예측하는 문제.

 

NNLM은 n-gram 언어 모델처럼 다음 단어를 예측할 때, 정해진 개수의 단어만을 참고.

n이 4이면 언어 모델은 'what will the fat cat'라는 단어 시퀀스가 주어졌을 때, 다음 단어를 예측하기 위해 앞의 4개 단어 'will the fat cat'까지만 참고하고 그 앞 단어인 what은 무시. 이 범위를 윈도우(window)라고 하기도 함.

 

NNLM은 위의 그림과 같이 총 4개의 층(layer)으로 이루어진 인공 신경망.

입력층(input layer)을 보면 윈도우의 크기는 4로 정하였으므로 입력은 4개의 단어 'will, the, fat, cat'의 원-핫 벡터.

출력층(output layer)을 보면 정답에 해당되는 단어 sit의 원-핫 벡터는 모델이 예측한 값의 오차를 구하기 위해 레이블로서 사용.

그리고 오차로부터 손실 함수를 사용하여 인공 신경망이 학습.

 

4개의 원-핫 벡터를 입력 받은 NNLM은 다음층인 투사층(projection layer)을 지나게 됨. 투사층은 일반 은닉층과 다르게 가중치 행렬과의 곱셈은 이루어지지만 활성화 함수가 존재하지 않음.

투사층의 크기를 M으로 설정하면, 각 입력 단어들은 투사층에서 V × M 크기의 가중치 행렬과 곱.

V는 단어 집합의 크기를 의미.

만약 원-핫 벡터의 차원이 7이고, M이 5라면 가중치 행렬 W는 7 × 5 행렬이 됨.

 

각 단어의 원-핫 벡터x와 가중치 W 행렬의 곱이 있음.

i번째 인덱스에 1이라는 값을 가지고 그 외의 0의 값을 가지는 원-핫 벡터와 가중치 W 행렬의 곱은, W행렬의 i번째 행을 그대로 읽어오는 것과(lookup) 동일.

그래서 이 작업을 룩업 테이블(lookup table)이라고 함.

 

룩업 테이블 후 V차원을 가지는 원-핫 벡터는 M차원의 벡터로 맵핑.

위 그림에서 단어 fat을 의미하는 원-핫 벡터를 x_fat으로 표현, 테이블 룩업 과정을 거친 후의 단어 벡터는 e_fat으로 표현.

이 벡터들은 초기에는 랜덤한 값을 가지지만 학습 과정에서 값이 계속 변경,이 단어 벡터를 임베딩 벡터(embedding vector)라고 함.

 

각 단어가 테이블 룩업을 통해 임베딩 벡터로 변경되고, 투사층에서 모든 임베딩 벡터들의 값은 연결(concatenate). 여기서 벡터의 연결 연산은 벡터들을 이어붙이는 것을 의미.

 


파이토치(PyTorch)의 nn.Embedding()

파이토치에서 임베딩 벡터를 사용하는 방법이 크게 두 가지.

- 임베딩 층(embedding layer)을 만들어 훈련 데이터로부터 처음부터 임베딩 벡터를 학습하는 방법

- 미리 사전에 훈련된 임베딩 벡터(pre-trained word embedding)들을 가져와 사용하는 방법.

 

다음은 전자에 해당되는 방법. 파이토치에서 nn.Embedding()를 사용하여 구현.

 

1. 임베딩 층은 룩업 테이블이다.

어떤 단어 → 단어에 부여된 고유한 정수값 → 임베딩 층 통과 → 밀집 벡터

임베딩 층은 입력 정수에 대해 밀집 벡터(dense vector)로 맵핑하고, 이 밀집 벡터는 인공 신경망의 학습 과정에서 가중치가 학습되는 것과 같은 방식으로 훈련하고 업데이트 됨. 그리고 이 밀집 벡터를 임베딩 벡터라고 부름.

 

정수를 밀집 벡터 또는 임베딩 벡터로 맵핑한다 =  특정 단어와 맵핑되는 정수를 인덱스로 가지는 테이블로부터 임베딩 벡터 값을 가져오는 룩업 테이블이다.

이 테이블은 단어 집합의 크기만큼의 행을 가지므로, 모든 단어는 고유한 임베딩 벡터를 가진다.

 

위 그림에서 임베딩 벡터의 차원이 4로 설정.

단어 great은 정수 인코딩 과정에서 1,918의 정수로 인코딩, 단어 집합의 크기만큼의 행을 가지는 테이블에서 인덱스 1,918번에 위치한 행을 단어 great의 임베딩 벡터로 사용.

이 임베딩 벡터는 모델의 입력이 되고, 역전파 과정에서 단어 great의 임베딩 벡터값이 학습.

 

파이토치는 임베딩 층의 입력이 원-핫 벡터가 아니어도 동작한다는 점에 헷갈릴 수 있음.

- 단어를 정수 인덱스로 바꾸고, 원-핫 벡터로 한번 더 바꾸고, 임베딩 층의 입력으로 사용하는 것 X

- 단어를 정수 인덱스로만 바꾼채, 임베딩 층의 입력으로 사용 O

-> 룩업 테이블 된 결과인 임베딩 벡터를 리턴.

 

2. 룩업 테이블 과정을 nn.Embedding()을 사용하지 않고 구현하는 예시

우선 임의의 문장으로부터 단어 집합을 만들고 각 단어에 정수를 부여.

train_data = 'you need to know how to code'

# 중복을 제거한 단어들의 집합인 단어 집합 생성.
word_set = set(train_data.split())

# 단어 집합의 각 단어에 고유한 정수 맵핑.
vocab = {word: i+2 for i, word in enumerate(word_set)}
vocab['<unk>'] = 0
vocab['<pad>'] = 1
print(vocab)
{'code': 2, 'you': 3, 'know': 4, 'to': 5, 'need': 6, 'how': 7, '<unk>': 0, '<pad>': 1}

이제 단어 집합의 크기를 행으로 가지는 임베딩 테이블을 구현. 임베딩 벡터의 차원은 3으로 정함.

# 단어 집합의 크기만큼의 행을 가지는 테이블 생성.
embedding_table = torch.FloatTensor([
                               [ 0.0,  0.0,  0.0],
                               [ 0.0,  0.0,  0.0],
                               [ 0.2,  0.9,  0.3],
                               [ 0.1,  0.5,  0.7],
                               [ 0.2,  0.1,  0.8],
                               [ 0.4,  0.1,  0.1],
                               [ 0.1,  0.8,  0.9],
                               [ 0.6,  0.1,  0.1]])

 

임의의 문장 'you need to run'에 대해서 룩업 테이블을 통해 임베딩 벡터들을 가져옴.

sample = 'you need to run'.split()
idxes = []

# 각 단어를 정수로 변환
for word in sample:
  try:
    idxes.append(vocab[word])
  # 단어 집합에 없는 단어일 경우 <unk>로 대체된다.
  except KeyError:
    idxes.append(vocab['<unk>'])
idxes = torch.LongTensor(idxes)

# 각 정수를 인덱스로 임베딩 테이블에서 값을 가져온다.
lookup_result = embedding_table[idxes, :]
print(lookup_result)
tensor([[0.1000, 0.5000, 0.7000],
        [0.1000, 0.8000, 0.9000],
        [0.4000, 0.1000, 0.1000],
        [0.0000, 0.0000, 0.0000]])

3. nn.Embedding()을 통해 임베딩 층 사용하기

전처리는 동일한 과정.

train_data = 'you need to know how to code'

# 중복을 제거한 단어들의 집합인 단어 집합 생성.
word_set = set(train_data.split())

# 단어 집합의 각 단어에 고유한 정수 맵핑.
vocab = {tkn: i+2 for i, tkn in enumerate(word_set)}
vocab['<unk>'] = 0
vocab['<pad>'] = 1

nn.Embedding()을 사용하여 학습가능한 임베딩 테이블을 만듦

import torch.nn as nn
embedding_layer = nn.Embedding(num_embeddings=len(vocab), 
                               embedding_dim=3,
                               padding_idx=1)

nn.Embedding은 두 가지 인자를 받음. num_embeddings과 embedding_dim.

  • num_embeddings : 임베딩을 할 단어들의 개수. 다시 말해 단어 집합의 크기.
  • embedding_dim : 임베딩 할 벡터의 차원. 사용자가 정해주는 하이퍼파라미터.
  • padding_idx : 선택적으로 사용하는 인자. 패딩을 위한 토큰의 인덱스를 알려줌.
print(embedding_layer.weight)
Parameter containing:
tensor([[-0.1778, -1.9974, -1.2478],
        [ 0.0000,  0.0000,  0.0000],
        [ 1.0921,  0.0416, -0.7896],
        [ 0.0960, -0.6029,  0.3721],
        [ 0.2780, -0.4300, -1.9770],
        [ 0.0727,  0.5782, -3.2617],
        [-0.0173, -0.7092,  0.9121],
        [-0.4817, -1.1222,  2.2774]], requires_grad=True)

2.와 단어 집합의 크기의 행을 가지는 임베딩 테이블이 생성

 


참고

https://wikidocs.net/45609

 

07-12 피드 포워드 신경망 언어 모델(Neural Network Language Model, NNLM)

파이썬 등과 같은 프로그래밍 언어를 사용할 때는 명세되어져 있는 튜플, 클래스 등과 같은 용어와 작성할 때 지켜야 하는 문법을 바탕으로 코드를 작성합니다. 문법에 맞지 않으면 에…

wikidocs.net

https://wikidocs.net/64779

 

09-07 파이토치(PyTorch)의 nn.Embedding()

파이토치에서는 임베딩 벡터를 사용하는 방법이 크게 두 가지가 있습니다. 바로 임베딩 층(embedding layer)을 만들어 훈련 데이터로부터 처음부터 임베딩 벡터를 학습하는 …

wikidocs.net