본문 바로가기
  • 데이터에 가치를 더하다, 서영석입니다.
공부하는 습관을 들이자/Deep Learning (NLP,LLM)

[딥러닝 자연어처리] 11. 8) 사전 훈련된 워드 임베딩 ~ 09) 사전 훈련된 워드 임베딩 사용하기

by 꿀먹은데이터 2024. 1. 2.

딥러닝을 이용한 자연어처리 입문 #11 8) 사전 훈련된 워드 임베딩 ~ 09) 사전 훈련된 워드 임베딩 (Pre-Trained Word Embedding) 사용하기

08) 사전 훈련된 워드 임베딩 (Pre-trained Word Embedding)

1. 케라스 임베딩 층 (Keras Embedding Layer)

  • 케라스 : 훈련 데이터의 단어들에 대해 워드 임베딩을 수행하는 도구 Embedding() 제공
  • Embedding() : 인공 신경망 구조 관점에서 임베딩 층 구현

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

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

ex) 영단어 : great

  • 임베딩 차원 : 4로 설정
  • great은 정수 인코딩 과정에서 1,918의 정수로 인코딩 되었고, 그에 따라 단어 집합의 크기만큼의 행을 가지는 테이블에서 인덱스 1,918번에 위치한 행을 great의 임베딩 벡터로 사용
  • 임베딩 벡터 : 모델의 입력
    • 역전파 과정에서 단어 great의 임베딩 벡터값이 학습됨

1) 케라스의 임베딩 층 구현 코드

vocab_size = 20000 # 텍스트 데이터의 전체 단어 집합의 크기
output_dim = 128 # 워드 임베딩 후의 임베딩 벡터의 차원
input_length = 500 # 입력 시퀀스의 길이

v = Embedding(vocab_size, output_dim, input_length=input_length)

갖고 있는 각 샘플의 길이가 500개이면, input_length는 500이다.

  • Embedding() : 2D 정수 텐서 입력 받음
  • 각 샘플은 정수 인코딩이 된 결과의 정수 시퀀스
  • Embedding() : 워드 임베딩 작업 수행 후 3D 실수 텐서 리턴

2) 임베딩 층 사용하기

  • 문장의 긍/부정 판단하는 감정 분류 모델 생성
import numpy as np
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences

sentences = ['nice great best amazing', 'stop lies', 'pitiful nerd', 'excellent work', 'supreme quality', 'bad', 'highly respectable'] # 문장 생성
y_train = [1, 0, 0, 1, 1, 0, 1] # 긍정 : 1, 부정 : 0
tokenizer = Tokenizer()
tokenizer.fit_on_texts(sentences)
vocab_size = len(tokenizer.word_index) + 1 # 패딩을 고려하여 +1
print('단어 집합 :',vocab_size)

# 케라스의 Tokenizer()사용하여 단어 집합 생성 후 크기 확인

>>> 단어 집합 : 16
# 각 문장에 대해 정수 인코딩 수행
X_encoded = tokenizer.texts_to_sequences(sentences)
print('정수 인코딩 결과 :',X_encoded)

>>> 정수 인코딩 결과 : [[1, 2, 3, 4], [5, 6], [7, 8], [9, 10], [11, 12], [13], [14, 15]]
max_len = max(len(l) for l in X_encoded) # 가장 길이가 긴 문장의 길이 구함
print('최대 길이 :',max_len)

>>> 최대 길이 : 4
X_train = pad_sequences(X_encoded, maxlen=max_len, padding='post') # 최대 길이로 모든 샘플에 대해서 패딩 진행
y_train = np.array(y_train)
print('패딩 결과 :')
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]]
  • 훈련 데이터에 대한 전처리 완료
  • 이진 분류 모델 설계
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Embedding, Flatten

embedding_dim = 4

model = Sequential()
model.add(Embedding(vocab_size, embedding_dim, input_length=max_len))
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)
  • 출력층 : 1개의 뉴런 배치, 활성화 함수 : 시그모이드 함수
  • 손실 함수 : binary_crossentropy
  • epoch : 100으로 학습

