09-01. Word Embedding

Sparse Representation

  • 벡터 또는 행렬의 값이 대부분 0으로 표현되는 방법, one-hot vector 등
  • 단어의 개수가 늘어나면 벡터의 차원이 한없이 커지는 문제, 공간적 낭비 발생

Dense Representation

  • 사용자가 설정한 값으로 모든 단어의 벡터 표현의 차원을 맞춤 (0과 1뿐 아니라 실수 포함)

Word Embedding

  • 단어를 밀집 벡터의 형태로 표현하는 방법
  • Embedding Vector: 워드 임베딩 과정을 통해 나온 결과
  • LSA, Word2Vec, FastText, Glove 등

09-02. Word2Vec

Distributed Representation

  • 희소 표현을 다차원 공간에 벡터화하는 방법
  • 분산 표현 방법은 분포 가설이라는 가정 하에 만들어진 표현 방법
  • 분포 가설: 비슷한 문맥에서 등장하는 단어들은 비슷한 의미를 가진다 (귀엽다, 예쁘다, 애교 등의 단어가 주로 함께 등장할 경우 벡터화 시 유사한 벡터값을 가짐)
  • 저차원에 단어의 의미를 여러 차원에다가 분산하여 표현하기 대문에 단어 벡터 간 유의미한 유사도 계산 가능
  • Word2Vec의 학습 방식으로 CBOW(Continuous Bag of Words)와 Skip-Gram이 존재

CBOW

  • 주변에 있는 단어들을 입력으로 중간에 있는 단어들을 예측하는 방법
  • 중심 단어를 기준으로 window size만큼 앞뒤로 단어를 확인 (2n개의 단어 확인)
  • Sliding Window: window를 옆으로 움직여서 주변 단어와 중심 단어의 선택을 변경해가며 학습 데이터셋 생성
  • CBOW의 인공 신경망은 주변 단어들의 one-hot vector를 입력으로 중간 단어의 one-hot vector를 예측
  • Word2Vec은 은닉층이 1개이며, 활성화 함수 없이 룩업 테이블이라는 연산을 담당하는 projection layer로 불림
  • 입력층과 투사층 사이의 가중치 W는 ${V}\times{M}$ 행렬, 투사층에서 출력층 사이의 가중치 W’는 ${M}\times{V}$ 행렬
  • W와 W’는 동일한 행렬의 전치가 아니라 서로 다른 행렬이기 대문에 CBOW는 W와 W’를 학습해가는 구조를 가짐
  • 입력 벡터와 가중치 W 행렬의 곱은 W 행렬의 i번째 행을 그대로 읽어오는 것(lookup)과 동일
  • 주변 단어의 one-hot vector와 가중치 W를 곱한 결과 벡터들은 투사층에서 만나 평균인 벡터를 구함
  • 평균 벡터는 두 번째 가중치 행렬 W’와 곱해져서 one-hot vector들과 차원이 V로 동일한 벡터가 나옴
  • 해당 벡터에 softmax 함수를 거쳐 다중 클래스 분류 문제를 위한 score vector를 생성
  • score vector의 j번째 인덱스가 가진 값은 j번째 단어가 중심 단어일 확률로,
    score vector $\hat{y}$와 중심 단어의 one-hot vector $y$의 오차를 줄이기 위해 cross-entropy 함수 사용

Skip-gram

  • CBOW와 반대로 중심 단어에서 주변 단어를 예측
  • 중심 단어만을 입력으로 받기 때문에 투사층에서 벡터들의 평균을 구하는 과정은 없음
  • 전반적으로 Skip-gram이 CBOW보다 성능이 좋다고 알려짐

NNLM vs Word2Vec

  • NNLM은 다음 단어를 예측하는 목적이지만, Word2Vec(CBOW)은 중심 단어를 예측하는 목적, 때문에 NNLM이 이전 단어들만 참고한다면, Word2Vec은 예측 단어의 전후 단어들을 모두 참고
  • Word2Vec은 NNLM에 존재하던 활성화 함수가 있는 은닉층을 제거하여 학습 속도에서 강점을 가짐

