본문 바로가기
Data Science/딥러닝

6장 텍스트 시퀀스를 딥러닝1

by 코딩은 잼있어 2020. 11. 8.
728x90

6장 텍스트 시퀀스를 딥러닝1

시퀀스 데이터를 위한 딥러닝 모델은 기본적으로 순환 신경망(Recurrent Neural Network)과 1D 컨브넷이 있다.

6.1 텍스트 데이터 다루기

텍스트 데이터를 딥러닝 학습을 위한 수치형 텐서로 변환하여 딥러닝학습을 진행한다.

  1. 텍스트를 단어로 나누고 각 단어를 하나의 벡터로 변환
  2. 텍스트를 문자로 나누고 각 문자를 하나의 벡터로 변환
  3. 텍스트에서 단어나 문자의 n-그램을 추추랗여 각 n-그램을 하나의 백터로 변환

6.1.1 단어와 문자의 원-핫 인코딩

모든 단어에 고유한 정수 인덱스틀 부여하고 이 정수 인덱스 i를 크기가 N인 이진 벡터로 변환한다. 이 벡터는 i번째 원소만 1이고 나머지는 0이다.

Tokenizer

원-핫 인코딩: 각각의 항목을 벡터차원으로 변환하고, 표현하고 싶은 항목의 인덱스에 1, 나머지 모든 인덱스에 0을 표기하는 벡터 표현 방식

케라스의 Tokenizer 메소드로 간단하게 원-핫 인코딩을 할수 있다.

'''
케라스를 사용한 단어 수준의 원-핫인코딩
'''
from keras.preprocessing.text import Tokenizer

samples = ['The cat sat on the mat.', 'The dog ate my homework.']

# 가장 빈도가 높은 1,000개의 단어만 선택하도록 Tokenizer 객체를 만듭니다.
tokenizer = Tokenizer(num_words=1000)

# 단어 인덱스를 구축합니다.
tokenizer.fit_on_texts(samples)
>>> tokenizer

# 문자열을 정수 인덱스의 리스트로 변환합니다.
sequences = tokenizer.texts_to_sequences(samples)
>>> sequences
[[1, 2, 3, 4, 1, 5], [1, 6, 7, 8, 9]]

# 직접 원-핫 이진 벡터 표현을 얻을 수 있다.
one_hot_results = tokenizer.texts_to_matrix(samples, mode='binary')
>>> one_hot_results
[[0. 1. 1. ... 0. 0. 0.]
 [0. 1. 0. ... 0. 0. 0.]]

# 계산된 단어 인덱스를 구합니다.
word_index = tokenizer.word_index
>>> word_index
{'the': 1, 'cat': 2, 'sat': 3, 'on': 4, 'mat': 5, 'dog': 6, 'ate': 7, 'my': 8, 'homework': 9}

케라스에는 원본 텍스트 데이터를 단어 도는 문자 수준의 원-핫 인코딩으로 변환해주는 유틸리티가 있다. 해당 유틸리티를 활용하여 특수문자 제거나 빈도가 높은 단어만 뽑아낼수 있다.

6.1.2 단어 임베딩 사용하기

원-핫 인코딩으로 얻은 단어표현은 희소하고 고차원이고 수동이지만, 단어 임베딩은 조밀하고 비교적 저차원이며 데이터로부터 학습된다.

단어 임베딩은 만드는 방법은 두가지다.

  1. 관심 대상인 문제와 함께 단어 임베딩을 학습한다. 이런 경우에 랜덤한 단어 벡터로 시작해서 신경망의 가중치를 학습하는 것과 같은 방식으로 단어 벡터를 학습한다.
  2. 사전에 훈련된 단어 임베딩을 로드한다.

Embedding 층을 사용하여 단어 임베딩 학습시키기

랜덤하게 단어를 학습시켜나간다면 구조적이지 못한 결과가 나와 비슷한 의미의 단어라도 완전히 다른 의미를 갖게 된다. 단어 벡터 사이에 추상적이고 기하학적 관계를 얻으려면 단어 사이에 의미 관계를 반영해야만 한다.

img

위 그림에서 4가지의 동물이 2D 평면에 임베딩 되어이있다. 이 벡터 표현을 통해 단어 간의 의미관계를 기하하적 변환으로 인코딩 할수 있다.

