자연어처리(NLP) 11일차 (케라스)

정민수
18 min readJun 14, 2019

--

2019.06.14

출처 : https://wikidocs.net/book/2155

핵심키워드

  • 케라스(Keras)

케라스(Keras) 훑어보기

“딥 러닝을 이용한 자연어처리 입문, https://wikidocs.net/book/2155” 에서는 딥 러닝을 쉽게 할 수 있는 파이썬 라이브러리인 케라스(Keras)를 사용한다. 케라스는 계산을 직접 수행하는 내부 레벨의 라이브러리가 아니라, 유저가 손쉽게 딥 러닝을 구현할 수 있도록 도와주는 상위 레벨의 인터페이스이다. 케라스를 사용하면 딥 러닝을 쉽게 구현할 수 있다.

케라스는 수많은 기능들을 지원한다. 이 모든 기능들을 열거하는 것만으로도 하나의 책의 분량이고, 이 책에서 모든 걸 다룰 수 없다고 한다. 가장 좋은 방법은 케라스 공식 문서 (https://keras.io/)를 참고하는 것이다. 여기서는 딥 러닝을 위해 대표적으로 사용되는 케라스의 각 모듈들을 이해한다.

1. 전처리(Preprocessing)

Tokenizer() : 토큰화와 정수 인코딩(단어에 대한 인덱싱)을 위해 사용된다. 이에 대한 상세한 설명은 정수 인코딩 챕터를 확인하면 된다.

from keras.preprocessing.text import Tokenizer
t = Tokenizer()
fit_text = "The earth is an awesome place live"
t.fit_on_texts([fit_text])
test_text = "The earth is an great place live"
sequence = t.texts_to_sequences([test_text])[0]
print("sequences : ",sequence,'\n')
# great는 단어 집합(Vocabulary)에 없으므로 출력되지 않는다.
print("word_index : ",t.word_index)
# 단어 집합(Vocabulary) 출력
sequences : [1, 2, 3, 4, 6, 7]

word_index : {'the': 1, 'earth': 2, 'is': 3, 'an': 4, 'awesome': 5, 'place': 6, 'live': 7}

pad_sequence() : 각 훈련 데이터는 길이가 서로 다를 수 있다. 또는 각 문서 또는 문장은 단어의 수가 제각각이다. 모델의 입력으로 사용하려면 모든 훈련 데이터의 길이를 동일하게 맞추어야할 때가 있다. 이를 자연어 처리에서는 패딩(padding) 작업이라고 하는데, 보통 숫자 0을 넣어서 길이가 다른 데이터들의 길이를 맞춰준다. 앞으로도 자연어 처리를 하면서 지속적으로 보게 될 작업이니 기억해두자. 이때는 케라스에서 전처리를 위해 제공하는 pad_sequence()를 사용한다.

pad_sequnce()는 정해준 길이에 모든 데이터의 길이를 동일하게 바꿔준다. 정해진 길이보다 길이가 긴 데이터는 데이터를 잘라버리고, 정해준 길이보다 길이가 짧은 데이터에는 0으로 채운다.

from keras.preprocessing.sequence import pad_sequencespad_sequences([[1, 2, 3],[3, 4, 5, 6], [7, 8]], maxlen=3, padding='pre')
# 전처리가 끝나서 각 단어에 대한 정수 인코딩이 끝났다고 가정하고, 3개의 데이터를 입력으로 한다.
array([[1, 2, 3],
[4, 5, 6],
[0, 7, 8]])

첫번째 인자 = 패딩을 진행할 데이터

maxlen = 모든 데이터에 대해서 정규화할 길이

padding = ‘pre’를 선택하면 앞에 0을 채우고 ‘post’를 선택하면 뒤에 0을 채움

2. 워드 임베딩(Word Embedding)

9챕터에서 다시 다루겠지만 워드 임베딩이란 텍스트를 기계가 숫자로서 텍스트를 이해할 수 있도록 텍스트로 표현되었던 단어들을 밀집 벡터(Dense Vector)로 만드는 것을 말한다. 밀집 벡터가 뭘까? 이미 배운 개념인 원-핫 벡터로부터 비교하여 이해해보자. 앞서 배운 원-핫 벡터는 대부분이 0의 값을 가지고, 단 하나의 1의 값을 가지는 벡터였다. 또한 벡터의 차원이 대체적으로 크다는 성질을 가졌다. 원-핫 벡터의 예는 다음과 같다.

ex) [0 1 0 0 0 0 … 0 0 0 0 0 0] # 차원이 굉장히 크면서 대부분의 값이 0

