中文情感分析——snownlp类库 源码注释及使用

  • 时间:
  • 浏览:0

最近发现了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类库的安装:

使用snownlp进行爱情分析:

# -*- coding:utf-8 -*-
from snownlp import SnowNLP

#创建snownlp对象,设置要测试的搞笑的话
s = SnowNLP(u'买来给家婆用来洗儿子的衣服的')

print("1",s.words)   
                #将搞笑的话分成单词      
                # ['买', '来', '给', '家婆', '用', '来', '洗', '儿子', '的', '衣服', '的']

s.tags         
                # 你是什么:[(u'很多', u'r'), (u'东西', u'n'),
                #  (u'真心', u'd'), (u'很', u'd'),
                #  (u'赞', u'Vg')]

# 调用sentiments妙招获取积极爱情概率 positive的概率
print("2",s.sentiments)

s.pinyin        # 将汉字搞笑的话转换为Pinyin搞笑的话
                # 你是什么:[u'zhe', u'ge', u'dong', u'xi',
                #  u'zhen', u'xin', u'hen', u'zan']
#————————————————————————————————————————————————————————————————————————————————————————————————————————
s = SnowNLP(u'「繁體字」「繁體中文」的叫法在臺灣亦很常見。')

s.han           #将繁体字转换为简体字      
                # u'「繁体字」「繁体中文」的叫法
                # 在台湾亦很常见。'
#————————————————————————————————————————————————————————————————————————————————————————————————————————
text = u'''
自然语言正确处理是计算机科学领域与人工智能领域中的1个多多


重要方向。
它研究能实现人与计算机之间用自然语言进行有效通信的各种理论和妙招。
自然语言正确处理是一门融语言学、计算机科学、数学于一体的科学。
我希望,很多领域的研究将涉及自然语言,即亲们日常使用的语言,
很多它与语言学的研究有着密切的联系,但又有重要的区别。
自然语言正确处理并都会一般地研究自然语言,
而在于研制能有效地实现自然语言通信的计算机系统,
有点硬是其中的软件系统。因而它是计算机科学的一每种。
'''

s = SnowNLP(text)

s.keywords(3)    # [u'语言', u'自然', u'计算机']

s.summary(3)    # [u'因而它是计算机科学的一每种',
                #  u'自然语言正确处理是一门融语言学、计算机科学、
                #     数学于一体的科学',
                #  u'自然语言正确处理是计算机科学领域与人工智能
                #     领域中的1个多多


重要方向']
s.sentences
                #分成搞笑的话
#————————————————————————————————————————————————————————————————————————————————————————————————————————
s = SnowNLP([[u'这篇', u'文章'],
             [u'那篇', u'论文'],
             [u'很多']])
print(s.tf)     #TF意思是词频(Term Frequency)
print(s.idf)    #IDF意思是逆文本频率指数(Inverse Document Frequency)  
s.sim([u'文章'])# [0.37515070762985226, 0, 0]

实现过程:

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标签,也贴到

到1个多多


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’] ........]]

还要能想看 每十根评论都会1个多多 list,其中又饱含了评论分词后的list和评论的分类标签

# 训练数据集
    def train(self, neg_docs, pos_docs):
        data = []
        #读撤除极评论list,对每条评论分词,并加进去去neg标签,也贴到

到1个多多


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为标签对应的所有分词数据,是1个多多


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:
                #机会这麼该标签,加入标签,值是1个多多


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对象中,有1个多多 属性d和total,d是1个多多 数据字典,total存储所有分类的总词数,经过train妙招训练数据集后,d中存储的是每个分类标签的数据key为分类标签,value是1个多多 AddOneProb对象。

def __init__(self):
        self.d = {}
        self.total = 0.0

在AddOneProb对象中,同样占据 d和total属性,这里的total存储的是每个分类各自 的单词总数,d中存储的是所有经常跳出过的单词,单词作为key,单词经常跳出的次数作为value.

为了下次计算概率时,不不重新训练,还要能将训练得到的数据序列化到文件中,下次直接加载文件,将文件反序列为对象,从对象中获取数据即可(save和load妙招)。

4.得到训练数据后,使用朴素贝叶斯分类进行分类

该妙招可自行查阅。