스팸 메일 분류하기

Tae Yoon·2024년 10월 8일
0

데이터, 라이브러리 로드

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import urllib.request
from sklearn.model_selection import train_test_split
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences

urllib.request.urlretrieve("https://raw.githubusercontent.com/ukairia777/tensorflow-nlp-tutorial/main/10.%20RNN%20Text%20Classification/dataset/spam.csv", filename="spam.csv")
data = pd.read_csv('spam.csv', encoding='latin1')
print('총 샘플의 수 :',len(data))

데이터 설명
v1: 스팸인지 아닌지 나타내는 레이블
v2: 메일의 내용

데이터 전처리

v1, v2열만 필요하기 때문에 나머지 열 삭제, v1열에 있는 'ham', 'spam'을 0,1로 변경

del data['Unnamed: 2']
del data['Unnamed: 3']
del data['Unnamed: 4']
data['v1']=data['v1'].replace(['ham', 'spam'], [0,1])

데이터 정보 확인

data.info()

#   Column  Non-Null Count  Dtype 
---  ------  --------------  ----- 
 0   v1      5572 non-null   int64 
 1   v2      5572 non-null   object

v2열의 중복 제거한 값의 개수 확인

print('v2열의 유니크한 값 :',data['v2'].nunique())

v2열의 유니크한 값 : 5169

403개의 중복 샘플 확인, 중복 제거하고 전체 샘플 수 확인

data.drop_duplicates(subset=['v2'], inplace=True)
print('총 샘플의 수 :',len(data))

총 샘플의 수 : 5169

레이블의 분포 시각화

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

대부분이 정상 메일

각 레이블 별 개수 확인

print(data.groupby('v1').size().reset_index(name='count'))

    v1  count
0   0   4516
1   1    653

v2, v1열을 X, y 데이터로 저장

X_data = data['v2']
y_data = data['v1']

훈련 데이터와 테스트 데이터를 분리(레이블의 개수가 불균형 하기 때문에 stratify 매개변수를 y_data로 지정)

X_train, X_test, y_train, y_test = train_test_split(X_data, y_data, test_size=0.2, random_state=0, stratify=y_data)

훈련 데이터에 토큰화, 정수 인코딩 수행

tokenizer = Tokenizer()
tokenizer.fit_on_texts(X_train)
X_train_encoded = tokenizer.texts_to_sequences(X_train)

각 단어에 대한 등장 빈도수는 tokenizer.word_counts.items()를 이용해 확인 가능

등장 빈도수가 1회 밖에 않되는 단어들이 전체 단어 집합에서 얼마나 차지하는지 확인

threshold=2
total_cnt=len(word_to_index)
rare_cnt=0
total_freq=0
rare_freq=0

for key,value in tokenizer.word_counts.items():
  total_freq+=value
  if value<threshold:
    rare_cnt+=1
    rare_freq+=value

print('등장 빈도가 %s번 이하인 희귀 단어의 수: %s'%(threshold - 1, rare_cnt))
print("단어 집합(vocabulary)에서 희귀 단어의 비율:", (rare_cnt / total_cnt)*100)
print("전체 등장 빈도에서 희귀 단어 등장 빈도 비율:", (rare_freq / total_freq)*100)

등장 빈도가 1번 이하인 희귀 단어의 수: 4337
단어 집합(vocabulary)에서 희귀 단어의 비율: 55.45326684567191
전체 등장 빈도에서 희귀 단어 등장 빈도 비율: 6.65745644331875

희귀 단어들은 전체 집합에서 절반 이상 차지하지만, 실제 훈련 데이터에서 빈도는 6% 박에 되지 않는다

희귀 단어들을 제외하고 싶다면 -> tokenizer 선언 할 때 단어 집합의 크기 제한 가능

단어 집합의 크기를 저장(패딩의 위한 토큰인 0번 단어를 고려해 1을 더해서 저장)

vocab_size = len(word_to_index) + 1
print('단어 집합의 크기: {}'.format((vocab_size)))

단어 집합의 크기: 7822

가장 긴 메일과 전체 메일의 길이 분포 확인

print('메일의 최대 길이 : %d' % max(len(sample) for sample in X_train_encoded))
print('메일의 평균 길이 : %f' % (sum(map(len, X_train_encoded))/len(X_train_encoded)))
plt.hist([len(sample) for sample in X_data], bins=50)
plt.xlabel('length of samples')
plt.ylabel('number of samples')
plt.show()

메일의 최대 길이 : 189
메일의 평균 길이 : 15.754534

가장 긴 길이인 189를 활용해 패딩

max_len = 189
X_train_padded = pad_sequences(X_train_encoded, maxlen = max_len)
print("훈련 데이터의 크기(shape):", X_train_padded.shape)

훈련 데이터의 크기(shape): (4135, 189)

RNN 모델 설계

다 대 일 구조의 RNN
임베딩 차원:32
은닉 상태 크기: 32
스팸인지 아닌지 이진 분류 -> 출력층 활성화 함수: 시그모이드, 손실 함수: 크로스 엔트로피
배치 크기: 64
검증 데이터 크기: 0.2

from tensorflow.keras.layers import SimpleRNN, Embedding, Dense
from tensorflow.keras.models import Sequential

embedding_dim = 32
hidden_units = 32

model = Sequential()
model.add(Embedding(vocab_size, embedding_dim))
model.add(SimpleRNN(hidden_units))
model.add(Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
history = model.fit(X_train_padded, y_train, epochs=4, batch_size=64, validation_split=0.2)

테스트 데이터 정확도 확인

X_test_encoded = tokenizer.texts_to_sequences(X_test)
X_test_padded = pad_sequences(X_test_encoded, maxlen = max_len)
print("\n 테스트 정확도: %.4f" % (model.evaluate(X_test_padded, y_test)[1]))

테스트 정확도: 0.9797

에폭스에 따른 손실 확인

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('epochs')
plt.legend(['train', 'val'], loc='upper left')
plt.show()

CNN 모델 설계

from tensorflow.keras.layers import Dense, Conv1D, GlobalMaxPooling1D, Embedding, Dropout, MaxPooling1D
from tensorflow.keras.models import Sequential
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint

embedding_dim = 32
dropout_ratio = 0.3
num_filters = 32
kernel_size = 5

model=Sequential()
model.add(Embedding(vocab_size, embedding_dim))
model.add(Dropout(dropout_ratio))
model.add(Conv1D(num_filters, kernel_size, padding='valid', activation='relu'))
model.add(GlobalMaxPooling1D())
model.add(Dropout(dropout_ratio))
model.add(Dense(1, activation='sigmoid'))

es = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=3)
mc = ModelCheckpoint('best_model.keras', monitor = 'val_acc', mode='max', verbose=1, save_best_only=True)

model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['acc'])

history=model.fit(X_train_padded, y_train, epochs=10, batch_size=64, validation_split=0.2, callbacks=[es, mc])

테스트 데이터 정확도 확인

X_test_encoded = tokenizer.texts_to_sequences(X_test)
X_test_padded = pad_sequences(X_test_encoded, maxlen = max_len)
print("\n 테스트 정확도: %.4f" % (model.evaluate(X_test_padded, y_test)[1]))

테스트 정확도: 0.9807

0개의 댓글

관련 채용 정보