1. 程式人生 > >Python自然語言處理—文字分類—樸素貝葉斯

Python自然語言處理—文字分類—樸素貝葉斯

一 貝葉斯公式

P(Y|X) = \frac{P(X,Y)}{P(X)}=\frac{P(X|Y)P(Y)}{P(X)}

公式很好理解,當我們相求已知狀態X下打上Y標籤的概率的時候,可以將問題分以下三個問題

1,求標籤Y下X狀態的概率

2,求標籤Y的概率

3,求X狀態的概率

以上三個問題可以簡單的統計已知樣本就可以獲取得到,這個工作是可以大規模並行處理的。

我們再數學一點的解釋一下,當我們想求Y的後驗概率的時候—P(Y|X),可以先獲取它的先驗概率—P(Y),再通過已有樣本計算出調整因子將先驗概率調整一下,就可以獲得後驗概率了。其實分母P(X) 還可以用全概率公式拆開,全概率公式我不寫了,但是原理我可以畫給大家,圓的面積=1+2+3,  1,2,3的面積又等於圓和紅黃綠的交集。

盜個圖舉個例子

 

二 樸素貝葉斯

一)   條件獨立假設

P(Y|X) = \frac{P(X,Y)}{P(X)}=\frac{P(x1,x2,x3,x4....|Y)P(Y)}{P(X)}=\frac{P(x1|Y)P(x2|Y)P(x3|Y)...P(Y)}{P(X)}

這就是條件獨立假設,我舉一個情感分析的例子,現在要分析“今天天氣真好空氣聞起來都是甜的”這句話的情感,當然文字分析第一步切詞,然後分析情感

P(正面|'今天天氣真好空氣聞起來都是甜的') = P('今天'|正面)*P('天氣'|正面)P('真好'|正面)P('空氣'|正面)P('聞起來'|正面)P('都是'|正面)P('甜的'|正面)P(正面)/P(X)

P(負面|'今天天氣真好空氣聞起來都是甜的') = P('今天'|負面)*P('天氣'|負面)P('真好'|負面)P('空氣'|負面)P('聞起來'|負面)P('都是'|負面)P('甜的'|負面)P(負面)/P(X)

該條文字的情感 P(正面|'今天天氣真好空氣聞起來都是甜的')/P(負面|'今天天氣真好空氣聞起來都是甜的')> 1則為正,小於1則為負,這樣除的好處就是不用處理麻煩的P(X)。

貝葉斯公式+獨立條件假設 = 樸素(Naive)貝葉斯,其實當你決定使用樸素貝葉斯模型處理文字的時候,你就已經默認了詞於詞之間沒有關係了(雖然假設不合理,但是樸素貝葉斯模型效果還是不錯的)

二)   計算

詞的概率 :

P('今天'|正面) = P(正面情感文字中多少出現了‘今天’)/P(所有的詞分別出現的篇數)  保證所有詞出現的概率相加要等於1

句子的概率:

1.一個句子中重複出現的詞重複計算概率——多項式模型

2.一個句子中重複出現的詞不重複計算——伯努利模型

3.訓練時候考慮重複詞計算,測試時候不考慮重複計算——混合模型

三)  平滑

模型的訓練其實是計算P('詞'|'狀態'),那麼如果測試集中遇到訓練集中未見過的詞怎麼處理?這時候平滑的作用就需要了

P('今天'|正面) = [P(正面情感文字中多少出現了‘今天’)+1]  /  [P(所有的詞分別出現的篇數) +V]

其實平滑更傾向解決的不是從未出現在詞庫中的詞,而是在解決詞只出現在某一類中的這種情況!!!大家自己回頭看看公式就能理解了!!這也是我寫公式的時候突然想到的!!

四)  小技巧

1.取對數,將乘法變為加法更方便

logP = logP('今天'|正面)+logP('天氣'|正面)+logP('真好'|正面)+logP('空氣'|正面)+logP('聞起來'|正面)+logP('都是'|正面)+logP('甜的'|正面)+logP(正面)

 

三 例子

例子是Kaggle資料——電影評價,可以通過百度網盤下載https://pan.baidu.com/s/1Zf5FeM4G61TGZGAzyviAiA

第一步清洗資料

處理資料的過程不是一蹴而就的,需要不停的print出當前資料的狀態,再決定下一步如何處理資料,所以大家可以在程式中看到被我註釋的語句。