09-03. Word2Vec 실습

1
2
3
4
from gensim.models import Word2Vec
from gensim.models import KeyedVectors

model = Word2Vec(sentences=result, size=100, window=5, min_count=5, workers=4, sg=0)
  • size: 워드 벡터의 특징 값, 임베딩된 벡터의 차원
  • window: context window size
  • min_count: 단어 최소 빈도 수 제한 (빈도가 적은 단어들은 무시)
  • workers: 학습을 위한 프로세스 수
  • sg: 0은 CBOW, 1은 Skip-gram
1
2
3
4
5
# 입력한 단어에 대해서 가장 유사한 단어들을 출력
model_result = model.wv.most_similar("man")
print(model_result)

[('woman', 0.842622697353363), ('guy', 0.8178728818893433), ('boy', 0.7774451375007629), ('lady', 0.7767927646636963), ('girl', 0.7583760023117065), ('gentleman', 0.7437191009521484), ('soldier', 0.7413754463195801), ('poet', 0.7060446739196777), ('kid', 0.6925194263458252), ('friend', 0.6572611331939697)]
1
2
model.wv.save_word2vec_format('eng_w2v') # 모델 저장
loaded_model = KeyedVectors.load_word2vec_format("eng_w2v") # 모델 로드
1
2
3
4
5
# 사전 훈련된 Word2Vec 임베딩
urllib.request.urlretrieve("https://s3.amazonaws.com/dl4j-distribution/GoogleNews-vectors-negative300.bin.gz",
							filename="GoogleNews-vectors-negative300.bin.gz")
word2vec_model = KeyedVectors.load_word2vec_format('GoogleNews-vectors-negative300.bin.gz', binary=True)
# shape(3000000, 300)
1
2
# 두 단어의 유사도 계산
print(word2vec_model.similarity('this', 'is')) # 0.407970363878

09-04. Negative Sampling

  • Word2Vec이 학습 과정에서 전체 단어 집합이 아니라 일부 단어 집합에만 집중할 수 있도록 하는 방법
  • 중심 단어에 대해 무작위로 선택된 일부 단어 집합에 대해 긍정 또는 부정을 예측하는 이진 분류 수행
  • 전체 단어 집합의 크기만큼 선택지를 두고 다중 클래스 분류 문제를 푸는 Word2Vec보다 효율적인 연산

SGNS

  • 네거티브 샘플링을 사용하는 Skip-gram은 중심 단어와 주변 단어가 모두 입력이 되고,
    두 단어가 실제로 윈도우 크개 내에 존재하는 이웃 관계인지 확률을 예측
  • 중심 단어에 대한 라벨로 주변 단어를 사용하지 않고,
    중심 단어와 주변 단어에 대한 이웃 관계를 표시하기 위한 라벨로 1 또는 0을 사용

SGNS 구현

  • 20newsgroups 데이터 사용
1
2
3
4
5
6
7
8
# 네거티브 샘플링 데이터셋 생성
from tensorflow.keras.preprocessing.sequence import skipgrams

skip_grams = [skipgrams(sample, vocabulary_size=vocab_size, window_size=10) for sample in encoded]

# (commited (7837), badar (34572)) -> 0
# (whole (217), realize (1036)) -> 1
# (reason (149), commited (7837)) -> 1
1
2
3
from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.layers import Embedding, Reshape, Activation, Input
from tensorflow.keras.layers import Dot
1
2
3
4
5
6
7
8
9
embedding_dim = 100

# 중심 단어를 위한 임베딩 테이블
w_inputs = Input(shape=(1, ), dtype='int32')
word_embedding = Embedding(vocab_size, embedding_dim)(w_inputs)

