1.朴素贝叶斯
(1)朴素贝叶斯的原理
朴素:特征独立
贝叶斯:基于贝叶斯定理
根据贝叶斯定理,对一个分类问题,给定样本特征x,样本属于类别y的概率是
在这里,x是一个特征向量,将设x维度为M。因为朴素的假设,即特征条件独立,根据全概率公式展开,公式(1)
可以表达为
这里,只要分别估计出,特征在每一类的条件概率就可以了。类别y的先验概率可以通过训练集算出,同样通过训练集上的统计,可以得出对应每一类上的,条件独立的特征对应的条件概率向量。 由于分母中为全概率可视为常数,实际可不计算。
如果从样本中算出的概率值为0该怎么办呢?下面介绍一种简单方法,给学习步骤中的两个概率计算公式,分子和 分母都分别加上一个常数,就可以避免这个问题。更新过后的公式如下:
(2)利用朴素贝叶斯模型进行文本分类
朴素贝叶斯最典型的应用就是文本分类,训练集中包括侮辱性/非侮辱性两类,将句子中的单词视为特征,单个
词语有极性表征,整个句子所包含的单词的极性表征就是句子的极性。算法步骤如下:
实现代码参考http://blog.csdn.net/tanhongguang1/article/details/45016421,经测试有效,由于训练数据较少,代码运行速度很快,基本分类正确。
# -*- coding:utf-8 -*-
import numpy as np
def loadDataSet(): # 数据格式
postingList = []
for line in open("data.txt"):
postingList.append(line.split())
print(postingList)
'''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
def createVocabList(dataSet): # 创建词汇表
vocabSet = set([])
for document in dataSet:
vocabSet = vocabSet | set(document) # 创建并集
return list(vocabSet)
def bagOfWord2VecMN(vocabList, inputSet): # 根据词汇表,讲句子转化为向量
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix) # 向量的个数
numWords = len(trainMatrix[0]) # 总共32个词
pAbusive = sum(trainCategory) / float(numTrainDocs) # Category = 1 所占比例(先验概率)
p0Num = np.ones(numWords);
p1Num = np.ones(numWords) # 计算频数初始化为1
p0Denom = 2.0;
p1Denom = 2.0 # 即拉普拉斯平滑
for i in range(numTrainDocs):
if trainCategory[i] == 1:
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = np.log(p1Num / p1Denom) # 注意 计算单词出现的概率 分母是这一类所有词的个数
p0Vect = np.log(p0Num / p0Denom) # 注意
return p0Vect, p1Vect, pAbusive # 返回各类对应特征的条件概率向量
# 和各类的先验概率
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + np.log(pClass1) # 注意
p0 = sum(vec2Classify * p0Vec) + np.log(1 - pClass1) # 注意
if p1 > p0:
return 1
else:
return 0
def testingNB(): # 流程展示
listOPosts, listClasses = loadDataSet() # 加载数据
myVocabList = createVocabList(listOPosts) # 建立词汇表
trainMat = []
for postinDoc in listOPosts:
trainMat.append(bagOfWord2VecMN(myVocabList, postinDoc))
p0V, p1V, pAb = trainNB0(trainMat, listClasses) # 训练
# 测试
# testEntry = ['love','my','dalmation','dog','stupid']
testEntry = ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid']
thisDoc = bagOfWord2VecMN(myVocabList, testEntry)
print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
testingNB()
其中data.txt:
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
2.SVM模型
(1)SVM的原理
1) 什么是SVM
SVM的英文全称是Support Vector Machines,我们叫它支持向量机。支持向量机是我们用于分类的一种算法。让我们以一个小故事的形式,开启我们的SVM之旅吧。
在很久以前的情人节,一位大侠要去救他的爱人,但天空中的魔鬼和他玩了一个游戏。
魔鬼在桌子上似乎有规律放了两种颜色的球,说:”你用一根棍分开它们?要求:尽量在放更多球之后,仍然适用。”
于是大侠这样放,干的不错?
然后魔鬼,又在桌上放了更多的球,似乎有一个球站错了阵营。显然,大侠需要对棍做出调整。
现在好了,即使魔鬼放了更多的球,棍仍然是一个好的分界线。
魔鬼看到大侠已经学会了一个trick(方法、招式),于是魔鬼给了大侠一个新的挑战。
现在,大侠没有棍可以很好帮他分开两种球了,现在怎么办呢?当然像所有武侠片中一样大侠桌子一拍,球飞到空中。然后,凭借大侠的轻功,大侠抓起一张纸,插到了两种球的中间。
现在,从空中的魔鬼的角度看这些球,这些球看起来像是被一条曲线分开了。
再之后,无聊的大人们,把这些球叫做data,把棍子叫做classifier, 找到最大间隙的trick叫做optimization,拍桌子叫做kernelling, 那张纸叫做hyperplane。
2) 线性SVM
先看下线性可分的二分类问题
上图中的(a)是已有的数据,红色和蓝色分别代表两个不同的类别。数据显然是线性可分的,但是将两类数据点分开的直线显然不止一条。上图的(b)和(c)分别给出了B、C两种不同的分类方案,其中黑色实线为分界线,术语称为“决策面”。每个决策面对应了一个线性分类器。虽然从分类结果上看,分类器A和分类器B的效果是相同的。但是他们的性能是有差距的,看下图:
在”决策面”不变的情况下,我又添加了一个红点。可以看到,分类器B依然能很好的分类结果,而分类器C则出现了分类错误。显然分类器B的”决策面”放置的位置优于分类器C的”决策面”放置的位置,SVM算法也是这么认为的,它的依据就是分类器B的分类间隔比分类器C的分类间隔大。这里涉及到第一个SVM独有的概念”分类间隔”。在保证决策面方向不变且不会出现错分样本的情况下移动决策面,会在原来的决策面两侧找到两个极限位置(越过该位置就会产生错分现象),如虚线所示。虚线的位置由决策面的方向和距离原决策面最近的几个样本的位置决定。而这两条平行虚线正中间的分界线就是在保持当前决策面方向不变的前提下的最优决策面。两条虚线之间的垂直距离就是这个最优决策面对应的分类间隔。显然每一个可能把数据集正确分开的方向都有一个最优决策面(有些方向无论如何移动决策面的位置也不可能将两类样本完全分开),而不同方向的最优决策面的分类间隔通常是不同的,那个具有“最大间隔”的决策面就是SVM要寻找的最优解。而这个真正的最优解对应的两侧虚线所穿过的样本点,就是SVM中的支持样本点,称为”支持向量”。
(2)利用SVM模型进行文本分类
1) 准备数据和分词处理
数据:https://blog.csdn.net/alicelmx/article/details/79083903
分词:
# 本程序用于将搜狗语料库中的文本进行分词,并且去除停用词
# coding=utf-8
import jieba
import jieba.posseg as pseg
import time
import os
'''
训练集:1200
测试集:200
'''
# 文本分词
def cutText(dirname):
# dirname数据目录
for category in os.listdir(dirname):
catdir = os.path.join(dirname,category)
if not os.path.isdir(catdir):
continue
files = os.listdir(catdir)
i = 0
for cur_file in files:
print("正在处理"+category+"中的第"+str(i)+"个文件.............")
filename = os.path.join(catdir,cur_file)
#读取文本
with open(filename,"r",encoding='utf-8') as f:
content = f.read()
#进行分词
words = pseg.cut(content)
# 用于剔除停用词的列表
finalContent = []
# 停用词列表
stopWords = [line.strip() for line in open('Chinesestopword.txt', 'r', encoding='utf-8').readlines()]
for word in words:
word = str(word.word)
# 如果该单词非空格、换行符、不在听用词表中就将其添加进入最终分词列表中
if len(word) > 1 and word != '\n' and word != '\u3000' and word not in stopWords:
finalContent.append(word)
# 组合成最终需要的字符串
finalStr = " ".join(finalContent)
# 写入文件
writeFileName = writeFilePathPrefix+"/"+category+"/"+str(i)+".txt"
print(writeFileName)
with open(writeFileName,"w",encoding = 'utf-8') as f:
f.write(finalStr)
i = i + 1
print("成功处理"+category+"中的第"+str(i)+"个文件~哦耶!")
if __name__ == '__main__':
# 记录开始时间
t1=time.time()
readFilePathPrefix = "SogouData/ClassFile"
writeFilePathPrefix = "SogouDataCut"
cutText(readFilePathPrefix)
# 记录结束时间
t2=time.time()
#反馈结果
print("您的分词终于完成,耗时:"+str(t2-t1)+"秒。")
2)利用卡方检验特征选择
**卡方检验:**在构建每个类别的词向量后,对每一类的每一个单词进行其卡方统计值的计算。
(a)首先对卡方 检验所需的 a、b、c、d 进行计算。 a 为在这个分类下包含这个词的文档数量; b 为不在该分类下包 含 这个词的文档数量; c 为在这个分类下不包含这个词的文档数量; d 为不在该分类下,且不包含这个词的文档数量。
(b)然后得到该类中该词的卡方统计值 公式为 float(pow((ad - bc), 2)) /float((a+c) * (a+b) * (b+d) * (c+d))。
(c)对每一类别的所有词按卡方值进行排序,取前 k 个作为该类的特征值,这里我们取 k 为 1000
# 使用开方检验选择特征
# 按UTF-8编码格式读取文件
import codecs
import math
import sys
ClassCode = [ '财经','房产','股票','家居','科技','时政','娱乐' ]
# 构建每个类别的词Set
# 分词后的文件路径
textCutBasePath = "SogouDataCut/"
# 构建每个类别的词向量
def buildItemSets(classDocCount):
termDic = dict()
# 每个类别下的文档集合用list<set>表示, 每个set表示一个文档,整体用一个dict表示
termClassDic = dict()
for eachclass in ClassCode:
currClassPath = textCutBasePath+eachclass+"/"
eachClassWordSets = set()
eachClassWordList = list()
for i in range(classDocCount):
eachDocPath = currClassPath+str(i)+".txt"
eachFileObj = open(eachDocPath, 'r')
eachFileContent = eachFileObj.read()
eachFileWords = eachFileContent.split(" ")
eachFileSet = set()
for eachword in eachFileWords:
stripEachWord = eachword.strip(" ")
if len(stripEachWord) > 0:
eachFileSet.add(eachword)
eachClassWordSets.add(eachword)
eachClassWordList.append(eachFileSet)
termDic[eachclass] = eachClassWordSets
termClassDic[eachclass] = eachClassWordList
return termDic, termClassDic
# 对得到的两个词典进行计算,可以得到a b c d 值
# K为每个类别选取的特征个数
# 卡方计算公式
def ChiCalc(a, b, c, d):
result = float(pow((a*d - b*c), 2)) /float((a+c) * (a+b) * (b+d) * (c+d))
return result
def featureSelection(termDic, termClassDic, K):
termCountDic = dict()
for key in termDic:
# C000008
classWordSets = termDic[key]
# print(classWordSets)
classTermCountDic = dict()
for eachword in classWordSets: # 对某个类别下的每一个单词的 a b c d 进行计算
# 对卡方检验所需的 a b c d 进行计算
# a:在这个分类下包含这个词的文档数量
# b:不在该分类下包含这个词的文档数量
# c:在这个分类下不包含这个词的文档数量
# d:不在该分类下,且不包含这个词的文档数量
a = 0
b = 0
c = 0
d = 0
for eachclass in termClassDic:
# C000008
if eachclass == key: #在这个类别下进行处理
for eachdocset in termClassDic[eachclass]:
if eachword in eachdocset:
a = a + 1
else:
c = c + 1
else: # 不在这个类别下进行处理
for eachdocset in termClassDic[eachclass]:
if eachword in eachdocset:
b = b + 1
else:
d = d + 1
eachwordcount = ChiCalc(a, b, c, d)
classTermCountDic[eachword] = eachwordcount
# 对生成的计数进行排序选择前K个
# 这个排序后返回的是元组的列表
sortedClassTermCountDic = sorted(classTermCountDic.items(), key=lambda d:d[1], reverse=True)
count = 0
subDic = dict()
for i in range(K):
subDic[sortedClassTermCountDic[i][0]] = sortedClassTermCountDic[i][1]
termCountDic[key] = subDic
return termCountDic
def writeFeatureToFile(termCountDic , fileName):
featureSet = set()
for key in termCountDic:
for eachkey in termCountDic[key]:
featureSet.add(eachkey)
count = 1
file = open(fileName, 'w')
for feature in featureSet:
# 判断feature 不为空
stripfeature = feature.strip(" ")
if len(stripfeature) > 0 and feature != " " :
file.write(str(count)+" " +feature+"\n")
count = count + 1
print(feature)
file.close()
if __name__ == '__main__':
# 调用buildItemSets
# buildItemSets形参表示每个类别的文档数目,在这里训练模型时每个类别取前200个文件
termDic, termClassDic = buildItemSets(1200)
termCountDic = featureSelection(termDic, termClassDic, 1000)
writeFeatureToFile(termCountDic, "SVMFeature.txt")
3)利用TFIDF算法进行特征权重计算
# import FeatureSelecion
import math
import sys
# 采用TF-IDF 算法对选取得到的特征进行计算权重
documentCount = 200 # 每个类别选取200篇文档
ClassCode = [ '财经','房产','股票','家居','科技','时政','娱乐' ]
# 构建每个类别的词Set
# 分词后的文件路径
textCutBasePath = "SogouDataCut/"
def readFeature(featureName):
featureFile = open(featureName, 'r')
featureContent = featureFile.read().split('\n')
featureFile.close()
feature = list()
for eachfeature in featureContent:
eachfeature = eachfeature.split(" ")
if (len(eachfeature)==2):
feature.append(eachfeature[1])
# print(feature)
return feature
# 读取所有类别的训练样本到字典中,每个文档是一个list
def readFileToList(textCutBasePath, ClassCode, documentCount):
dic = dict()
for eachclass in ClassCode:
currClassPath = textCutBasePath + eachclass + "/"
eachclasslist = list()
for i in range(documentCount):
eachfile = open(currClassPath+str(i)+".txt")
eachfilecontent = eachfile.read()
eachfilewords = eachfilecontent.split(" ")
eachclasslist.append(eachfilewords)
# print(eachfilewords)
dic[eachclass] = eachclasslist
return dic
# 计算特征的逆文档频率
def featureIDF(dic, feature, dffilename):
dffile = open(dffilename, "w")
dffile.close()
dffile = open(dffilename, "a")
totalDocCount = 0
idffeature = dict()
dffeature = dict()
for eachfeature in feature:
docFeature = 0
for key in dic:
totalDocCount = totalDocCount + len(dic[key])
classfiles = dic[key]
for eachfile in classfiles:
if eachfeature in eachfile:
docFeature = docFeature + 1
# 计算特征的逆文档频率
featurevalue = math.log(float(totalDocCount)/(docFeature+1))
dffeature[eachfeature] = docFeature
# 写入文件,特征的文档频率
dffile.write(eachfeature + " " + str(docFeature)+"\n")
# print(eachfeature+" "+str(docFeature))
idffeature[eachfeature] = featurevalue
dffile.close()
return idffeature
# 计算Feature's TF-IDF 值
def TFIDFCal(feature, dic,idffeature,filename):
file = open(filename, 'w')
file.close()
file = open(filename, 'a')
for key in dic:
classFiles = dic[key]
# 谨记字典的键是无序的
classid = ClassCode.index(key)
for eachfile in classFiles:
# 对每个文件进行特征向量转化
file.write(str(classid)+" ")
for i in range(len(feature)):
if feature[i] in eachfile:
currentfeature = feature[i]
featurecount = eachfile.count(feature[i])
tf = float(featurecount)/(len(eachfile))
# 计算逆文档频率
featurevalue = idffeature[currentfeature]*tf
file.write(str(i+1)+":"+str(featurevalue) + " ")
file.write("\n")
if __name__ == '__main__':
dic = readFileToList(textCutBasePath, ClassCode, documentCount)
feature = readFeature("SVMFeature.txt")
# print(len(feature))
idffeature = featureIDF(dic, feature, "dffeature.txt")
TFIDFCal(feature, dic,idffeature, "train.svm")
4)使用LISBVM库训练SVM模型
使用libsvm对train.svm进行模型训练,和对test.svm模型进行预测
测试命令:
对train.svm文件数据进行缩放到[0,1]区间
./svm-scale -l 0 -u 1 train.svm > trainscale.svm
对test.svm文件数据进行缩放到[0,1]区间
./svm-scale -l 0 -u 1 test.svm > testscale.svm
对trainscale.svm 文件进行模型训练
./svm-train -s 1 trainscale.svm trainscale.model
对testscale.svm 文件进行模型预测,得到预测结果,控制台会输出正确率
./svm-predict testscale.svm trainscale.model testscale.result
参考:https://github.com/alicelmx/SVM-Chinese-Classification
3. LDA主题模型
(1)主题模型
主题模型这样理解一篇文章的生成过程:
1) 确定文章的K个主题。
2) 重复选择K个主题之一,按主题-词语概率生成词语。
3) 所有词语组成文章。
这里可以看到,主题模型仅仅考虑词语的数量,不考虑词语的顺序,所以主题模型是词袋模型。
主题模型有两个关键的过程:
1) doc -> topic
2) topic -> word
其中topic -> word是定值,doc -> topic是随机值。这是显而易见的,对于不同的文章,它的主题不尽相同,但是对于同一个主题,它的词语概率应该是一致的。好比记者写了一篇科技新闻和一篇金融新闻,两篇新闻的主题分布必然不同,但是这两篇文章都包含数学主题,那么对于数学主题出现的词语应该大致相同。
主题模型的关键就是要计算出topic -> word过程,也就是topic-word概率分布。对于一篇新的文章,我们已知它的词语数量分布,又训练出了topic-word概率分布,则可以使用最优化方法分析出文章对应的K个最大似然主题。
(2)pLSA、共轭先验分布
1)pLSA
PSLA主题模型正是上述思想的直接体现,文章生成过程如下
PLSA主题模型图形化过程如下:
我们考虑第m篇文档的生成过程,其中涉及1个doc-topic骰子,K个topic-word骰子。记第m篇文档为,第m篇文章出现第z个主题的概率为,第z个主题生成词语w的概率为(这里与文档有关系,与文档没有关系)。对于某个词语的生成概率,即投掷一次doc-topic骰子与一次topic-word骰子生成词语w的概率为
于是第m篇文档的n个词语生成概率为
如果我们有M篇文档,考虑到文档之间独立,则所有词语生成的概率为M个的乘积。
PLSA模型最优化包含两个参数求解,可以使用EM算法计算。
2)共轭先验分布
在贝叶斯统计中,如果后验分布与先验分布属于同类,则先验分布与后验分布被称为共轭分布,而先验分布被称为似然函数的共轭先验。比如,高斯分布家族在高斯似然函数下与其自身共轭(自共轭)。这个概念,以及"共轭先验"这个说法,由霍华德·拉法拉和罗伯特·施莱弗尔在他们关于贝叶斯决策理论的工作中提出。类似的概念也曾由乔治·阿尔弗雷德·巴纳德独立提出。
具体地说,就是给定贝叶斯公式 假定似然函数 是已知的,问题就是选取什么样的先验分布
会让后验分布与先验分布具有相同的数学形式。
共轭先验的好处主要在于代数上的方便性,可以直接给出后验分布的封闭形式,否则的话只能数值计算。共轭先验也有助于获得关于似然函数如何更新先验分布的直观印象
(3)使用LDA生成主题特征,在之前特征的基础上加入主题特征进行文本分类
原理:https://blog.csdn.net/Kaiyuan_sjtu/article/details/83572927
代码部分:
1)调用数据集
from gensim.test.utils import common_texts
from gensim.corpora.dictionary import Dictionary
# Create a corpus from a list of texts
common_dictionary = Dictionary(common_texts)
common_corpus = [common_dictionary.doc2bow(text) for text in common_texts]
# Train the model on the corpus.
lda = LdaModel(common_corpus, num_topics=10)
2)词袋模型
from gensim.corpora import Dictionary
dct = Dictionary(["máma mele maso".split(), "ema má máma".split()])
dct.doc2bow(["this", "is", "máma"])
[(2, 1)]
dct.doc2bow(["this", "is", "máma"], return_missing=True)
([(2, 1)], {u'this': 1, u'is': 1})
3)运用lda模型
from gensim.models import LdaModel
lda = LdaModel(common_corpus, num_topics=10)
lda.print_topic(1, topn=2)
'0.500*"9" + 0.045*"10"
参考:https://blog.csdn.net/starmoth/article/details/88366732