대부분의 값이 0인 이러한 벡터를 희소 벡터(Sparse vector)라고 한다. 즉, 원-핫 벡터는 희소벡터의 하나의 예이다. 원-핫 벡터는 단어의 수만큼 벡터의 차원을 가져야 한다는 단점이 있다. 반면, 희소 벡터와 표기상으로도 의미상으로도 반대인 벡터가 있다. 바로 대부분의 값이 실수이고, 대체적으로 저차원 벡터인 밀집 벡터(Dense vector)이다. 아래는 밀집 벡터의 예이다.

ex)[0.1 -1.2 0.8 0.2 1.8] # 차원이 대체적으로 작고 수값을 가짐

간단하게 표로 정리해보면 아래와 같이 정리해볼 수 있다.

단어를 원-핫 벡터로 만드는 과정을 원-핫 인코딩이라고 불렀다. 단어를 이러한 밀집 벡터로 만드는 작업을 워드 임베딩(Word embedding)이라고 한다. 그리고 이 밀집 벡터를 워드 임베딩 과정을 통해 나온 결과라하고 하여 임베딩 벡터(Embedding Vector)라고도 한다. 원-핫 벡터의 차원이 주로 20,000 이상을 넘어가는 것에 비해 임베딩 벡터는 주로 256, 512, 1024 등의 차원을 가진다.

케라스의 Embedding()이 만들어내는 임베딩 벡터는 초기에는 랜덤의 값을 가지다가, 인공 신경망의 가중치가 학습되는 방법과 같은 방식으로 점차 값이 바뀌어간다.

Embedding() : Embedding()은 단어를 밀집 벡터로 만드는 역할을 한다. 인공 신경망의 구조적 관점에서는 임베딩 층(Embedding layer)을 만드는 역할을 한다고 말할 수 있다. 이때, Embedding()의 입력으로 각 단어는 이미 정수 인코딩이 된 상태여야 한다. Embedding()은 정수 인코딩이 된 상태의 입력을 받아서 임베딩을 수행한다.

아래의 코드는 실제 사용되는 코드가 아니라 의사 코드(pseudo-code)로, 이해를 돕기 위해 작성된 코드이다. 실제로는 Embedding()을 사용하기 위해서는 뒤에 배우게 될 Sequential()을 먼저 사용해야 한다.

Embedding()은 (number of samples, input_length)인 2D 정수 텐서를 입력받는다. 이 때 각 sample은 정수 인코딩이 된 결과로, 정수의 시퀀스이다. Embedding()은 워드 임베딩 작업을 수행하고 (number of samples, input_length, embedding word dimentionality)인 3D 텐서를 리턴한다.

