본문 바로가기

딥러닝/딥러닝을 이용한 자연어처리 입문

[딥러닝 NLP] 07. 딥러닝(2) Keras, NNLM

# 07-05 케라스

1. 케라스 훑어보기

Keras: 딥러닝을 쉽게 구현할 수 있도록 도와주는 파이썬 라이브러리

케라스의 모든 기능을 보고 싶다면 아래 공식 문서를 참고

https://keras.io/ or https://www.tensorflow.org/guide/keras?hl=ko

 

(1) 전처리

Tokenizer() : 토큰화, 정수인코딩에 사용

pad_sequences() : 패딩. 정해준 길이보다 긴 샘플은 값을 자르고 짧은 샘플은 0으로 채워 길이를 맞춰줌

from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences

tokenizer = Tokenizer() 
tokenizer.fit_on_texts([훈련할 텍스트]) # 단어 집합 생성
tokenizer.texts_to_sequences([정수인코딩할 텍스트])[0] # 정수인코딩
pad_sequences(패딩을 진행할 데이터, maxlen=길이, padding='pre') # pre는 앞에 채움, post는 뒤에 채움

 

(2) 워드 임베딩

워드 임베딩: 단어를 밀집 벡터로 만드는 작업

Embedding() : 임베딩 층을 만드는 역할

Embedding(단어개수, 출력차원, inpput_length=입력시퀀스 길이)

 

(3) 모델링

Sequential() : 층을 추가하는 역할

Dense() : 전결합층을 추가하는 역할

summary() : 모델정보를 요약

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense

model = Sequential()
model.add(Embedding(단어개수, 출력차원, 입력시퀀스 길이)) # 임베딩 층 추가
model.add(Dense(출력차원, input_dim=입력차원, activation='활성화함수')) # 전결합층 추가
model.add(Dense(출력차원, activation='활성화함수')) # 두 번째 전결합층 추가
# 활성화함수 = linear(디폴트), sigmoid, softmax, relu

model.summary()

 

(4) 컴파일과 훈련

compile() : 모델을 기계가 이해할 수 있도록 컴파일하는 역할

fit() : 모델을 학습시킴

model.compile(optimizer='옵티마이저', loss='손실함수', metrics=['모니터링지표'])
model.fit(X_train, y_train, epochs=에포크, batch_size=배치크기, 
			verbose=학습중출력문구선택, validation_data(X_val, y_val))

 

 

-대표적으로 사용되는 손실함수와 출력층의 활성화함수 조합

  손실함수 출력층 활성화함수
회귀문제 mean_squared_error -
다중클래스분류 categorical_crossentropy 소프트맥스
다중클래스분류 sparse_ categorical_crossentropy 소프트맥스
이진분류 binary_crossentropy 시그모이드

 

-batch_size=32(디폴트), None(미니배치 경사하강법을 사용하고 싶지 않을 때)
-verbose= 0(출력X), 1(진행도를 보여주는 진행막대), 2(미니배치마다의 손실정보)
-validation_data(X_val, y_val)=검증데이터 직접 지정.

-validation_split=비율 : 검증데이터를 직접 지정하지 않고 훈련데이터를 일부 사용

 

(5) 평가와 예측

evaluate() : 테스트데이터로 모델의 정확도 평가

predict() : 예측 수행

model.evaluate(X 테스트데이터, y 테스트 데이터, batch_size=32)
model.predict(X 예측데이터, batch_size=32)

 

(6) 저장과 로드

save() : 모델을 hdf5 파일에 저장

load_model() : 저장한 모델을 로드

model.save("모델명.h5")
from tensorflow.keras.models import load_model
model = load_model("모델명.h5")

 

 

2. Keras Functional API

Sequential API: 직관적으로 층을 하나씩 쌓는 방식, 복잡한 신경망 구현 불가능