# 주변 단어를 위한 임베딩 테이블
c_inputs = Input(shape=(1, ), dtype='int32')
context_embedding  = Embedding(vocab_size, embedding_dim)(c_inputs)
1
2
3
4
5
6
7
8
# 두 임베딩 테이블에 대한 내적의 결과로 1 또는 0을 예측하기 위해 시그모이드 함수 사용
dot_product = Dot(axes=2)([word_embedding, context_embedding])
dot_product = Reshape((1,), input_shape=(1, 1))(dot_product)
output = Activation('sigmoid')(dot_product)

model = Model(inputs=[w_inputs, c_inputs], outputs=output)
model.summary()
model.compile(loss='binary_crossentropy', optimizer='adam')
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 5epochs 학습
for epoch in range(1, 6):
    loss = 0
    for _, elem in enumerate(skip_grams):
        first_elem = np.array(list(zip(*elem[0]))[0], dtype='int32')
        second_elem = np.array(list(zip(*elem[0]))[1], dtype='int32')
        labels = np.array(elem[1], dtype='int32')
        X = [first_elem, second_elem]
        Y = labels
        loss += model.train_on_batch(X,Y)  
    print('Epoch :',epoch, 'Loss :',loss)

09-05. GloVe

  • 카운트 기반과 예측 기반을 모두 사용하는 방법론으로, LSA와 Word2Vec의 단점 보완
  • LSA는 단어의 빈도수를 차원 축소하여 잠재된 의미를 끌어내지만, 같은 단어 의미의 유추 작업 성능은 떨어짐
  • Word2Vec는 예측 기반으로 단어 간 유추 작업에는 LSA보다 뛰어나지만,
    window size 내 주변 단어만을 고려하여 전체적인 통계 정보를 반영하지 못함
  • 임베딩 된 중심 단어와 주변 단어 벡터의 내적이 전체 코퍼스에서의 동시 등장 확률이 되도록 만드는 것이 목적

Window based Co-occurrence Matrix

  • 행과 열을 전체 단어 집합의 단어들로 구성하고,
    i 단어의 window size 내에서 k 단어가 등장한 횟수를 i행 k열에 기재한 행렬

Co-occurence Probability

  • 동시 등장 확률 $P(k|i)$는 동시 등장 행렬로부터 특정 단어 i의 전체 등장 횟수를 카운트하고,
    특정 단어 i가 등장했을 때 어떤 단어 k가 등장한 횟수를 카운트하여 계산한 조건부 확률
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# pip install glove_python_binary

from glove import Corpus, Glove

corpus = Corpus() 

# 훈련 데이터로부터 GloVe에서 사용할 동시 등장 행렬 생성
corpus.fit(result, window=5)
glove = Glove(no_components=100, learning_rate=0.05)

glove.fit(corpus.matrix, epochs=20, no_threads=4, verbose=True)
glove.add_dictionary(corpus.dictionary)

print(glove.most_similar("man"))

[('woman', 0.9621753707315267), ('guy', 0.8860281455579162), ('girl', 0.8609057388487154), ('kid', 0.8383640509911114)]

09-06. FastText

  • Word2Vec가 단어를 쪼개질 수 없는 단위로 생각한다면,
    FastText는 하나의 단어 안에도 여러 단어들이 존재하는 것으로 간주
  • 각 단어를 글자 단위 n-gram의 구성으로 취급하여 n에 따라 단어들이 얼마나 분리되는지 결정
  • tri-gram의 경우 apple에 대해서 [<ap, app, ppl, ple, le>]로 분리된 벡터 생성
    (<, >는 시작과 끝을 의미)
  • 내부 단어들을 Word2Vec로 벡터화하고 apple의 벡터값은 내부 단어의 벡터값들의 총 합으로 구성

Out Of Vocabulary

  • FastText는 데이터셋만 충분하다면 내부 단어를 통해 모르는 단어에 대해서도 유사도 계산 가능
  • birthplace를 학습하지 않은 상태라도, birth와 place라는 내부 단어가 있다면 벡터를 얻을 수 있음

