上次我們講到樸素貝葉斯分類,忘記的同學參考一文搞懂樸素貝葉斯分類,今天就通過樸素貝葉斯分來來實現(xiàn)一個簡單的垃圾短信分類器。
數(shù)據(jù)預(yù)處理
實現(xiàn)這個分類器我們使用的數(shù)據(jù)集來自倫敦大學學院的機器學習數(shù)據(jù)集(UCL machine learning),圖中所示為該數(shù)據(jù)集的部分數(shù)據(jù):
一般用 pandas 模塊來處理數(shù)據(jù), 在這里需要用到 pandas 的read_table()
方法,原因是文檔集兩列之間用了tab鍵來分割.如果對于read_table()
該選擇哪些參數(shù)不明確的話,需要先閱讀 pandas 的官方 API 文檔,這里就不詳細介紹了,很容易就搜索到。提一點,read_table()
默認分隔符就是以 tab 鍵來分割數(shù)據(jù)。
import pandas as pd
#讀取數(shù)據(jù)
df = pd.read_table("yourfilepath", names=['label', 'sms_message'])
df.head()
通過上述代碼可以讀取數(shù)據(jù)并把前 5 行數(shù)據(jù)打印出來:
接下來,進行數(shù)據(jù)預(yù)處理,將 ham 和 spam 分別用 1 和 0 表示,為什么這么做呢?原因是機器只能識別機器語言,不能識別自然語言,如果把 label 用 string 類型來表示的話,scikit-learn 最后會自動轉(zhuǎn)換成不識別的 float 型的值。
如果 label 按照 string 類型來表示,模型也照樣有能力預(yù)測,不過通常在計算性能指標的時候會出現(xiàn)問題,比如召回率,精度等。代碼如下:
# 做一個 map 表,0 表示‘ham',1 表示’spam‘
df['label'] = df.label.map({'ham':0, 'spam':1})
df.head()
詞袋方法
在處理文本信息中,選用的是詞袋方法,詞袋方法的思路就是每當輸入一個文檔片段,就會來計算這個片段中單詞出現(xiàn)的次數(shù)。就像將詞語放入一個袋子中,然后統(tǒng)計看看袋子中不同的詞語出現(xiàn)的次數(shù),而不會考慮單詞出現(xiàn)的順序,在文本處理中會把單詞出現(xiàn)次數(shù)當做訓練分類器的特征。
比如說:現(xiàn)在有 4 句話:
1.Hello, how are you!!
2.Country road, takes me home..
3.Shall we have a dinner tonight?
4.Hi, how is going your homework?
我們用詞袋模型將上面四句話轉(zhuǎn)成一個頻率分布的矩陣,一句話代表一行,列表示每個單詞。如下圖所示:
要實現(xiàn)這個 有兩種辦法,第一種自己實現(xiàn)一個詞袋模型,第二中是用 sklearn 的 CountVectorizer()
方法,該方法會將文檔集標記化(將每個文檔片段分割成單個單詞)并為每個標記提供一個整數(shù) ID,同時會記錄每個標記出現(xiàn)的次數(shù)。
自己造輪子
import string
import pprint
from collections import Counter
from sklearn.feature_extraction.text import CountVectorizer
documents = ['Hello, how are you!',
'Country road, takes me home.',
'Shall we have a dinner tonight?',
'Hi, how is going your homework?']
frequency_list = []
for i in documents:
# 轉(zhuǎn)換小寫,移除標點
remove_punc = i.lower().translate(str.maketrans('','', string.punctuation))
# 將句子分割成單個單詞
result = remove_punc.split(' ')
# 計算每句中每個單詞的頻率
word = Counter(result)
frequency_list.append(word)
pprint.pprint(frequency_list)
count_vector = CountVectorizer()
count_vector.fit()
上述代碼中有主要做了 3 件事:
- 將所有的單詞都轉(zhuǎn)換為小寫,為了便于統(tǒng)計
- 去除每一句中的標點符號,只需要關(guān)心單詞
- 計算單詞出現(xiàn)的次數(shù)
運行結(jié)果如圖:
用 sklearn 庫解決
先放 code:
from sklearn.feature_extraction.text import CountVectorizer
count_vector = CountVectorizer()
# 訓練文檔集
count_vector.fit(documents)
# 獲取文檔集中的不重復(fù)的單詞
count_vector.get_feature_names()
# 將文檔集向量化
doc_array = count_vector.transform(documents).toarray()
frequency_matrix = pd.DataFrame(doc_array, columns=count_vector.get_feature_names())
上面代碼提到的 CountVectorizer() 方法有幾個參數(shù)需要注意。
-
lowercase
會將所有單詞轉(zhuǎn)化為小寫,默認為True -
token_pattern
可以去掉所有標點符號,默認值為正則表達式:‘(?u)\b\w\w+\b’ -
stop_words
參數(shù)默認為‘None’,如果設(shè)置成‘english’,那么會按照sklearn定義的英語停用側(cè)列表來匹配我們數(shù)據(jù)集中的所有單詞,考慮到我們的數(shù)據(jù)集比較小,因此保留默認值即可。
fit()
方法用來訓練文檔集,get_feature_names()
用來將文檔集歸類,也就是將不重復(fù)的單詞都統(tǒng)計下來。transform()
方法會將文檔集轉(zhuǎn)成矩陣的形式,矩陣的行是文檔集的單詞,列表示單詞的次數(shù)。toarray()
將結(jié)果轉(zhuǎn)成數(shù)組的形式。最后把得到的結(jié)果用 pandas 的 DataFrame 的形式顯示出來。
這樣就成功用sklearn 實現(xiàn)了詞袋模型。最終得到剛開始的圖片效果:
[圖片上傳失敗...(image-247b2e-1531659038717)]
訓練模型并測試
訓練和測試模型直接使用 sklearn 提供的 api 即可。先分割訓練和測試集:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(df['sms_message'],
df['label'],
random_state=1)
print('Number of rows in the total set: {}'.format(df.shape[0]))
print('Number of rows in the training set: {}'.format(X_train.shape[0]))
print('Number of rows in the test set: {}'.format(X_test.shape[0]))
使用 sklearn 實現(xiàn)樸素貝葉斯定理
分割完訓練集和數(shù)據(jù)集以后,我們利用 sklearn 中的貝葉斯分類器來訓練模型并進行預(yù)測。
from sklearn.naive_bayes import MultinomialNB
# 初始化實例
count_vector = CountVectorizer()
# 訓練數(shù)據(jù)轉(zhuǎn)成矩陣
training_data = count_vector.fit_transform(X_train)
# 轉(zhuǎn)化測試集為矩陣
testing_data = count_vector.transform(X_test)
naive_bayes = MultinomialNB()
naive_bayes.fit(training_data, y_train)
predictions = naive_bayes.predict(testing_data)
需要注意的是,上述代碼使用的MultinomialNB()
只是適應(yīng)于離散數(shù)據(jù)(就像我們例子中文本分類是根據(jù)單詞計數(shù)實現(xiàn)的)。如果是連續(xù)數(shù)據(jù),則需要用其他方法,例如高斯樸素貝葉斯方法,不過數(shù)據(jù)也需要假設(shè)符合高斯分布才可以。
評估模型
評估模型主要用到四種指標:準確率(Accuracy)、精度(Precision)、召回率(Recall)和 F1 分數(shù)。后面有專門的文章來詳細介紹評估指標。
上文已經(jīng)預(yù)測數(shù)據(jù),現(xiàn)在根據(jù)評估指標來看一下模型是否滿足預(yù)測需求。將上面四種指標分別打印出來:
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
print('Accuracy score: ', format(accuracy_score(y_test, predictions)))
print('Precision score: ', format(precision_score(y_test, predictions)))
print('Recall score: ', format(recall_score(y_test, predictions)))
print('F1 score: ', format(f1_score(y_test, predictions)))
結(jié)果如下:
打印結(jié)果
最后 可以將模型預(yù)測的測試集的結(jié)果打印出來。
category_map = {0:'ham', 1:'spam'}
for message, category, real in zip(X_test[50:100], predictions[50:100], y_test[50:100]):
print('\n recevie message:', message, '\n prediction:', category_map[category], 'true value:', category_map[real])
限于篇幅原因只顯示部分結(jié)果:
至此,垃圾短信分類已經(jīng)完成。可能有人會疑問,如果新來數(shù)據(jù)該如何處理呢?上文中的
predictions = naive_bayes.predict(testing_data)
這行代碼是用來預(yù)測數(shù)據(jù)結(jié)果,當新來一個文檔集(本例中是短信)時,只需要將文檔集轉(zhuǎn)成詞袋模型,例如前面在自己造輪子環(huán)節(jié)中的例句,我們將變量doc_array
代入,即可得到結(jié)果為[0,0,0,0]
, 表示這 4 個文檔集均為非垃圾郵件。
PS: 在本文中使用的是 python3, 以及截圖在 jupyter notebook 下面運行的,這樣便于數(shù)據(jù)分析。完整的代碼已提交 github, 點擊獲取源碼。
原創(chuàng)文章,歡迎轉(zhuǎn)載,轉(zhuǎn)載請聲明作者和出處,謝謝!
歡迎關(guān)注機器學習club ,在這里我將持續(xù)輸出機器學習原創(chuàng)文章,盡量用樸實的語言帶你領(lǐng)略技術(shù)的美妙。