Tensorflow_NLP_기초
Keras의 Tensorflow를 활용해 NLP작업을 할때 필요한 기초적인 사용법에 대해서 포스팅 합니다.
1. 전처리
전처리는 자연어처리 뿐 아니라 어떤 데이터를 다루는데 필수적으로 필요한 작업입니다. 데이터 분석을 위해서는 원하는 형태의 데이터 형태로 만들어 분석할 필요가 있고 모델링의 경우 모델이 학습, 이해 할 수 있는 형태로 전처리를 해줄 필요가 있습니다. 이번에는 토크나이징과 패딩작업에 대해서만 다뤄보겠습니다.
1) 토크나이징
토크나이징은 자연어처리를 위해서 주어진 문장을 단어 단위 혹은 영어라면 알파벳, 한글이라면 자모 단위까지 분리하여 하나의 토큰으로 분해하는 과정을 말합니다. 쉽게 이야기하면 완성된 레고 장난감을 초기 상태였던 조립전의 각 블록들로 분리하는 작업을 의미합니다.
우선 Tensorflow에서 제공해주는 Tokenizer를 통해서 영어에 대한 토크나이징을 진행해보겠습니다.
'The earth is an awsome place live'라는 문장이 있고 이를 초기화 해준 tokenizer를 통해서 'fit_on_texts'로 리스트 형태로 넘겨주게 되면 tokenizer라는 객체가 알아서 토크나이징을 진행하고 그 결과에 대한 값들을 갖고 있게 됩니다. 그 이후 새로운 문장인 'The earth is an great place live'라는 문장에 대해서 tokenizer에게 컴퓨터에게 학습 시킬 수 있는 구조인 정수 시퀀스 형태로 요청하는 함수인 'text_to_sequences'를 호출하면 결과를 리턴해주는데 문제는 great라는 단어를 사전에 학습하지 못했기 때문에 새로운 문장 중 great에 대한 인코딩작업을 하지 못하였습니다.
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
tokenizer = Tokenizer()
train_text = "The earth is an awesome place live"
# 단어 집합 생성
tokenizer.fit_on_texts([train_text])
# 정수 인코딩
sub_text = "The earth is an great place live"
sequences = tokenizer.texts_to_sequences([sub_text])[0]
print("정수 인코딩 : ",sequences)
print("단어 집합 : ",tokenizer.word_index)
정수 인코딩 : [1, 2, 3, 4, 6, 7]
단어 집합 : {'the': 1, 'earth': 2, 'is': 3, 'an': 4, 'awesome': 5, 'place': 6, 'live': 7}
이렇게 편하게 모듈로 구현해주는 부분을 직접 구현해보고 이번에는 영어가 아닌 한글을 대상으로 진행해보겠습니다. 별도의 토크나이저를 사용하지 않고 문장의 형태소를 분리시켜주는 모듈은 KoNLPy의 Okt만을 사용해서 진행해보겠습니다. okt의 morphs를 사용해 문장을 형태소 단위로 분리하는 작업을 진행합니다.(형태소 분리기는 각 목적에 따라 분리되는 형태가 다릅니다. 여기서는 이부분에 대해서는 패스하겠습니다.) 이후 우리가 필요한 정보는 이렇게 분리된 형태소 단위를 정수 시퀀스로 변환 시켜주는게 필요합니다. 그렇기 때문에 형태소로 분리해준 각 단어마다 순서대로 숫자 라벨링을 진행해줍니다. 이후 새로운 한글 문장인 '대한민국은 법치주의 국가이다'라는 문장을 만들고 이를 정수 라벨링을 보내보겠습니다. 결과는 위 영어와 똑같습니다. 단어 사전을 만들때 '법치주의'라는 단어는 없었기 때문에 새로운 문장을 정수 인코딩할때 해당 부분이 빠져있습니다. 이후 과정에서 이러한 문제점을 해결하기 위해서 OOV(Out of Vector)라는 개념을 배우게 되는데 이부분은 word_to_index는 0부터 시작하고 영어의 숫자 라벨리은 1부터 시작한 것도 연관이 있지만 지금은 토크나이저를 알아보는 과정이니 넘어가겠습니다.
from konlpy.tag import Okt
okt = Okt()
korean_text = '대한민국은 민주주의 국가이다'
# 단어 집합(형태소 분리)
word_list = okt.morphs(korean_text)
word_to_index = {}
# 정수 인코딩
for idx, word in enumerate(word_list):
word_to_index[word] = idx
sub_korean_text = '대한민국은 법치주의 국가이다'
korea_sequences = [word_to_index[word] for word in okt.morphs(sub_korean_text) if word in word_to_index.keys()]
print('정수 인코딩 : "', korea_sequences)
print('단어 집합 : "', word_to_index)
정수 인코딩 : " [0, 1, 3, 4]
단어 집합 : " {'대한민국': 0, '은': 1, '민주주의': 2, '국가': 3, '이다': 4}
2) 패딩
패딩 작업은 모델의 입력으로 사용하려면 모든 샘플의 길이를 동일하게 맞추어주는 작업을 이야기 합니다. 보통 숫자 0을 넣어서 길이가 다른 샘플들의 길이를 맞춰줍니다. 케라스에서는 pad_sequence()를 사용합니다. 매개변수를 통해 샘플들의 길이와 패딩 방법에 대해서 지정해줄 수 있습니다.
maxlen = 모든 데이터에 대해서 정규화 할 길이
padding = 'pre -> 앞을 0을 채움 / 'post' -> 뒤를 0을 채움
pad_sequences([[1, 2, 3], [3, 4, 5, 6], [7, 8]], maxlen=3, padding='pre')
array([[1, 2, 3],
[4, 5, 6],
[0, 7, 8]], dtype=int32)
2. 워드 임베딩
워드 임베딩은 쉽게 이야기하면 원-핫 인코딩의 데이터가 많아지면 길이가 데이터 수 만큼 늘어나 데이터가 커지는 단점을 보완한 방법이라고 생각하시면 됩니다.예를 들어서 위에서 사용한 '대한민국은 민주주의 국가이다' '대한민국', '은', '민주주의' 국가', '이다'로 5개로 형태소로 분리되었습니다. 이를 원-핫 인코딩을 하게 되면 다음과 같은 형태가 됩니다.
[[1, 0, 0, 0, 0]
[0, 1, 0, 0, 0]
[0, 0, 1, 0, 0]
[0, 0, 0, 1, 0]
[0, 0, 0, 0, 1]]
컴퓨팅적인 부분을 제외하고 간단하게 5*5로 25개의 공간을 사용하고 있다고 생각해보겠습니다. 그런데 예를들어 단어가 10만개라면 어떨까요? 10만 * 10만으로 100억개의 공간을 사용하게 됩니다. 위 예시에서는 단어가 아닌 형태소로 분리했기 때문에 많은 문장 데이터가 있을때 이러한 작업을 하게 되면 100억개 아니라 더 많은 공간을 사용하게 될 것입니다. 이 경우 당연히 컴퓨터가 처리해야할게 많이집니다. 이 부분을 보완하고자 하는 방법이 워드 임베딩입니다. '대한민국'이라는 딘어를 [1, 0, 0, 0, 0]이 아닌 [2.1, 1.3, 0.5, 0.6, 1.1]과 같은 형태의 벡터로 표현하게됩니다. 이 벡터의 크기를 지정하여 리스트 안의 숫자의 개수를 지정해줄 수 있는 것이죠. 보통 128, 256, 512, 1024와 같은 형태로 많이 사용하는데 10만개 문장을 128개로 구성된 벡터로 워드 임베딩을 한다 하면 128만개로 이전에 100억개보다 확줄어든 것을 확인할 수 있습니다. 이는 Embedding()함수를 호출하여 적용해줄 수 있습니다. 각 매개변수는 다음과 같습니다.
첫번째 인자 = 단어 집합의 크기. 즉, 총 단어의 개수(vocab_size)
두번째 인자= 임베딩 벡터의 출력 차원. 결과로서 나오는 임베딩 벡터의 크기(embedding_dim)
input_length = 입력 시퀀스의 길이
예를들어 Embedding(5, 3, 4)라고 한다면 훈련시킬 모든 유니크한 단어각 5개이고, 결과를 3개의 벡터로 구성된 리스트로 받을 것이며, 입력으로 들어가는 시퀀스(문장)의 길이는 4라는 것입니다.
3. 모델링
Tensorflow에서 딥러닝 모델을 구현할때 각 층들이 구성이 되게 되는데 이는 Sequential를 통해구성할 수 있습니다. Sequential()을 model로 선언한 뒤에 model.add()라는 코드를 통해 층을 단계적으로 추가합니다. 예제코드를 보면 model을 초기화 한 후에 하나의 층(Dense)를 쌓는데 input_dim을 4로 설정하고 출력 뉴런의 수를 8, 활성화 함수는 'relu'로 설정하였습니다. 이렇게 하나의 층이 완성되고 다음 층을 또 쌓는데 이번에는 출력 뉴런을 1, 활성화함수를 'sigmoid'로 설정하였습니다. 여기서 input_dim이 없는 이유는 이전 층에서 8개의 출력뉴런으로 이미 정의를 해주었기 때문에 따로 정의해줄 필요가 없는 것입니다.
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
model = Sequential()
model.add(Dense(8, input_dim=4, activation='relu'))
model.add(Dense(1, activation='sigmoid')) # 출력층
model.summary()
4. 컴파일과 훈련
모델링은 배관을 연결해준것과 같습니다. 연결했으면 그 배관이 잘 작동하는지 무언가를 흘려보내봐야겠죠. 모델을 설계를 했으니 이제 준비한 데이터들이 모델에서 잘 흘러가는지(학습하고 예측하는지) 확인해볼 필요가 있습니다.
컴파일은 모델을 만들고 이제 이 모델을 컴퓨터에게 이해시키는 과정인데 여기서 optimizer, loss_fun, metrics 3개를 설정해 줍니다.
간단하게 이야기하면 optimizer는 딥러닝 모델이 학습과정중 가중치를 업데이트하는 과정을 거치게 되는데 이때 최적의 가중치 업데이트를 도와주는 역할을 한다고 생각하시면 됩니다. loss_fun 손실함수를 설정해주는 것이고 metrics는 정확도, f1스코어 등 훈련을 모니터링하기 위한 지표를 설정하는 것입니다. 모델에 출력층까지 층을 쌓았다면 다음과 같이 컴파일을 해주면됩니다.
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
이후 준비해둔 데이터를 통해 fit()을 호출하여 모델에 훈련을 시켜주면 됩니다.
model.fit(X_train, y_train, epochs=10, batch_size=32)
fit() 매개변수
첫번째 인자 = 훈련 데이터
두번째 인자 = 레이블 데이터(결과)
epochs = 총 훈련 횟수
batch_size = 배치 크기.
validation_data(x_val, y_val) = 검증 데이터를 지정. 모델 훈련에는 영향을 주지 않지만 검증 데이터의 loss를 통해 과정학 구간을 쉽게 알 수 있음.
validation_split = 검증 데이터를 직접 지정하지 않고 X_train과 y_train에서 일정 비율 분리하여 이를 검증 데이터로 사용
verbose = 학습 중 출력되는 문구를 설정합니다.
- 0 : 아무 것도 출력하지 않음
- 1 : 훈련의 진행도를 보여주는 진행 막대 출력
- 2 : 미니 배치마다 손실 정보를 출력
5. 평가와 예측
훈련이 완료된 데이터에 대해서 모델을 평가하고 예측하는 작업을 따로 진행할 수 있습니다.
# 모델 평가
model.evaluate(X_test, y_test, batch_size=32)
# 모델 예측
model.predict(X_input, batch_size=32)
6. 모델 저장
매번 모델을 훈련시켜서 사용할 수 없으니 모델을 저장하고 쉽게 불러오는 작업을 해야합니다. 그 방법은 간단하게 아래와 같습니다.
# 모델 저장
model.save("model_name.h5")
# 모델 로드
from tensorflow.keras.models import load_model
model = load_model("model_name.h5")