[Python] 모두의 딥러닝 - 05. 딥러닝의 활용[자연어 처리]

Updated:

모두의 딥러닝 교재를 토대로 공부한 내용입니다.

실습과정에서 필요에 따라 코드나 이론에 대한 추가, 수정사항이 있습니다.


기본 세팅

import numpy as np
import pandas as pd

import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns

import warnings
%matplotlib inline
%config InlineBackend.figure_format = 'retina'

mpl.rc('font', family='NanumGothic') # 폰트 설정
mpl.rc('axes', unicode_minus=False) # 유니코드에서 음수 부호 설정

# 차트 스타일 설정
sns.set(font="NanumGothic", rc={"axes.unicode_minus":False}, style='darkgrid')
plt.rc("figure", figsize=(10,8))

warnings.filterwarnings("ignore")

17. 자연어 처리

이번 챕터에선 딥러닝을 이용해서 자연어 처리(Natural Language Processing, NLP)의 기본을 다뤄보자.

머신러닝 완벽가이드에선 nltk 패키지를 주로 사용하여 텍스트 분석을 진행했었으니 참고하자.

17.1 텍스트 토큰화

from tensorflow.keras.preprocessing.text import text_to_word_sequence

text = "해보지 않으면 알 수 없다."
result = text_to_word_sequence(text)
result
['해보지', '않으면', '알', '수', '없다']
  • tensorflow.keras.preprocessing.texttext_to_word_sequence()를 이용하여 토큰화가 가능하다.
from tensorflow.keras.preprocessing.text import Tokenizer

# 문장
docs = [
    "먼저 텍스트의 각 단어를 나누어 토큰화 합니다.",
    "텍스트의 단어로 토큰화해야 딥러닝에서 인식됩니다.", 
    "토큰화한 결과는 딥러닝에서 사용할 수 있습니다."
       ]

# 토큰화
token = Tokenizer()
token.fit_on_texts(docs)
print(token.word_counts)
OrderedDict([('먼저', 1), ('텍스트의', 2), ('각', 1), ('단어를', 1), ('나누어', 1), ('토큰화', 1), ('합니다', 1), ('단어로', 1), ('토큰화해야', 1), ('딥러닝에서', 2), ('인식됩니다', 1), ('토큰화한', 1), ('결과는', 1), ('사용할', 1), ('수', 1), ('있습니다', 1)])
  • Tokenizer()를 이용해서 단어의 빈도 수를 쉽게 구할 수 있다.

  • fit_on_texts()로 텍스트를 입력하고 word_counts 속성으로 각 단어의 빈도 수를 추출 가능하다.

  • 머신러닝 완벽가이드 텍스트 분석[소개]에서 배웠던 BOW(Bag of Words) 방법이다.

token.document_count
3
  • document_count 속성으로 몇 개의 문장이 있는지 알 수 있다.
print(token.word_docs)
defaultdict(<class 'int'>, {'먼저': 1, '합니다': 1, '텍스트의': 2, '나누어': 1, '단어를': 1, '토큰화': 1, '각': 1, '단어로': 1, '인식됩니다': 1, '토큰화해야': 1, '딥러닝에서': 2, '결과는': 1, '사용할': 1, '토큰화한': 1, '수': 1, '있습니다': 1})
  • word_docs 속성을 이용하면 각 단어들이 몇 개의 문장에서 카운트 되는지 알 수 있다.
print(token.word_index)
{'텍스트의': 1, '딥러닝에서': 2, '먼저': 3, '각': 4, '단어를': 5, '나누어': 6, '토큰화': 7, '합니다': 8, '단어로': 9, '토큰화해야': 10, '인식됩니다': 11, '토큰화한': 12, '결과는': 13, '사용할': 14, '수': 15, '있습니다': 16}
  • word_index 속성은 각 단어의 인덱스 값을 확인해준다.

  • 인덱스 값은 1부터 시작한다.

17.2 단어 원-핫 인코딩

text = "오랫동안 꿈꾸는 이는 그 꿈을 닮아간다"

# 토큰화
token = Tokenizer()
token.fit_on_texts([text])  # 리스트로 입력
print(token.word_index)
{'오랫동안': 1, '꿈꾸는': 2, '이는': 3, '그': 4, '꿈을': 5, '닮아간다': 6}
  • 위와 같은 문장을 토큰화 했을 때 각 단어들의 인덱스를 확인하자.