09) 사전 훈련된 워드 임베딩 (Pre-Trained Word Embedding) 사용하기

  • 사용에 따라 훈련 데이터로 이미 학습되어져 있는 임베딩 벡터들을 사용 하는 것이 성능 개선에 좋음
  • 사전 훈련된 GloVe와 Word2Vec 임베딩을 사용해서 모델 훈련 실습 진행
# 훈련 데이터는 앞서 사용했던 데이터에 동일한 전처리까지 진행된 상태라고 가정
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]

1) 사전 훈련된 GloVe 사용하기

# 파일 다운로드
from urllib.request import urlretrieve, urlopen
import gzip
import zipfile

urlretrieve("<http://nlp.stanford.edu/data/glove.6B.zip>", filename="glove.6B.zip")
zf = zipfile.ZipFile('glove.6B.zip')
zf.extractall() 
zf.close()
embedding_dict = dict()

f = open('glove.6B.100d.txt', encoding="utf8") # glove.6B.100d.txt에 있는 모든 임베딩 벡터 불러옴

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가 있습니다.
  • 총 40만개의 임베딩 벡터 존재 확인
print(embedding_dict['respectable']) # 임베딩 벡터값 출력
print('벡터의 차원 수 :',len(embedding_dict['respectable'])) # 크기 출력

>>> [-0.049773   0.19903    0.10585 ... 중략 ... -0.032502   0.38025  ]
벡터의 차원 수 : 100
# 풀고자 하는 문제의 단어 집합 크기의 행과 100개의 열을 가지는 행렬 생성
embedding_matrix = np.zeros((vocab_size, 100)) # 행렬 값 전부 0으로 채움 -> 행렬에 사전 훈련된 임베딩 값 넣어줌
print('임베딩 행렬의 크기(shape) :',np.shape(embedding_matrix)

>>> 임베딩 행렬의 크기(shape) : (16, 100)
print(tokenizer.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), ('quality', 12), ('bad', 13), ('highly', 14), ('respectable', 15)])
  • 단어 great에 맵핑된 정수 : 2
print('단어 great의 맵핑된 정수 :',tokenizer.word_index['great']

>>> 단어 great의 맵핑된 정수 : 2
print(embedding_dict['great']) # 사전 훈련된 GloVe에서 great의 벡터값 확인

>>> [-0.013786   0.38216    0.53236    0.15261   -0.29694   -0.20558
.. 중략 ...
 -0.69183   -1.0426     0.28855    0.63056  ]
# 단어 집합의 모든 단어에 대해서 사전 훈련된 GloVe의 임베딩 벡터들을 맵핑한 후 great의 벡터값이 의도한 인덱스의 위치에 삽입되었는지 확인
for word, index in tokenizer.word_index.items():
    
		# 단어와 맵핑되는 사전 훈련된 임베딩 벡터값
		vector_value = embedding_dict.get(word)
    if vector_value is not None:
        embedding_matrix[index] = vector_value

embedding_matrix[2]

>>> array([-0.013786  ,  0.38216001,  0.53236002,  0.15261   , -0.29694   ,
        ... 중략 ...
       -0.39346001, -0.69182998, -1.04260004,  0.28854999,  0.63055998])
  • 사전에 훈련된 GloVe에서의 great의 벡터값과 일치
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Embedding, Flatten

output_dim = 100

model = Sequential()
e = Embedding(vocab_size, output_dim, weights=[embedding_matrix], input_length=max_len, trainable=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)
  • Embedding layer에 embedding_matrix를 초기값으로 설정
  • 사전 훈련된 워드 임베딩을 100차원의 값인 것으로 사용하고 있기 때문에 임베딩 층의 output_dim의 인자값으로 100을 주어야 함
  • 사전 훈련된 워드 임베딩을 그대로 사용할 경우 추가 훈련을 하지 않는다는 의미에서 trainable 인자값을 False로 선택

2) 사전 훈련된 Word2Vec 사용하기