Rare Word

  • Word2Vec는 등장 빈도 수가 적은 단어에 대해서 임베딩의 정확도가 높지 않은 단점
  • FastText는 희귀 단어라도 n-gram이 다른 단어의 n-gram과 겹치는 경우라면,
    Word2Vec보다 비교적 높은 임베딩 벡터값을 얻음
  • 오타와 같은 노이즈가 많은 코퍼스에서도 일정 수준의 성능을 보임 (apple, appple)
1
2
3
from gensim.models import FastText

model = FastText(result, size=100, window=5, min_count=5, workers=4, sg=1)

09-08. Pre-trained Word Embedding

  • 사전 훈련된 워드 임베딩 참고

09-09. ELMo

  • 언어 모델로 하는 임베딩이라는 뜻으로, 사전 훈련된 언어 모델을 사용
  • Word2Vec는 Bank Account와 River Bank에서 Bank의 차이를 구분하지 못하지만,
    ELMo는 문맥을 반영한 워드 임베딩을 수행
  • ELMo 표현을 기존 임베딩 벡터와 연결(concatenate)해서 입력으로 사용 가능

biLM

  • RNN 언어 모델에서 $h_t$는 시점이 지날수록 업데이트되기 때문에,
    문장의 문맥 정보를 점차적으로 반영함
  • ELMo는 양쪽 방향의 언어 모델(biLM)을 학습하여 활용
  • biLM은 은닉층이 최소 2개 이상인 다층 구조를 전제로 함
  • 양방향 RNN은 순방향 RNN의 hidden state와 역방향 RNN의 hidden state를 연결하는 것이지만,
    biLM은 순방향 언어 모델과 역방향 언어 모델이라는 두 개의 언어 모델을 별개의 모델로 보고 학습
  • 각 층(embedding, hidden state)의 출력값이 가진 정보가 서로 다른 것이므로,
    이를 모두 활용하여 순방향 언어 모델과 역방향 언어 모델의 각 층의 출력값을 연결

ELMo Representation

  1. 각 층의 출력값을 연결(concatenate)
  2. 각 층의 출력값 별로 가중치($s_1, s_2, s_3$) 부여
  3. 각 층의 출력값을 모두 더함 (2번과 3번을 요약하여 가중합이라 표현)
  4. 벡터의 크기를 결정하는 스칼라 매개변수($\gamma$)를 곱함

ELMo 활용

1
2
3
4
# 텐서플로우 1버전에서 사용 가능
%tensorflow_version 1.x
pip install tensorflow-hub
import tensorflow_hub as hub
1
2
3
4
5
6
7
# 텐서플로우 허브로부터 ELMo를 다운로드
elmo = hub.Module("https://tfhub.dev/google/elmo/1", trainable=True)

sess = tf.Session()
K.set_session(sess)
sess.run(tf.global_variables_initializer())
sess.run(tf.tables_initializer())
1
2
3
# 데이터의 이동이 케라스 → 텐서플로우 → 케라스가 되도록 하는 함수
def ELMoEmbedding(x):
    return elmo(tf.squeeze(tf.cast(x, tf.string)), as_dict=True, signature="default")
1
2
3
4
5
6
7
8
9
from keras.models import Model
from keras.layers import Dense, Lambda, Input

input_text = Input(shape=(1,), dtype=tf.string)
embedding_layer = Lambda(ELMoEmbedding, output_shape=(1024, ))(input_text)
hidden_layer = Dense(256, activation='relu')(embedding_layer)
output_layer = Dense(1, activation='sigmoid')(hidden_layer)
model = Model(inputs=[input_text], outputs=output_layer)
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

09-10. Embedding Visualization

1
2
3
# !python -m gensim.scripts.word2vec2tensor --input 모델이름 --output 모델이름
!python -m gensim.scripts.word2vec2tensor --input eng_w2v --output eng_w2v
# 임베딩 프로젝트에 사용할 metadata.tsv와 tensor.tsv 파일 생성

09-11. Document Embedding

  • 문서 벡터를 이용한 추천 시스템 참고
  • 문서 임베딩 : 워드 임베딩의 평균 참고
  • Doc2Vec으로 공시 사업보고서 유사도 계산하기 참고