# 인덱스 배열
x = token.texts_to_sequences([text])
x
[[1, 2, 3, 4, 5, 6]]
  • texts_to_sequences()로 토큰의 인덱스로만 채워진 배열을 생성 가능하다.
from keras.utils import to_categorical

# 원-핫 인코딩
word_size = len(token.word_index) + 1
x = to_categorical(x, num_classes = word_size)
x
Using TensorFlow backend.





array([[[0., 1., 0., 0., 0., 0., 0.],
        [0., 0., 1., 0., 0., 0., 0.],
        [0., 0., 0., 1., 0., 0., 0.],
        [0., 0., 0., 0., 1., 0., 0.],
        [0., 0., 0., 0., 0., 1., 0.],
        [0., 0., 0., 0., 0., 0., 1.]]], dtype=float32)
  • 원-핫 인코딩을 진행하되 카테고리 수를 1 증가시켜 인덱스 0에 대해선 모두 0으로 만들어 준다.

17.3 단어 임베딩

앞선 방식처럼 원-핫 인코딩을 그대로 사용하면 벡터의 길이가 너무 길어진다.

만약 단어가 10,000개이면 9,999개의 0과 하나의 1로 이루어진 단어 벡터를 10,000개 생성한다.

이러한 공간적 낭비를 해결하기 위해 단어 임베딩(word embedding) 방법을 사용한다.

이 부분 교재 설명에서 다시 한번 느낀점은 어려운 개념을 쉽게 설명하기 위해 많은 내용이 생략되어 있다.

전반적으로 개념에 대한 설명 생략과 용어 정리가 안되있는 느낌이다…

교재가 기본서이고 심층적으로 배우려면 본인이 노력해야하는게 당연하지만 추후 헷갈릴 것 같다.

다시 한번, 교재를 통해 딥러닝의 전반을 살짝 살펴본다는 마음으로 공부하는게 좋을 것 같다.

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

model = Sequential()
model.add(Embedding(16,4))
  • Embedding()을 이용해서 단어 임베딩이 가능하다.

  • 위 코드에선 입력될 총 단어수를 16, 임베딩 후 출력되는 벡터 크기는 4로 지정하였다.

  • input_length 옵션을 지정하면 단어를 매번 얼마나 입력할지 추가로 지정 가능하다.

17.4 긍정, 부정 예측

영화에 대한 리뷰를 딥러닝 모델로 학습해서, 각 리뷰가 긍정, 부정인지를 예측해보자.

# 리뷰 데이터 만들기
docs = ["너무 재밌네요", "최고예요", "참 잘 만든 영화예요", "추천하고 싶은 영화입니다", "한번 더 보고싶네요", 
        "글쎄요", "별로예요", "생각보다 지루하네요", "연기가 어색해요", "재미없어요"]

# 0: 부정, 1: 긍정
classes = np.array([1,1,1,1,1,0,0,0,0,0])
  • 먼저 텍스트 데이터와 긍정, 부정에 대한 target 데이터를 생성하였다.
# 토큰화 
token = Tokenizer()
token.fit_on_texts(docs)

print("문장 수:", token.document_count)
print(token.word_index)
문장 수: 10
{'너무': 1, '재밌네요': 2, '최고예요': 3, '참': 4, '잘': 5, '만든': 6, '영화예요': 7, '추천하고': 8, '싶은': 9, '영화입니다': 10, '한번': 11, '더': 12, '보고싶네요': 13, '글쎄요': 14, '별로예요': 15, '생각보다': 16, '지루하네요': 17, '연기가': 18, '어색해요': 19, '재미없어요': 20}
  • 우선 토큰화를 진행하고 각 단어들의 인덱스를 확인하였다.
# 인덱스 배열
x = token.texts_to_sequences(docs)
print(x)
[[1, 2], [3], [4, 5, 6, 7], [8, 9, 10], [11, 12, 13], [14], [15], [16, 17], [18, 19], [20]]
  • 인덱스 배열을 생성하였는데 각 문장별 단어 수가 다르다 보니 토큰 수가 각각 다르다.

  • 딥러닝 모델에 입력하기 위해선 길이가 모두 동일하여야 한다.

