snownlps是用Python寫的個中文情感分析的包,自帶了中文正負情感的訓練集,主要是評論的語料庫。使用的是朴素貝葉斯原理來訓練和預測數據。主要看了一下這個包的幾個主要的核心代碼,看的過程作了一些注釋,記錄一下免得以后再忘了。
1. sentiment文件夾下的__init__.py,主要是集成了前面寫的幾個模塊的功能,進行打包。
1 # -*- coding: utf-8 -*- 2 from __future__ import unicode_literals 3 4 import os 5 import codecs 6 7 from .. import normal 8 from .. import seg 9 from ..classification.bayes import Bayes 10 11 data_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 12 'sentiment.marshal') 13 14 15 class Sentiment(object): 16 17 def __init__(self):# 實例化Bayes()類作為屬性,下面的很多方法都是調用的Bayes()的方法完成的 18 self.classifier = Bayes() 19 20 def save(self, fname, iszip=True):# 保存最終的模型 21 self.classifier.save(fname, iszip) 22 23 def load(self, fname=data_path, iszip=True): 24 self.classifier.load(fname, iszip)# 加載貝葉斯模型 25 26 # 分詞以及去停用詞的操作 27 def handle(self, doc): 28 words = seg.seg(doc)# 分詞 29 words = normal.filter_stop(words)# 去停用詞 30 return words# 返回分詞后的結果,是一個list列表 31 32 def train(self, neg_docs, pos_docs): 33 data = [] 34 for sent in neg_docs:# 讀入負樣本 35 data.append([self.handle(sent), 'neg']) 36 # 所以可以看出進入bayes()的訓練的數據data格式是[[[第一行分詞],類別], 37 # [[第二行分詞], 類別], 38 # [[第n行分詞],類別] 39 # ] 40 for sent in pos_docs: # 讀入正樣本 41 data.append([self.handle(sent), 'pos']) 42 self.classifier.train(data) # 調用的是Bayes模型的訓練方法train() 43 44 def classify(self, sent): 45 ret, prob = self.classifier.classify(self.handle(sent))#得到分類結果和概率 46 if ret == 'pos':#默認返回的是pos('正面'),否則就是負面 47 return prob 48 return 1-prob 49 50 51 classifier = Sentiment()#實例化Sentiment()對象 52 classifier.load() 53 54 55 def train(neg_file, pos_file): 56 #讀取正負語料庫文本 57 neg_docs = codecs.open(neg_file, 'r', 'utf-8').readlines() 58 pos_docs = codecs.open(pos_file, 'r', 'utf-8').readlines() 59 global classifier#聲明classifier為全局變量,下面重新賦值,雖然值仍然是Sentiment()函數 60 classifier = Sentiment() 61 classifier.train(neg_docs, pos_docs)#調用Sentment()模塊里的train()方法 62 63 64 def save(fname, iszip=True): 65 classifier.save(fname, iszip) 66 67 68 def load(fname, iszip=True): 69 classifier.load(fname, iszip) 70 71 72 def classify(sent): 73 return classifier.classify(sent)
2.使用的朴素貝葉斯原理及公式變形
推薦一篇解釋得很好的文章:情感分析——深入snownlp原理和實踐

