[요약정리]딥 러닝을 이용한 자연어 처리 입문(Ch11. 스팸 메일 분류하기)


RNN을 이용해서 간단하게 스펨 메일 분류기를 구현해 보자


참고 사이트 : 딥 러닝을 이용한 자연어 처리 입문 에 대한 글을 요약하였다.

CH11. 텍스트 분류(Text Classification)


스팸 메일 분류하기(Spam Detection)

1. 스팸 메일 데이터에 대한 이해

import numpy as np
import pandas as pd
%matplotlib inline
import matplotlib.pyplot as plt
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
data = pd.read_csv('data/spam.csv',encoding='latin1')
data[:5]
v1v2Unnamed: 2Unnamed: 3Unnamed: 4
0hamGo until jurong point, crazy.. Available only ...NaNNaNNaN
1hamOk lar... Joking wif u oni...NaNNaNNaN
2spamFree entry in 2 a wkly comp to win FA Cup fina...NaNNaNNaN
3hamU dun say so early hor... U c already then say...NaNNaNNaN
4hamNah I don't think he goes to usf, he lives aro...NaNNaNNaN
# 필요한 v1, v2만 가져오고 v1은 레이블 0,1로 변환

del data['Unnamed: 2']
del data['Unnamed: 3']
del data['Unnamed: 4']
data['v1'] = data['v1'].replace(['ham','spam'],[0,1])
data[:5]
# 해당 data 정보 보기
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5572 entries, 0 to 5571
Data columns (total 2 columns):
 #   Column  Non-Null Count  Dtype 
---  ------  --------------  ----- 
 0   v1      5572 non-null   int64 
 1   v2      5572 non-null   object
dtypes: int64(1), object(1)
memory usage: 87.2+ KB
# null값 확인
data.isnull().values.any()
False
# 데이터 중복 확인
data['v2'].nunique(), data['v1'].nunique()
(5169, 2)

5572 샘플 중 403개는 중복됨

data['v1'].value_counts().plot(kind='bar');

png

print(data.groupby('v1').size().reset_index(name='count'))
   v1  count
0   0   4825
1   1    747
X_data = data['v2']
y_data = data['v1']
print('메일 본문의 개수: {}'.format(len(X_data)))
print('레이블의 개수: {}'.format(len(y_data)))
메일 본문의 개수: 5572
레이블의 개수: 5572
print('메일 본문의 개수: {}, 레이블의 개수: {}'.format(len(X_data), len(y_data)))
메일 본문의 개수: 5572, 레이블의 개수: 5572
tokenizer = Tokenizer()
tokenizer.fit_on_texts(X_data) #5572개의 행을 가진 X의 각 행에 토큰화를 수행
sequences = tokenizer.texts_to_sequences(X_data) #단어를 숫자값, 인덱스로 변환하여 저장
print(sequences[:5])
[[50, 469, 4410, 841, 751, 657, 64, 8, 1324, 89, 121, 349, 1325, 147, 2987, 1326, 67, 58, 4411, 144], [46, 336, 1495, 470, 6, 1929], [47, 486, 8, 19, 4, 796, 899, 2, 178, 1930, 1199, 658, 1931, 2320, 267, 2321, 71, 1930, 2, 1932, 2, 337, 486, 554, 955, 73, 388, 179, 659, 389, 2988], [6, 245, 152, 23, 379, 2989, 6, 140, 154, 57, 152], [1018, 1, 98, 107, 69, 487, 2, 956, 69, 1933, 218, 111, 471]]
word_to_index = tokenizer.word_index
print(word_to_index)
{'i': 1, 'to': 2, 'you': 3, 'a': 4, 'the': 5, 'u': 6, 'and': 7, 'in': 8, 'is': 9, 'me': 10, 'my': 11, 'for': 12, 'your': 13, 'it': 14, 'of': 15, 'call': 16, 'have': 17, 'on': 
...
vocab_size = len(word_to_index)+1
print('단어 집합의 크기: {}'.format((vocab_size)))
단어 집합의 크기: 8921
n_of_train = int(5572 * 0.8)
n_of_test = int(5572 - n_of_train)
print(n_of_train)
print(n_of_test)
4457
1115
X_data = sequences
print('메일의 최대 길이 : %d' % max(len(l) for l in X_data))
print('메일의 평균 길이 : %f' % (sum(map(len, X_data))/len(X_data)))
plt.hist([len(s) for s in X_data], bins=50)
plt.xlabel('length of Data')
plt.ylabel('number of Data')
plt.show()
메일의 최대 길이 : 189
메일의 평균 길이 : 15.794867

png

max_len = 189
# 전체 데이터셋의 길이는 189로 맞춥니다.
data = pad_sequences(X_data, maxlen=max_len)
print("data shape: ", data.shape)
data shape:  (5572, 189)
X_test = data[n_of_train:] #X_data 데이터 중에서 뒤의 1115개의 데이터만 저장
y_test = np.array(y_data[n_of_train:]) #y_data 데이터 중에서 뒤의 1115개의 데이터만 저장
X_train = data[:n_of_train] #X_data 데이터 중에서 앞의 4457개의 데이터만 저장
y_train = np.array(y_data[:n_of_train]) #y_data 데이터 중에서 앞의 4457개의 데이터만 저장

2. RNN으로 스팸 메일 분류하기

from tensorflow.keras.layers import SimpleRNN, Embedding, Dense
from tensorflow.keras.models import Sequential
model = Sequential()
model.add(Embedding(vocab_size, 32)) # 임베딩 벡터의 차원은 32
model.add(SimpleRNN(32)) # RNN 셀의 hidden_size는 32
model.add(Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
history = model.fit(X_train, y_train, epochs=4, batch_size=64, validation_split=0.2)
Train on 3565 samples, validate on 892 samples
Epoch 1/4
3565/3565 [==============================] - 4s 1ms/sample - loss: 0.3441 - acc: 0.8906 - val_loss: 0.2507 - val_acc: 0.9013
Epoch 2/4
3565/3565 [==============================] - 2s 607us/sample - loss: 0.0987 - acc: 0.9762 - val_loss: 0.0720 - val_acc: 0.9798
Epoch 3/4
3565/3565 [==============================] - 2s 626us/sample - loss: 0.0486 - acc: 0.9888 - val_loss: 0.0515 - val_acc: 0.9865
Epoch 4/4
3565/3565 [==============================] - 2s 618us/sample - loss: 0.0304 - acc: 0.9930 - val_loss: 0.0453 - val_acc: 0.9865
print("\n 테스트 정확도: %.4f" % (model.evaluate(X_test, y_test)[1]))
1115/1115 [==============================] - 1s 927us/sample - loss: 0.0531 - acc: 0.9839

 테스트 정확도: 0.9839
epochs = range(1, len(history.history['acc']) + 1)
plt.plot(epochs, history.history['loss'])
plt.plot(epochs, history.history['val_loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'val'], loc='upper left')
plt.show()

png

데이터의 양이 적어 과적합이 빠르게 시작 되므로 검증 데이터에 대한 오차가 시작하는 시점의 바로 직전인 3~4정도가 적당




© 2018. by statssy

Powered by statssy