中文情感分析——snownlp類庫 源碼註釋及使用

最近發現了snownlp這個庫,這個類庫是專門針對中文文本進行文本挖掘的。python

主要功能:

  • 中文分詞(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/snownlpgit

使用及源碼分析:

snownlp類庫的安裝:github

$ pip install snownlp

使用snownlp進行情感分析:web

# -*- 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'''
天然語言處理是計算機科學領域與人工智能領域中的一個重要方向。
它研究能實現人與計算機之間用天然語言進行有效通訊的各類理論和方法。
天然語言處理是一門融語言學、計算機科學、數學於一體的科學。
所以,這一領域的研究將涉及天然語言,即人們平常使用的語言,
因此它與語言學的研究有着密切的聯繫,但又有重要的區別。
天然語言處理並非通常地研究天然語言,
而在於研製能有效地實現天然語言通訊的計算機系統,
特別是其中的軟件系統。於是它是計算機科學的一部分。
'''

s = SnowNLP(text)

s.keywords(3)    # [u'語言', u'天然', u'計算機']

s.summary(3)    # [u'於是它是計算機科學的一部分',
                #  u'天然語言處理是一門融語言學、計算機科學、
                #     數學於一體的科學',
                #  u'天然語言處理是計算機科學領域與人工智能
                #     領域中的一個重要方向']
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.3756070762985226, 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__文件api

sentiment中建立了Sentiment對象app

首先調用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.獲得訓練數據後,使用樸素貝葉斯分類進行分類

該方法可自行查閱。

相關文章
相關標籤/搜索