最近在做一个商品评论分类的需求,主要是将商品的差评根据主题进行多次二分类,例如评论的内容是不是质量问题,物流问题等。
由于对机器学习属于小白水平,所以先从最简单的朴素贝叶斯算法下手。
优点:易懂易实现,文本容易向量化
缺点:分类效果一般
朴素贝叶斯算法是用统计学的方法来对数据进行二分类,其主要思想就是贝叶斯公式,这个公式大学本科就学过,所以它的原理很容易理解,
P(B|A)=P(A|B)P(B)/P(A)
即 事件A在事件B发生的前提下发生的概率=事件A在事件B发生的前提下发生的概率*事件B发生的概率/事件A发生的概率。
我们这里的事件主要是两类:
1)这个评论是A分类的概率是多大,记做P(A)
2)某个词Bi在A分类下出现的概率是多大,记做P(Bi|A),其中i为某个词
通过大数定律可知,当样本的数量足够大,某件事情发生的概率是收敛于这件事发生的期望的。所以当训练样本数量足够多时,我们就可以把某件事发生的概率近似作为期望。而上述两类事情的概率,都是可以通过训练集统计到的。然后对于一个新的文本,可以根据它的词向量来判断各种分类的概率,然后取最大概率的分类即可。
一.文本分词
使用python的结巴中文分词库对文本进行处理,由于评论主要是用户的主观感受,文本异常字符较多,所以这里需要对文本进行清洗,例如特殊字符,标点符号,停用词等;而对于某些领域,需要自己添加分词词典,方便提取有用的关键词(这里不考虑词频,所以分词的最终结果都做去重处理)。
训练集分词前:
训练集分词后:
二.构建分词向量
首先创建一个所有评论中不重复词的列表,然后向量化所有的评论,函数代码如下:
def setOfWords2Vec(vocabList,inputSet):
returnVec=[0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)]=1
else:
pass
# print "the word:%s is not in myVocabulary!" % word
return returnVec
其中 vocabList为所有训练集中不重复词的列表,可以选择集合作为它的数据类型,由于集合中每个元素都是唯一的,所以不需要考虑去重的问题。inputSet为评论分词后的结果,这样就将一个评论转化为一个向量,对于测试集或测试数据中的词如果在vocabList不存在则不予考虑。这个向量的维数就是vocabList的长度,如果某个词存在,则这个词的位数为1,否则为0.
选中20条评论作为训练数据,一共分出了129个词,生成的词向量组成的矩阵为20*129
三.训练
根据上文的描述,假设 P(A)表示这个评论是A分类的概率是多大,P(Bi|A)表示在A分类的前提下词Bi出现的概率,则向量B在A分类下的概率为P(B|A)=P(b0,b1,b2...bn|A),假设所有的词都互相独立,则上面的表达式也可写为P(b0|A)P(b1|A)P(b2|A)...P(bn|A)。根据训练数据,可以求出P(A),P(bi|A)。
若要判断一个向量C是属于A1还是A2,则求出向量C是A1和A2分类的概率并比较大小,哪个类别的概率大,则将其归为哪个类。其中:
P(A1|C)=P(C|A1)*P(A1)/P(C)
P(A2|C)=P(C|A2)*P(A2)/P(C)
要比较P(A1|C)和P(A2|C)的大小,只需要比较P(C|A1)*P(A1)和P(C|A2)*P(A2)的大小即可。
#朴素贝叶斯分类器训练函数,trainCategory为分类数组,trainMatrix为所有的评论向量
def trainNB0(trainMatrix,trainCategory):
numTrainDocs=len(trainMatrix)
numWords=len(trainMatrix[0])
#pAbusive表示为所有评论中出现,是该分类的概率
pAbusive=sum(trainCategory)/float(numTrainDocs)
p0Num=zeros(numWords);p1Num=zeros(numWords) #p0Num和p1Num为两个全0数组,长度为numWords,
# 这两个向量用来统计P0分类中每个词的个数和P1中每个词的个数
p0Denom=0.0;p1Denom=0.0#初始化
#for循环扫描每一个评论生成的向量
for i in range(numTrainDocs):
# 如果向量判定为1,p1Num加上每个词的个数
if trainCategory[i]==1:
p1Num =p1Num+trainMatrix[i]
#p1Denom是词向量中关键词的个数
p1Denom =p1Denom+sum(trainMatrix[i])
# 如果向量判定为0,p1Num加上每个词的个数
else:
p0Num =p0Num+ trainMatrix[i]
p0Denom =p0Denom+ sum(trainMatrix[i])
p1Vect=p1Num/p1Denom
p0Vect=p0Num/p0Denom
return p0Vect,p1Vect,pAbusive
p0Vect:P0分类下每个词的概率
p1Vect:P1分类下每个词的概率
pAbusive:P1分类的概率。
上面的算法有两个问题:
1)P(b0|A)P(b1|A)P(b2|A)...P(bn|A),如果其中一个概率值为0,那么最后的乘积也为0,所以我们将所有词的出现次数初始化为1,将出现的总次数初始化为2
p0Num=ones(numWords);p1Num=ones(numWords)
p0Denom=2.0;p1Denom=2.0
2)P(b0|A)P(b1|A)P(b2|A)...P(bn|A)大部分因子都很小,所以结果很容易四舍五入得到0,为了降低这种影响,我们对其取对数则,虽然函数并不一样,但是对他们比较大小的结果并不影响,所以
p1Vect=log(p1Num/p1Denom)
p0Vect=log(p0Num/p0Denom)
四.分类
传入一个新的向量,判断它是那种分类,即比较P(A1|C)和P(A2|C)的大小
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
p1=sum(vec2Classify*p1Vec)+log(pClass1)
p0=sum(vec2Classify*p0Vec)+log(1-pClass1)
if p1>p0:
return 1
else:
return 0
其中vec2Classify为需要判定的向量,p0Vec表示每个词在分类0中的概率的对数向量,p1Vec表示每个词在分类1中的对数向量,pClass1为分类为1的概率,由于是二分类问题,可知pClass0=1-pClass1
由于p0Vec和p1Vec是概率的对数,所以对概率的乘积大小比较和对其对数求和的大小比较,结果是一致的。
五.测试正确率
将测试数据进行判定,将返回的结果值和人工判定值进行对比,如果结果一致则表示分类成功。
def testing(testVec,predictVec):
vecLen=len(testVec)
count=0.0
for i in range(vecLen):
if testVec[i]-predictVec[i]==0:
count=count+1
return count/vecLen
testVec表示人工判定的分类向量,predictVec表示算法判定的分类向量,即可算出正确率。
#分类效果不能令人满意,需要继续尝试其他方法。