垃圾短信檢測(端到端的項目)
我們都聽說過一個流行詞——“數(shù)據(jù)科學(xué)”。我們大多數(shù)人都對“它是什么?我可以成為數(shù)據(jù)分析師或數(shù)據(jù)科學(xué)家嗎?我需要什么技能?并不是很了解。例如:我想開始一個數(shù)據(jù)科學(xué)項目,但我卻不知道如何著手進(jìn)行。
我們大多數(shù)人都是通過一些在線課程了解了這個領(lǐng)域。我們對課程中布置的作業(yè)和項目感到游刃有余。但是,當(dāng)開始分析全新或未知的數(shù)據(jù)集時,我們會迷失方向。為了在分析我們遇到的任何數(shù)據(jù)集和問題時,我們需要通過不斷的練習(xí)。我覺得最好的方式之一就是在項目中進(jìn)行學(xué)習(xí)。所以每個人都需要開始自己的第一個項目。因此,我準(zhǔn)備寫一個專欄,帶大家一起完成數(shù)據(jù)科學(xué)項目,感興趣的朋友可以一起交流學(xué)習(xí)。本專欄是一個以實戰(zhàn)為主的專欄。
0.引言
隨著產(chǎn)品和服務(wù)在線消費(fèi)的增加,消費(fèi)者面臨著收件箱中大量垃圾郵件的巨大問題,這些垃圾郵件要么是基于促銷的,要么是欺詐性的。由于這個原因,一些非常重要的消息/電子郵件被當(dāng)做垃圾短信處理了。在本文中,我們將創(chuàng)建一個 垃圾短信/郵件檢測模型,該模型將使用樸素貝葉斯和自然語言處理(NLP) 來確定是否為垃圾短信/郵件。
在這里我使用的是colab內(nèi)置環(huán)境,完整代碼文末獲取。
額外的所需包如下,大家自行安裝
nltk
streamlit
pickle
1.數(shù)據(jù)收集和加載
我們將使用kaggle提供的數(shù)據(jù)集:數(shù)據(jù)集
該數(shù)據(jù)集 包含一組帶有標(biāo)記的短信文本,這些消息被歸類為正常短信和垃圾短信。 每行包含一條消息。每行由兩列組成:v1 帶有標(biāo)簽,(spam 或 ham),v2 是文本內(nèi)容。
df=pd.read_csv('/content/spam/spam.csv',encoding='latin-1')#這里encoding需要指定為latin-1
# 查看一下數(shù)據(jù)基本情況
df.head()
v1 | v2 | Unnamed: 2 | Unnamed: 3 | Unnamed: 4 | |
---|---|---|---|---|---|
0 | ham | Go until jurong point, crazy.. Available only ... | NaN | NaN | NaN |
1 | ham | Ok lar... Joking wif u oni... | NaN | NaN | NaN |
2 | spam | Free entry in 2 a wkly comp to win FA Cup fina... | NaN | NaN | NaN |
3 | ham | U dun say so early hor... U c already then say... | NaN | NaN | NaN |
4 | ham | Nah I don't think he goes to usf, he lives aro... | NaN | NaN | NaN |
該數(shù)據(jù)包含一組帶有標(biāo)記的短信數(shù)據(jù),其中:
- v1表示短信標(biāo)簽,ham表示正常信息,spam表示垃圾信息
- v2是短信的內(nèi)容
#去除不需要的列
df=df.iloc[:,:2]
#重命名列
df=df.rename(columns={"v1":"label","v2":"message"})
df.head()
label | message | |
---|---|---|
0 | ham | Go until jurong point, crazy.. Available only ... |
1 | ham | Ok lar... Joking wif u oni... |
2 | spam | Free entry in 2 a wkly comp to win FA Cup fina... |
3 | ham | U dun say so early hor... U c already then say... |
4 | ham | Nah I don't think he goes to usf, he lives aro... |
# 將lable進(jìn)行one-hot編碼,其中0:ham,1:spam
from sklearn.preprocessing import LabelEncoder
encoder = LabelEncoder()
df['label']=encoder.fit_transform(df['label'])
df['label'].value_counts()
0 4825
1 747
Name: label, dtype: int64
可以看出一共有747個垃圾短信
# 查看缺失值
df.isnull().sum()
# 數(shù)據(jù)沒有缺失值
label 0
message 0
dtype: int64
2.探索性數(shù)據(jù)分析(EDA)
通過可視化分析來更好的理解數(shù)據(jù)
import matplotlib.pyplot as plt
plt.style.use('ggplot')
plt.figure(figsize=(9,4))
plt.subplot(1,2,1)
plt.pie(df['label'].value_counts(),labels=['not spam','spam'],autopct="%0.2f")
plt.subplot(1,2,2)
sns.barplot(x=df['label'].value_counts().index,y=df['label'].value_counts(),data=df)
plt.show()
?
在特征工程部分,我簡單創(chuàng)建了一些單獨(dú)的特征來提取信息
- 字符數(shù)
- 單詞數(shù)
- 句子數(shù)
#1.字符數(shù)
df['char']=df['message'].apply(len)
nltk.download('punkt')
[nltk_data] Downloading package punkt to /root/nltk_data...
[nltk_data] Unzipping tokenizers/punkt.zip.
True
#2.單詞數(shù),這里我們首先要對其進(jìn)行分詞處理,使用nltk
#分詞處理
df['words']=df['message'].apply(lambda x: len(nltk.word_tokenize(x)))
# 3.句子數(shù)
df['sen']=df['message'].apply(lambda x: len(nltk.sent_tokenize(x)))
df.head()
label | message | char | words | sen | |
---|---|---|---|---|---|
0 | 0 | Go until jurong point, crazy.. Available only ... | 111 | 24 | 2 |
1 | 0 | Ok lar... Joking wif u oni... | 29 | 8 | 2 |
2 | 1 | Free entry in 2 a wkly comp to win FA Cup fina... | 155 | 37 | 2 |
3 | 0 | U dun say so early hor... U c already then say... | 49 | 13 | 1 |
4 | 0 | Nah I don't think he goes to usf, he lives aro... | 61 | 15 | 1 |
描述性統(tǒng)計
# 描述性統(tǒng)計
df.describe()
index | label | char | words | sen |
---|---|---|---|---|
count | 5572.0 | 5572.0 | 5572.0 | 5572.0 |
mean | 0.13406317300789664 | 80.11880832735105 | 18.69562096195262 | 1.9707465900933239 |
std | 0.34075075489776974 | 59.6908407765033 | 13.742586801744975 | 1.4177777134026657 |
min | 0.0 | 2.0 | 1.0 | 1.0 |
25% | 0.0 | 36.0 | 9.0 | 1.0 |
50% | 0.0 | 61.0 | 15.0 | 1.0 |
75% | 0.0 | 121.0 | 27.0 | 2.0 |
max | 1.0 | 910.0 | 220.0 | 28.0 |
下面我們通過可視化比較一下不同短信在這些數(shù)字特征上的分布情況
# 字符數(shù)比較
plt.figure(figsize=(12,6))
sns.histplot(df[df['label']==0]['char'],color='red')#正常短信
sns.histplot(df[df['label']==1]['char'],color = 'blue')#垃圾短信
<matplotlib.axes._subplots.AxesSubplot at 0x7fce63763dd0>
# 比較
plt.figure(figsize=(12,6))
sns.histplot(df[df['label']==0]['words'],color='red')#正常短信
sns.histplot(df[df['label']==1]['words'],color = 'blue')#垃圾短信
<matplotlib.axes._subplots.AxesSubplot at 0x7fce63f4bed0>
?
?
sns.pairplot(df,hue='label')
#刪除數(shù)據(jù)集中存在的一些異常值
i=df[df['char']>500].index
df.drop(i,axis=0,inplace=True)
df=df.reset_index()
df.drop("index",inplace=True,axis=1)
#相關(guān)系數(shù)矩陣
sns.heatmap(df.corr(),annot=True)
<matplotlib.axes._subplots.AxesSubplot at 0x7fce606d0250>
?
我們這里看到存在多重共線性,因此,我們不使用所有的列,在這里選擇與label相關(guān)性最強(qiáng)的char
3.數(shù)據(jù)預(yù)處理
對于英文文本數(shù)據(jù),我們常用的數(shù)據(jù)預(yù)處理方式如下
- 去除標(biāo)點(diǎn)符號
- 去除停用詞
- 去除專有名詞
- 變換成小寫
- 分詞處理
- 詞根、詞綴處理
下面我們來看看如何實現(xiàn)這些步驟
nltk.download('stopwords')
[nltk_data] Downloading package stopwords to /root/nltk_data...
[nltk_data] Unzipping corpora/stopwords.zip.
True
# 首先導(dǎo)入需要使用到的包
from nltk.corpus import stopwords
from nltk.stem import PorterStemmer
from wordcloud import WordCloud
import string,time
# 標(biāo)點(diǎn)符號
string.punctuation
'!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
# 停用詞
stopwords.words('english')
3.1清洗文本數(shù)據(jù)
- 去除web鏈接
- 去除郵件
- 取掉數(shù)字
下面使用正則表達(dá)式來處理這些數(shù)據(jù)。
def remove_website_links(text):
no_website_links = text.replace(r"http\S+", "")#去除網(wǎng)絡(luò)連接
return no_website_links
def remove_numbers(text):
removed_numbers = text.replace(r'\d+','')#去除數(shù)字
return removed_numbers
def remove_emails(text):
no_emails = text.replace(r"\S*@\S*\s?",'')#去除郵件
return no_emails
df['message'] = df['message'].apply(remove_website_links)
df['message'] = df['message'].apply(remove_numbers)
df['message'] = df['message'].apply(remove_emails)
df.head()
label | message | char | words | sen | |
---|---|---|---|---|---|
0 | 0 | Go until jurong point, crazy.. Available only ... | 111 | 24 | 2 |
1 | 0 | Ok lar... Joking wif u oni... | 29 | 8 | 2 |
2 | 1 | Free entry in 2 a wkly comp to win FA Cup fina... | 155 | 37 | 2 |
3 | 0 | U dun say so early hor... U c already then say... | 49 | 13 | 1 |
4 | 0 | Nah I don't think he goes to usf, he lives aro... | 61 | 15 | 1 |
3.2 文本特征轉(zhuǎn)換
def message_transform(text):
text = text.lower()#轉(zhuǎn)換為小寫
text = nltk.word_tokenize(text)#分詞處理
# 去除停用詞和標(biāo)點(diǎn)
y = []#創(chuàng)建一個空列表
for word in text:
stopwords_punc = stopwords.words('english')+list(string.punctuation)#存放停用詞和標(biāo)點(diǎn)
if word.isalnum()==True and word not in stopwords_punc:
y.append(word)
# 詞根變換
message=y[:]
y.clear()
for i in message:
ps=PorterStemmer()
y.append(ps.stem(i))
return " ".join(y)#返回字符串形式
df['message'] = df['message'].apply(message_transform)
df['num_words_transform']=df['message'].apply(lambda x: len(str(x).split()))
df.head()
label | message | char | words | sen | |
---|---|---|---|---|---|
0 | 0 | Go until jurong point, crazy.. Available only ... | 111 | 24 | 2 |
1 | 0 | Ok lar... Joking wif u oni... | 29 | 8 | 2 |
2 | 1 | Free entry in 2 a wkly comp to win FA Cup fina... | 155 | 37 | 2 |
3 | 0 | U dun say so early hor... U c already then say... | 49 | 13 | 1 |
4 | 0 | Nah I don't think he goes to usf, he lives aro... | 61 | 15 | 1 |
4.詞頻統(tǒng)計
4.1繪制詞云
#繪制信息中出現(xiàn)最多的詞的詞云
from wordcloud import WordCloud
#首先,創(chuàng)建一個object
wc=WordCloud(width=500,height=500,min_font_size=10,background_color='white')
# 垃圾信息的詞云
spam_wc=wc.generate(df[df['label']==1]['message'].str.cat(sep=""))
plt.figure(figsize=(18,12))
plt.imshow(spam_wc)
<matplotlib.image.AxesImage at 0x7fce5d938710>
可以看出,這些垃圾郵件出現(xiàn)頻次最多的單詞是:free、call等這種具有誘導(dǎo)性的信息
# 正常信息的詞云
ham_wc = wc.generate(df[df['label']==0]['message'].str.cat(sep=''))
plt.figure(figsize=(18,12))
plt.imshow(ham_wc)
<matplotlib.image.AxesImage at 0x7fce607af190>
可以看出正常信息出現(xiàn)頻次較多的單詞為u、go、got、want等一些傳達(dá)信息的單詞
為了簡化詞云圖的信息,我們現(xiàn)在分別統(tǒng)計垃圾短信和正常短信頻次top30的單詞
4.2找出詞數(shù)top30的單詞
垃圾短信:
# 統(tǒng)計詞頻
spam_corpus=[]
for i in df[df['label']==1]['message'].tolist():
for word in i.split():
spam_corpus.append(word)
from collections import Counter
Counter(spam_corpus)#記數(shù)
Counter(spam_corpus).most_common(30)#取最多的30個單詞
plt.figure(figsize=(10,7))
sns.barplot(y=pd.DataFrame(Counter(spam_corpus).most_common(30))[0],x=pd.DataFrame(Counter(spam_corpus).most_common(30))[1])
plt.xticks()
plt.xlabel("Frequnecy")
plt.ylabel("Spam Words")
plt.show()
正常短信
ham_corpus=[]
for i in df[df['label']==0]['message'].tolist():
for word in i.split():
ham_corpus.append(word)
from collections import Counter
plt.figure(figsize=(10,7))
sns.barplot(y=pd.DataFrame(Counter(ham_corpus).most_common(30))[0],x=pd.DataFrame(Counter(ham_corpus).most_common(30))[1])
plt.xticks()
plt.xlabel("Frequnecy")
plt.ylabel("Ham Words")
plt.show()
下面進(jìn)一步分析垃圾短信和非垃圾短信的單詞和字符數(shù)分布情況
# 字符數(shù)
fig,(ax1,ax2)=plt.subplots(1,2,figsize=(15,6))
text_len=df[df['label']==1]['text'].str.len()
ax1.hist(text_len,color='green')
ax1.set_title('Original text')
text_len=df[df['label']==0]['text'].str.len()
ax2.hist(text_len,color='red')
ax2.set_title('Fake text')
fig.suptitle('Characters in texts')
plt.show()
#單詞數(shù)
fig,(ax1,ax2)=plt.subplots(1,2,figsize=(15,6))
text_len=df[df['label']==1]['num_words_transform']
ax1.hist(text_len,color='red')
ax1.set_title('Original text')
text_len=df[df['label']==0]['num_words_transform']
ax2.hist(text_len,color='green')
ax2.set_title('Fake text')
fig.suptitle('Words in texts')
plt.show()
總結(jié)
經(jīng)過上面分析,我們可以得出結(jié)論,垃圾短信文本與非垃圾短信文本相比具有更多的單詞和字符。
- 垃圾短信中包含的平均字符數(shù)約為 90 個字符
- 垃圾短信中包含的平均字?jǐn)?shù)約為 15 個字
5.模型構(gòu)建
根據(jù)歷史經(jīng)驗,在文本數(shù)據(jù)上樸素貝葉斯算法效果很好,因此我們將使用它,但在此過程中還將它與不同的算法進(jìn)行比較。
在統(tǒng)計學(xué)中,樸素貝葉斯分類器是一系列簡單的“概率分類器”,它們基于應(yīng)用貝葉斯定理和特征之間的(樸素)條件獨(dú)立假設(shè)。它們是最簡單的貝葉斯網(wǎng)絡(luò)模型之一,但與核密度估計相結(jié)合,它們可以達(dá)到更高的準(zhǔn)確度水平。
首先,我們這里的輸入數(shù)據(jù)是文本數(shù)據(jù),不能夠直接建立模型。因此,我們必須將這些文本數(shù)據(jù)進(jìn)行特征提取。比較常用的幾種方法:
- 詞袋模型(Bag of words) 存在稀疏性問題
- TF-IDF
- Word2vec
因為是實戰(zhàn)訓(xùn)練,在這里不具體展開的幾種方法的原理,在這里我選擇TF-IDF。
我也試了一下Word embedding,結(jié)合一些深度學(xué)習(xí)的方法,精度能夠有所提高,感興趣的小伙伴可以自己嘗試一下,基本步驟類似。下面我們首先建立貝葉斯模型。
5.1 構(gòu)建貝葉斯模型
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf = TfidfVectorizer(max_features=3000)
X = tfidf.fit_transform(df['message']).toarray()
y = df['label'].values
array([0, 0, 1, ..., 0, 0, 0])
from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.2,random_state=2)#訓(xùn)練集測試集劃分
from sklearn.naive_bayes import GaussianNB,MultinomialNB,BernoulliNB
from sklearn.metrics import accuracy_score,confusion_matrix,precision_score
這里我們比較三種不同的貝葉斯模型的各個評估指標(biāo)結(jié)果
#GaussianNB
gnb = GaussianNB()
gnb.fit(X_train,y_train)
y_pred1 = gnb.predict(X_test)
print("Accuracy Score -",accuracy_score(y_test,y_pred1))
print("Precision Score -",precision_score(y_test,y_pred1))
print(confusion_matrix(y_test,y_pred1))
Accuracy Score - 0.8456014362657092
Precision Score - 0.47038327526132406
[[807 152]
[ 20 135]]
#MultinomialNB
mnb = MultinomialNB()
mnb.fit(X_train,y_train)
y_pred2 = mnb.predict(X_test)
print("Accuracy Score -",accuracy_score(y_test,y_pred2))
print("Precision Score -",precision_score(y_test,y_pred2))
print(confusion_matrix(y_test,y_pred2))
Accuracy Score - 0.9793536804308797
Precision Score - 0.9925373134328358
[[958 1]
[ 22 133]]
#Bernuli
bnb = BernoulliNB()
bnb.fit(X_train,y_train)
y_pred3 = bnb.predict(X_test)
print("Accuracy Score -",accuracy_score(y_test,y_pred3))
print("Precision Score -",precision_score(y_test,y_pred3))
print(confusion_matrix(y_test,y_pred3))
Accuracy Score - 0.9829443447037702
Precision Score - 1.0
[[959 0]
[ 19 136]]
從上述結(jié)果來看,我選擇了BNB來建模
5.2 模型比較
下面我們繼續(xù)比較其他幾種常見的分類模型的效果
#導(dǎo)入基本庫
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.naive_bayes import MultinomialNB
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import AdaBoostClassifier
from sklearn.ensemble import BaggingClassifier
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.ensemble import GradientBoostingClassifier
from xgboost import XGBClassifier
from sklearn.metrics import precision_score, recall_score, plot_confusion_matrix, classification_report, accuracy_score, f1_score
from sklearn.model_selection import cross_val_score
# 構(gòu)建多個分類器
classifiers={"svc":SVC(kernel='sigmoid', gamma=1.0),
"knc": KNeighborsClassifier(),
"bnb" : BernoulliNB(),
"dtc" : DecisionTreeClassifier(max_depth=5),
"lr" : LogisticRegression(solver='liblinear', penalty='l1'),
"rfc" : RandomForestClassifier(n_estimators=50, random_state=2),
"adb" : AdaBoostClassifier(n_estimators=50, random_state=2),
"xgb" : XGBClassifier(n_estimators=50,random_state=2),
"gbc" : GradientBoostingClassifier(n_estimators=50,random_state=2)
}
# 訓(xùn)練分類器函數(shù)
def train_classifier(clf,X_train,y_train,X_test,y_test):
clf.fit(X_train,y_train)
y_pred = clf.predict(X_test)
accuracy = accuracy_score(y_test,y_pred)
precision = precision_score(y_test,y_pred)
train_accuracy = clf.score(X_train,y_train)
return accuracy,precision,train_accuracy
# 得到各個模型的評估結(jié)果
accuracy_scores = []
precision_scores = []
train_accuracy_score=[]
for name,clf in classifiers.items():
current_accuracy,current_precision,current_train_score = train_classifier(clf, X_train,y_train,X_test,y_test)
print("For ",name)
print("Accuracy - ",current_accuracy)
print("Precision - ",current_precision)
accuracy_scores.append(current_accuracy)
precision_scores.append(current_precision)
train_accuracy_score.append(current_train_score)
print()
For svc
Accuracy - 0.9802513464991023
Precision - 0.9784172661870504
For knc
Accuracy - 0.9093357271095153
Precision - 1.0
For bnb
Accuracy - 0.9829443447037702
Precision - 1.0
For dtc
Accuracy - 0.9299820466786356
Precision - 0.8811881188118812
For lr
Accuracy - 0.9622980251346499
Precision - 0.959349593495935
For rfc
Accuracy - 0.9721723518850988
Precision - 0.9920634920634921
For adb
Accuracy - 0.966786355475763
Precision - 0.9338235294117647
For xgb
Accuracy - 0.9443447037701975
Precision - 0.9514563106796117
For gbc
Accuracy - 0.9542190305206463
Precision - 0.9642857142857143
?
為了方便對比,將上述結(jié)果存放到dataframe中
df1=pd.DataFrame({'Algorithm':classifiers.keys(),'Precision':precision_scores,
'Test Accuracy':accuracy_scores}).round(3)
df2=df1.sort_values(['Precision','Test Accuracy'],ascending=False)#排序
df2
Algorithm | Precision | Test Accuracy | |
---|---|---|---|
2 | bnb | 1.000 | 0.983 |
1 | knc | 1.000 | 0.909 |
5 | rfc | 0.992 | 0.972 |
0 | svc | 0.978 | 0.980 |
8 | gbc | 0.964 | 0.954 |
4 | lr | 0.959 | 0.962 |
7 | xgb | 0.951 | 0.944 |
6 | adb | 0.934 | 0.967 |
3 | dtc | 0.881 | 0.930 |
通過對比,選擇BNB模型,模型的precision為100%,accuracy為98.3%
6.模型部署
import pickle
pickle.dump(tfidf,open('vectorizer.pkl','wb'))
pickle.dump(mnb,open('model.pkl','wb'))
然后打開 IDE 并創(chuàng)建自己的虛擬環(huán)境。使用 pip 或 conda 安裝所需的所有包。我們將使用 streamlit 構(gòu)建我們的網(wǎng)站。
設(shè)置完成后,創(chuàng)建app.py
文件
import streamlit as st
import pickle
import string
from nltk.corpus import stopwords
import nltk
from nltk.stem.porter import PorterStemmer
ps = PorterStemmer()
def transform_text(text):
text = text.lower()
text = nltk.word_tokenize(text)
y = []
for i in text:
if i.isalnum():
y.append(i)
text = y[:]
y.clear()
for i in text:
if i not in stopwords.words('english') and i not in string.punctuation:
y.append(i)
text = y[:]
y.clear()
for i in text:
y.append(ps.stem(i))
return " ".join(y)
tfidf = pickle.load(open('vectorizer.pkl','rb'))
model = pickle.load(open('model.pkl','rb'))
st.title("垃圾短信/郵件分類器")
input_sms = st.text_area("輸入你要檢測的內(nèi)容")
if st.button('Predict'):
# 1. preprocess
transformed_sms = transform_text(input_sms)
# 2. vectorize
vector_input = tfidf.transform([transformed_sms])
# 3. predict
result = model.predict(vector_input)[0]
# 4. Display
if result == 1:
st.header("垃圾短信!")
else:
st.header("正常短信~")
然后在本地運(yùn)行
streamlit run app.py
然后就能到達(dá)下面這個界面了,將短信或者郵件輸入點(diǎn)擊預(yù)測就可以了
篇幅有限,完整代碼可以在我的github上面查看,歡迎大家star,fork。
github地址:完整代碼文章來源:http://www.zghlxwxcb.cn/news/detail-786848.html
如果訪問不了github的可以私信我獲取源碼。文章來源地址http://www.zghlxwxcb.cn/news/detail-786848.html
到了這里,關(guān)于【數(shù)據(jù)科學(xué)項目02】:NLP應(yīng)用之垃圾短信/郵件檢測(端到端的項目)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!