最近发现了snownlp这个库,这个类库是专门针对中文文本进行文本挖掘的。
主要功能:
- 中文分词(Character-Based Generative Model)
- 词性标注(TnT 3-gram 隐马)
- 情感分析(现在训练数据主要是买卖东西时的评价,所以对其他的一些可能效果不是很好,待解决)
- 文本分类(Naive Bayes)
- 转换成拼音(Trie树实现的最大匹配)
- 繁体转简体(Trie树实现的最大匹配)
- 提取文本关键词(TextRank算法)
- 提取文本摘要(TextRank算法)
- tf,idf
- Tokenization(分割成句子)
- 文本相似(BM25)
- 支持python3(感谢erning)
官网信息:
snownlp github:https://github.com/isnowfy/snownlp
使用及源码分析:
使用snownlp进行情感分析:
from snownlp import SnowNLP #创建snownlp对象,设置要测试的语句 s = SnowNLP(‘这东西不错。。‘) # 调用sentiments方法获取积极情感概率 print(s.sentiments)
实现过程:
1.首先从SnowNLP入手,看一下sentiments方法,在sentiments方法中,调用了sentiment下的分类方法。
# -*- coding: utf-8 -*- from __future__ import unicode_literals from . import normal from . import seg from . import tag from . import sentiment from .sim import bm25 from .summary import textrank from .summary import words_merge class SnowNLP(object): def __init__(self, doc): self.doc = doc self.bm25 = bm25.BM25(doc) @property def words(self): return seg.seg(self.doc) @property def sentences(self): return normal.get_sentences(self.doc) @property def han(self): return normal.zh2hans(self.doc) @property def pinyin(self): return normal.get_pinyin(self.doc) @property def sentiments(self): return sentiment.classify(self.doc)#调用了sentiment的classify分类方法 @property def tags(self): words = self.words tags = tag.tag(words) return zip(words, tags) @property def tf(self): return self.bm25.f @property def idf(self): return self.bm25.idf def sim(self, doc): return self.bm25.simall(doc) def summary(self, limit=5): doc = [] sents = self.sentences for sent in sents: words = seg.seg(sent) words = normal.filter_stop(words) doc.append(words) rank = textrank.TextRank(doc) rank.solve() ret = [] for index in rank.top_index(limit): ret.append(sents[index]) return ret def keywords(self, limit=5, merge=False): doc = [] sents = self.sentences for sent in sents: words = seg.seg(sent) words = normal.filter_stop(words) doc.append(words) rank = textrank.KeywordTextRank(doc) rank.solve() ret = [] for w in rank.top_index(limit): ret.append(w) if merge: wm = words_merge.SimpleMerge(self.doc, ret) return wm.merge() return ret
2.sentiment文件夹下的__init__文件
sentiment中创建了Sentiment对象
首先调用load方法加载训练好的数据字典,然后调用classify方法,在classify方法中实际调用的是Bayes对象中的classify方法。
# -*- coding: utf-8 -*- from __future__ import unicode_literals import os import codecs from .. import normal from .. import seg from ..classification.bayes import Bayes #数据文件路径 data_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), ‘sentiment.marshal‘) class Sentiment(object): def __init__(self): #创建Bayes对象 self.classifier = Bayes() #保存训练好的字典数据 def save(self, fname, iszip=True): self.classifier.save(fname, iszip) #加载字典数据 def load(self, fname=data_path, iszip=True): self.classifier.load(fname, iszip) #对文档分词 def handle(self, doc): words = seg.seg(doc) words = normal.filter_stop(words) return words # 训练数据集 def train(self, neg_docs, pos_docs): data = [] #读取消极评论list,同时为每条评论加上neg标签,也放入到一个list中 for sent in neg_docs: data.append([self.handle(sent), ‘neg‘]) #读取积极评论list,为每条评论加上pos标签 for sent in pos_docs: data.append([self.handle(sent), ‘pos‘]) #调用分类器的训练数据集方法,对模型进行训练 self.classifier.train(data) #分类 def classify(self, sent): #调用贝叶斯分类器的分类方法,获取分类标签和概率 ret, prob = self.classifier.classify(self.handle(sent)) #如果分类标签是pos直接返回概率值 if ret == ‘pos‘: return prob #如果返回的是neg,由于显示的是积极概率值,因此用1减去消极概率值 return 1-prob classifier = Sentiment() classifier.load() #训练数据 def train(neg_file, pos_file): #打开消极数据文件 neg = codecs.open(neg_file, ‘r‘, ‘utf-8‘).readlines() pos = codecs.open(pos_file, ‘r‘, ‘utf-8‘).readlines() neg_docs = [] pos_docs = [] #遍历每一条消极评论,放入到list中 for line in neg: neg_docs.append(line.rstrip("\r\n")) #遍历每一条积极评论,放入到list中 for line in pos: pos_docs.append(line.rstrip("\r\n")) global classifier classifier = Sentiment() #训练数据,传入积极、消极评论list classifier.train(neg_docs, pos_docs) #保存数据字典 def save(fname, iszip=True): classifier.save(fname, iszip) #加载数据字典 def load(fname, iszip=True): classifier.load(fname, iszip) #对语句进行分类 def classify(sent): return classifier.classify(sent)
sentiment中包含了训练数据集的方法,看一下是如何训练数据集的:
在sentiment文件夹下,包含了以下文件:
neg.txt和pos.txt是已经分类好的评论数据,neg.txt中都是消极评论,pos中是积极评论
sentiment.marshal和sentiment.marshal.3中存放的是序列化后的数据字典,这个也稍后再说
(1)在train()方法中,首先读取消极和积极评论txt文件,然后获取每一条评论,放入到list集合中,格式大致如下
[ ‘ 还没有收到书!!!还没有收到书 ‘ , ‘ 小熊宝宝我觉得孩子不喜欢,能换别的吗 ‘ , ......]
#训练数据 def train(neg_file, pos_file): #打开消极数据文件 neg = codecs.open(neg_file, ‘r‘, ‘utf-8‘).readlines() pos = codecs.open(pos_file, ‘r‘, ‘utf-8‘).readlines() neg_docs = [] pos_docs = [] #遍历每一条消极评论,放入到list中 for line in neg: neg_docs.append(line.rstrip("\r\n")) #遍历每一条积极评论,放入到list中 for line in pos: pos_docs.append(line.rstrip("\r\n")) global classifier classifier = Sentiment() #训练数据,传入积极、消极评论list classifier.train(neg_docs, pos_docs)
然后调用了Sentiment对象中的train()方法:
在train方法中,遍历了传入的积极、消极评论list,为每条评论进行分词,并为加上了分类标签,此时的数据格式如下:
评论分词后的数据格式:[‘收到‘,‘没有‘...]
加上标签后的数据格式(以消极评论为例):[ [[‘收到‘,‘没有‘ ...],‘neg‘] , [[‘小熊‘,‘宝宝‘ ...],‘neg’] ........]]
可以看到每一条评论都是一个list,其中又包含了评论分词后的list和评论的分类标签
# 训练数据集 def train(self, neg_docs, pos_docs): data = [] #读取消极评论list,对每条评论分词,并加上neg标签,也放入到一个list中 for sent in neg_docs: data.append([self.handle(sent), ‘neg‘]) #读取积极评论list,为每条评论分词,加上pos标签 for sent in pos_docs: data.append([self.handle(sent), ‘pos‘]) #调用分类器的训练数据集方法,对模型进行训练 self.classifier.train(data)
经过了此步骤,已经对数据处理完毕,接下来就可以对数据进行训练
3.classification下的bayes.py
# -*- coding: utf-8 -*- from __future__ import unicode_literals import sys import gzip import marshal from math import log, exp from ..utils.frequency import AddOneProb class Bayes(object): def __init__(self): #标签数据对象 self.d = {} #所有分类的词数之和 self.total = 0 #保存字典数据 def save(self, fname, iszip=True): #创建对象,用来存储训练结果 d = {} #添加total,也就是积极消极评论分词总词数 d[‘total‘] = self.total #d为分类标签,存储每个标签的数据对象 d[‘d‘] = {} for k, v in self.d.items(): #k为分类标签,v为标签对应的所有分词数据,是一个AddOneProb对象 d[‘d‘][k] = v.__dict__ #这里判断python版本 if sys.version_info[0] == 3: fname = fname + ‘.3‘ #这里可有两种方法可以选择进行存储 if not iszip: ##将序列化后的二进制数据直接写入文件 marshal.dump(d, open(fname, ‘wb‘)) else: #首先获取序列化后的二进制数据,然后写入文件 f = gzip.open(fname, ‘wb‘) f.write(marshal.dumps(d)) f.close() #加载数据字典 def load(self, fname, iszip=True): #判断版本 if sys.version_info[0] == 3: fname = fname + ‘.3‘ #判断打开文件方式 if not iszip: d = marshal.load(open(fname, ‘rb‘)) else: try: f = gzip.open(fname, ‘rb‘) d = marshal.loads(f.read()) except IOError: f = open(fname, ‘rb‘) d = marshal.loads(f.read()) f.close() #从文件中读取数据,为total和d对象赋值 self.total = d[‘total‘] self.d = {} for k, v in d[‘d‘].items(): self.d[k] = AddOneProb() self.d[k].__dict__ = v #训练数据集 def train(self, data): #遍历数据集 for d in data: #d[1]标签-->分类类别 c = d[1] #判断数据字典中是否有当前的标签 if c not in self.d: #如果没有该标签,加入标签,值是一个AddOneProb对象 self.d[c] = AddOneProb() #d[0]是评论的分词list,遍历分词list for word in d[0]: #调用AddOneProb中的add方法,添加单词 self.d[c].add(word, 1) #计算总词数 self.total = sum(map(lambda x: self.d[x].getsum(), self.d.keys())) #贝叶斯分类 def classify(self, x): tmp = {} #遍历每个分类标签 for k in self.d: #获取每个分类标签下的总词数和所有标签总词数,求对数差相当于log(某标签下的总词数/所有标签总词数) tmp[k] = log(self.d[k].getsum()) - log(self.total) for word in x: #获取每个单词出现的频率,log[(某标签下的总词数/所有标签总词数)*单词出现频率] tmp[k] += log(self.d[k].freq(word)) #计算概率,由于直接得到的概率值比较小,这里应该使用了一种方法来转换,原理还不是很明白 ret, prob = 0, 0 for k in self.d: now = 0 try: for otherk in self.d: now += exp(tmp[otherk]-tmp[k]) now = 1/now except OverflowError: now = 0 if now > prob: ret, prob = k, now return (ret, prob)
from . import good_turing class BaseProb(object): def __init__(self): self.d = {} self.total = 0.0 self.none = 0 def exists(self, key): return key in self.d def getsum(self): return self.total def get(self, key): if not self.exists(key): return False, self.none return True, self.d[key] def freq(self, key): return float(self.get(key)[1])/self.total def samples(self): return self.d.keys() class NormalProb(BaseProb): def add(self, key, value): if not self.exists(key): self.d[key] = 0 self.d[key] += value self.total += value class AddOneProb(BaseProb): def __init__(self): self.d = {} self.total = 0.0 self.none = 1 #添加单词 def add(self, key, value): #更新该类别下的单词总数 self.total += value #如果单词未出现过 if not self.exists(key): #将单词加入对应标签的数据字典中,value设为1 self.d[key] = 1 #更新总词数 self.total += 1 #如果单词出现过,对该单词的value值加1 self.d[key] += value
在bayes对象中,有两个属性d和total,d是一个数据字典,total存储所有分类的总词数,经过train方法训练数据集后,d中存储的是每个分类标签的数据key为分类标签,value是一个AddOneProb对象。
def __init__(self): self.d = {} self.total = 0.0
在AddOneProb对象中,同样存在d和total属性,这里的total存储的是每个分类各自的单词总数,d中存储的是所有出现过的单词,单词作为key,单词出现的次数作为value.
为了下次计算概率时,不用重新训练,可以将训练得到的数据序列化到文件中,下次直接加载文件,将文件反序列为对象,从对象中获取数据即可(save和load方法)。
4.得到训练数据后,使用朴素贝叶斯分类进行分类
该方法可自行查阅。