# 구글의 사전 훈련된 Word2Vec 모델 로드
import gensim

urlretrieve("<https://s3.amazonaws.com/dl4j-distribution/GoogleNews-vectors-negative300.bin.gz>", \\
                           filename="GoogleNews-vectors-negative300.bin.gz")
word2vec_model = gensim.models.KeyedVectors.load_word2vec_format('GoogleNews-vectors-negative300.bin.gz', binary=True)

print('모델의 크기(shape) :',word2vec_model.vectors.shape) # 모델의 크기 확인

>>> 모델의 크기(shape) : (3000000, 300)
  • 300의 차원을 가진 Word2Vec 벡터 3,000,000개 존재
# 모든 값이 0으로 채워진 임베딩 행렬 생성
# 풀고자 하는 문제의 단어 집합 크기의 행과 300개의 열을 가진 행렬 생성
embedding_matrix = np.zeros((vocab_size, 300)) 

print('임베딩 행렬의 크기(shape) :',np.shape(embedding_matrix)

>>> 임베딩 행렬의 크기(shape) : (16, 300)
def get_vector(word):
    if word in word2vec_model:
        return word2vec_model[word]
    else:
        return None
  • get_vector() 함수 생성
    • word2vec_model : 특정 단어 입력하면 해당 단어의 임베딩 벡터 리턴
    • 만약, word2vec_model에 특정 단어의 임베딩 벡터가 없다면 None 리턴
# 단어 집합으로부터 단어를 1개씩 호출하여 word2vec_model에 해당하는 단어의 임베딩 벡터값이 존재하는지 확인
for word, index in tokenizer.word_index.items():
    # 단어와 맵핑되는 사전 훈련된 임베딩 벡터값
    vector_value = get_vector(word)
    if vector_value is not None: # 만약 None이 아니면 존재한다는 뜻
        embedding_matrix[index] = vector_value # 임베딩 행렬에 해당 단어의 인덱스 위치의 행에 임베딩 벡터의 값 저장
  • 현재 풀고자 하는 문제의 16개의 단어와 맵핑되는 임베딩 행렬 완성
  • 제대로 맵핑이 되었는지 확인
print(word2vec_model['nice']) # nice 의 임베딩 벡터값 확인

>>>
[ 0.15820312  0.10595703 -0.18945312  0.38671875  0.08349609 -0.26757812
  0.08349609  0.11328125 -0.10400391  0.17871094 -0.12353516 -0.22265625
  ... 중략 ...
 -0.16894531 -0.08642578 -0.08544922  0.18945312 -0.14648438  0.13476562
 -0.04077148  0.03271484  0.08935547 -0.26757812  0.00836182 -0.21386719]
print('단어 nice의 맵핑된 정수 :', tokenizer.word_index['nice']) # nice의 맵핑된 정수 확인

>>> 단어 nice의 맵핑된 정수 : 1
print(embedding_matrix[1]) # 1이므로 embedding_matrix[1]에는 nice의 임베딩 벡터값이 있어야 함

>>>
[ 0.15820312  0.10595703 -0.18945312  0.38671875  0.08349609 -0.26757812
  0.08349609  0.11328125 -0.10400391  0.17871094 -0.12353516 -0.22265625
  ... 중략 ...
 -0.16894531 -0.08642578 -0.08544922  0.18945312 -0.14648438  0.13476562
 -0.04077148  0.03271484  0.08935547 -0.26757812  0.00836182 -0.21386719]
  • word2vec_model 의 값과 동일
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Embedding, Flatten, Input

model = Sequential()
model.add(Input(shape=(max_len,), dtype='int32'))
e = Embedding(vocab_size, 300, weights=[embedding_matrix], input_length=max_len, trainable=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)
  • Embedding()에 사전 훈련된 embedding_matrix를 입력으로 넣고 모델 학습