本段程式有兩點需要注意,

1. 訓練集我只取了前10000作為訓練,又取了5000作為測試集。

2.dataframe是可以直接用apply呼叫方法的,當然定義引數的輸入要稍作修改。

import re  #正則表示式
from bs4 import BeautifulSoup  #html標籤處理
import pandas as pd  #讀取資料
from nltk.corpus import stopwords  #刪除停用詞

def review_to_wordlist(review):

    # 去掉<br /><br />
    review_text = BeautifulSoup(review, "html.parser").get_text()
    # 去除標點
    review_text = re.sub("[^a-zA-Z]"," ", review_text)
    # 分詞
    words = review_text.lower().split()
    #去除停用詞
    words = [w for w in words if not w in stopwords.words("english")]
    # 返回words
    return words

# 使用pandas讀入訓練和測試csv檔案這裡,“header= 0”表示該檔案的第一行包含列名稱,
#“delimiter=\t”表示欄位由\t分割, quoting=3告訴Python忽略雙引號
train = pd.read_csv(r'C:\Users\BF\Desktop\NLTK\Kaggle影評觀眾情緒分類\labeledTrainData.tsv', header=0, delimiter="\t", quoting=3)



#預覽資料
#print(train.head())
#print(train['review'][0])
#print(list(review_to_wordlist(train['review'][0])))

# 取出情感標籤,positive/褒 或者 negative/貶
y_train = train['sentiment'][:10000]



#train['data'] = train.apply(review_to_wordlist, axis=1)
#test['data'] = test.apply(review_to_wordlist, axis=1)
y_test = train['sentiment'][10000:15000]
# 將訓練和測試資料都轉成詞list
train_data = []
for i in range(0,10000):
    train_data.append(" ".join(review_to_wordlist(train['review'][i])))
test_data = []
for i in range(10000,15000):
    test_data.append(" ".join(review_to_wordlist(train['review'][i])))
    

第二步 將詞轉為向量

這裡不再是簡單的詞頻統計,而是使用TF-IDF。一個詞的重要程度和詞頻成正比,和出現的篇幅數成反比,這就是TFIDF的核心思想。

本段程式有兩點需要注意,

1. 我只用訓練集訓練TFIDF,對TFIDF也是需要提前訓練好,這樣新的語句過來才能快速計算出新句子的TFIDF向量。訓練會生成一個詞彙表,以及每個詞的IDF值。所以新句子中的新詞如何處理呢——根據我的輸出我發現新詞都會被忽視。

2. TfidfVectorizer的引數,我也是用幾百條記錄慢慢測試+網上的講解才弄清楚大部分引數

引數來源於https://scikit-learn.org/stable/modules/feature_extraction.html我添加了自己的解釋。

analyzer:string,{'word', 'char'} or callable

    選word則你是算每個詞的tfidf,選char就變成算字元a-z的tfidf了,當然你也可以自己寫一個提取規則。注意最終詞表還和ngram_range引數有關!

tokenizer:callable or None(default)

   定義一個函式,接受文字,返回分詞的list,所以分詞的操作可以放在該函式內進行

>>> def my_tokenizer(s):
...     return s.split()
...
>>> vectorizer = CountVectorizer(tokenizer=my_tokenizer)
>>> vectorizer.build_analyzer()(u"Some... punctuation!") == (
...     ['some...', 'punctuation!'])

ngram_range: tuple(min_n, max_n)

    詞和詞是有關係的,當你max_n = 2時候,你會發現你最終的詞表會有 word1 word2,word2 word3這種形式的存在。會增加計算量,但是這種特徵是有有效且合理的存在

>>> bigram_vectorizer = CountVectorizer(ngram_range=(1, 2),
...                                     token_pattern=r'\b\w+\b', min_df=1)
>>> analyze = bigram_vectorizer.build_analyzer()
>>> analyze('Bi-grams are cool!') == (
...     ['bi', 'grams', 'are', 'cool', 'bi grams', 'grams are', 'are cool'])

stop_words:string {'english'}, list, or None(default)

    如果未english,用於英語內建的停用詞列表

    如果未list,該列表被假定為包含停用詞,列表中的所有詞都將從令牌中刪除

    如果None,不使用停用詞。max_df可以被設定為範圍[0.7, 1.0)的值,基於內部預料詞頻來自動檢測和過濾停用詞