10. RNN Text Classification

Bayes’ Theorem

$$P(A|B)=\frac{P(B|A)P(A)}{P(B)}$$

Naive Bayes Classifier

  • 베이즈 정리를 이용한 스팸 메일 확률 표현
    P(정상 메일 | 입력 텍스트) = (P(입력 텍스트 | 정상 메일) x P(정상 메일)) / P(입력 텍스트)
    P(스팸 메일 | 입력 텍스트) = (P(입력 텍스트 | 스팸 메일) x P(스팸 메일)) / P(입력 텍스트)
  • 나이브 베이즈 분류기에서 토큰화 이전의 단어의 순서는 중요하지 않음
    (BoW와 같이 단어의 순서를 무시하고 빈도수만 고려)
  • 정상 메일에 입력 텍스트가 없어 확률이 0%가 되는 것을 방지하기 위해
    각 단어에 대한 확률의 분모, 분자에 전부 숫자를 더해서 분자가 0이 되는 것을 방지하는 라플라스 스무딩 사용
1
2
3
4
5
6
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.naive_bayes import MultinomialNB # 다항분포 나이브 베이즈 모델

# alpha=1.0: 라플라스 스무딩 적용
model = MultinomialNB() # MultinomialNB(alpha=1.0, class_prior=None, fit_prior=True)
model.fit(tfidfv, newsdata.target)

11-01. Convolutional Neural Network

  • 이미지 처리에 탁월한 성능을 보이는 신경망
  • 합성곱 신경망은 convolutional layer와 pooling layer로 구성
  • 합성곱 연산(CONV)의 결과가 ReLU를 거쳐서 POOL 구간을 지나는 과정
  • Channel: 이미지는 (높이, 너비, 채널)이라는 3차원 텐서로 구성, 채널은 색 성분을 의미
  • 합성곱 신경망은 이미지의 모든 픽셀이 아닌, 커널과 맵핑되는 픽셀만을 입력으로 사용하여
    다층 퍼셉트론보다 훨씬 적은 수의 가중치를 사용하여 공간적 구조 정보를 보존
  • 편향을 추가할 경우 커널을 적용한 뒤에 더해지며, 단 하나의 편향이 커널이 적용된 결과의 모든 원소에 더해짐
  • 다수의 채널을 가진 입력 데이터일 경우 커널의 채널 수도 입력의 채널 수만큼 존재,
    각 채널 간 합성곱 연산을 마치고 그 결과를 모두 더해서 하나의 채널을 가지는 특성 맵 생성

Convolution Operation

  • 이미지의 특징을 추출
  • Kernel(filter)라는 ${n}\times{m}$ 크기의 행렬로 각 이미지를 순차적으로 훑음
  • Feature map: 합성곱 연산을 통해 나온 결과
  • Stride: 커널의 이동 범위, 특성 맵의 크기
  • Padding: 합성곱 연산 이후에도 특성 맵의 크기가 입력과 동일하도록 행과 열 추가

11-02. 1D CNN

1D Convolutions

  • LSTM과 동일하게 각 단어가 벡터로 변환된 문장 행렬을 입력으로 받음
  • 커널의 너비는 임베딩 벡터의 차원과 동일, 커널의 높이만으로 해당 커널의 크기라 간주
  • 커널의 너비가 임베딩 벡터의 차원이기 때문에 너비 방향으로 움직이지 못하고 높이 방향으로만 움직임

Max-pooling

  • 1D CNN에서의 폴링 층
  • 각 합성곱 연산으로부터 얻은 결과 벡터에서 가장 큰 값을 가진 스칼라 값을 빼내는 연산을 수행

1D CNN 구현

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, Dropout, Conv1D, GlobalMaxPooling1D, Dense
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint

embedding_dim = 256 # 임베딩 벡터의 차원
dropout_ratio = 0.3 # 드롭아웃 비율
num_filters = 256 # 커널의 수
kernel_size = 3 # 커널의 크기
hidden_units = 128 # 뉴런의 수