Functional API: 각 층을 일종의 함수로 정의해 신경망 설계( https://keras.io/getting-started/functional-api-guide/ )

 

Input() : 모델 앞단에 입력층을 정의하고 시작

from tensorflow.keras.layers import Input, Dense
from tensorflow.keras.models import Model

inputs = Input(shape=(10,)) # 입력층 정의
hidden1 = Dense(64, activation='relu')(inputs)  # 은닉층 추가
hidden2 = Dense(64, activation='relu')(hidden1) # 은닉층 추가
output = Dense(1, activation='sigmoid')(hidden2) # 출력층 추가
model = Model(inputs=inputs, outputs=output) # 입력과 출력 정의

 

활성화함수에 linear, sigmoid 등을 넣어 선형회귀, 로지스틱회귀 구현 가능

 

concatenate() : 다중 입력, 다중 출력 구현 가능

from tensorflow.keras.layers import Input, Dense, concatenate
from tensorflow.keras.models import Model

# 두 개의 입력층을 정의
inputA = Input(shape=(64,))
inputB = Input(shape=(128,))

# 첫번째 입력층으로부터 분기되어 진행되는 인공 신경망을 정의
x = Dense(16, activation="relu")(inputA)
x = Dense(8, activation="relu")(x)
x = Model(inputs=inputA, outputs=x)

# 두번째 입력층으로부터 분기되어 진행되는 인공 신경망을 정의
y = Dense(64, activation="relu")(inputB)
y = Dense(32, activation="relu")(y)
y = Dense(8, activation="relu")(y)
y = Model(inputs=inputB, outputs=y)

# 두개의 인공 신경망의 출력을 연결(concatenate)
result = concatenate([x.output, y.output])

z = Dense(2, activation="relu")(result)
z = Dense(1, activation="linear")(z)

model = Model(inputs=[x.input, y.input], outputs=z)

 

LSTM() : RNN 은닉층 설계

RNN에 대해서는 다음 챕터에서 자세히 배운다.

from tensorflow.keras.layers import Input, Dense, LSTM # LSTM 불러와서
from tensorflow.keras.models import Model

inputs = Input(shape=(50,1))
lstm_layer = LSTM(10)(inputs) # RNN layer 추가
x = Dense(10, activation='relu')(lstm_layer)
output = Dense(1, activation='sigmoid')(x)

model = Model(inputs=inputs, outputs=output)

 

 

3. Keras Subclassing API

Subclassing API : Functional API 보다도 복잡한, 밑바닥부터 새로운 수준의 구조를 구현해야하는 모델에 사용

대부분의 딥러닝 모델은 Functional API 수준에서도 구현 가능

Subclassing API 의 구조

 

tf.keras.Model을 상속받아 init()에서 모델의 구조와 동적을 정의

super() 함수를 불러 tf.keras.Model 클래스의 속성들을 가지고 초기화

call() 함수를 불러 입력값에 대한 예측값을 리턴하는 포워드(forward) 연산 진행

import tensorflow as tf

class LinearRegression(tf.keras.Model):
  def __init__(self):
    super(LinearRegression, self).__init__()
    self.linear_layer = tf.keras.layers.Dense(1, input_dim=1, activation='linear')

  def call(self, x):
    y_pred = self.linear_layer(x)

    return y_pred

 

-Sequential API: 사용이 간단하지만 복잡한 모델 구현 불가능

-Functional API: 복잡한 모델 구현이 가능하지만 모델 앞단에 입력층을 정의해주어야 함

-Subclassing API: 보다 복잡한 모델 구현이 가능하지만 코드 짜기가 까다로움

 

4. 다층 퍼셉트론(MLP)로 텍스트 분류 실습

(1) 다층퍼셉트론이란?

은닉층이 2개 이상인 신경망으로, 피드포워드 신경망(FFNN)의 가장 기본 형태

 

(2) 케라스의 texts_to_matrix()

import numpy as np
from tensorflow.keras.preprocessing.text import Tokenizer

texts = ['먹고 싶은 사과', '먹고 싶은 바나나', '길고 노란 바나나 바나나', '저는 과일이 좋아요']

# 정수인코딩
tokenizer = Tokenizer()
tokenizer.fit_on_texts(texts)

# texts_to_matrix(): 텍스트로부터 행렬 생성, 모드에는 'binary', 'count', 'freq', 'tfidf'의 4개 존재
print(tokenizer.texts_to_matrix(texts, mode = 'count'))
print(tokenizer.texts_to_matrix(texts, mode = 'count'))
print(tokenizer.texts_to_matrix(texts, mode = 'binary'))
print(tokenizer.texts_to_matrix(texts, mode = 'tfidf').round(2)) # 둘째자리까지 반올림

 

모드에 상관없이 출력되는 행렬의 인덱스는 0부터 시작, 각 행의 첫 번째 열의 값은 항상 0 (단어가 9개였다면 인덱스 10개)

'count' 모드: DTM 생성, 빈도수 기반이므로 순서 정보 보존 X

'binary' 모드: 단어의 존재 유무로만 표현

'tfidf' 모드: TF-IDF 생성, 다만 TfidfVectorizer의 식과 조금 다름

'freq' 모드: 각 문서내에서 각 단어의 등장 확률로 표현

 

(3) 사이킷런 뉴스그룹 데이터

사이킷런에서는 20개의 다른 주제를 가진 18,846개의 뉴스 그룹 이메일 데이터를 제공한다.

import pandas as pd
import matplotlib.pyplot as plt
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.utils import to_categorical
from sklearn.datasets import fetch_20newsgroups

# 데이터 확인
newsdata = fetch_20newsgroups(subset = 'train') # 'train','test','all' 중 선택
print(newsdata.keys()) # dict_keys(['data', 'filenames', 'target_names', 'target', 'DESCR'])
print(len(newsdata.data)) # 훈련데이터 개수
print(len(newsdata.target_names)) # 주제 개수
print(newsdata.target_names) # 주제 이름들
print(newsdata.target[0]) # 첫번째 샘플의 레이블
print(newsdata.target_names[7]) # 7번 레이블이 의미하는 주제
print(newsdata.data[0]) # 첫번째 샘플 출력

# 데이터 통계분석
data = pd.DataFrame(newsdata.data, columns = ['email']) # 메일본문 추가
data['target'] = pd.Series(newsdata.target) # 메일레이블 추가
data.isnull().values.any() # 결측값 확인
print(data['email'].nunique()) # 중복을 제외하고 개수 출력
print(data['target'].nunique()) # 중복을 제외하고 개수 출력
data['target'].value_counts().plot(kind='bar'); # 레이블 분포 시각화
print(data.groupby('target').size().reset_index(name='count')) # 레이블별 개수 출력

# train, test 데이터 준비
newsdata_test = fetch_20newsgroups(subset='test', shuffle=True)
train_email = data['email']
train_label = data['target']
test_email = newsdata_test.data
test_label = newsdata_test.target

# 전처리 함수 정의(단어 토큰화 + 행렬 생성)
vocab_size = 10000
num_classes = 20
def prepare_data(train_data, test_data, mode):
    tokenizer = Tokenizer(num_words = vocab_size) # 개수만큼의 단어만 사용
    tokenizer.fit_on_texts(train_data) # 단어 토큰화
    X_train = tokenizer.texts_to_matrix(train_data, mode=mode) # 샘플 수 × vocab_size 크기의 행렬 생성
    X_test = tokenizer.texts_to_matrix(test_data, mode=mode) # 샘플 수 × vocab_size 크기의 행렬 생성
    return X_train, X_test, tokenizer.index_word

# 전처리 진행(입력데이터 X에는 전처리, 출력레이블 y에는 원핫인코딩)
X_train, X_test, index_to_word = prepare_data(train_email, test_email, 'binary')
y_train = to_categorical(train_label, num_classes) # 원-핫 인코딩
y_test = to_categorical(test_label, num_classes) # 원-핫 인코딩

# 빈도수 상위 1번과 9999번 단어 확인
print('빈도수 상위 1번 단어 : {}'.format(index_to_word[1])) # the
print('빈도수 상위 9999번 단어 : {}'.format(index_to_word[9999])) # mic

 

(4) 다층퍼셉트론 모델로 텍스트분류

아래 구조와 같은 다층퍼셉트론 모델을 설계한다.

설계할 다층퍼셉트론 모델의 구조

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout

def fit_and_evaluate(X_train, y_train, X_test, y_test):
    model = Sequential()
    model.add(Dense(256, input_shape=(vocab_size,), activation='relu')) # 입력층 -> 은닉층 1
    model.add(Dropout(0.5))
    model.add(Dense(128, activation='relu')) # 은닉층 1 -> 은닉층 2
    model.add(Dropout(0.5))
    model.add(Dense(num_classes, activation='softmax')) # 은닉층 2 -> 출력층

    model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
    model.fit(X_train, y_train, batch_size=128, epochs=5, verbose=1, validation_split=0.1)
    score = model.evaluate(X_test, y_test, batch_size=128, verbose=0)
    return score[1]
    
    # model.evaluate이 반환하는 출력값은 [loss, metric_1, metric_2, ..., metric_n]
    즉, [손실값, 사용자가 지정한 모델 지표1, ..., 사용자가 지정한 모델 지표n]
    따라서 위 코드에서 score[1]은 metrics로 지정한 'accuracy'

 

 

-과적합을 막기 위해 두 번의 드롭아웃 적용

-다중클래스 분류를 수행하기 위해 출력층 활성화함수로 소프트맥스 사용

-손실함수로 크로스 엔트로피 함수 사용

# 4개의 모드에 대해 각각 모델을 적용해보고 정확도를 비교하자
modes = ['binary', 'count', 'tfidf', 'freq']
for mode in modes:
    X_train, X_test, _ = prepare_data(train_email, test_email, mode) # (3)에서 만든 전처리함수
    score = fit_and_evaluate(X_train, y_train, X_test, y_test) # (4)에서 만든 모델훈련&평가 함수
    print(mode + ' 모드의 테스트 정확도:', score)
    
# 결과
binary 모드의 테스트 정확도: 0.8312533
count 모드의 테스트 정확도: 0.8239511
tfidf 모드의 테스트 정확도: 0.8381572
freq 모드의 테스트 정확도: 0.6902549

 

# 07-06 피드 포워드 신경망 언어모델(NNLM)

1. NNLM(Neural Network Language Model) 이란?

-언어 모델링(Language Modeling): 문장에 확률을 할당하여 주어진 문맥으로부터 아직 모르는 단어를 예측하는 것

-n-gram 언어모델: 바로 앞 n-1개의 단어를 참고해 다음 단어의 등장 확률 예측

-희소문제(sparsity problem): 충분한 훈련 데이터가 없으면 정확한 모델링이 불가능

-NNLM: 훈련 데이터에 존재하지 않는 단어여도, 워드임베딩으로 단어벡터간 유사도를 구해 보다 정확한 예측을 하는 모델

 

2. NNLM 모델의 학습과정

  • 예문: "what will the fat cat sit on" 일 때, 'what will the fat cat ___" 를 예측해보자.

(1) 각 단어에 대해 원핫인코딩 수행

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]

 

