數據挖掘入門系列教程(七)之朴素貝葉斯進行文本分類
貝葉斯分類算法是一類分類算法的總和,均以貝葉斯定理為基礎,故稱之為貝葉斯分類。而朴素貝葉斯分類算法就是其中最簡單的分類算法。
朴素貝葉斯分類算法
朴素貝葉斯分類算法很簡單很簡單,就一個公式如下所示:
上面的公式就是朴素貝葉斯分類算法的核心。現在不理解沒關系,只要能夠知道並能夠推導出這個公式是正確的就🆗了。(至於怎么推導,emm,概率論里面最基礎的公式\(P(B|A) = \frac{P(A,B)}{P(A)}\))。
首先我們來理解\(P(B|A)\)這個公式,在概率論上這個公式表示“在A發生的情況下,B發生的概率”。A那么在數據挖掘中,\(A和B\)又代表着什么呢?我們可以這樣理解,對於分類,我們肯定已知被預測物體特征,然后想通過這些特征來判斷該物體屬於哪一個類別。因此:
對於數據挖掘來說,\(A\)通常是觀察樣本個體(也就是特征),\(B\)為被測個體所屬的類別。
那么這個公式變成了:
至此,我們就可以拿這個公式進行分類了。但是這里還是有個問題,一個物體的特征不可能只有一個,肯定是有多個,那么下面這個公式怎么計算。
首先,我們假設\(特征1,特征2,特征3……\)相互獨立,那么有以下結論:
so,有以下公式:
因此朴素貝葉斯公式如下:
這里,又有一個問題,如果我的模型已經訓練好了,然后我們使用測試數據去測試的時候,如果某個\(P(特征_{ij}) = 0\)怎么辦(\(特征_{ij}表示特征i的特征值是j\)) 此時\(h=0\),這樣肯定不行。那么有什么解決方法沒?拉普拉斯平滑解決了這個問題。
引入拉普拉斯平滑之后公式如下:
很顯然,拉普拉斯平滑避免了因為訓練集中樣本不充分而導致概率估計值為0的問題。至於具體的朴素貝葉斯的應用舉例子,可以參考這位博主的兩篇博客:帶你理解朴素貝葉斯分類算法和理解朴素貝葉斯分類的拉普拉斯平滑,寫的比我好多了,emm。
🆗,目前我們已經能夠計算出不同特征的特征值在不同類別下的\(P\)值(或者說比較標准\(h\)),然后我們通過\(P\)值的大小,以判斷某一條數據屬於那一個類別。
朴素貝葉斯的介紹就到這里,下面來介紹文本的數據挖掘流程。
加載數據集
首先先說這篇博客挖掘文本數據目的(使用《Python數據挖掘入門與實踐》上面的例子):在twitter的話題中,“Python”可以代表一種編程語言,也可以代表蟒蛇,也可以代表一個鞋子牌子。而我們的目的就是判斷一個文本中“Python”的具體含義(至於為什么不用微博的話題,emm,沒有現成的數據集,懶得自己再去弄了)。
文本的數據與前面的Iris數據集的不同之處在於他的數據特征不是那么的明顯,因為文本數據不是固定格式的,不同的文章不可能按照相同的格式去寫,而我們第一步就是需要將文本數據轉換成數據挖掘算法能夠識別的數據格式。
數據集來自這位博主的博客,在我寫這篇博客的時候,我twitter開發者還未申請成功,並且我也不想做手工標注類別的狠人。
數據集:Github
數據集中一共有95條推特,同時對每一條推特進行標記,1代表這條推特中“python”表示編程語言,0代表非編程語言(至於數據集為什么這么少,估計上面的那一位博主也不想做一個狼人去手工標注那么多數據集)。
加載數據集如下:
在數據集中,數據是以json格式保存的,在json中“text”表示推文內容:
import json
import os
# 推文數據集
data_file = "./Data/python_tweets.json"
# 類別數據集
class_file = "./Data/python_classes.json"
data_list = []
class_list = []
# 加載推文
with open(data_file) as f:
for line in f:
# 如果內容為空
if(len(line.strip()) == 0):
continue
data_list.append(json.loads(line)["text"])
# 加載類別
with open(class_file) as f:
class_list = json.loads(f.read())
import numpy as np
class_list = np.array(class_list)
data_list = np.array(data_list)
數據集我們已經加載完畢了,然后我們就要從文本中提取特征。
文本轉化
文本數據轉成算法可識別的特征有很多種方式,比如說文本的語言,長短,以及來源都是一種特征,根據不同的需求我們需要不同的特征。在這篇博客中,我們的目的是為了判斷“Python”的分類,因此我們需要獲得文本的內容特征。
我們可以使用一種很簡單但是卻不錯的方法,那就是統計數據集中每一條推文中每種單詞出現的情況。《Python數據挖掘入門與實踐》稱之為布袋模型。有多種統計方法:
-
統計每一種單詞出現的次數。比如說“go go go, come on”中"go"出現了三次
-
統計每一種單詞出現的詞率。比如說“go go go, come on”中"go"的詞頻是\(\frac{3}{6}\)(包括逗號)
-
統計每一種單詞是否出現。比如說“go go go, come on”中"go"出現了為True
-
詞頻-逆文檔頻率法。以后做介紹
這里我們使用第三種方法,那么首先第一個步驟就是我們得對其進行分詞。與中文的jieba分詞類似,不過這里我們是將一個單詞分為一個詞。這里使用NLTK庫進行分詞,使用如下:
NLTK庫安裝可以看一下其他博主的博客,但是在下載package的時候要記得掛代理下載,或者從其他地方下載好離線安裝(不然一杯茶一根煙,一個package下一天)。
這里我們自定義一個轉換器,目的是對數據集進行預處理。在下面的代碼中主要是對推文進行分詞,然后將出現過的詞進行保存。
from sklearn.base import TransformerMixin
from nltk import word_tokenize
class NLTKBOW(TransformerMixin):
def fit(self,X,y=None):
return self
def transform(self,X):
return [
{
word:True for word in word_tokenize(document)
}for document in X
]
通過這種處理后,部分的數據如下:
將字典轉成矩陣
通過上面的操作我們獲得了一個字典的列表,但是對於sklean的分類器,它並不直接支持這些數據格式的操作,so,我們需要將這些數據轉成矩陣,這里我們可以使用DictVectorizer(也是一個轉化器,可以用於流水線)進行操作。舉個官方的例子:
from sklearn.feature_extraction import DictVectorizer
D = [{'foo': 1, 'bar': 1}, {'foo': 1, 'baz': 1},{'baz':1,'dd':1}]
dv = DictVectorizer(sparse=False)
y = dv.fit_transform(D)
x = dv.feature_names_
print(x)
print(y)
其中sparse代表的是是否生成稀疏矩陣(默認為True)。結果如下:
貝葉斯分類器
在sklearn中提供了多種貝葉斯分類器用於使用,具體使用可以看官方文檔。在這里,因為我們的數據是二值化數據(True or False),因此我們使用專門用於二值分類特征的Bernoulli Naive Bayes分類器。
-
如果特征值\(x_i\)值為1,那么\(P\left(x_{i} | y_{k}\right)=P\left(x_{i}=1 | y_{k}\right)\)
-
如果特征值\(x_i\)值為0,那么\(P(x_{i}|y_{k}) = 1-P(x_{i}=1|y_{k})\)
其中1代表這個值出現,0代表沒有出現。這個意味着"沒有某一個特征也是一個特征"。
綜上:
F1 評估
前面博客中我們都是使用accuracy進行評估,但是這里可能會有一個問題,如果我的測試集中100個樣本,99個反例,1個正例。這個時候有一個進水了的模型,無論怎樣它都將數據預測成反例,那么,\(accuracy = 99\%\)。但是實際上這個模型是個“水”模型,因此我們選擇另一個常用的評價指標F1值。
F1值以每個類別為基礎進行定義,包括兩個概念:
假如我們有一個集合,里面有兩個類(蘋果100個,梨子100個),假設我們現在以識別蘋果為目標,然后識別出了90個蘋果和20個梨子。
-
准確率(precision):准確度也就是正確率。
\[正確率 = \frac{90}{90+20} = 0.818 \] -
召回率(recall):召回率是指被正確預測為某個類別的個體數量與數據集中該類別個體總量的比例。
\[召回率 = \frac{90}{100} = 0.9 \]
F1值就是准確率和召回率的調和平均數。
流水線走起
通過前面的操作,我們涉及了:
- 文本布袋模型轉化
- 字典轉矩陣
- 貝葉斯分類
- F1值評估
流水線的介紹在前面的博客已經介紹過了,這里就不多介紹了。
#流水線
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score
pipeline = Pipeline([('布袋模型轉換',NLTKBOW()),
('字典列表轉矩陣',DictVectorizer(sparse=True)),
('素貝葉斯分類器',BernoulliNB())],
verbose=True
)
scores = cross_val_score(pipeline,data_list,class_list,scoring='f1')
print(scores)
import numpy as np
print("Score: {}".format(np.mean(scores)))
最后的結果如下:
emm,這個也太低了吧,使用accuracy評判評判標准結果如下:
gan!!!訓練了個寂寞……
總結
上面之所以預測結果如此之差,主要是因為數據集太少了,才95條數據,如果數據集大一點的話,應該會得到更好的結果。
項目地址:Github
參考
- 帶你理解朴素貝葉斯分類算法
- 理解朴素貝葉斯分類的拉普拉斯平滑
- 《機器學習——周志華》
- 《Python數據挖掘入門與實踐》
- 使用朴素貝葉斯進行社會媒體挖掘之推特
- 知乎