model = Sequential()
model.add(Embedding(vocab_size, embedding_dim))
model.add(Dropout(dropout_ratio))
model.add(Conv1D(num_filters, kernel_size, padding='valid', activation='relu'))
model.add(GlobalMaxPooling1D())
model.add(Dense(hidden_units, activation='relu'))
model.add(Dropout(dropout_ratio))
model.add(Dense(1, activation='sigmoid'))

es = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=3)
mc = ModelCheckpoint('best_model.h5', monitor='val_acc', mode='max', verbose=1, save_best_only=True)

model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['acc'])
history = model.fit(X_train, y_train, epochs=20, validation_data=(X_test, y_test), callbacks=[es, mc])

11-06. Intent Classification

  • 사전 훈련된 워드 임베딩을 이용한 의도 분류 참고

11-07. Character Embedding

  • ‘misunderstand’의 의미를 ‘mis-‘라는 접두사와 ‘understand’를 통해 추측하는 것과 같이,
    사람의 이해 능력을 흉내내는 알고리즘
  • 1D CNN에서는 단어를 문자 단위로 쪼개기만하면 되기 때문에 OOV라도 벡터를 얻을 수 있음
  • BiLSTM에서도 문자에 대한 임베딩을 통해 얻은 벡터를 단어에 대한 벡터로 사용

12. Tagging Task

BIO 표현

  • 개체명이 시작되는 부분에 B(Begin), 개체명의 내부에 I(Inside), 나머지로 O(Outside) 태깅
  • 개체명 태깅엔 LOC(location), ORG(organization), PER(person), MISC(miscellaneous)
    태그가 추가로 붙음 (B-ORG 등)

CRF(Conditional Random Field)

  • LSTM 위에 CRF 층을 추가하면 모델은 예측 개체명(레이블 간 의존성)을 고려
  • 기존 양방향 LSTM 모델은 활성화 함수를 지난 시점에서 개체명을 결정했지만,
    CRF 층을 추가한 모델에서는 활성화 함수의 결과들이 CRF 층의 입력으로 전달
  • CRF 층은 [문장의 첫번쨰 단어에서는 I가 나오지 않는다, O-I 패턴은 나오지 않는다] 등의 제약사항을 학습
  • 양방향 LSTM은 입력 단어에 대한 양방향 문맥을 반영하며, CRF는 출력 레이블에 대한 양방향 문맥을 반영
  • CRF 층은 one-hot encoding된 라벨을 지원하지 않음

13-01. Byte Pair Encoding

  • UNK(Unknown Token) 등의 OOV 문제를 해결하기 위해 서브워드 분리 작업을 수행
  • BPE 알고리즘은 연속적으로 가장 많이 등장한 글자의 쌍을 찾아서 하나의 글자로 병합
  • BPE는 글자 단위에서 점차적으로 단어 집합을 만들어내는 Bottom up 방식의 접근 사용

WordPiece Tokenizer

  • BPE의 변형 알고리즘으로, 코퍼스의 likelihood를 가장 높이는 쌍을 병합
  • 모든 단어의 맨 앞에 _를 붙이고, 단어는 subword로 통계에 기반하여 띄어쓰기로 분리
  • WordPiece TOkenizer 겨로가를 되돌리기 위해서는 모든 띄어쓰기를 제거하고 언더바를 띄어쓰기로 바꿈

Unigram Language Model Tokenizer

  • 각각의 서브워드들에 대해서 손실(loss)을 계산
  • 서브 단어의 손실은 해당 서브워드가 단어 집합에서 제거되었을 경우, 코퍼스의 likelihood가 감소하는 정도
  • 서브워드들의 손실의 정도를 정렬하여, 최악의 영향을 주는 10~20%의 토큰을 제거

13-02. SentencePiece

  • 내부 단어 분리를 위한 구글의 패키지
  • 사전 토큰화 작업없이 단어 분리 토큰화를 수행하여 언어에 종속적이지 않음
1
2
3
4
import sentencepiece as spm

