前言
本系列為機器學習算法的總結和歸納,目的為了清晰闡述算法原理,同時附帶上手代碼實例,便於理解。
目錄
組合算法(Ensemble Method)
機器學習算法總結
本章為朴素貝葉斯,內容包括模型介紹及代碼實現(包括自主實現和sklearn案例)。
一、算法簡介
1.1 背景
監督學習分為生成模型 (generative model) 與判別模型 (discriminative model),貝葉斯方法是生貝葉斯方法正是生成模型的代表 (還有隱馬爾科夫模型)。
在概率論與統計學中,貝葉斯定理 (Bayes' theorem) 表達了一個事件發生的概率,而確定這一概率的方法是基於與該事件相關的條件先驗知識 (prior knowledge)。而利用相應先驗知識進行概率推斷的過程為貝葉斯推斷 (Bayesian inference)。
1.2 貝葉斯定理
條件概率 (conditional probability) 是指在事件 B 發生的情況下,事件 A 發生的概率。通常記為 P(A | B)。

因此

可得

由此可以推出貝葉斯公式

這也是條件概率的計算公式。
此外,由全概率公式,可得條件概率的另一種寫法

其中樣本空間由A和A'構成,由此求得事件B的概率。
1.3 貝葉斯推斷
貝葉斯公式中,P(A)稱為"先驗概率"(Prior probability),即在B事件發生之前,對A事件概率的一個判斷。
P(A|B)稱為"后驗概率"(Posterior probability),即在B事件發生之后,對A事件概率的重新評估。
P(B|A)/P(B)稱為"可能性函數"(Likelyhood),這是一個調整因子,使得預估概率更接近真實概率。
所以,條件概率可以理解成下面的式子:后驗概率=先驗概率 x 調整因子
這就是貝葉斯推斷的含義。我們先預估一個"先驗概率",然后加入實驗結果,看這個實驗到底是增強還是削弱了"先驗概率",由此得到更接近事實的"后驗概率"。因為在分類中,只需要找出可能性最大的那個選項,而不需要知道具體那個類別的概率是多少,所以為了減少計算量,全概率公式在實際編程中可以不使用。
而朴素貝葉斯推斷,是在貝葉斯推斷的基礎上,對條件概率分布做了條件獨立性的假設。因此可得朴素貝葉斯分類器的表達式。因為以自變量之間的獨立(條件特征獨立)性和連續變量的正態性假設為前提,就會導致算法精度在某種程度上受影響。

1.4 朴素貝葉斯的參數推斷
實際在機器學習的分類問題的應用中,朴素貝葉斯分類器的訓練過程就是基於訓練集 D 來估計類先驗概率 P(c) ,並為每個屬性估計條件概率 P(xi | c) 。這里就需要使用極大似然估計 (maximum likelihood estimation, 簡稱 MLE) 來估計相應的概率。
令 Dc 表示訓練集 D 中的第 c 類樣本組成的集合,若有充足的獨立同分布樣本,則可容易地估計出類別的先驗概率:

對於離散屬性而言,令 Dc,xi 表示 Dc 中在第 i 個屬性上取值為 xi 的樣本組成的集合,則條件概率 P(xi | c) 可估計為:

對於連續屬性可考慮概率密度函數,假定

μ和sigma分別是第 c 類樣本在第 i 個屬性上取值的均值和方差,則有:

1.5 算法流程

- 若任務對預測速度要求較高,則對給定的訓練集,可將朴素貝葉斯分類器涉及的所有概率估值事先計算好存儲起來,這樣在進行預測時只需要 “查表” 即可進行判別;
- 若任務數據更替頻繁,則可采用 “懶惰學習” (lazy learning) 方式,先不進行任何訓練,待收到預測請求時再根據當前數據集進行概率估值;
- 若數據不斷增加,則可在現有估值的基礎上,僅對新增樣本的屬性值所涉及的概率估值進行計數修正即可實現增量學習。
二、代碼案例
2.1 留言分類(參考機器學習實戰)

# -*- coding: UTF-8 -*- import numpy as np from functools import reduce """ 函數說明:創建實驗樣本 Parameters: 無 Returns: postingList - 實驗樣本切分的詞條 classVec - 類別標簽向量 """ def loadDataSet(): postingList=[['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'], #切分的詞條 ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'], ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'], ['stop', 'posting', 'stupid', 'worthless', 'garbage'], ['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'], ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']] classVec = [0,1,0,1,0,1] #類別標簽向量,1代表侮辱性詞匯,0代表不是 return postingList,classVec #返回實驗樣本切分的詞條和類別標簽向量 """ 函數說明:將切分的實驗樣本詞條整理成不重復的詞條列表,也就是詞匯表 Parameters: dataSet - 整理的樣本數據集 Returns: vocabSet - 返回不重復的詞條列表,也就是詞匯表 """ def createVocabList(dataSet): vocabSet = set([]) #創建一個空的不重復列表 for document in dataSet: vocabSet = vocabSet | set(document) #取並集 return list(vocabSet) """ 函數說明:根據vocabList詞匯表,將inputSet向量化,向量的每個元素為1或0 Parameters: vocabList - createVocabList返回的列表 inputSet - 切分的詞條列表 Returns: returnVec - 文檔向量,詞集模型 """ def setOfWords2Vec(vocabList, inputSet): returnVec = [0] * len(vocabList) #創建一個其中所含元素都為0的向量 for word in inputSet: #遍歷每個詞條 if word in vocabList: #如果詞條存在於詞匯表中,則置1 returnVec[vocabList.index(word)] = 1 else: print("the word: %s is not in my Vocabulary!" % word) return returnVec #返回文檔向量 """ 函數說明:朴素貝葉斯分類器訓練函數 Parameters: trainMatrix - 訓練文檔矩陣,即setOfWords2Vec返回的returnVec構成的矩陣 trainCategory - 訓練類別標簽向量,即loadDataSet返回的classVec Returns: p0Vect - 非侮辱類的條件概率數組 p1Vect - 侮辱類的條件概率數組 pAbusive - 文檔屬於侮辱類的概率 """ def trainNB0(trainMatrix,trainCategory): numTrainDocs = len(trainMatrix) #計算訓練的文檔數目 numWords = len(trainMatrix[0]) #計算每篇文檔的詞條數 pAbusive = sum(trainCategory)/float(numTrainDocs) #文檔屬於侮辱類的概率 p0Num = np.zeros(numWords); p1Num = np.zeros(numWords) #創建numpy.zeros數組, p0Denom = 0.0; p1Denom = 0.0 #分母初始化為0.0 for i in range(numTrainDocs): if trainCategory[i] == 1: #統計屬於侮辱類的條件概率所需的數據,即P(w0|1),P(w1|1),P(w2|1)··· p1Num += trainMatrix[i] p1Denom += sum(trainMatrix[i]) else: #統計屬於非侮辱類的條件概率所需的數據,即P(w0|0),P(w1|0),P(w2|0)··· p0Num += trainMatrix[i] p0Denom += sum(trainMatrix[i]) p1Vect = p1Num/p1Denom #相除 p0Vect = p0Num/p0Denom return p0Vect,p1Vect,pAbusive #返回屬於侮辱類的條件概率數組,屬於非侮辱類的條件概率數組,文檔屬於侮辱類的概率 """ 函數說明:朴素貝葉斯分類器分類函數 Parameters: vec2Classify - 待分類的詞條數組 p0Vec - 侮辱類的條件概率數組 p1Vec -非侮辱類的條件概率數組 pClass1 - 文檔屬於侮辱類的概率 Returns: 0 - 屬於非侮辱類 1 - 屬於侮辱類 """ def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1): p1 = reduce(lambda x,y:x*y, vec2Classify * p1Vec) * pClass1 #對應元素相乘 p0 = reduce(lambda x,y:x*y, vec2Classify * p0Vec) * (1.0 - pClass1) print('p0:',p0) print('p1:',p1) if p1 > p0: return 1 else: return 0 """ 函數說明:測試朴素貝葉斯分類器 Parameters: 無 Returns: 無 """ def testingNB(): listOPosts,listClasses = loadDataSet() #創建實驗樣本 myVocabList = createVocabList(listOPosts) #創建詞匯表 trainMat=[] for postinDoc in listOPosts: trainMat.append(setOfWords2Vec(myVocabList, postinDoc)) #將實驗樣本向量化 p0V,p1V,pAb = trainNB0(np.array(trainMat),np.array(listClasses)) #訓練朴素貝葉斯分類器 testEntry = ['love', 'my', 'dalmation'] #測試樣本1 thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry)) #測試樣本向量化 if classifyNB(thisDoc,p0V,p1V,pAb): print(testEntry,'屬於侮辱類') #執行分類並打印分類結果 else: print(testEntry,'屬於非侮辱類') #執行分類並打印分類結果 testEntry = ['stupid', 'garbage'] #測試樣本2 thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry)) #測試樣本向量化 if classifyNB(thisDoc,p0V,p1V,pAb): print(testEntry,'屬於侮辱類') #執行分類並打印分類結果 else: print(testEntry,'屬於非侮辱類') #執行分類並打印分類結果 if __name__ == '__main__': testingNB()
2.2 sklearn實現
scikit-learn中朴素貝葉斯類庫的使用也比較簡單。相對於決策樹,KNN之類的算法,朴素貝葉斯需要關注的參數是比較少的。
在scikit-learn中,一共有3個朴素貝葉斯的分類算法類。分別是GaussianNB,MultinomialNB和BernoulliNB。其中GaussianNB就是先驗為高斯分布的朴素貝葉斯,MultinomialNB就是先驗為多項式分布的朴素貝葉斯,而BernoulliNB就是先驗為伯努利分布的朴素貝葉斯。
MultinamialNB這個函數,只有3個參數:alpha(拉普拉斯平滑),fit_prior(表示是否要考慮先驗概率),和class_prior:可選參數
MultinomialNB一個重要的功能是有partial_fit方法,這個方法的一般用在如果訓練集數據量非常大,一次不能全部載入內存的時候。這時我們可以把訓練集分成若干等分,重復調用partial_fit來一步步的學習訓練集,非常方便。

# -*- coding: UTF-8 -*- from sklearn.naive_bayes import MultinomialNB import matplotlib.pyplot as plt import os import random import jieba """ 函數說明:中文文本處理 Parameters: folder_path - 文本存放的路徑 test_size - 測試集占比,默認占所有數據集的百分之20 Returns: all_words_list - 按詞頻降序排序的訓練集列表 train_data_list - 訓練集列表 test_data_list - 測試集列表 train_class_list - 訓練集標簽列表 test_class_list - 測試集標簽列表 """ def TextProcessing(folder_path, test_size = 0.2): folder_list = os.listdir(folder_path) #查看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) #存放子文件夾下的txt文件的列表 j = 1 #遍歷每個txt文件 for file in files: if j > 100: #每類txt樣本數最多100個 break with open(os.path.join(new_folder_path, file), 'r', encoding = 'utf-8') as f: #打開txt文件 raw = f.read() word_cut = jieba.cut(raw, cut_all = False) #精簡模式,返回一個可迭代的generator word_list = list(word_cut) #generator轉換為list data_list.append(word_list) #添加數據集數據 class_list.append(folder) #添加數據集類別 j += 1 data_class_list = list(zip(data_list, class_list)) #zip壓縮合並,將數據與標簽對應壓縮 random.shuffle(data_class_list) #將data_class_list亂序 index = int(len(data_class_list) * test_size) + 1 #訓練集和測試集切分的索引值 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_dict = {} #統計訓練集詞頻 for word_list in train_data_list: for word in word_list: if word in all_words_dict.keys(): all_words_dict[word] += 1 else: all_words_dict[word] = 1 #根據鍵的值倒序排序 all_words_tuple_list = sorted(all_words_dict.items(), key = lambda f:f[1], reverse = True) all_words_list, all_words_nums = zip(*all_words_tuple_list) #解壓縮 all_words_list = list(all_words_list) #轉換成列表 return all_words_list, train_data_list, test_data_list, train_class_list, test_class_list """ 函數說明:讀取文件里的內容,並去重 Parameters: words_file - 文件路徑 Returns: words_set - 讀取的內容的set集合 """ def MakeWordsSet(words_file): words_set = set() #創建set集合 with open(words_file, 'r', encoding = 'utf-8') as f: #打開文件 for line in f.readlines(): #一行一行讀取 word = line.strip() #去回車 if len(word) > 0: #有文本,則添加到words_set中 words_set.add(word) return words_set #返回處理結果 """ 函數說明:根據feature_words將文本向量化 Parameters: train_data_list - 訓練集 test_data_list - 測試集 feature_words - 特征集 Returns: train_feature_list - 訓練集向量化列表 test_feature_list - 測試集向量化列表 """ def TextFeatures(train_data_list, test_data_list, feature_words): def text_features(text, feature_words): #出現在特征集中,則置1 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 #返回結果 """ 函數說明:文本特征選取 Parameters: all_words_list - 訓練集所有文本列表 deleteN - 刪除詞頻最高的deleteN個詞 stopwords_set - 指定的結束語 Returns: feature_words - 特征集 """ def words_dict(all_words_list, deleteN, stopwords_set = set()): feature_words = [] #特征列表 n = 1 for t in range(deleteN, len(all_words_list), 1): if n > 1000: #feature_words的維度為1000 break #如果這個詞不是數字,並且不是指定的結束語,並且單詞長度大於1小於5,那么這個詞就可以作為特征詞 if not all_words_list[t].isdigit() and all_words_list[t] not in stopwords_set and 1 < len(all_words_list[t]) < 5: feature_words.append(all_words_list[t]) n += 1 return feature_words """ 函數說明:新聞分類器 Parameters: train_feature_list - 訓練集向量化的特征文本 test_feature_list - 測試集向量化的特征文本 train_class_list - 訓練集分類標簽 test_class_list - 測試集分類標簽 Returns: test_accuracy - 分類器精度 """ def TextClassifier(train_feature_list, test_feature_list, train_class_list, test_class_list): classifier = MultinomialNB().fit(train_feature_list, train_class_list) test_accuracy = classifier.score(test_feature_list, test_class_list) return test_accuracy if __name__ == '__main__': #文本預處理 folder_path = './SogouC/Sample' #訓練集存放地址 all_words_list, train_data_list, test_data_list, train_class_list, test_class_list = TextProcessing(folder_path, test_size=0.2) # 生成stopwords_set stopwords_file = './stopwords_cn.txt' stopwords_set = MakeWordsSet(stopwords_file) test_accuracy_list = [] deleteNs = range(0, 1000, 20) #0 20 40 60 ... 980 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) # ave = lambda c: sum(c) / len(c) # print(ave(test_accuracy_list)) plt.figure() plt.plot(deleteNs, test_accuracy_list) plt.title('Relationship of deleteNs and test_accuracy') plt.xlabel('deleteNs') plt.ylabel('test_accuracy') plt.show()