일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- textmining
- 분석변수처리
- 공빅데
- 빅데이터
- ADsP3과목
- 데이터전처리
- 머신러닝
- decisiontree
- 텍스트마이닝
- k-means
- 2023공빅데
- 데이터분석
- 공공빅데이터청년인턴
- 공빅
- Keras
- ADSP
- Kaggle
- machinelearning
- DeepLearning
- 2023공공빅데이터청년인재양성
- datascience
- NLP
- SQL
- 공공빅데이터청년인재양성
- 2023공공빅데이터청년인재양성후기
- DL
- 오버샘플링
- ML
- data
- 클러스터링
- Today
- Total
愛林
[Python/NLP] 스팸 메일 분류하기 (Spam Detection) 본문
NLP 를 이용한 스팸 메일 분류
NLP 를 이용해서 스팸 메일을 분류해보자.
kaggle 에서 제공하는 스팸 메일 데이터를 이용했다.
https://www.kaggle.com/datasets/uciml/sms-spam-collection-dataset
SMS Spam Collection Dataset
Collection of SMS messages tagged as spam or legitimate
www.kaggle.com
여기서 csv파일을 다운받거나 urllib 를 사용해서 다운받으면 된다.
Enviroment
Python 3.9.12
Tensorflow 2.9.3
numpy 1.21.0
pandas 1.4.4
Tensorflow.keras 2.9.0
에서 문제없이 실행 가능했습니다.
Library Import & Data Import
필요한 라이브러리를 import 해주고 데이터를 불러온다.
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
tokenizer 는 keras 의 Tokenizer 를 사용해주었다.
EDA, Data Processing
data = pd.read_csv('spam.csv', encoding = 'latin1')
print("총 샘플의 수 : ", len(data))
총 샘플의 수 : 5572
5572개의 sample 이 존재한다.
불러온 데이터를 간단하게 확인해준다.
data[:5]
필요없는 Unnamed column 들이 보인다.
삭제해주도록 한다.
ham 은 스팸이 아닌 메일, spam 은 스팸 메일이다. ham 실화냐
del data['Unnamed: 2']
del data['Unnamed: 3']
del data['Unnamed: 4']
data['v1'] = data['v1'].replace(['ham','spam'],[0,1])
data[:5]
data.info()
간단하게 정보를 확인해주었다.
5572 개의 데이터 중 Non-Null 이 두 칼럼 모두 5572개이므로 Null 값은 없는 데이터라는 것을 확인했다.
데이터에 중복이 없는 지 확인해보았다.
내용 기준으로 중복을 확인해야하기 때문에 v2 칼럼을 기준으로
nunique() 함수를 사용해주었다.
print('v2열의 유니크한 값 :',data['v2'].nunique())
v2열의 유니크한 값 : 5169
데이터의 총 개수는 5572개인데
유니크 값은 5169개이다.
중복값에 대한 처리가 필요할 것 같다.
# 중복 제거
data.drop_duplicates(subset = ['v2'],inplace = True)
print(len(data))
5169
drop_duplicates 함수를 이용하여 v2 칼럼 기준으로 중복값을 drop 시켜주었다.
데이터가 5169개. 유니크한 값의 개수와 같아진 것을 확인할 수 있다.
스팸 데이터와 스팸 데이터가 아닌 데이터의 비율을 확인하기 위해 barplot 을 그려 확인해주었다.
data['v1'].value_counts().plot(kind = 'bar')
plt.show()
대부분이 정상 데이터(0) 이었다.
정확한 개수를 파악해주었다.
# 정확한 개수 파악
data.groupby('v1').size().reset_index(name = 'count')
%로 환산하여 비율을 확인했다.
# %로 환산
print(f'정상메일의 비율 = {round(data["v1"].value_counts()[0]/len(data) * 100,3)}%')
print(f'스팸 메일의 비율 = {round(data["v1"].value_counts()[1]/len(data) * 100,3)} %')
정상메일의 비율 = 87.367%
스팸 메일의 비율 = 12.633 %
X 데이터와 y 데이터를 분리해주었다.
X_data = data['v2']
y_data = data['v1']
# train test split
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)
train_test_split 을 이용하여 검증용 데이터와 학습용 데이터를 나누어주었다.
Keras Tokenizer 를 사용하여 훈련 데이터를 Tokenize 해주고 정수 인코딩까지 진행해주었다.
tokenizer = Tokenizer()
tokenizer.fit_on_texts(X_train)
X_train_encoded = tokenizer.texts_to_sequences(X_train)
print(X_train_encoded[:5])
[[102, 1, 210, 230, 3, 17, 39], [1, 59, 8, 427, 17, 5, 137, 2, 2326], [157, 180, 12, 13, 98, 93, 47, 9, 40, 3485, 247, 8, 7, 87, 6, 80, 1312, 5, 3486, 7, 2327, 11, 660, 306, 20, 25, 467, 708, 1028, 203, 129, 193, 800, 2328, 23, 1, 144, 71, 2, 111, 78, 43, 2, 130, 11, 800, 186, 122, 1512], [1, 1154, 13, 104, 292], [222, 622, 857, 540, 623, 22, 23, 83, 10, 47, 6, 257, 32, 6, 26, 64, 936, 407]]
정수 인코딩이 잘 되었다.
토큰화의 결과와 각 토큰이 어떻게 정수화되었는지 확인하기 위해 word_index 를 사용하여 확인했다.
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, 'have': 16, 'on': 17, 'call': 18,
'that': 19, 'are': 20, '2': 21, 'now': 22, 'so': 23, 'but': 24, 'not': 25, 'can': 26, 'or': 27,
"i'm": 28, 'get': 29, 'at': 30, 'do': 31, 'if': 32, 'be': 33, 'will': 34, 'just': 35,
'with': 36, 'we': 37, 'no': 38, 'this': 39, 'ur': 40, 'up': 41, '4': 42, 'how': 43,
'gt': 44, 'lt': 45, 'go': 46, 'when': 47, 'from': 48, 'what': 49, 'ok': 50, 'out': 51,
..
중략
너무 길어서 중략해주었다.
이 정수 인코딩은 빈도가 많을수록 숫자가 적다.
i 가 가장 빈도가 높고, to 가 그 다음, you 가 3번째로 빈도 수가 높은 것이다.
등장 빈도 수가 낮은 토큰은 중요하지 않을 가능성이 크므로 제거해주도록 하자.
threshold = 2
total_cnt = len(word_to_index) # 단어의 수
rare_cnt = 0 # 등장 빈도수가 threshold보다 작은 단어의 개수를 카운트
total_freq = 0 # 훈련 데이터의 전체 단어 빈도수 총 합
rare_freq = 0 # 등장 빈도수가 threshold보다 작은 단어의 등장 빈도수의 총 합
# 단어와 빈도수의 쌍(pair)을 key와 value로 받는다.
for key, value in tokenizer.word_counts.items():
total_freq = total_freq + value
# 단어의 등장 빈도수가 threshold보다 작으면
if(value < threshold):
rare_cnt = rare_cnt + 1
rare_freq = rare_freq + value
print('등장 빈도가 %s번 이하인 희귀 단어의 수: %s'%(threshold - 1, rare_cnt))
print("단어 집합(vocabulary)에서 희귀 단어의 비율:", (rare_cnt / total_cnt)*100)
print("전체 등장 빈도에서 희귀 단어 등장 빈도 비율:", (rare_freq / total_freq)*100)
등장빈도가 1번밖에 되지 않는 단어를 제거해주도록 하자.
단어와 빈도수의 쌍을 key 와 value로 받는다.
value . 그 단어의 빈도수가 threshold (우리가 설정한 등장 빈도. 2번보다 낮아야 하니 2이다.) 보다 작으면
그 단어는 rare_cnt 에 들어간다.
등장 빈도가 1번 이하인 희귀 단어의 수: 4337
단어 집합(vocabulary)에서 희귀 단어의 비율: 55.45326684567191
전체 등장 빈도에서 희귀 단어 등장 빈도 비율: 6.65745644331875
등장 빈도가 1번 이하인 희귀 단어의 수는 4337개였다.
단어 집합에서 꽤나 비율이 높았다.
하지만 실제로 전체 단어들이 등장하는 빈도에서 등장하는 비율은 6.66% 밖에 되지 않는다.
만약 이런 분석을 통해서 등장 빈도가 지나치게 낮은 단어들은 처리에서 제외하고 싶을 때는
keras Tokenizer 선언 시에 단어 집합의 크기를
- tokenizer = Tokenizer(num_words = total_cnt - rare_cnt + 1)
와 같은 코드로 제한할 수 있다.
위 코드는 등장 빈도가 1회인 단어를 제외하고 토큰화시키는 코드이다.
이번 실습에서는 따로 단어 집합의 크기를 제한하지는 않고, 단어 집합의 크기를 vocab_size 에 저장한다.
길이를 맞추어주는 패딩 작업을 위해서 토큰이 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, 평균적으로는 15정도의 길이를 가지고 있으며,
0 - 100 사이의 길이가 가장 흔하다.
max_len = 189
X_train_padded = pad_sequences(X_train_encoded, maxlen = max_len)
print("훈련 데이터의 크기(shape) : ", X_train_padded.shape)
훈련 데이터의 크기(shape) : (4135, 189)
padding 을 진행했다.
max_len 에는 189 를 넣어주었다.
이는 4135개의 X_train_encoded 의 길이를 전부 189로 맞추어주는 것이다.
189보다 길이가 짧은 메일 샘플은 전부 숫자 0이 패딩되어 189의 길이를 가진다.
Modeling
Tensorflow 의 RNN 을 사용하여 모델링 해주었다.
from tensorflow.keras.layers import SimpleRNN, Embedding, Dense
from tensorflow.keras.models import Sequential
다 대 일 구조의 RNN 을 사용한다.
해당 문제는 마지막 시점에서 두 개의 선택지 중 하나를 예측하여 선정하는 이진분류 문제이다.
그러므로 출력층에서 Rogistic Regrssion 을 사용해야 하므로 활성화 함수로는 Sigmoid 함수를 사용하고,
손실 함수로는 Cross-Entropy 함수를 사용한다.
4번의 Epoch 을 수행한다.
하이퍼파라미터인 Batch size 는 64이며, validation_split = 0.2 를 사용하여 훈련 데이터의 20%를 검증 데이터로 분리해서 사용하고, 검증 데이터를 사용하여 훈련이 적절하게 되고 있는 지를 확인한다.
검증 데이터는 기계가 훈련 데이터에 과적합 되고 있는 것이 아닌지를 확인하는 용도로 사용한다.
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)
Epoch 1/4
52/52 [==============================] - 6s 85ms/step - loss: 0.4452 - acc: 0.8455 - val_loss: 0.2675 - val_acc: 0.9021
Epoch 2/4
52/52 [==============================] - 4s 72ms/step - loss: 0.1581 - acc: 0.9640 - val_loss: 0.1539 - val_acc: 0.9480
Epoch 3/4
52/52 [==============================] - 4s 72ms/step - loss: 0.1460 - acc: 0.9525 - val_loss: 0.1085 - val_acc: 0.9734
Epoch 4/4
52/52 [==============================] - 4s 72ms/step - loss: 0.0967 - acc: 0.9692 - val_loss: 0.1548 - val_acc: 0.9468
test 데이터에도 위와 똑같은 처리를 해주고, 테스트 데이터를 모델에 넣어 정확도를 확인해준다.
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]))
33/33 [==============================] - 1s 15ms/step - loss: 0.1577 - acc: 0.9420
테스트 정확도 : 0.9420
약 94% 의 정확도를 보여주었다.
이번 실습에서는 훈련 데이터와 검증 데이터에 대해 같이 정확도를 확인하며 훈련했기 때문에,
이를 비교해서 loss 그래프를 그려서 과적합이 되지는 않는지 확인해주었다.
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()
이번 실습 데이터는 데이터의 양이 적어서 과적합이 빠르게 시작된다.
그러므로 검증 데이터에 대한 오차가 증가하기 시작하는 3 Epoch 정도가 적절하다.
Review
DL, NLP 에 대한 이해가 아직도 부족하다는 생각이 들었다.
DL 에 대한 공부도 많이 해야겠다는 생각이 든다.
Tensorflow , keras 를 이용한 NLP 가 편하지만 한국어를 할 수 없어 아쉬웠다.
다음 시간에는 한국어 자연어처리를 해보자.
참조
해당 책 필사, 참조
10-02 스팸 메일 분류하기(Spam Detection)
캐글에서 제공하는 스팸 메일 데이터를 학습시켜 스팸 메일 분류기를 구현해보겠습니다. ## 1. 스팸 메일 데이터에 대한 이해 * 다운로드 링크 : https://www.k…
wikidocs.net
'Data Science > Text Mining, 자연어처리' 카테고리의 다른 글
[Python/NLP] 센텐스피스 (Sentence Piece) (2) | 2023.02.01 |
---|---|
[Python/NLP] 서브워드 토크나이저 (Subward Tokenizer) :: BPE (3) | 2023.02.01 |
[Python/NLP] Windows 에서 Mecab 사용하기, Mecab 설치 (3) | 2023.01.06 |
[Text Mining] 텍스트 마이닝 - Gensim 을 이용한 토픽 모델링 (Topic Modeling) , LDA (3) | 2022.08.28 |
[Text Mining] 텍스트 마이닝 - Scikit learn 을 이용한 토픽 모델링(Topic Modeling), LDA (3) | 2022.08.28 |