Table of Contents
樸素貝葉斯的分類
在sklearn中,一共有3個樸素貝葉斯的分類算法,分別是GaussianNB,MultinomialNB和BernoulliNB
GaussianNB
GaussianNB就是先驗爲高斯分佈(正態分佈)的樸素貝葉斯,假設每個標籤的數據都服從簡單的正態分佈。
其中爲Y的第k類類別,和爲需要從訓練集估計的值
理解起來可能有點抽象,那我們從樸素貝葉斯的原理說起:
我們需要計算在給定的特徵下,每種類別的概率值的大小,即等號左邊的P(類別|特徵),選取其中概率最大的類別,即爲最終的判斷結果。
爲了求的等號左邊的P(類別|特徵),我們需要計算等號右邊的分式。
對於這個分式,每個類別在計算的時候,對應的分母都是一樣的,而且我們只是想要比大小,所以只需要計算出每個類別的情況下,分子的大小
P(類別)計算很簡單,就是這個類別的樣本數/總樣本數
P(特徵|類別)的計算,我們之前採用的是數數的方法,數一下,在這個類別下,這個標籤所佔的比例是多少。這對於離散型的變量是比較容易的數的,但是對於連續性的變量來說,就不現實了。所以,對於連續性變量,我們可以通過高斯公式計算出P(特徵|類別)
以鳶尾花數據集爲例,理解GaussianNB
鳶尾花數據集給出了每朵花的特徵,以及花的類別,下面來導入鳶尾花數據集
import numpy as np
import pandas as pd
dataSet = pd.read_csv('iris.txt',header=None)
dataSet.columns=['feature0','feature1','feature2','feature3','label']
dataSet.head(3)
feature0 | feature1 | feature2 | feature3 | label | |
---|---|---|---|---|---|
0 | 5.1 | 3.5 | 1.4 | 0.2 | Iris-setosa |
1 | 4.9 | 3.0 | 1.4 | 0.2 | Iris-setosa |
2 | 4.7 | 3.2 | 1.3 | 0.2 | Iris-setosa |
可以看出鳶尾花數據集一共有4個特徵,最後一列是標籤列
dataSet.iloc[:,-1].unique()
array(['Iris-setosa', 'Iris-versicolor', 'Iris-virginica'], dtype=object)
可以看出,鳶尾花數據集的標籤一共是3種,代表三種鳶尾花
那麼測試集就是給出一朵花的4個特徵,判斷是屬於哪類鳶尾花
假如我們的測試集是test = [4.2 , 4.5 , 1.2 , 0.5]
如果我們想通過GaussianNB來判斷結果的話,我們需要分別計算在給定特徵的情況下,是每種標籤的可能性,即:
- P(‘Iris-setosa’|[4.2 , 4.5 , 1.2 , 0.5])
- P(‘Iris-versicolor’|[4.2 , 4.5 , 1.2 , 0.5] )
- P(‘Iris-virginica’|[4.2 , 4.5 , 1.2 , 0.5] )
進一步展開的話
因爲我們只是需要找出這三個概率的最大值,所以這三個概率求解過程中,對應的相同的分母就不需要計算了
所以對於P(‘Iris-setosa’|[4.2 , 4.5 , 1.2 , 0.5]) ,我們只需要計算出分子的部分,即P([4.2 , 4.5 , 1.2 , 0.5]|‘Iris-setosa’)P(‘Iris-setosa’),其他同理
接下來我們以P(‘Iris-setosa’|[4.2 , 4.5 , 1.2 , 0.5]) 的分子求解爲例
先計算最容易的P(‘Iris-setosa’)
sum((dataSet.iloc[:,-1]=='Iris-setosa').values)/dataSet.shape[0] #先篩選出dataSet標籤爲'Iris-setosa'中的類,並計數,再除以總樣本數
0.3333333333333333
所以P(‘Iris-setosa’)=0.3333333333333333
接下來就剩P([4.2 , 4.5 , 1.2 , 0.5]|‘Iris-virginica’)的求解了。
在求解之前,因爲樸素貝葉斯假設各特徵之間都是獨立的,所以
我們以等號右邊的P(feature0=4.2|‘Iris-setosa’)爲例,講解求解過程,這個會了其他的就都會啦
GaussianNB假設每個特徵是符合高斯分佈的,所以
其中是在’Iris-setosa’數據集中feature0數據的標準差,則是對應的均值
我們來計算下和
data_setosa = dataSet.loc[dataSet.iloc[:,-1]=='Iris-setosa',:] #從全部數據集中篩選出seotosa這一類的數據集
feature0=pd.DataFrame(data_setosa.iloc[:,0]).T #單獨看feature0這一列的數據
feature0
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
feature0 | 5.1 | 4.9 | 4.7 | 4.6 | 5.0 | 5.4 | 4.6 | 5.0 | 4.4 | 4.9 | ... | 5.0 | 4.5 | 4.4 | 5.0 | 5.1 | 4.8 | 5.1 | 4.6 | 5.3 | 5.0 |
1 rows × 50 columns
#分別求平均
mu = float(feature0.mean(axis=1))
sigma = float(feature0.std(axis=1))
print(f'均值爲{mu}')
print(f'標準差爲{sigma}')
均值爲5.005999999999999
標準差爲0.3524896872134512
帶入得
1/(np.sqrt(2*np.pi*sigma**2))*np.e**(-(4.2-mu)**2/(2*sigma**2))
0.08287221251829666
所以P(feature0=4.2|‘Iris-setosa’)=0.082872,剩下的3個概率也是一樣的計算方法:
- 先篩選數據
- 分別計算均值和標準差
- 帶入高斯公式,得到結果
我們也可以通過圖像,來驗證下高斯分佈的假設對於我們的數據集是否合適
import matplotlib.pyplot as plt
plt.hist(feature0,bins=15,density=True,stacked=True)
x = np.arange(4,6,0.03)
y = np.exp(-(x-mu)**2/(2*sigma**2))*(1/np.sqrt(2*np.pi*sigma**2))
plt.plot(x,y)
[<matplotlib.lines.Line2D at 0x11a6364a8>]
數據是符合高斯分佈的假設基本上是成立的
實例:使用GaussianNB對鳶尾花數據集進行分類
接下來我們就直接敲python代碼,完成整個實例
#導入數據集
import numpy as np
import pandas as pd
dataSet = pd.read_csv('iris.txt',header=None)
dataSet.head()
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
0 | 5.1 | 3.5 | 1.4 | 0.2 | Iris-setosa |
1 | 4.9 | 3.0 | 1.4 | 0.2 | Iris-setosa |
2 | 4.7 | 3.2 | 1.3 | 0.2 | Iris-setosa |
3 | 4.6 | 3.1 | 1.5 | 0.2 | Iris-setosa |
4 | 5.0 | 3.6 | 1.4 | 0.2 | Iris-setosa |
#隨機切分數據集
'''
函數名稱:randSplit
函數功能:隨機切分訓練集和測試集
參數說明:
dataSet-輸入的數據集
train_rate-訓練集所佔的比例
返回:
train-切分好的訓練集
test-切分好的測試集
'''
def randSplit(dataSet,train_rate=0.8):
import random
m = dataSet.shape[0]
index = list(dataSet.index)
random.shuffle(index)
dataSet.index = index
train = dataSet.loc[range(int(m*train_rate)),:]#取的是索引
test = dataSet.loc[range(int(m*train_rate),m),:]
test.index=list(range(test.shape[0]))
dataSet.index = list(range(dataSet.shape[0]))
return train,test
train,test=randSplit(dataSet)
test.shape
(30, 5)
#構建高斯樸素貝葉斯分類器
'''
函數名稱:gnb_classify
函數說明:構建高斯樸素貝葉斯分類器,
參數說明:
train-訓練集
test-測試集
返回:test-添加一列預測結果的測試集
modify:2019-05-28
'''
def gnb_classify(train,test):
labellist = train.iloc[:,-1].unique().tolist()
p = []
means = []
stds = []
for label in labellist:
subdata = train.loc[train.iloc[:,-1] == label]
p.append(subdata.shape[0]/train.shape[0])
means.append(subdata.iloc[:,:-1].mean().tolist())
stds.append(subdata.iloc[:,:-1].std().tolist())
res = []
p = np.array(p)
means = np.array(means)
stds = np.array(stds)
for i in range(test.shape[0]):
t = np.array(test.iloc[i,:-1]).T
pro = np.e**(-(t-means)**2/(2*stds**2))*(1/(np.sqrt(2*np.pi*stds**2)))
P = pro.prod(axis=1)
P = P*p
res.append(labellist[P.argmax()])
test['predict']=res
print(f'錯誤率爲{(test.iloc[:,-1]!=test.iloc[:,-2]).mean()}')
return test
test = gnb_classify(train,test)
錯誤率爲0.06666666666666667
實例:使用sklearn.GaussianNB進行鳶尾花數據集分析
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import train_test_split
from sklearn import datasets
#切分數據集
iris = datasets.load_iris()
Xtrain,Xtest,Ytrain,Ytest = train_test_split(iris.data,iris.target,test_size=0.2)
#建模
clf = GaussianNB()
clf.fit(Xtrain,Ytrain)
GaussianNB(priors=None, var_smoothing=1e-09)
#在測試集上執行預測,proba導出的是每個樣本屬於某類的概率
a = clf.predict(Xtest)
prob = clf.predict_proba(Xtest)
#預測準確率
clf.score(Xtest,Ytest)
1.0
待學習
混淆矩陣
布里爾分數
MultinomialNB
先驗爲多項式分佈的樸素貝葉斯,它的假設特徵是由一個簡單多項式分佈生成。多項分佈可以描述各種類型樣本出行次數的頻率,因此多項式樸素貝葉斯非常適合用於描述出現次數或者出現次數比例的特徵。
該模型常用於文本分類,特徵表示的是次數,例如某個詞語的出現次數
多項式分佈公式如下:
其中,指的是第k個類別的第j爲特徵的第l個取值條件概率。是訓練集中輸出第k類的樣本個數,是一個大於0的常數,常常取值爲1,即拉普拉斯平滑,也可以取其他值
sklearn中MultinomialNB
class sklearn.naive_bayes.MultinomialNB(alpha=1.0,fit_prior=True,class_prior=None)
參數說明:
- alpha:浮點型可選參數,默認爲1,就是添加拉普拉斯平滑,即上述公式的,如果這個參數設置爲0,就是不添加平滑
- fit_prior:布爾型可選參數,默認爲True。表示是否要考慮先驗概率。如果爲false,則所有樣本都輸出相同的先驗概率,否則可以讓算法自己從訓練集樣本來計算先驗概率,即或者通過第三個參數class_prior輸入先驗參數
- class_prior:認爲設定的先驗參數
MultinomialNB中的方法:
- fit
- get_params
- set_params
- partial_fit:非常重要的方法,用於訓練集數據非常大,不能一次全部參入內存的時候。這時我們可以把訓練集分成若干等分,重複調用partial_fit來一步步學習。
- predict:直接給出測試集的預測類別輸出
- predict_proba:給出測試集樣本在各個類別上預測的概率
- predict_log_proba:給出測試集樣本在各個類別上預測的概率的對數轉化
- score
實例:使用sklearn.MultionmialNB進行新浪新聞分類
我們的數據集是已經分類好的新聞,儲存在文件夾SogouC中。
這個例子的關鍵在於使用中文分詞,可以直接使用python中的jieba組件,完成中文的分詞
jieba庫的使用教程:
https://github.com/fxsjy/jieba
import os
import jieba
'''
函數名稱:TextProcessing
函數說明:導入分好類的新聞文本,並進行分詞,最後按照test_size的比例將全部數據切分爲訓練集和測試集,並返回訓練集中的詞條
參數:folder_path-新聞文本所在的文件夾
返回:all_words_list - 訓練集中的全部詞條
train_data_list - 訓練集數據
train_class_list - 訓練集標籤
test_data_list - 測試集數據
test_class_list - 測試集標籤
modify:2019-05-30
'''
def TextProcessing(folder_path,test_size = 0.2):
folder_list = os.listdir(folder_path)
data_list = []
class_list = []
for folder in folder_list:
new_folder_path = os.path.join(folder_path,folder) #根據子文件夾,生成新的路徑
files = os.listdir(new_folder_path)
for file in files:
with open(os.path.join(new_folder_path,file)) as f:
raw = f.read()
word_cut = jieba.lcut(raw)
data_list.append(word_cut)
class_list.append(folder)
#切分數據集
data_class_list = list(zip(data_list,class_list))
import random
random.shuffle(data_class_list)
index = int(len(data_list)*test_size)
train_list = data_class_list[index:]
test_list = data_class_list[:index]
train_data_list,train_class_list = zip(*train_list) #解壓
test_data_list,test_class_list = zip(*test_list) #解壓
#統計訓練集詞條
all_words={}
for sentence in train_data_list:
for word in sentence:
all_words[word]=all_words.get(word,0)+1
sort_all_words = sorted(all_words.items(),key = lambda x:x[1], reverse=True) #對詞頻進行排序
all_words_list,all_words_nums = zip(*sort_all_words)
all_words_list = list(all_words_list)
return all_words_list,train_data_list,train_class_list,test_data_list,test_class_list
all_words_list,train_data_list,train_class_list,test_data_list,test_class_list = TextProcessing('SogouC/Sample')
all_words_list[:12]
Building prefix dict from the default dictionary ...
Loading model from cache /var/folders/bp/tnctfw1x0zg2yt473wrd5ltw0000gn/T/jieba.cache
Loading model cost 0.761 seconds.
Prefix dict has been built succesfully.
[',', '的', '\u3000', '。', '\n', ' ', ';', '&', 'nbsp', '、', '在', '了']
我們會發現,訓練集詞條中存在很多標點符號或者無實際意義的詞條,但統計這些詞條出現的頻率並不能幫助我們判斷新聞的類別。所以接下來我們需要去掉訓練集詞條中這些無意義的詞。
去除方法:
- 無意義的詞->在’stopwords_cn.txt’文檔中存儲了常見的無意義的詞條,如果訓練集詞條在’stopwords_cn.txt’文檔中出現,則去掉訓練集中的這個詞條
- 標籤符號和數字->用詞條的長度和string.isdigit()函數來判斷
'''
函數名稱:MakeWordsSet
函數說明:讀取文檔中的內容,並去重
參數說明:words_file - 文件路徑
返回:words_set - 讀取內容的set集合
'''
def MakeWordsSet(words_file):
words_set = set()
with open(words_file) as f:
contend = f.readlines()
for line in contend:
words_set = words_set | set([line.strip()])
return words_set
words_set = MakeWordsSet('stopwords_cn.txt') #存儲'stopwords_cn'中的詞條
'''
函數名稱:words_dict
函數說明:文本特徵提取,去除無意義的詞
參數說明:
all_words_list - 訓練集所有文本列表
deleteN - 刪除詞頻最高的deleteN個詞
stopwords_set - 無意義詞集
返回:
feature_words - 特徵集
modify:2019-05-30
'''
def words_dict(all_words_list,deleteN,stopwords_set=set()):
feature_words=[]
for i in range(deleteN,len(all_words_list)):#前deleteN詞不要
if(len(feature_words) == 1000):
break #控制feature_words的維數不超過1000
word = all_words_list[i]
if not word.isdigit() and not word in stopwords_set and 1<len(word)<5:
feature_words.append(word)
return feature_words
feature_words = words_dict(all_words_list,10,words_set)
feature_words[:10]
['中國', '遊客', '旅遊', '公司', '考生', '一個', '導彈', '大陸', '市場', '火炮']
可以看出,現在feature_words就很適合作爲新聞分類的特徵了
接下里就要使用sklearn中MultinomialNB,來確定deleteN這個參數的合適值
在使用sklearn之前,要先將文本向量化
'''
函數名稱:TextFeatures
函數說明:根據feature_words將文本向量化
函數參數:
train_data_list - 訓練集
test_data_list - 測試集
feature_words - 特徵集
返回:
train_feature_list - 訓練集向量化列表
test_feature_list - 測試集向量話列表
'''
def TextFeatures(train_data_list,test_data_list,feature_words):
def text_features(text,feature_words):
text_words = set(text)
features = [1 if word in text_words else 0 for word in feature_words]
return features
train_feature_list = [text_features(text,feature_words) for text in train_data_list]
test_feature_list = [text_features(text,feature_words) for text in test_data_list]
return train_feature_list,test_feature_list
train_feature_list,test_feature_list = TextFeatures(train_data_list,test_data_list,feature_words)
pd.DataFrame(train_feature_list).head() #方便查看詞向量
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 990 | 991 | 992 | 993 | 994 | 995 | 996 | 997 | 998 | 999 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
5 rows × 1000 columns
'''
函數名稱:TextClassifier
函數說明:新聞分類器
參數:
train_feature_list - 向量化的訓練集特徵文本
test_feature_list - 向量化的測試集特徵文本
train_class_list - 訓練集分類標籤
test_class_list - 測試集分類標籤
返回:test_accuray - 分類器精度
modify:2019-05-30
'''
def TextClassifier(train_feature_list,test_feature_list,train_class_list,test_class_list):
from sklearn.naive_bayes import MultinomialNB
clf = MultinomialNB()
clf.fit(train_feature_list,train_class_list)
test_accuray = clf.score(test_feature_list,test_class_list)
return test_accuray
test_accuray = TextClassifier(train_feature_list,test_feature_list,train_class_list,test_class_list)
test_accuray #超級慘的準確度
0.5555555555555556
有了上面的全部函數後,我們就可以編寫函數,繪製分類正確率與deleteN之間的關係曲線,選擇合適的deleteN的數值
寫之前,先回顧下之前寫的全部函數:
- TextProcessing(folder_path,test_size = 0.2)
- return all_words_list,train_data_list,train_class_list,test_data_list,test_class_list
- 導入分好類的新聞文本,並進行分詞,最後按照test_size的比例將全部數據切分爲訓練集和測試集,並返回訓練集中的詞條
- MakeWordsSet(words_file)
- return words_set
- 讀取文檔中的內容,並去重
- words_dict(all_words_list,deleteN,stopwords_set=set())
- return feature_words
- 文本特徵提取,去除無意義的詞
- TextFeatures(train_data_list,test_data_list,feature_words)
- return train_feature_list,test_feature_list
- 根據feature_words將文本向量化
- TextClassifier(train_feature_list,test_feature_list,train_class_list,test_class_list)
- return test_accuray
- 新聞分類器
accuracy = []
for i in range(30):
#獲取數據
folder_path = 'SogouC/Sample'
all_words_list,train_data_list,train_class_list,test_data_list,test_class_list = TextProcessing(folder_path,test_size = 0.2)
#整理'stopwords_cn.txt文檔'
stopwords_file = 'stopwords_cn.txt'
stopwords_set = MakeWordsSet(stopwords_file)
#改變deleteN的值,記錄對應的正確率
test_accuracy_list =[]
deleteNs = range(0,1500,20)
for deleteN in deleteNs:
feature_words = words_dict(all_words_list,deleteN,stopwords_set)
train_feature_list,test_feature_list = TextFeatures(train_data_list,test_data_list,feature_words)
test_accuracy = TextClassifier(train_feature_list,test_feature_list,train_class_list,test_class_list)
test_accuracy_list.append(test_accuracy)
accuracy.append(test_accuracy_list)
import pandas as pd
meanaccuracy = pd.DataFrame(accuracy,columns = range(0,1500,20)).mean()
#繪圖
import matplotlib.pyplot as plt
plt.figure()
plt.plot(deleteNs,meanaccuracy)
plt.title('Relationship of deleteNs and test_accuracy')
plt.xlabel('deleteN')
plt.ylabel('test_accuracy')
plt.show()
import numpy as np
meanaccuracy.idxmax()
480
這樣就可以確定一定較爲合適的deleteN的值
BernoulliNB
BernoulliNB就是先驗爲伯努利分佈的樸素貝葉斯,假設特徵的先驗概率爲二元伯努利分佈,即如下式:
此時l只有兩種取值,即x_{jl}只能取0或者1
在伯努利模型中,每個特徵的取值是布爾型,即true和false,在文本分類中,就只一個特徵有沒有在一個文檔中出現。
總結
- 一般來說,如果樣本特徵的分佈大部分是連續值,使用GaussianNB會比較好
- 如果樣本特徵的分佈大部分是多元離散值,使用MultinomialNB會比較好
- 如果樣本特徵的分佈是二元離散值或者很稀疏的多元離散值,應該使用BernoulliNB
總結
樸素貝葉斯推斷的優點:
- 生成式模型,通過計算概率來進行分類,可以用來處理多分類問題
- 對小規模的數據表現很好,算法簡單,適合增量式訓練
樸素貝葉斯推斷的缺點:
- 對輸入數據的表達形式很敏感
- 由於樸素貝葉斯的“樸素”特點,所以會帶來精確率上的損失
- 需要計算先驗概率,分類決策存在錯誤率