classification文件夾下的Bayes.py模塊主要包含兩個方法train(data)和classify(X),訓練和預測方法
1 # 訓練數據集 2 # 訓練的數據data格式是[[['分詞1','分詞2','分詞x'],類別], 3 # [[第二行分詞], 類別], 4 # [[第n行分詞],類別] 5 # ] 6 def train(self, data):#訓練后得到的self.d={'neg':AddOneProb,'pos':AddOneProb},AddOneProb包含重要的分詞信息,他里面也有一個self.d={'分詞1':v1,'分詞2':v2,'分詞3':v3,...}包含分詞和相應的分詞個數。 7 8 # 遍歷數據集 9 for d in data: 10 # d[1]標簽-->分類類別 11 c = d[1] 12 # 判斷數據字典中是否有當前的標簽 13 if c not in self.d: 14 # 如果沒有該標簽,加入標簽,值是一個AddOneProb對象。其實就是為每個分詞建一個AddOnePro對象來計數 15 self.d[c] = AddOneProb() 16 # d[0]是評論的分詞list,遍歷分詞list 17 for word in d[0]: 18 # 調用AddOneProb中的add方法,添加單詞 19 self.d[c].add(word, 1)#self.d[c]是AddOneProb對象,調用AddOneProb的add()函數來對詞word計數,重點看frequency.py中的幾個類 20 # 計算總詞數 21 self.total = sum(map(lambda x: self.d[x].getsum(), self.d.keys()))#self.d[x].getsum()是調用AddOneProb對象的getsum()函數計算詞 22 23 #對句子x分類,而x是被分過詞的列表,(將句子分詞的步驟會在Sentiment類中的分類函數classify()執行,這里不要管,只要知道x是分詞后的的列表) 24 def classify(self, x): 25 tmp = {} 26 # 遍歷每個分類標簽,本案例中只有兩個類別 27 for k in self.d: 28 tmp[k] = log(self.d[k].getsum()) - log(self.total)#計算先驗概率,即p(neg)和p(pos)兩個類別的概率 29 for word in x: 30 tmp[k] += log(self.d[k].freq(word))#計算后驗概率,即每個類別條件下某個分詞的概率p('詞A'|neg)和p('詞A'|pos),# 詞頻,詞word不在字典里的話就為0 31 ret, prob = 0, 0 32 for k in self.d:#遍歷兩個類 33 now = 0#預測值賦初值為0 34 try: 35 for otherk in self.d:#當類相同時now=1,類不同時now累加exp(tmp[otherk]-tmp[k]),最終計算now為變形后的朴素貝葉斯預測值的分母 36 now += exp(tmp[otherk]-tmp[k])#朴素貝葉斯變形式可見博客 37 now = 1/now#求倒數為得到的這個類的預測值 38 except OverflowError: 39 now = 0 40 if now > prob:#比較兩個類別的概率誰大,大的就是這個文本的類別。注意:初始prob等於0,經過遍歷后悔更新prob並且prob等於相應類別的朴素貝葉斯概率 41 ret, prob = k, now 42 return (ret, prob) 43 #這里用朴素貝葉斯方法計算
注意:classify()方法中的朴素貝葉斯變形方法的編寫,下面兩個for循環,先是遍歷兩個類,比例第一個類,now表示對這個類計算的貝葉斯預測值,賦初值為0,第二個循環遍歷第一個類(otherk=k),
exp(tmp[otherk]-tmp[k])=exp(0)=1,則now=1,再遍歷第二個類得到上面變形公式中的第二部分值,這兩個相加得到的new就是

最后再倒數就得到了

再附加一個上面的AddOneProb()方法的源代碼解析,他就是一個對輸入的分詞計數的函數,將語料庫的詞進行分類計數,為了訓練做得到先后驗概率准備:
1 '''對詞計算頻數''' 2 class BaseProb(object): 3 4 def __init__(self): 5 self.d = {}#用來存儲分詞和分詞的個數,鍵是分詞,值是分詞的個數 6 self.total = 0.0#計數總共的詞個數 7 self.none = 0 8 9 def exists(self, key):#判斷字典self.d中是否存在這個詞key 10 return key in self.d 11 12 def getsum(self):#返回語self.d中存儲的詞的總數 13 return self.total 14 15 def get(self, key):#判斷字典中是否存在這個詞key,並且返回這分詞的詞個數 16 if not self.exists(key): 17 return False, self.none 18 return True, self.d[key] 19 20 def freq(self, key):#計算詞key的頻率 21 return float(self.get(key)[1])/self.total 22 23 def samples(self):#返回字典的鍵,其實就是返回所有的分詞,以列表形式 24 return self.d.keys() 25 26 27 class NormalProb(BaseProb): 28 29 def add(self, key, value): 30 if not self.exists(key): 31 self.d[key] = 0 32 self.d[key] += value 33 self.total += value 34 35 36 '''對詞計數''' 37 class AddOneProb(BaseProb):#繼承BaseProb類,所以BaseProb類中的屬性和函數都能用。 38 39 def __init__(self): 40 self.d = {} 41 self.total = 0.0 42 self.none = 1 43 44 def add(self, key, value): 45 self.total += value#計算總詞數 46 if not self.exists(key):#如果這個詞key不在self.d中的話,那么在字典中加上這個詞,即鍵為此,並且給這個詞計數1,同時總的詞數量total加1. 47 self.d[key] = 1 48 self.total += 1#感覺不應該再加1了,上面都已經計算過總數了????說是后面預測要用到,可能是要平滑 49 self.d[key] += value#如果字典已經有這個詞了的話,那么給這個詞數量加1
對於局進行分詞的方法Handle()感覺也很重要,也需要看一看。