from tensorflow.keras.preprocessing.sequence import pad_sequences

# 패딩
padded_x = pad_sequences(x, 4)  
print(padded_x)
[[ 0  0  1  2]
 [ 0  0  0  3]
 [ 4  5  6  7]
 [ 0  8  9 10]
 [ 0 11 12 13]
 [ 0  0  0 14]
 [ 0  0  0 15]
 [ 0  0 16 17]
 [ 0  0 18 19]
 [ 0  0  0 20]]
  • 길이를 똑같이 맞춰 주는 작업을 패딩 과정이라고 한다.

  • pad_sequences()를 사용하여 패딩 작업이 가능하며 지정한 길이보다 짧으면 0을, 길면 잘라서 같은 길이로 맞춘다.

from tensorflow.keras.layers import Dense, Flatten

# 단어 인덱스 수 + 1
word_size = len(token.word_index) + 1
 
# 모델 설정
model = Sequential()
model.add(Embedding(word_size, 8, input_length=4))    # 단어 임베딩
model.add(Flatten())                                  # 1차원 만들기
model.add(Dense(1, activation='sigmoid'))

# 모델 컴파일
model.compile(loss="binary_crossentropy", optimizer="adam", metrics=["accuracy"])

# 모델 실행
model.fit(padded_x, classes, epochs=20)

# 결과 출력
print("-"*100)
print(f"Accuracy: {model.evaluate(padded_x, classes, verbose=0)[1]: .4f}")
Train on 10 samples
Epoch 1/20
10/10 [==============================] - 0s 47ms/sample - loss: 0.6795 - accuracy: 0.9000
Epoch 2/20
10/10 [==============================] - 0s 196us/sample - loss: 0.6772 - accuracy: 0.9000
Epoch 3/20
10/10 [==============================] - 0s 196us/sample - loss: 0.6750 - accuracy: 1.0000
Epoch 4/20
10/10 [==============================] - 0s 100us/sample - loss: 0.6728 - accuracy: 1.0000
Epoch 5/20
10/10 [==============================] - 0s 103us/sample - loss: 0.6705 - accuracy: 1.0000
Epoch 6/20
10/10 [==============================] - 0s 199us/sample - loss: 0.6683 - accuracy: 1.0000
Epoch 7/20
10/10 [==============================] - 0s 400us/sample - loss: 0.6661 - accuracy: 1.0000
Epoch 8/20
10/10 [==============================] - 0s 321us/sample - loss: 0.6638 - accuracy: 1.0000
Epoch 9/20
10/10 [==============================] - 0s 202us/sample - loss: 0.6616 - accuracy: 1.0000
Epoch 10/20
10/10 [==============================] - 0s 300us/sample - loss: 0.6593 - accuracy: 1.0000
Epoch 11/20
10/10 [==============================] - 0s 303us/sample - loss: 0.6571 - accuracy: 1.0000
Epoch 12/20
10/10 [==============================] - 0s 202us/sample - loss: 0.6548 - accuracy: 1.0000
Epoch 13/20
10/10 [==============================] - 0s 183us/sample - loss: 0.6526 - accuracy: 1.0000
Epoch 14/20
10/10 [==============================] - 0s 200us/sample - loss: 0.6503 - accuracy: 1.0000
Epoch 15/20
10/10 [==============================] - 0s 200us/sample - loss: 0.6480 - accuracy: 1.0000
Epoch 16/20
10/10 [==============================] - 0s 315us/sample - loss: 0.6457 - accuracy: 1.0000
Epoch 17/20
10/10 [==============================] - 0s 306us/sample - loss: 0.6434 - accuracy: 1.0000
Epoch 18/20
10/10 [==============================] - 0s 201us/sample - loss: 0.6411 - accuracy: 1.0000
Epoch 19/20
10/10 [==============================] - 0s 300us/sample - loss: 0.6388 - accuracy: 1.0000
Epoch 20/20
10/10 [==============================] - 0s 300us/sample - loss: 0.6364 - accuracy: 1.0000
----------------------------------------------------------------------------------------------------
Accuracy:  1.0000

Leave a comment