text = [['Hope', 'to', 'see', 'you', 'soon'], ['Nice', 'to', 'see','you', 'again']]
# 문장 토큰화와 단어 토큰화가 되어있는 상태라고 가정
text = [[0, 1, 2, 3, 4],[5, 1, 2, 3, 6]]
# 위의 데이터에 대해서 정수 인코딩을 수행하였다고 가정
# 다음과 같이 위의 데이터에 대해서 임베딩 층을 만들고, 위의 데이터를 통과시킨다고 가정
from keras.layers import Embedding
Embedding(7, 2, input_length=5)
# 7은 단어의 개수. 즉, 단어 집합(Vocabulary)의 크기이다.
# 2는 임베딩한 후의 벡터의 크기이다.
# 5는 각 입력 시퀀스의 길이. 즉, input_length이다.
# 각각의 단어에 대해서 Embeddig()이 리턴한 임베딩 벡터 또는 밀집 벡터의 예는 다음과 같다. +------------+------------+
| index | Embedding |
+------------+------------+
| 0 | [1.2, 3.1] |
| 1 | [0.1, 4.2] |
| 2 | [1.0, 3.1] |
| 3 | [0.3, 2.1] |
| 4 | [2.2, 1.4] |
| 5 | [0.7, 1.7] |
| 6 | [4.1, 2.0] |
+------------+------------+
# 위의 표는 임베딩 벡터가 된 결과를 예로서 정리한 것이고 Embedding()의 출력인 3D 텐서를 보여주는 것이 아니다.

Embedding()에 넣어야하는 대표적인 인자는 다음과 같다.

첫번째 인자 = 단어 집합의 크기. 즉, 총 단어의 개수

두번째 인자 = 임베딩 벡터의 출력 차원. 결과로서 나오는 임베딩 벡터의 크기

input_length = 입력 시퀀스의 길이

3. 모델링(Modeling)

Sequential() : 앞서 퍼셉트론 챕터와 인공 신경망 챕터에서 입력층, 은닉층, 출력층에 대해서 배운 바 있다. 케라스에서는 이러한 층을 구성하는 함수로 Sequential() 함수를 사용한다. 사용 방법은 Sequential() 함수를 model로 선언해 놓고 model.add()라는 코드를 통해 새로운 층을 만든다.

사실 앞서 배운 Embedding()을 통해 생성하는 임베딩 층(embedding layer) 또한 인공 신경망 층의 하나이므로, Sequential() 함수를 model로 선언해 놓고 model.add()로 추가해야 한다. 아래는 임베딩 층을 추가하는 예제코드이다.

from keras.models import Sequential
model = Sequential()
model.add(Embedding(vocabulary, hidden_size, input_length))
# vocabulary, hidden_size, input_length가 정의되어야 함.

Dense() : 몇 개의 뉴런을 만들지 숫자를 기재하여, 결정하는 역할을 한다. Sequential() 함수를 model로 선언해 놓고 model.add()를 통해 추가할 수 있다.

from keras.models import Sequential
from keras.layers import Dense
model = Sequential()
model.add(Dense(1, input_dim=3, init='uniform', activation='relu'))

위의 코드에서 Dense()는 한번 사용되었다. 우선, Dense() 함수의 대표적인 인자를 확인하자.

첫번째 인자 = 출력 뉴런의 수

input_dim = 입력 뉴런의 수 (입력의 차원)

init = 가중치 초기화 방법

  • uniform : 균일 분포
  • normal : 가우시안 분포

activation = 활성화 함수

  • linear : 디폴트 값으로 별도 활성화 함수 없이 입력 뉴런과 가중치의 계산 결과 그대로 출력. ex) 선형 회귀
  • sigmoid : 시그모이드 함수. 이진 분류 문제에서 출력층에 주로 사용되는 활성화 함수.
  • softmax : 소프트맥스 함수. 셋 이상을 분류하는 다중 클래스 분류 문제에서 출력층에 주로 사용되는 활성화 함수.
  • relu : 렐루 함수. 은닉층에 주로 사용되는 활성화 함수.

코드에서 사용된 Dense()를 보자. 첫번째 인자의 입력값은 1인데 이는 총 1개의 출력 뉴런을 만들겠다는 것을 의미한다. Dense()의 두번째 인자인 input_dim은 입력값의 개수, 또는 입력층의 뉴런 수를 의미한다. 이 경우에는 3이 사용되었다. 즉, 3개의 입력층 뉴런과 1개의 출력층 뉴런을 만든 것이다. 이를 시각화하면 다음과 같다.