예를 들면, y축으로 증가하는 방향은 애완동물에서 야생동물로 이동하는것으로 해석이 되고, x축으로 증가하는 방향은 개과에서 고양이과로 이동하는것으로 해석될수 있다.

[1] IMDB 데이터 로드

from keras.datasets import imdb
from keras import preprocessing
from keras.models import Sequential
from keras.layers import Flatten, Dense, Embedding

max_features = 10000
maxlen = 20
# IMDB 데이터(리뷰데이터)에서 빈도수가 높은 1만개의 단어를 추출하고 최대 길이를 20으로 제한하여 추출합니다.
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)

# pad_sequences : 리스트를 같은 크기의 2D 정수 텐서로 변환
x_train = preprocessing.sequence.pad_sequences(x_train, maxlen= maxlen)
x_test = preprocessing.sequence.pad_sequences(x_test, maxlen= maxlen)

pad_sequences

정수 시퀀스를 같은 크기로 변환해주는 역할

pad_sequences(x_train, maxlen = maxlen)

정수 텐서 x_train의 모든 시퀀스의 길이를 maxlen로 맞춰주는 메소드

숫자 0으로 채운다.

[2] Embedding 층과 분류기 사용

model = Sequential()

# Embedding 층
model.add(Embedding(10000, 8, input_length = maxlen))

# 2D로 펼쳐서 분류를 위한 Dense층을 훈련준비
model.add(Flatten())
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
model.summary()
model.fit(x_train, y_train, epochs=10, batch_size=32, validation_split=0.2)

>>> model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
embedding_1 (Embedding)      (None, 20, 8)             80000
_________________________________________________________________
flatten_1 (Flatten)          (None, 160)               0
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 161
=================================================================

Embedding 층 : model.add(Embedding(10000, 8, input_length = maxlen))

1만개의 단어에 대해 8차원의 임베딩 학습하여 정수 시퀀수(2D 정수 텐서)에서 임베딩 시퀀스(3D 실수형 텐서)로 변환

약 75%의 검증정확도가 나온다.

사전 훈련된 단어 임베딩 사용하기

데이터가 부족할 경우 사전에 훈련된 데이터를 활용하자~!!

Word2vec: Word2vec의 차원은 성별처럼 구체적인 의미가 있는 속성을 잡아낸다.

GloVe: 단어의 동시 출현 통계를 기록한 행렬을 분해하는 기법을 사용한다.

6.1.3 모든 내용을 적용하기: 원본 텍스트에서 단어 임베딩까지

[1] 원본 IMDB 텍스트 다운

케라스의 IMDB 데이터가 아닌 원본 IMDB를 다운받아, 훈련데이터를 문자열 리스트와 긍정 부정 레이블로 만들어보자

import os

imdb_dir = './datasets/aclImdb'
train_dir = os.path.join(imdb_dir, 'train')
labels = []
texts = []
for label_type in ['neg', 'pos']:
    dir_name = os.path.join(train_dir, label_type)
    for fname in os.listdir(dir_name):
        if fname[-4:] == '.txt':
            f = open(os.path.join(dir_name, fname), encoding='utf8')
            texts.append(f.read())
            f.close()
            if label_type == 'neg':
                labels.append(0)
            else:
                labels.append(1)

[2] 데이터 토큰화

200개의 소규모 훈련데이터를 사용해서 모델을 만들자.

텍스트를 벡터로 만들기 위해 Tokenizer메소드를 활용해 원-핫인코딩을 진행한 뒤, 훈련과 검증을 위한 데이터셋으로 나눈다.

from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import numpy as np

maxlen = 100  # 100개 단어 이후는 버립니다
training_samples = 200  # 훈련 샘플은 200개입니다
validation_samples = 10000  # 검증 샘플은 10,000개입니다
max_words = 10000  # 데이터셋에서 가장 빈도 높은 10,000개의 단어만 사용합니다

tokenizer = Tokenizer(num_words=max_words)
tokenizer.fit_on_texts(texts)

# 원-핫 인코딩
sequences = tokenizer.texts_to_sequences(texts)

word_index = tokenizer.word_index
print('%s개의 고유한 토큰을 찾았습니다.' % len(word_index))
>>> 88582