lowercase:boolean, default True

    在令牌標記前轉換所有的字元為小寫

token_pattern:string

    正則表示式顯示了”token“的構成,僅當analyzer == ‘word’時才被使用。兩個或多個字母數字字元的正則表示式(標點符號完全被忽略,始終被視為一個標記分隔符)。如果你發現你中文中的單字詞都被幹掉了!別猶豫就是這個引數搞的鬼!其預設引數為r"(?u)\b\w\w+\b",其中的兩個\w決定了其匹配長度至少為2的單詞

max_df: float in range [0.0, 1.0] or int, optional, 1.0 by default

    當構建詞彙表時,嚴格忽略高於給出閾值的文件頻率的詞條,語料指定的停用詞。如果是浮點值,該引數代表文件的比例,整型絕對計數值,如果詞彙表不為None,此引數被忽略。高頻詞其實都是無意義的詞,忽視是一個不錯的選擇!

min_df:float in range [0.0, 1.0] or int, optional, 1.0 by default

當構建詞彙表時,嚴格忽略低於給出閾值的文件頻率的詞條,語料指定的停用詞。如果是浮點值,該引數代表文件的比例,整型絕對計數值,如果詞彙表不為None,此引數被忽略。

max_features: optional, None by default

    如果不為None,構建一個詞彙表,僅考慮max_features--按語料詞頻排序,如果詞彙表不為None,這個引數被忽略

vocabulary:Mapping or iterable, optional

    我們可以構建自己的詞彙表!也是一個對映(Map)(例如,字典),其中鍵是詞條而值是在特徵矩陣中索引,或詞條中的迭代器。如果沒有給出,詞彙表被確定來自輸入檔案。在對映中索引不能有重複,並且不能在0到最大索引值之間有間斷。

use_idf:boolean, optional

    啟動inverse-document-frequency重新計算權重

smooth_idf:boolean,optional

    通過加1到文件頻率平滑idf權重,為防止除零,加入一個額外的文件

sublinear_tf:boolean, optional

    應用線性縮放TF,例如,使用1+log(tf)覆蓋tf

from sklearn.feature_extraction.text import TfidfVectorizer as TFIV
# min_df=3去除低詞頻的詞,分析的視角是詞,啟用ngram_range,啟用ITF,啟用idf平滑smooth_idf=1
#用1 + log(tf)替換tfsublinear_tf=1
tfv = TFIV(min_df=3,  strip_accents='unicode', analyzer='word',ngram_range=(1, 2)
, use_idf=1,smooth_idf=1,sublinear_tf=1)

# 注意我只用訓練集訓練
tfv.fit(train_data)



X_all = train_data + test_data
len_train = len(train_data)
X_all = tfv.transform(X_all)


#words = tfv.get_feature_names()
#print(words)
#for i in range(10):
#    print('----Document %d----' % (i))
#    for j in range(len(words)):
#        if tfidf[i,j] > 1e-5:
#              print( words[j], tfidf[i,j])



# 恢復成訓練集和測試集部分
X = X_all[:len_train] 
X_test = X_all[len_train:]

第三步 使用樸素貝葉斯

本次使用的多項式貝葉斯模型,貝葉斯的訓練過程中需要計算P,上文中都是使用詞頻計算P,現在我們用TF-IDF代替詞頻後,模型也會相應的根據TFIDF來計算P,這點請注意。

from sklearn.naive_bayes import MultinomialNB as MNB
MNB(alpha=1.0, class_prior=None, fit_prior=True)
'''
alpha : float,optional(預設值= 1.0)
拉普拉斯平滑引數(0表示無平滑)。

fit_prior : boolean,optional(default = True)
如果為假,則使用統一的先驗。

class_prior : 可選(預設=無)
類的先驗概率。如果指定,則不根據資料調整先驗。
'''
model_NB = MNB()
model_NB.fit(X, y_train) #特徵資料直接灌進來

#使用交叉驗證來檢驗模型的好壞
from sklearn.model_selection import cross_val_score
import numpy as np
print ("交叉驗證得分: ", np.mean(cross_val_score(model_NB, X,
                                                       y_train, cv=20, scoring='roc_auc')))
#使用測試集測試效果
model_NB.score(X_test, y_test)

最終在從未見過的測試集上達到了88%的正確率,例子來源於http://blog.csdn.net/han_xiaoyang/article/details/50629608!!