# IMDB 리뷰 데이터 사용
spm.SentencePieceTrainer.Train('--input=imdb_review.txt --model_prefix=imdb --vocab_size=5000 --model_type=bpe --max_sentence_length=9999')
  • input: 학습시킬 파일
  • model_prefix: 만들어질 모델 이름
  • vocab_size: 단어 집합의 크기
  • model_type: 사용할 모델 (unigram(default), bpe, char, word)
  • max_sentence_length: 문장의 최대 길이

13-03. SubwordTextEncoder

  • Wordpiece 모델을 채택한 텐서플로우의 서브워드 토크나이저
1
2
3
4
import tensorflow_datasets as tfds

tokenizer = tfds.features.text.SubwordTextEncoder.build_from_corpus(
    train_df['review'], target_vocab_size=2**13)

13-04. Huggingface Tokenizer

  • BertWordPieceTokenizer: BERT에서 사용된 워드피스 토크나이저(WordPiece Tokenizer)
  • CharBPETokenizer: 오리지널 BPE
  • ByteLevelBPETokenizer: BPE의 바이트 레벨 버전
  • SentencePieceBPETokenizer: 앞서 본 패키지 센텐스피스(SentencePiece)와 호환되는 BPE 구현체

BertWordPieceTokenizer

1
2
3
from tokenizers import BertWordPieceTokenizer

tokenizer = BertWordPieceTokenizer(lowercase=False, trip_accents=False)
1
2
3
4
5
6
7
8
9
data_file = 'naver_review.txt'
vocab_size = 30000
limit_alphabet = 6000
min_frequency = 5

tokenizer.train(files=data_file,
                vocab_size=vocab_size,
                limit_alphabet=limit_alphabet,
                min_frequency=min_frequency)

14-01. Sequence-to-Sequence(seq2seq)

  • seq2seq는 입력된 시퀀스로부터 다른 도메인의 시퀀스를 출력하는 다양한 분야에서 사용되는 모델
  • 챗봇, 기계 번역, 내용 요약, STT(Speech to Text) 등에서 주로 사용
  • seq2seq는 인코더와 디코더로 나눠지며, 둘 다 LSTM 셀 또는 GRU 셀을 사용하는 RNN 아키텍처로 구성
  • softmax 함수를 통해 출력 시퀀스의 각 단어별 확률값을 반환하고, 디코더는 출력 단어를 결정

Encoder

  • 입력 문장의 모든 단어들을 순차적으로 입력받고 모든 단어 정보들을 압축해서 하나의 벡터 생성
  • 인코더 RNN 셀의 마지막 hidden state를 context vector로 디코더에 넘겨줌

Decoder

  • 압축된 컨텍스트 벡터를 받아서 번역된 단어를 한 개씩 순차적으로 출력
  • 기본적으로 RNNLM으로, 초기 입력으로 문장의 시작을 의미하는 심볼 가 들어감
  • 첫번째 시점의 디코더 RNN 셀은 예측된 단어를 다음 시점의 RNN 셀 입력으로 넣으며,
    문장의 끝을 의미하는 심볼인 가 다음 단어로 예측될 때까지 반복해서 예측
  • 훈련 과정에서는 실제 정답 상황에서 가 나와야 된다고 정답을 알려줌
    (교사 강요: 이전 시점의 디코더 셀의 예측이 틀릴 경우 연쇄 작용을 방지)

seq2seq 구현

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Encoder

encoder_inputs = Input(shape=(None, src_vocab_size))
encoder_lstm = LSTM(units=256, return_state=True)

# encoder_outputs은 여기서는 불필요
encoder_outputs, state_h, state_c = encoder_lstm(encoder_inputs)

# LSTM은 바닐라 RNN과는 달리 상태가 두 개, 은닉 상태와 셀 상태
encoder_states = [state_h, state_c]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Decoder

decoder_inputs = Input(shape=(None, tar_vocab_size))
decoder_lstm = LSTM(units=256, return_sequences=True, return_state=True)

