'''创建数据集和类标签'''
def loadDataSet():
docList = [];classList = [] # 文档列表、类别列表
dirlist = ['C3-Art','C4-Literature','C5-Education','C6-Philosophy','C7-History']
for j in range(5):
for i in range(1, 11): # 总共10个文档
# 切分,解析数据,并归类为 1 类别
wordList = textParse(open('./fudan/%s/%d.txt' % (dirlist[j],i),encoding='UTF-8').read())
docList.append(wordList)
classList.append(j)
# print(i,'\t','./fudan/%s/%d.txt' % (dirlist[j],i),'\t',j)
return docList,classList
''' 利用jieba对文本进行分词,返回切词后的list '''
def textParse(str_doc):
# 正则过滤掉特殊符号、标点、英文、数字等。
import re
r1 = '[a-zA-Z0-9’!"#$%&\'()*+,-./:;<=>?@,。?★、…【】《》?“”‘’![\\]^_`{|}~]+'
str_doc=re.sub(r1, '', str_doc)
# 创建停用词列表
stwlist = set([line.strip() for line in open('./stopwords.txt', 'r', encoding='utf-8').readlines()])
sent_list = str_doc.split('\n')
# word_2dlist = [rm_tokens(jieba.cut(part), stwlist) for part in sent_list] # 分词并去停用词
word_2dlist = [rm_tokens([word+"/"+flag+" " for word, flag in pseg.cut(part) if flag in ['n','v','a','ns','nr','nt']], stwlist) for part in sent_list] # 带词性分词并去停用词
word_list = list(itertools.chain(*word_2dlist)) # 合并列表
return word_list
''' 去掉一些停用词、数字、特殊符号 '''
def rm_tokens(words, stwlist):
words_list = list(words)
for i in range(words_list.__len__())[::-1]:
word = words_list[i]
if word in stwlist: # 去除停用词
words_list.pop(i)
elif len(word) == 1: # 去除单个字符
words_list.pop(i)
elif word == " ": # 去除空字符
words_list.pop(i)
return words_list
'''获取所有文档单词的集合'''
def createVocabList(dataSet):
vocabSet = set([])
for document in dataSet:
vocabSet = vocabSet | set(document) # 操作符 | 用于求两个集合的并集
# print(len(vocabSet),len(set(vocabSet)))
return list(vocabSet)
'''文档词袋模型,创建矩阵数据'''
def bagOfWords2VecMN(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]) # 总单词数
p1Num=p2Num=p3Num=p4Num=p5Num = ones(numWords) # 各类为1的矩阵
p1Denom=p2Denom=p3Denom=p4Denom=p5Denom = 2.0 # 各类特征和
num1=num2=num3=num4=num5 = 0 # 各类文档数目
pNumlist=[p1Num,p2Num,p3Num,p4Num,p5Num]
pDenomlist =[p1Denom,p2Denom,p3Denom,p4Denom,p5Denom]
Numlist = [num1,num2,num3,num4,num5]
for i in range(numTrainDocs): # 遍历每篇训练文档
for j in range(5): # 遍历每个类别
if trainCategory[i] == j: # 如果在类别下的文档
pNumlist[j] += trainMatrix[i] # 增加词条计数值
pDenomlist[j] += sum(trainMatrix[i]) # 增加该类下所有词条计数值
Numlist[j] +=1 # 该类文档数目加1
pVect,pi = [],[]
for index in range(5):
pVect.append(log(pNumlist[index] / pDenomlist[index]))
pi.append(Numlist[index] / float(numTrainDocs))
return pVect, pi
'''朴素贝叶斯分类函数,将乘法转换为加法'''
def classifyNB(vec2Classify, pVect,pi):
# 计算公式 log(P(F1|C))+log(P(F2|C))+....+log(P(Fn|C))+log(P(C))
bnpi = [] # 文档分类到各类的概率值列表
for x in range(5):
bnpi.append(sum(vec2Classify * pVect[x]) + log(pi[x]))
# print([bnp for bnp in bnpi])
# 分类集合
reslist = ['Art','Literature','Education','Philosophy','History']
# 根据最大概率,选择索引值
index = [bnpi.index(res) for res in bnpi if res==max(bnpi)]
return reslist[index[0]] # 返回分类值
'''朴素贝叶斯新闻分类应用'''
def testingNB():
# 1. 加载数据集
dataSet,Classlabels = loadDataSet()
# 2. 创建单词集合
myVocabList = createVocabList(dataSet)
# 3. 计算单词是否出现并创建数据矩阵
trainMat = []
for postinDoc in dataSet:
trainMat.append(bagOfWords2VecMN(myVocabList, postinDoc))
with open('./word-bag.txt','w') as f:
for i in trainMat:
f.write(str(i)+'\r\n')
# 4. 训练数据
pVect,pi= trainNB0(array(trainMat), array(Classlabels))
# 5. 测试数据
testEntry = textParse(open('./fudan/test/C5-1.txt',encoding='UTF-8').read())
thisDoc = array(bagOfWords2VecMN(myVocabList, testEntry))
print(testEntry[:10], '分类结果是: ', classifyNB(thisDoc, pVect,pi))
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\ADMINI~1\AppData\Local\Temp\jieba.cache
Loading model cost 0.892 seconds.
Prefix dict has been built succesfully.
['全国/n ', '举办/v ', '电影/n ', '新华社/nt ', '北京/ns ', '国家教委/nt ', '广播电影电视部/nt ', '文化部/n ', '联合/v ', '决定/v '] 分类结果是: Literature
耗时:29.4882 s
结果分析:我们运行分类器得出结果易知,预测结果是文化类,且运行时间为29s。首先分析为什么预测错误,这里面主要是训练集样本比较少和特征选择的原因。运行时间是由于将特征矩阵存储本地后,后面直接读取文本,相当于加载缓存,大大缩短运行时间。但是这里还有值得优化的地方,比如每次运行都会加载训练模型,大大消耗时间,我们能不能训练模型加载一次,多次调用呢?当然是可以的,这个问题下文继续优化。我们重点关注下特征选择问题
特征选择问题讨论
- 做文本分类的时候,遇到特征矩阵1.5w。在测试篇幅小的文章总是分类错误?这个时候如何做特征选择?是不是说去掉特征集中频率极高和极低的一部分,对结果有所提升?
答:你说的这个情况是很普遍的现象,篇幅小的文章,特征小,所以模型更容易判断出错!去掉高频和低频通常是可以使得训练的模型泛化能力变强 - 比如:艺术,文化,历史,教育。界限本来就不明显,比如测试数据“我爱艺术,艺术是我的全部”。结果会分类为文化。其实这个里面还有就是不同特征词的权重问题,采用tf-idf优化下应该会好一些?
答:我个人觉得做文本特征提取,还是需要自己去分析文本本身内容的文字特点,你可以把每一类的文本的实体提取出来,然后统计一下每个词在每一类上的数量,看看数量分布,也许可以发现一些数据特点 - 我就是按照这个思路做的,还有改进时候的停用词,其实可以分析特征文本,针对不同业务,使用自定义的停用词要比通用的好
还有提前各类见最具表征性的词汇加权,凸显本类的权重是吧?
答:比如,艺术类文章中,哪些词出现较多,哪些词出现少,再观察这些词的词性主要是哪些,这样可能会对你制定提取特征规则方式的时候提供一定的思路参考,我可以告诉你的是,有些词绝对会某一类文章出出现多,然后在其他类文章出现很少,这一类的词就是文章的特征词 - 那样的思路可以是:对某类文章单独构建类内的词汇表再进行选择。最后对类间词汇表叠加就ok了。
答:词汇表有个缺点就是,不能很好的适应新词 - 改进思路呢
答:我给你一个改进思路:你只提取每个文本中的名词、动词、形容词、地名,用这些词的作为文本的特征来训练试一试,用文本分类用主题模型(LDA)来向量化文本,再训练模型试一试。如果效果还是不够好,再将文本向量用PCA进行一次特征降维,然后再训练模型试一试,按常理来说,效果应该会有提高 - 还有我之前个人写的程序分类效果不理想,后来改用sklearn内置BN运行依旧不理想。适当改进了特征提取,还是不理想。估计每类10篇文章的训练数据太少了
答:文本本身特征提取就相对难一些,再加上训练数据少,训练出来的模型效果可想而已,正常的