2019.06.19
출처 : https://wikidocs.net/book/2155
핵심키워드
- Pre-trained Word Embedding
사전 훈련된 워드 임베딩(Pre-trained Word Embedding)
이번 챕터에서는 케라스의 임베딩 층(Embedding layer)과 사전 훈련된 워드 임베딩(Pre-trained Word Embedding)을 가져와서 사용하는 것을 비교해보자. 실제 태스크에서 Word2Vec, FastText, Glove 등의 임베딩 벡터를 사용한다고 하면 그 순간 갖고 있는 훈련 데이터를 해당 방법론으로 직접 훈련시키는 경우도 있지만, 위키피디아나 e-book과 같은 방대한 코퍼스를 가지고 Word2vec, FastText, Glove 등을 통해서 이미 사전에 훈련되어져 있는 임베딩 벡터를 불러오는 방법을 사용한다는 의미인 경우가 더 많다. 이는 현재 갖고 있는 훈련 데이터로 처음부터 학습을 하는 Embedding()을 사용해서 얻는 임베딩 벡터와는 대조된다.
1. 케라스 임베딩 층(Keras Embedding layer)
케라스는 텍스트 데이터에 대해서 워드 임베딩을 수행하는 임베딩 층(Embedding layer)를 제공한다. 임베딩 층을 사용하기 위해서 입력 시퀀스의 각 입력은 모두 정수 인코딩이 되어야 한다. 즉, 각각의 입력은 전체 데이터에서 유일한 정수로 변환된 상태로 임베딩 층을 지난다.
단어에 부여된 정수 값 → 임베딩 층 → 밀집 벡터
임베딩 층은 임베딩 층의 입력인 각 정수 인덱스 값에 대해서 밀집 벡터(Dense Vector)로 변환하고 이 밀집 벡터는 인공 신경망의 학습 과정에서 가중치가 학습되는 것과 같은 방식으로 훈련된다. 이렇게 되면 모델이 풀고자 하는 문제에 특화된 임베딩 벡터가 된다.
# 아래의 각 인자는 저자가 임의로 선정한 숫자들이며 의미있는 선정 기준이 아니다.
v = Embedding(20001 ,256, input_length=500)
임베딩 층은 다음과 같은 세 개의 인자를 받는다.
첫번째 인자 : 텍스트 데이터의 전체 단어 집합의 크기다. 만약 갖고 있는 데이터의 단어들이 인코딩이 0부터 20,000까지 되었다면 단어 집합의 크기는 20,001이 되어야 한다. (인덱스에 주의)
두번째 인자 : 임베딩이 되고 난 후의 단어의 차원이다. 만약, 이 값을 256으로 준다면 모든 단어의 차원이 256이 된다.
input_length : 입력 시퀀스의 길이다. 만약 갖고 있는 각 데이터의 길이가 500개의 단어로 구성되어있다면 이 값은 500이 된다.
Embedding()은 (number of samples, input_length)인 2D 정수 텐서를 입력받는다. 이 때 각 sample은 정수 인코딩이 된 결과로, 정수의 시퀀스다. Embedding()은 워드 임베딩 작업을 수행하고 (number of samples, input_length, embedding word dimentionality)인 3D 실수 텐서를 리턴한다. 케라스의 임베딩 층(Embedding layer)을 사용하는 간단한 실습을 진행해보도록 하자.
문장의 긍정, 부정을 판단하는 간단한 감성 분류 모델을 만들어본다.
sentence = ['멋있어 최고야 짱이다 감탄이다', '헛소리 지껄이네', '닥쳐 자식아', '우와 대단하다', '우수한 성적', '형편없다', '최상의 퀄리티']
y_train = [1, 0, 0, 1, 1, 0, 1]
문장과 레이블 데이터를 만들었다.
from keras.preprocessing.text import Tokenizer
t = Tokenizer()
t.fit_on_texts(sentence)
vocab_size = len(t.word_index)+1print(vocab_size)
16
케라스의 Tokenizer()를 사용하여 토큰화를 시켰다.
x_encoded = t.texts_to_sequences(sentence)print(x_encoded)
[[1, 2, 3, 4], [5, 6], [7, 8], [9, 10], [11, 12], [13], [14, 15]]
각 문장에 대해서 정수 인코딩을 수행한다.
max_length = max(len(l) for l in x_encoded)
print(max_length)
4
문장 중에서 가장 길이가 긴 문장의 길이는 4이다.
from keras.preprocessing.sequence import pad_sequences
x_train = pad_sequences(x_encoded, maxlen=max_length, padding='post')print(x_train)
[[ 1 2 3 4]
[ 5 6 0 0]
[ 7 8 0 0]
[ 9 10 0 0]
[11 12 0 0]
[13 0 0 0]
[14 15 0 0]]
모든 문장을 패딩하여 길이를 4로 만들어주었다.
from keras.models import Sequential
from keras.layers import Dense, Embedding, Flattenmodel = Sequential()
model.add(Embedding(vocab_size, 4, input_length=max_length))
# 모든 임베딩 벡터는 4차원을 가지게 됨.model.add(Flatten()) # Dense의 입력으로 넣기 위함임.
model.add(Dense(1, activation='sigmoid'))
출력층에 1개의 뉴런에 활성화 함수로는 시그모이드 함수를 사용하여 이진 분류를 수행한다.
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['acc'])
model.fit(x_train, y_train, epochs=100, verbose=2)Epoch 1/100
- 0s - loss: 0.6972 - acc: 0.4286
Epoch 2/100
- 0s - loss: 0.6959 - acc: 0.5714
...
중략
...
Epoch 99/100
- 0s - loss: 0.5424 - acc: 1.0000
Epoch 100/100
- 0s - loss: 0.5403 - acc: 1.0000
100%의 정확도를 얻었다. 좀 더 복잡한 모델에서 케라스의 Embedding()이 사용되는 사례는 뒤의 텍스트 분류 챕터나 태깅 작업 챕터에서 확인할 수 있다.
2. 사전 훈련된 글로브 임베딩(Pre-Trained GloVe Embedding) 사용하기
임베딩 벡터를 얻기 위해서 케라스의 Embedding()을 사용하기도 하지만, 떄로는 이미 훈련되어져 있는 워드 임베딩을 불러서 이를 임베딩 벡터로 사용하기도 한다. 훈련 데이터가 적은 상황이라면 모델에 케라스의 Embedding()을 사용하는 것보다 다른 텍스트 데이터로 사전 훈련되어 있는 임베딩 벡터를 불러오는 것이 나은 선택일 수 있다.
그 이유는 훈련 데이터가 적다면 케라스의 Embedding()으로 해당 문제에 충분히 특화된 임베딩 벡터를 만들어내는 것이 쉽지 않다. 차라리 해당 문제에 특화된 임베딩 벡터를 만드는 것이 어렵다면, 해당 문제에 특화된 것은 아니지만 위키피디아나 백과 사전 등 보다 일반적인 훈련 데이터로 Word2Vec이나 GloVe 등으로 학습하여 단어의 일반적인 의미들이 표현되어져 있는 임베딩 벡터들을 사용하는 것이 성능의 개선을 가져올 수 있다.
Word2Vec 챕터에서 사전 훈련된 Word2Vec 임베딩을 불러오는 실습을 해보았기 때문에 여기서는 GloVe를 불러와 보도록 한다.
다운로드 링크 : http://nlp.stanford.edu/data/glove.6B.zip
위에서는 케라스의 Embedding()을 사용하여 자체 훈련하였기 때문에 단어가 한국어여도 무방하였으나 이번 실습에서 사용할 사전 훈련된 워드 임베딩은 영어로 훈련된 파일이기 때문에 영어인 훈련 데이터에 대해서 실습을 진행한다.
sentences = ['nice great best amazing', 'stop lies', 'pitiful nerd', 'excellent work', 'supreme queality', 'bad', 'highly respectable']y_train = [1, 0, 0, 1, 1, 0, 1]from keras.preprocessing.text import Tokenizer
t = Tokenizer()
t.fit_on_texts(sentences)
vocab_size = len(t.word_index)+1X_encoded = t.texts_to_sequences(sentences)
max_length = max(len(l) for l in X_encoded)from keras.preprocessing.sequence import pad_sequences
X_train = pad_sequences(X_encoded, maxlen=max_length, padding='post')print(X_train)[[ 1 2 3 4]
[ 5 6 0 0]
[ 7 8 0 0]
[ 9 10 0 0]
[11 12 0 0]
[13 0 0 0]
[14 15 0 0]]print(y_train)
[1, 0, 0, 1, 1, 0, 1]
이제 임베딩 층을 설계하기 위한 과정부터 달라진다. 우선 다운로드 받은 파일인 glove.6B.zip의 압축을 풀면 그 안에 4개의 파일이 있는데 여기서 사용할 파일은 glove.6B.100d.txt 파일이다. 해당 파일은 하나의 줄(line)당 101개의 값을 가지는 리스트(list)를 가지고 있다. 두 개의 줄만 읽어보도록 한다.
n = 0
f = open('./model/glove.6B/glove.6B.100d.txt', encoding='utf8')for line in f:
word_vector = line.split()
print(word_vector)
word = word_vector[0]
print(word)
n += 1
if n == 2:
break
f.close()['the', '-0.038194', '-0.24487', '0.72812', '-0.39961', '0.083172', ...
중략
...'-0.51058', '-0.52028', '-0.1459', '0.8278', '0.27062']
the[',', '-0.10767', '0.11053', '0.59812', '-0.54361', '0.67396',...
중략
... '0.61214', '-0.35111', '-0.83155', '0.45293', '0.082577']
,print(type(word_vector))
print(len(word_vector))
<class 'list'>
101
101개의 값 중에서 첫번째 값은 임베딩 벡터가 의미하는 단어를 의미하며, 두번째 값부터 마지막 값은 해당 단어의 임베딩 벡터의 100개의 차원에서 각 값을 의미한다. 즉, glove.6B.100d.txt는 수많은 단어에 대해서 100개의 차원을 가지는 임베딩 벡터로 제공하고 있다. 위의 출력 결과는 단어 ‘the’에 대해서 100개의 차원을 가지는 임베딩 벡터와 단어 ‘,’에 대해서 100개의 차원을 가지는 임베딩 벡터를 보여준다. 그러면 이제 glove.6B.100d.txt 에 있는 모든 임베딩 벡터들을 불러와본다. 형식은 키(key)와 값(value)의 쌍(pair)를 가지는 파이썬의 사전형 구조를 사용한다.
import numpy as np
embedding_dict = dict()f = open('./model/glove.6B/glove.6B.100d.txt', encoding='utf8')
for line in f:
word_vector = line.split()
word = word_vector[0]
word_vector_arr = np.asarray(word_vector[1:], dtype='float32')
embedding_dict[word] = word_vector_arr
f.close()print("%s개의 Embedding Vector가 있습니다." % len(embedding_dict))
400000개의 Embedding Vector가 있습니다.
임의의 단어 ‘respectable’에 대해서 임베딩 벡터를 출력해본다.
print(embedding_dict['respectable'])
print(len(embedding_dict['respectable']))[-0.049773 0.19903 0.10585 0.1391 -0.32395 0.44053
0.3947 -0.22805 -0.25793 0.49768 0.15384 -0.08831
...
중략
...
-0.21616 -0.19187 -0.032502 0.38025 ]
100
벡터값이 출력되며 길이는 100인 것을 확인할 수 있다.
embedding_matrix = np.zeros((vocab_size, 100))
# 단어 집합 크기의 행과 100개의 열을 가지는 행렬 생성, 값은 전부 0으로 채워진다.np.shape(embedding_matrix)
(16, 100)print(t.word_index.items())
dict_items([('nice', 1), ('great', 2), ('best', 3), ('amazing', 4), ('stop', 5), ('lies', 6), ('pitiful', 7), ('nerd', 8), ('excellent', 9), ('work', 10), ('supreme', 11), ('queality', 12), ('bad', 13), ('highly', 14), ('respectable', 15)])for word, i in t.word_index.items():
# 훈련 데이터의 단어 집합에서 단어를 1개씩 꺼내온다.
temp = embedding_dict.get(word)
# 단어(key) 해당되는 임베딩 벡터의 100개의 값(value)를
# 임시 변수에 저장
if temp is not None:
embedding_matrix[i] = temp
# 임시 변수의 값을 단어와 맵핑되는 인덱스의 행에 삽입
이제 훈련 데이터의 단어 집합의 모든 단어에 대해서 사전 훈련된 GloVe의 임베딩 벡터들을 맵핑하였다. 이제 이를 이용하여 임베딩 층(Embedding layer)를 만들어본다.
from keras.models import Sequential
from keras.layers import Dense, Embedding, Flattenmodel = Sequential()
e = Embedding(vocab_size, 100, weights=[embedding_matrix],
input_length=max_length, trainable=False)
현재 실습에서 사전 훈련된 워드 임베딩을 100차원의 값인 것으로 사용하고 있기 때문에 임베딩 층의 output_dim 의 인자값을 100으로 주어야 한다. 그리고 사전 훈련된 워드 임베딩을 그대로 사용할 것이므로, 별도로 더 이상 훈련을 하지 않는다는 옵션을 준다. 이는 trainble = False로 선택할 수 있다.
model.add(e)
model.add(Flatten())
model.add(Dense(1, activation='sigmoid'))model.compile(optimizer='adam',loss='binary_crossentropy', metrics=['acc'])
model.fit(X_train, y_train, epochs=100, verbose=2)Epoch 1/100
- 0s - loss: 0.6741 - acc: 0.5714
Epoch 2/100
- 0s - loss: 0.6561 - acc: 0.7143
...
중략
...
Epoch 99/100
- 0s - loss: 0.1131 - acc: 1.0000
Epoch 100/100
- 0s - loss: 0.1117 - acc: 1.0000