# 디코더에게 인코더의 은닉 상태, 셀 상태를 전달
decoder_outputs, _, _= decoder_lstm(decoder_inputs, initial_state=encoder_states)

decoder_softmax_layer = Dense(tar_vocab_size, activation='softmax')
decoder_outputs = decoder_softmax_layer(decoder_outputs)

model = Model([encoder_inputs, decoder_inputs], decoder_outputs)
model.compile(optimizer="rmsprop", loss="categorical_crossentropy")
1
model.fit(x=[encoder_input, decoder_input], y=decoder_target, batch_size=64, epochs=40, validation_split=0.2)

seq2seq 동작

  1. 번역하고자 하는 입력 문장이 인코더에 들어가서 은닉 상태와 셀 상태를 얻음
  2. 상태와 에 해당하는 ‘\t’를 디코더로 보냄
  3. 디코더가 에 해당하는 ‘\n’이 나올 때까지 다음 문자를 예측하는 행동을 반복

14-02. BLEU Score

Bilingual Evaluation Understudy(BLEU)

  • 기계 번역 결과와 사람이 직접 번역한 결과가 얼마나 유사한지 비교하여 번역에 대한 성능을 측정하는 방법
  • 측정 기준은 n-gram에 기반
  • 언어에 구애받지 않고 사용할 수 있으며, 계산 속도가 빠른 이점
  • PPL과 달리 높을 수록 성능이 더 좋음을 의미

Unigram Precision

  • 사람이 번역한 문장 중 어느 한 문장이라도 등장한 단어의 개수를 카운트하는 측정 방법
  • 기계 번역기가 번역한 문장을 Ca, 사람이 번역한 문장을 Ref라 표현

$$\text{Unigram Precision}=\frac{\text{Ref들 중에서 존재하는 Ca의 단어의 수}}{\text{Ca의 총 단어 수}}$$

Modified Unigram Precision

  • 하나의 단어가 여러번 반복되는 경우에서 정밀도가 1이 나오는 문제를 개선하기 위해
    유니그램이 이미 매칭된 적이 있는지를 고려
  • $Max_Ref_Count$: 유니그램이 하나의 Ref에서 최대 몇 번 등장했는지 카운트
  • $Count_{dip}=min(Count,Max_Ref_Count)$

$$\text{Modified Unigram Precision}=\frac{\text{Ca의 각 유니그램에 대해 }Count_{dip}\text{을 수행한 값의 총 합}}{\text{Ca의 총 유니그램 수}}$$

BLEU Score

  • 유니그램 정밀도는 단어의 빈도수로 접근하기 때문에 단어의 순서를 고려하기 위해 n-gram 이용
  • BLEU 최종 식은 보정된 정밀도 $p_1,p_2,…,p_n$을 모두 조합
  • 해당 BLEU 식의 경우 문장의 길이가 짧을 때 높은 점수를 받는 문제가 있기 때문에,
    길이가 짧은 문장에게 Brevity Penalty를 줄 필요가 있음
  • $BP$는 Ca와 가장 길이 차이가 작은 Ref의 길이 $r$을 기준으로 $e^{(1-r/c)}$ 값을 곱하며,
    문장이 $r$보다 길어 패널티를 줄 필요가 없는 경우 1이어야 함

$$\text{보정된 정밀도 } p_1=\frac{\Sigma_{{unigram}\in{Candidate}}Count_{dip}(unigram)}{\Sigma_{{unigram}\in{Candidate}}Count(unigram)}$$ $$\text{n-gram 일반화 } p_n=\frac{\Sigma_{{n\text{-}gram}\in{Candidate}}Count_{dip}(n\text{-}gram)}{\Sigma_{{n\text{-}gram}\in{Candidate}}Count(n\text{-}gram)}$$ $$BLEU={BP}\times{exp(\Sigma^N_{n=1}{w_n}{\log{p_n}})}$$

1
2
3
import nltk.translate.bleu_score

bleu_score(candidate.split(),list(map(lambda ref: ref.split(), references)))