data = pad_sequences(sequences, maxlen=maxlen)

labels = np.asarray(labels)
print('데이터 텐서의 크기:', data.shape)
>>> (25000, 100)
print('레이블 텐서의 크기:', labels.shape)
>>> (25000,)

indices = np.arange(data.shape[0])
np.random.shuffle(indices)
data = data[indices]
labels = labels[indices]

# 200개의 훈련데이터
x_train = data[:training_samples]
y_train = labels[:training_samples]
# 10000개의 검증데이터
x_val = data[training_samples: training_samples + validation_samples]
y_val = labels[training_samples: training_samples + validation_samples]

[3] GloVe 단어 임베딩 내려받기

40만개의 단어에 대한 100차원 임베딩 벡터를 포함하는 GloVe에서 glove.6B.zip을 다운 받습니다.

[4] 임베딩 전처리

txt파일에서 단어와 이에 상응하는 벡터표현을 매핑하는 인덱스를 만든다.

glove_dir = './datasets/'

embeddings_index = {}
f = open(os.path.join(glove_dir, 'glove.6B.100d.txt'), encoding="utf8")
for line in f:
    values = line.split()
    word = values[0]
    coefs = np.asarray(values[1:], dtype='float32')
    embeddings_index[word] = coefs
f.close()

embeddings_index에는 40만개의 key값인 단어와 value값인 숫자벡터가 존재합니다.

Embedding 층에 주입할 수 있는 임배딩행렬(embedding_matrix)을 만들자

embedding_dim = 100

embedding_matrix = np.zeros((max_words, embedding_dim))
for word, i in word_index.items():
    embedding_vector = embeddings_index.get(word)
    if i < max_words:
        if embedding_vector is not None:
            # 임베딩 인덱스(GloVe)에 없는 단어는 모두 0이 됩니다.
            embedding_matrix[i] = embedding_vector

IMDB(word_index)와 GloVe(embeddings_index)를 비교하면서 GloVe에 없는 단어들의 인덱스를 모두 0으로 합니다.

이때 0은 어떤 단어나 토큰도 아닐경우를 나타낸다.

[5] 모델 정의 및 GloVe임베딩 로드

from keras.models import Sequential
from keras.layers import Embedding, Flatten, Dense

max_words = 10000
embedding_dim = 100
maxlen = 100

model = Sequential()
model.add(Embedding(max_words, embedding_dim, input_length=maxlen))
model.add(Flatten())
model.add(Dense(32, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
>>> model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_3 (Embedding)      (None, 100, 100)          1000000   
_________________________________________________________________
flatten_2 (Flatten)          (None, 10000)             0         
_________________________________________________________________
dense_2 (Dense)              (None, 32)                320032    
_________________________________________________________________
dense_3 (Dense)              (None, 1)                 33        
=================================================================

임베딩 로드하기

model.layers[0].set_weights([embedding_matrix])
model.layers[0].trainable = False

모델의 첫번째 층인 Embedding 층에 embedding_matrix(GloVe 행렬)를 로드합니다

그리고 Embedding 층을 동결한다. 그 이유는 컨브넷과 같다. 사전에 훈련된 데이터의 경우 특성을 유지하기 위해 학습을 시키지 않는다.

[6] 모델 훈련과 평가

model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['acc'])
history = model.fit(x_train, y_train,
                    epochs=10,
                    batch_size=32,
                    validation_data=(x_val, y_val))

약 50% 후반의 검증 정확도를 보인다.

만약에 사전에 훈련된 데이터를 사용하지 않을경우 50%초반의 검증정확도는 50% 초반에 머무른다.

물론 정확도를 높이기 위해 가장 좋은건 데이터가 많은거다!! 하지만 데이터가 부족할경우 위 방식으로 정확도를 조금이라도 높일수 있다!! 참고하자!!!!!!1

6.1.4 정리

원본 텍스트를 신경망이 처리할 수 있는 형태로 변환

Embedding 층을 추가하여 어떤 작업에 특화된 토큰 임베딩을 학습

데이터가 부족한 자연어 처리문제에서 사전 훈련된 단어 임베딩을 사용하여 성능향상을 할수있다!!

728x90