from keras.models import Sequential
from keras.layers import Dense
model = Sequential()
model.add(Dense(8, input_dim=4, init='uniform', activation='relu'))
model.add(Dense(1, activation='sigmoid')) # 출력층

이번에는 Dense()가 두 번 사용되었다. Dense()가 처음으로 사용되었을 때와 추가적으로 사용되었을 때는 조금 인자가 다르다. 이제 첫번째 사용된 Dense()의 첫번째 인자에서의 8이라는 값은 더 이상 출력층의 뉴런이 아니라 은닉층의 뉴런이라고 봐야한다. 뒤에 층이 하나 더 생겼기 때문이다.

두번째 Dense()는 input_dim의 인자가 없어졌는데, 이는 Dense()가 첫번째로 사용된 것이 아닌 경우에는 이미 이전층의 뉴런의 수를 알 수 있기 때문이다. 이 경우에는 8개의 은닉층으로부터 값이 오는 것을 알고 있는 셈이다. 그리고 위의 코드에서 두번째 Dense()는 또한 model.add()를 통해 추가된 마지막 층이기도 하기 때문에, 첫번째 인자 1은 결국 출력층의 뉴런의 개수가 된다. 이를 시각화하면 다음과 같다.

이 외에도 SimpleRNN, LSTM, GRU 등 다양한 층(layer)를 만들 수 있지만, 이에 대해서는 각 챕터에서 배운다. 이 책에서는 다루지 않지만 Convolution2D, BatchNormalization 등의 여러 층도 존재한다.

summary() : 모델의 정보를 요약해서 보여준다.

model.summary()_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense_2 (Dense) (None, 8) 40
_________________________________________________________________
dense_3 (Dense) (None, 1) 9
=================================================================
Total params: 49
Trainable params: 49
Non-trainable params: 0
________________________________________________________________

4. 컴파일(Compile)과 훈련(Training)

compile() : 모델링한 모델을 기계(컴퓨터)가 이해할 수 있도록 컴파일 한다. 여기서는 오차 함수와 최적호 방법, 그리고 모델 수행 결과를 나타내는 메트릭 함수를 선택할 수 있다.

from keras.layers import SimpleRNN, Embedding, Dense
from keras.models import Sequential
max_features = 10000
model = Sequential()
model.add(Embedding(max_features, 32))
model.add(SimpleRNN(32))
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer='rmsprop',loss='binary_crossentropy',metrics=['acc'])

위 코드는 Sequential() 함수를 model로 선언하고, model.add()를 통해 임베딩층과 다양한 형태의 은닉층을 추가한 후에, 마지막으로 컴파일 하는 과정을 보여준다.

optimizer : 훈련 과정을 설정하는 옵티마이저를 설정한다. ‘adam’이나 ‘sgd’와 같이 문자열로 지정할 수 있다.

loss : 최적화 과정에서 최소화될 손실 함수(loss function)를 설정한다. 평균 제곱 오차(mse)와 categorical_crossentropy, binary_crossentropy 등이 자주 사용된다. 손실 함수의 이름을 지정하면 된다.

metrics : 훈련을 모니터링할 지표를 선택한다.

여기서 사용할 수 있는 손실 함수는 다양한 종류가 있는데, 대표적으로 사용되는 손실 함수(loss function)와 활성화 함수(Activation)의 조합은 다음과 같다. 더 많은 손실 함순느 케라스 공식문서를 통해 확인할 수 있다.

fit() : 모델을 실제로 학습한다. 모델이 오차로부터 매개 변수를 업데이트 하는 과정을 학습 또는 훈련이라고 한다. 그런데 이를 적합(fitting)이라고 하기도 하는데, 이는 모델이 데이터에 적합해져가는 과정이기 때문이다. 그런 의미에서fit()은 모델의 훈련을 시작한다는 의미를 가지고 있다.

model.fit(X_train, y_train, epochs=10, batch_size=32)