모든 단어가 단어집합의 크기(=차원)이 7인 원핫벡터가 되었다.

 

(2) 윈도우 크기 설정

ex. 윈도우 크기를 4라고 한다면, ___ 앞의 4개의 단어 will, the, fat, cat 을 Input으로 사용

전체 구조 요약

 

(3) 투사층(Projection layer)

-투사층에서는 일반 은닉층(비선형층)과는 다르게 가중치는 곱해지지만 활성화함수는 없음(선형층)

-단어집합의 크기가 V , 투사층의 크기가 M일 때 가중치행렬 W의 크기는 V x M

ex. M=5라고 할 때, V=7이었으므로 다음과 같다.

 

투사층에서 원핫벡터 x에 가중치행렬 W가 곱해지고 있다. V차원 -> M 차원으로 크기 축소

-lookup table: 원핫벡터에서 1에 해당하는 인덱스를 i라고 할 때 W에서 i번째 행을 그대로 읽어오는 (=lookup) 행위

-embedding vector: lookup table 과정을 거친 후의 단어벡터 e

-concatenate: 모든 단어 x가 e로 변경되었다면, 모든 e를 연결

ex. 5차원 벡터 4개를 연결해 20차원 벡터가 되었다.

 

(4) 은닉층(hidden layer)과 출력층

-투사층의 출력은 h의 크기를 가진 은닉층을 지나면서 가중치가 곱해지고, 편향이 더해지고, 활성화함수(tanh)를 거친다.

-은닉층의 출력은 V의 크기를 가진 출력층으로 향하면서 가중치가 곱해지고, 편향이 더해지고, 활성화함수(softmax)를 거친다. 이 때 예측값은 0과 1 사이의 실수값이고, 이것을 정답레이블인 원핫벡터와 비교한다.

-손실함수로 크로스 엔트로피 함수를 사용해 역전파를 수행한다.

은닉층과 출력층

-> 여기서는 V의 크기가 고작 7개였지만 충분한 훈련데이터에 대해 학습할 경우 NNLM은 수많은 문장으로부터 유사하게 사용되는 단어들에 대해 유사한 임베딩 벡터값을 얻게끔 해줌

 

3. NNLM의 장단점

-장점: 단어의 유사도를 통해 희소문제 해결

-단점: 고정된 길이(=윈도우크기)만큼만 참고 가능 -> 대안: RNN 언어모델(RNNLM). 다음 챕터에서 다룬다.