3.朴素贝叶斯和KNN算法的推导和python实现
前面一个博客我们用Scikit-Learn实现了中文文本分类的全过程,这篇博客,着重分析项目最核心的部分分类算法:朴素贝叶斯算法以及KNN算法的基本原理和简单python实现。
3.1 贝叶斯公式的推导
简单介绍一下什么是贝叶斯:
1 看着后视镜往前开车
2 结合开车来理解贝叶斯公式
3 贝叶斯定理与人脑
![](https://img2018.cnblogs.com/blog/723656/201906/723656-20190603084214130-1448418791.png)
第(3)步的各个条件概率,我们可以按以下步骤计算:
![](https://img2018.cnblogs.com/blog/723656/201906/723656-20190603084335950-30781628.png)
3.2 贝叶斯算法的实现
为了将主要的精力集中在算法本身,我们使用简单的英文语料作为数据集。
import sys import os from numpy import * import numpy as np 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','my'], ['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 is abusive, 0 not return postingList,classVec #编写贝叶斯算法类,创建默认的构造方法 class NBayes(object): def __init__(self): self.vocabulary = [] #词典 self.idf = 0 #词典的IDF权值向量 self.tf = 0 #训练集的权值矩阵 self.tdm=0 #P(x|yi) self.Pcates = {} #p(yi)是一个类别字典,训练样本中0占多少比例,1占多少比例 self.labels= [] #对应每个文本的分类,是一个外部导入的列表 self.doclength = 0 #训练集文本数 self.vocablen = 0 #词典词长 self.testset = 0 #测试集 #导入和训练数据集,生成算法所必需的参数和数据结构 def train_set(self,trainset,classVec): self.cate_prob(classVec)#计算每个分类在数据集中的概率P(Yi) self.doclength = len(trainset)#训练集合的个数 print(self.doclength) tempset = set() #生成词典(去重后的词典,即训练样本中的所有词汇) [tempset.add(word) for doc in trainset for word in doc] print(tempset) #训练样本词典 self.vocabulary = list(tempset) #词典长度 self.vocablen = len(self.vocabulary) self.calc_wordfreq(trainset)#计算词频数据集 self.build_tdm() #按分类累计向量空间的每维值P(X|Yi) #计算在数据集中每个分类的概率P(Yi) def cate_prob(self,classVec): self.labels = classVec #输出各个分类 print(self.labels) #转成集合,去重,只有0,1 labeltemps = set(self.labels)#获取全部分类 print(labeltemps) for labeltemp in labeltemps: # 统计列表中重复的分类:self.labels.count(labeltemp) #0的比例占多少,1的比例占多少 self.Pcates[labeltemp] = float(self.labels.count(labeltemp))/float(len(self.labels)) print(self.Pcates[labeltemp]) #生成普通的词频向量 def calc_wordfreq(self,trainset): self.idf = np.zeros([1,self.vocablen])#1*词典单词数,统计的是所有的样本单词的次数 print(self.idf) self.tf = np.zeros([self.doclength,self.vocablen])#训练集文件数*词典数,统计的是每一个训练样本单词的次数 print(self.tf) #遍历训练集 for indx in range(self.doclength): # 遍历每一个训练样本中的每个词 for word in trainset[indx]: #相应位置上的单词+1,第indx行,self.vocabulary.index(word)列 #self.vocabulary.index(word)返回的是word在词典中的下标 self.tf[indx,self.vocabulary.index(word)] += 1 #整个词典的计数加1 for signleword in set(trainset[indx]): self.idf[0,self.vocabulary.index(signleword)] += 1 print(self.idf) print(self.tf) #按分类累计计算向量空间的每维值P(X|Yi) def build_tdm(self): self.tdm = np.zeros([len(self.Pcates),self.vocablen])#类别个数*词典长度 print(self.tdm) sumlist = np.zeros([len(self.Pcates),1])#类别个数*1 print(sumlist) #遍历所有的训练样本 for indx in range(self.doclength): #求同一类别的词向量空间值加总,加的是每一个训练样本对应的每个词的个数 self.tdm[self.labels[indx]] += self.tf[indx] #统计每个分类的总的词数 sumlist[self.labels[indx]] = np.sum(self.tdm[self.labels[indx]]) print(self.tdm) print(sumlist) self.tdm = self.tdm/sumlist#生成P(X|Yi),即求每个分类的单词,占该分类的所有单词总数的比例 print(self.tdm) #将测试集映射到当前词典 def map2vocab(self,testdata): #计算测试集的特征向量(非线性代数的特征向量,而是一行中对词的统计信息) self.testset = np.zeros([1,self.vocablen]) for word in testdata: self.testset[0,self.vocabulary.index(word)] += 1 #将预测分类,输出预测的分类类别 def predict(self,testset): #np.shape(testset)[1]列值 if np.shape(testset)[1] != self.vocablen:#如果测试集长度与字典不相同则退出程序 print("输入错误") exit(0) predvalue = 0 #初始化后类别概率 predclass = "" #初始化类别名称 #tdm是每一类某个单词,占该类的总的单词的比例,后面一个参数是所有类别 for tdm_vect,keyclass in zip(self.tdm,self.Pcates): #P(X|Yi) P(Yi) #变量tdm,计算最大分类值 print("---"*30) print(testset*tdm_vect*self.Pcates[keyclass]) temp = np.sum(testset*tdm_vect*self.Pcates[keyclass]) print(temp) #选择概率最大的一个 if temp > predvalue: predvalue = temp predclass = keyclass return predclass #dataset:句子的词向量 #listClasses是句子所属于的类别[0,1,0,1,0,1] dataSet,listClasses = loadDataSet()#导入外部数据集 nb = NBayes()#实例化 nb.train_set(dataSet,listClasses)#训练数据集 nb.map2vocab(dataSet[2])#随机选择一个测试句 print(nb.predict(nb.testset))#输出分类结果
3.3 kNN分类算法
前面使用了朴素贝叶斯算法实现了文本分类。下面我们可以考虑通过计算向量间的距离衡量相似度来进行文本分类。这就是kNN算法,一种基于向量间相似度的分类算法。
3.3.1 kNN算法原理
k最近邻(k-Nearest Neighbor)算法是比较简单的机器学习算法。采用测量不同特征值之间的距离方法进行分类。它的思想很简单:如果一个样本在特征空间中的k个最近邻(最相似)的样本中的大多数都属于某一个类别,则该样本也属于这个类别。第一个字母k可以小写,表示外部定义的近邻数量。举个栗子:
有一个数据集,这个数据集很简单,由二维空间上的4个点构成一个矩阵:
其中前 两个点构成一个类别A,后两个点构成一个类别B。
代码:
import sys import os from numpy import * import numpy as np import matplotlib.pyplot as plt import operator def createDataSet(): group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]]) labels = ['A','A','B','B'] return group,labels dataSet,labels = createDataSet() #绘图 fig = plt.figure() ax = fig.add_subplot(111) indx = 0 for point in dataSet: if labels[indx] == 'A': ax.scatter(point[0],point[1],c='blue',marker='o',linewidths=0,s=300) plt.annotate("("+str(point[0])+","+str(point[1])+")",xy = (point[0],point[1])) else: ax.scatter(point[0], point[1], c='red', marker='^', linewidths=0, s=300) plt.annotate("(" + str(point[0]) + "," + str(point[1]) + ")", xy=(point[0], point[1])) indx += 1 plt.show()
如下图
如图所示,可以清晰得看到由4个点构成的训练集。该训练集呗分成两个类别:A类----蓝色圆圈,B类----红色三角形。可以看出红色区域的点距比它们到蓝色区域内的点距要小得多,这种分类也是自然而然的。
下面再给出测试集,并把它加入到刚才的矩阵中去。
对测试集进行绘图:
import sys import os from numpy import * import numpy as np import matplotlib.pyplot as plt import operator def createDataSet(): group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]]) labels = ['A','A','B','B'] return group,labels dataSet,labels = createDataSet() #绘图 fig = plt.figure() ax = fig.add_subplot(111) indx = 0 for point in dataSet: if labels[indx] == 'A': ax.scatter(point[0],point[1],c='blue',marker='o',linewidths=0,s=300) plt.annotate("("+str(point[0])+","+str(point[1])+")",xy = (point[0],point[1])) else: ax.scatter(point[0], point[1], c='red', marker='^', linewidths=0, s=300) plt.annotate("(" + str(point[0]) + "," + str(point[1]) + ")", xy=(point[0], point[1])) indx += 1 testdata=[0.2,0.2] ax.scatter(testdata[0], testdata[1], c='green', marker='^', linewidths=0, s=300) plt.annotate("(" + str(testdata[0]) + "," + str(testdata[1]) + ")", xy=(testdata[0], testdata[1])) plt.show()
很清晰,从距离上看,它更接近红色的三角形范围,应该归入B类,这就是kNN算法的基本原理。
由此可见,kNN算法应该由如下的步骤构成:
第三阶段:统计这k个样本点钟各个类别的数量。如上图所示,如果我们选定k值为3,则B类样本(三角形)有两个,A类样本(圆形)有1个,那么我们就把这个数据点定为B类,根据k个样本中数量最多的样本是什么类别,我们就把这个数据点定为什么类别。
3.3.2 kNN算法的Python实现
import sys import os from numpy import * import numpy as np import operator from Nbayes_lib import * 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','my'], ['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 is abusive, 0 not return postingList,classVec #编写贝叶斯算法类,创建默认的构造方法 class NBayes(object): def __init__(self): self.vocabulary = [] #词典 self.idf = 0 #词典的IDF权值向量 self.tf = 0 #训练集的权值矩阵 self.tdm=0 #P(x|yi) self.Pcates = {} #p(yi)是一个类别字典,训练样本中0占多少比例,1占多少比例 self.labels= [] #对应每个文本的分类,是一个外部导入的列表 self.doclength = 0 #训练集文本数 self.vocablen = 0 #词典词长 self.testset = 0 #测试集 #导入和训练数据集,生成算法所必需的参数和数据结构 def train_set(self,trainset,classVec): self.cate_prob(classVec)#计算每个分类在数据集中的概率P(Yi) self.doclength = len(trainset)#训练集合的个数 print(self.doclength) tempset = set() #生成词典(去重后的词典,即训练样本中的所有词汇) [tempset.add(word) for doc in trainset for word in doc] print(tempset) #训练样本词典 self.vocabulary = list(tempset) #词典长度 self.vocablen = len(self.vocabulary) self.calc_wordfreq(trainset)#计算词频数据集 self.build_tdm() #按分类累计向量空间的每维值P(X|Yi) #计算在数据集中每个分类的概率P(Yi) def cate_prob(self,classVec): self.labels = classVec #输出各个分类 print(self.labels) #转成集合,去重,只有0,1 labeltemps = set(self.labels)#获取全部分类 print(labeltemps) for labeltemp in labeltemps: # 统计列表中重复的分类:self.labels.count(labeltemp) #0的比例占多少,1的比例占多少 self.Pcates[labeltemp] = float(self.labels.count(labeltemp))/float(len(self.labels)) print(self.Pcates[labeltemp]) #生成普通的词频向量 def calc_wordfreq(self,trainset): self.idf = np.zeros([1,self.vocablen])#1*词典单词数,统计的是所有的样本单词的次数 print(self.idf) self.tf = np.zeros([self.doclength,self.vocablen])#训练集文件数*词典数,统计的是每一个训练样本单词的次数 print(self.tf) #遍历训练集 for indx in range(self.doclength): # 遍历每一个训练样本中的每个词 for word in trainset[indx]: #相应位置上的单词+1,第indx行,self.vocabulary.index(word)列 #self.vocabulary.index(word)返回的是word在词典中的下标 self.tf[indx,self.vocabulary.index(word)] += 1 #整个词典的计数加1 for signleword in set(trainset[indx]): self.idf[0,self.vocabulary.index(signleword)] += 1 print(self.idf) print(self.tf) #按分类累计计算向量空间的每维值P(X|Yi) def build_tdm(self): self.tdm = np.zeros([len(self.Pcates),self.vocablen])#类别个数*词典长度 print(self.tdm) sumlist = np.zeros([len(self.Pcates),1])#类别个数*1 print(sumlist) #遍历所有的训练样本 for indx in range(self.doclength): #求同一类别的词向量空间值加总,加的是每一个训练样本对应的每个词的个数 self.tdm[self.labels[indx]] += self.tf[indx] #统计每个分类的总的词数 sumlist[self.labels[indx]] = np.sum(self.tdm[self.labels[indx]]) print(self.tdm) print(sumlist) self.tdm = self.tdm/sumlist#生成P(X|Yi),即求每个分类的单词,占该分类的所有单词总数的比例 print(self.tdm) #将测试集映射到当前词典 def map2vocab(self,testdata): #计算测试集的特征向量(非线性代数的特征向量,而是一行中对词的统计信息) self.testset = np.zeros([1,self.vocablen]) for word in testdata: self.testset[0,self.vocabulary.index(word)] += 1 #将预测分类,输出预测的分类类别 def predict(self,testset): #np.shape(testset)[1]列值 if np.shape(testset)[1] != self.vocablen:#如果测试集长度与字典不相同则退出程序 print("输入错误") exit(0) predvalue = 0 #初始化后类别概率 predclass = "" #初始化类别名称 #tdm是每一类某个单词,占该类的总的单词的比例,后面一个参数是所有类别 for tdm_vect,keyclass in zip(self.tdm,self.Pcates): #P(X|Yi) P(Yi) #变量tdm,计算最大分类值 print("---"*30) print(testset*tdm_vect*self.Pcates[keyclass]) temp = np.sum(testset*tdm_vect*self.Pcates[keyclass]) print(temp) #选择概率最大的一个 if temp > predvalue: predvalue = temp predclass = keyclass return predclass k=3 #夹角余弦距离公式 def cosdist(vector1,vector2): return dot(vector1,vector2)/(linalg.norm(vector1)*linalg.norm(vector2)) #kNN实现分类器 #测试集:testdata 训练集:trainSet 类别标签:listClasses k:k个邻居数 def classify(testdata,trainSet,listClasses,k): dataSetSize = trainSet.shape[0]#放回样本集的行数 print(dataSetSize) distances = array(zeros(dataSetSize)) print(distances) #计算测试集与训练集直接的距离:夹角余弦 for indx in range(dataSetSize): distances[indx] = cosdist(testdata,trainSet[indx]) #根据夹角余弦从大到小排序,结果为索引号 sortedDistIndicies = argsort(-distances) print(sortedDistIndicies) classCount = {} for i in range(k):#获取角度最小的前k项作为参考项 #按排序顺序返回样本集对应的类别标签 voteIlabel = listClasses[sortedDistIndicies[i]] try: classCount[voteIlabel] += 1 except: classCount[voteIlabel] = 1 print(listClasses) print(classCount) #对分类字典classCount按value重新排序 #sorted(data.iteritems(),key=operator.itemgetter(1),reversee=True) #该句是按字典值排序的固定用法 #classCount.iteritems():字典迭代器函数 #key:排序参数 operator.itemgetter(1):多级排序 sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True) print(sortedClassCount) return sortedClassCount[0][0]#返回序最高的一项 dataSet,listClasses = loadDataSet() nb = NBayes() nb.train_set(dataSet,listClasses) print("------"*20) #nb.tf:统计的是每一个训练样本单词的次数 print(classify(nb.tf[3],nb.tf,listClasses,k))