첫번째 인자 = 훈련 데이터

두번째 인자 = 지도 학습 관점에서 레이블 데이터

epochs = 에포크라고 읽으며 에포크 1은 전체 데이터를 한 차례 훑고 지나갔음을 의미함. 정수값 기재 필요. 총 훈련 횟수를 정의함.

batch_size = 배치의 크기. 케라스에서는 배치 크기의 기본값은 32. 미니 배치 경사 하강법을 사용하고 싶지 않을 경우에는 batch_size=None을 통해 선택 가능함.

좀 더 많은 인자를 사용할 때를 보자.

model.fit(X_train, y_train, epochs=10, batch_size=32, verbose=0, validation_data(X_val, y_val))

validation_data(x_val, y_val) = 검증 데이터(validation data)를 사용한다. 이 검증 데이터를 사용하면 각 에포크마다 검증 데이터의 정확도도 함께 출력이 되는데, 이는 훈련이 잘 되고 있는지를 보여줄 뿐이며 실제로 훈련에 반영되지 않는다. 검증 데이터의 loss가 점차 낮아지다가 높아지기 시작하면 이는 과적합(Overfitting)의 신호이다.

validation_split = validation_data 대신 사용할 수 있다. 검증 데이터를 사용하는 것은 동일하지만, 별도로 존재하는 검증 데이터를 주는 것이 아니라 X_train과 y_train에서 일정 비율을 분리하여 이를 검증 데이터로 사용한다. 역시나 훈련 자체에는 반영되지 않고 훈련 과정을 지켜보기 위한 용도로 사용한다. 아래는 validation_data 대신에 validation_split을 사용했을 경우를 보여준다.

model.fit(X_train, y_train, epochs=10, batch_size=32, verbose=0, validation_split=0.2)) # 훈련 데이터의 20%를 검증 데이터로 사용.

이제 다른 파라미터에 대해 알아보자.

verbose = 학습 중 출력되는 문구를 설정한다.

  • 0 : 아무 것도 출력하지 않는다
  • 2 : 에포크 횟수당 한 줄씨 출력한다.
  • 1 : 훈련의 진행도를 보여주는 진행 막대를 보여준다.

아래는 verbose의 값ㄷ이 2일 때와 1일 때를 보여준다.

# verbose = 2일 경우. 
Epoch 88/100 - 0s - loss: 0.1475 - acc: 1.0000
# verbose = 1일 경우. Epoch 88/100 7/7 [==============================] - 0s 143us/step - loss: 0.1029 - acc: 1.0000

5. 평가(Evaluation)와 예측(Prediction)

evaluate() : 테스트 데이터를 통해 학습한 모델에 대한 정확도를 평가한다.

# 위의 fit() 코드의 연장선상인 코드 
model.evaluate(X_test, y_test, batch_size=32)

첫번째 인자 = 테스트 데이터

두번째 인자 = 지도 학습 관점에서 레이블 테스트 데이터

batch_size = 배치 크기

predict() : 임의의 입력에 대한 모델의 출력값을 확인

# 위의 fit() 코드의 연장선상인 코드 
model.predict(X_input, batch_size=32)

첫번째 인자 = 예측하고자 하는 데이터

batch_size = 배치 크기

6. 모델의 저장(Save)와 로드(Load)

개념 이해를 목표로 하는 이 책에서는 거의 사용할 일이 없지만, 복습을 위한 스터디나 실제 어플리케이션 개발 단게에서 구현한 모델을 저장하고 불러오는 일은 중요하다. 모델을 저장한다는 것은 학습이 끝난 신경망의 구졸르 보존하고 계속해서 사용할 수 있다는 의미이다.

save() : 인공 신경망 모델을 hdf5 파일에 저장한다.

model.save("model_name.h5")

load_model() : 저장해둔 모델을 불러온다.

from keras.models import load_model
model = load_model("model_name.h5")

--

--

정민수
정민수

Responses (1)