數(shù)據(jù)分析入門(mén)系列教程-貝葉斯實(shí)戰(zhàn)
樸素貝葉斯分類(lèi)最適合的場(chǎng)景就是文本分類(lèi)了,無(wú)論是情感分析還是文檔分類(lèi)及垃圾郵件識(shí)別,都是樸素貝葉斯最為擅長(zhǎng)的地方,其也成為了自然語(yǔ)言處理 NLP 方向的重要工具。
文本到向量
既然說(shuō)到了 NLP,那么就不得不提及從文本到向量的轉(zhuǎn)換。我們都知道,計(jì)算機(jī)是比較擅長(zhǎng)處理數(shù)字類(lèi)型的數(shù)據(jù)的,而對(duì)于字符類(lèi)型數(shù)據(jù)往往都需要轉(zhuǎn)換成數(shù)字類(lèi)型,再進(jìn)行相關(guān)運(yùn)算。在自然語(yǔ)言處理領(lǐng)域同樣如此,拿到文本后,我們需要先把文本轉(zhuǎn)化成向量,然后再做處理。
為現(xiàn)在較為流行的文本轉(zhuǎn)向量的方式有兩種,詞袋和詞頻逆文檔。在應(yīng)用這兩種方式的時(shí)候,都是需要有一個(gè)詞典庫(kù)的。這個(gè)就相當(dāng)于,如果你想分析金融領(lǐng)域的文本,那么這個(gè)詞典庫(kù)中包含的單詞就應(yīng)該是與金融相關(guān)的;如果你想分析教育領(lǐng)域的文本,那么詞典庫(kù)應(yīng)該是與教育相關(guān)的。
為了方便起見(jiàn),我這里設(shè)置一個(gè)簡(jiǎn)單的詞典庫(kù)如下
詞典庫(kù) [“我們”,“跑步”,“早飯”,“吃”,"去","出發(fā)","早上"]
詞袋模型
詞袋模型又可以理解為 count vector,就是查看詞典庫(kù)中的詞語(yǔ)出現(xiàn)在文本中的次數(shù),出現(xiàn)幾次就標(biāo)注為數(shù)字幾
文本1:早上去跑步
文本2:我們吃早飯去跑步
文本3:我們出發(fā)吃早飯我們跑步
首先把三個(gè)文件進(jìn)行單詞分割,可以表示成如下形式:
文本1:早上|去|跑步
文本2:我們|吃|早飯|去|跑步
文本3:我們|出發(fā)|吃|早飯|我們|跑步
下面就可以查看各個(gè)文本中出現(xiàn)詞庫(kù)里單詞的數(shù)量了
文本1:
早上去跑步
(0,1,0,0,1,0,1)
文本2:
我們吃早飯去跑步
(1,1,1,1,1,0,,0)
文本3:
我們出發(fā)吃早飯我們跑步
(2,1,1,1,0,1,0)
至此,通過(guò)詞袋模型,我們已經(jīng)成功的將一個(gè)文本,轉(zhuǎn)換成了向量的形式。
當(dāng)然也可以看到詞袋模型的缺點(diǎn),它拋棄了文檔中的前后邏輯,語(yǔ)義結(jié)構(gòu)等信息,僅僅以詞語(yǔ)出現(xiàn)的次數(shù)作為評(píng)判標(biāo)準(zhǔn)。
正是為了解決這一缺點(diǎn),又出現(xiàn)了詞頻逆文檔模型(TF-IDF)
詞頻逆文檔
詞頻逆文檔又稱(chēng)為 TF-IDF,TF 就是詞頻的意思,為 IDF 則為逆向文檔頻率的意思。
TF:計(jì)算一個(gè)單詞在文檔中出現(xiàn)的頻次
IDF:指一個(gè)單詞在文檔中的區(qū)分度,該模型認(rèn)為一個(gè)單詞出現(xiàn)的文檔數(shù)越少,則越能夠通過(guò)該單詞區(qū)分不同的文檔,IDF 越大就代表單詞的區(qū)分度越高。
計(jì)算方式:
TF = 單詞出現(xiàn)的次數(shù)/該文檔的總單詞數(shù)
IDF = log(文檔總數(shù)/該單詞出現(xiàn)的文檔數(shù)+1)
TF-IDF = TF * IDF
這樣你應(yīng)該可以看出,一些出現(xiàn)頻率很高的指向詞,比如你我他等,雖然 TF 會(huì)很高,但是 IDF 的值會(huì)很低,所以它們的 TF-IDF 值也不高。而我們通過(guò)經(jīng)驗(yàn)也可以知道,通過(guò)你我他這些詞是很難區(qū)分不同文檔的,即它們的重要性比較低。
情感分析
本節(jié)所有的數(shù)據(jù)集到保存在 GitHub 上
https://github.com/zhouwei713/DataAnalyse/tree/master/Naive_Bayes
下面我們就開(kāi)始進(jìn)入今天的實(shí)戰(zhàn)部分,首先來(lái)看一個(gè)例子,根據(jù)用戶(hù)評(píng)論,分析是正向評(píng)論還是負(fù)向評(píng)論。
我們先來(lái)看下語(yǔ)料庫(kù)

分別是停用詞,測(cè)試數(shù)據(jù)集,訓(xùn)練數(shù)據(jù)集(負(fù)向評(píng)論和正向評(píng)論)
停用詞:在文本分析領(lǐng)域,一般都會(huì)把一些經(jīng)常出現(xiàn)的但是又沒(méi)有實(shí)際意思或者不影響語(yǔ)義的詞語(yǔ)去除掉,就是停用詞
測(cè)試數(shù)據(jù)集:我們看下它長(zhǎng)什么樣子

在
訓(xùn)練集:

數(shù)據(jù)格式也是類(lèi)似的,文本保存在
提取文本
我們首先要做的就是對(duì)語(yǔ)料庫(kù)做處理,提取出我們需要的文本內(nèi)容
我們先處理測(cè)試集數(shù)據(jù)
with?open('sentiment/test.txt',?'r',?encoding='utf-8')?as?f:
????reviews,?labels?=?[],?[]
????start?=?False
????for?file?in?f:
????????file?=?file.strip()
????????if?not?file:
????????????continue
????????if?file.startswith(")?and?not?start:
????????????start?=?True
????????????if?"label"?in?file:
????????????????labels.append(file.split('"')[3])
????????????continue
????????if?start?and?file?==?r" ":
????????????start?=?False
????????????continue
????????if?start:
????????????reviews.append(file)????print(reviews,?labels)
通過(guò) with 函數(shù)打開(kāi)文件,初始化兩個(gè)列表 reviews 和 labels,分別用來(lái)保存評(píng)論內(nèi)容和對(duì)用的 label。
start = False 用來(lái)做判斷,控制是否開(kāi)始評(píng)論內(nèi)容
file = file.strip() 去除字符串中的空格
file.startswith("file.split('"')[3] 切分字符串,并去除第4個(gè)原始(腳標(biāo)從0開(kāi)始)
接著再來(lái)處理訓(xùn)練集數(shù)據(jù),由于訓(xùn)練集數(shù)據(jù)和測(cè)試集數(shù)據(jù)格式非常類(lèi)似,我們可以把上面的代碼做些修改,改寫(xiě)成函數(shù)供后面調(diào)用
def?load_data(file,?is_positive=None):
????reviews,?labels?=?[],?[]
????with?open(file,?'r',?encoding='utf-8')?as?f:
????????start?=?False
????????review_text?=?[]
????????for?file?in?f:
????????????file?=?file.strip()
????????????if?not?file:
????????????????continue
????????????if?file.startswith(")?and?not?start:
????????????????start?=?True
????????????????if?"label"?in?file:
????????????????????labels.append(int(file.split('"')[3]))
????????????????continue
????????????if?start?and?file?==?r" ":
????????????????start?=?False
????????????????reviews.append("?".join(review_text))
????????????????review_text?=?[]
????????????????continue
????????????if?start:
????????????????review_text.append(file)
????if?is_positive:
????????labels?=?[1]?*?len(reviews)
????elif?is_positive?==?False:
????????labels?=?[0]?*?len(reviews)
????return?reviews,?labels
整體沒(méi)有太多變化,只是增加了標(biāo)簽的管理。
下面再寫(xiě)一個(gè)函數(shù),把訓(xùn)練集合并起來(lái)
def?process_file():
????train_pos_file?=?"sentiment/train.positive.txt"
????train_neg_file?=?"sentiment/train.negative.txt"
????test_comb_file?=?"sentiment/test.txt"
????#?讀取文件部分,把具體的內(nèi)容寫(xiě)入到變量里面
????train_pos_cmts,?train_pos_lbs?=?load_data(train_pos_file,?True)
????train_neg_cmts,?train_neg_lbs?=?load_data(train_neg_file,?False)
????train_comments?=?train_pos_cmts?+?train_neg_cmts
????train_labels?=?train_pos_lbs?+?train_neg_lbs
????test_comments,?test_labels?=?load_data(test_comb_file)
????return?train_comments,?train_labels,?test_comments,?test_labels
可以簡(jiǎn)單查看下訓(xùn)練數(shù)據(jù)
print(len(train_comments),?len(test_comments))
print(train_comments[3],?train_labels[3])
>>>
8064?2500
先付款的???有信用?1
數(shù)據(jù)預(yù)處理
下面可以進(jìn)行一些簡(jiǎn)單的數(shù)據(jù)預(yù)處理,把文本中的中文字符和數(shù)字處理掉,并去掉停用詞
def?deal_text(text,?stop_path):
????stopwords?=?set()
????with?open(stop_path,?'r',?encoding='utf-8')?as?in_file:
????????for?line?in?in_file:
????????????stopwords.add(line.strip())
????text?=?re.sub('[!!]+',?"!",?text)
????text?=?re.sub('[??]+',?"?",?text)
????text?=?re.sub("[a-zA-Z#$%&\'()*+,-./:;:<=>@,。★、…【】《》“”‘’[\\]^_`{|}~]+",?"?UNK?",?text)
????text?=?re.sub(r"\d+",?'?NUM?',?text)
????text?=?re.sub(r"\s+",?"?",?text)??
????text?=?"?".join([term?for?term?in?jieba.cut(text)?if?term?and?not?term?in?stopwords])
????return?text
train_comments_new?=?[deal_text(comment,?"sentiment/stopwords.txt")?for?comment?in?train_comments]
test_comments_new?=?[deal_text(comment,?"sentiment/stopwords.txt")?for?comment?in?test_comments]print(train_comments_new[0],?test_comments_new[0])
>>>
發(fā)短信?特別?不?方便 ! 背后?屏幕?很大?起來(lái)?不?舒服?? UNK ??手觸?屏 ! 切換?屏幕?很?麻煩 ! 終于?找到?同道中人?初中?? UNK ??已經(jīng)?喜歡?上?? UNK ??同學(xué)?都?鄙夷?眼光?看?? UNK ??人為?? UNK ??樣子?古怪?說(shuō)?"?丑?"?當(dāng)場(chǎng)?氣暈?現(xiàn)在?同道中人?? UNK ??好開(kāi)心 ! ? UNK ? ! ? UNK ?
用到了正則表達(dá)式,我們?cè)诘谝还?jié) Python 基礎(chǔ)課中已經(jīng)介紹過(guò)了
還用到了 jieba 分詞庫(kù),這里重點(diǎn)介紹下
jieba 分詞庫(kù)
jieba 分詞庫(kù)是中文語(yǔ)言處理中非常常用的分詞工具,現(xiàn)在給出簡(jiǎn)單的用法
mytext?=?r"今天是個(gè)好日子,是一個(gè)適合學(xué)習(xí)的好日子"
fenci?=?jieba.cut(mytext)
print(type(fenci))
for?i?in?fenci:
????print(i)
>>>
'generator'>
今天
是
個(gè)
好日子
,
是
一個(gè)
適合
學(xué)習(xí)
的
好日子
可以看到,cut 函數(shù)返回的是一個(gè)生成器,我們通過(guò) for 循環(huán)逐個(gè)取出生成器中的內(nèi)容,已經(jīng)是一個(gè)個(gè)被切分的單詞了。
還記得我們前面講解文本到向量里提到的,把文本分割成單詞,就可以(也是最常用)使用這里的 jieba 分詞庫(kù)工具。
文本向量化
接下來(lái)我們就需要把已經(jīng)處理過(guò)的文本進(jìn)行向量化
首先使用 count vector,在 sklearn 中直接導(dǎo)入使用即可
from?sklearn.feature_extraction.text?import?CountVectorizer
然后就可以使用 CountVectorizer 來(lái)擬合數(shù)據(jù),生成一個(gè)稀疏矩陣
稀疏矩陣是指大部分元素都是0的矩陣
count_vector?=?CountVectorizer()
X_train?=?count_vector.fit_transform(train_comments_new)
y_train?=?train_labels
print(X_train)
>>>
??(0,?22983)????1
??(0,?4011)????1
??(0,?10618)????1
??(0,?1)????1
??(0,?18550)????1
最終我們得到的 X_train 就是一個(gè)稀疏矩陣,前面括號(hào)里的數(shù)字表示矩陣位置,后面的數(shù)字代表詞頻
對(duì)測(cè)試數(shù)據(jù)同樣進(jìn)行轉(zhuǎn)換
X_test?=?count_vector.transform(test_comments_new)
y_test?=?test_labels
查看訓(xùn)練數(shù)據(jù)和測(cè)試數(shù)據(jù)的大小
print(np.shape(X_train),?np.shape(X_test),?np.shape(y_train),?np.shape(y_test))
>>>
(8064,?23101)?(2500,?23101)?(8064,)?(2500,)
總共有8064個(gè)樣本,每個(gè)樣本的維度是23101維,即詞庫(kù)中單詞的個(gè)數(shù)是23101個(gè)
訓(xùn)練樸素貝葉斯分類(lèi)器
在 sklearn 中,提供了三種樸素貝葉斯模型, ? ?分別是 GaussianNB,MultinomialNB 和BernoulliNB。
GaussianNB 是先驗(yàn)概率屬于高斯分布的樸素貝葉斯,適用于特征變量為連續(xù)變量,比如人的身高,物體的長(zhǎng)度等。
MultinomialNB 是先驗(yàn)概率為多項(xiàng)式分布的樸素貝葉斯,也就是上一節(jié)我們推導(dǎo)的樸素貝葉斯,適用于特征變量是離散型變量,比如詞袋模型中體現(xiàn)的詞頻。
BernoulliNB 是先驗(yàn)概率為伯努利分布的樸素貝葉斯,即符合0/1分布的變量,比如文檔分類(lèi)中檢查單詞是否出現(xiàn)。
本課程只介紹 MultinomialNB 算法,其他兩個(gè)可以作為課后的拓展內(nèi)容學(xué)習(xí)。MultinomialNB 的一些重要參數(shù)如下:
alpha:為平滑參數(shù)。還記得我們的貝葉斯公式吧,如果每個(gè)特征在訓(xùn)練樣本中時(shí)沒(méi)有出現(xiàn)的,那么這個(gè)特征的概率就是0,從而整體的概率也就是0了,這是不合理的,所以引入平滑參數(shù)來(lái)規(guī)避概率為0的情況。
fit_prior:默認(rèn)為 True,表示十分要考慮先驗(yàn)概率,如果是 False,則所有的樣本類(lèi)別輸出都有相同的類(lèi)別先驗(yàn)概率。否則可以讓 MultinomialNB 自己從訓(xùn)練集樣本來(lái)計(jì)算先驗(yàn)概率。
這里我們使用 MultinomialNB 算法模型進(jìn)行訓(xùn)練
from?sklearn.naive_bayes?import?MultinomialNB
from?sklearn.metrics?import?accuracy_score
clf?=?MultinomialNB(alpha=1.0,?fit_prior=True)
#?利用樸素貝葉斯做訓(xùn)練
clf.fit(X_train,?y_train)
y_pred?=?clf.predict(X_test)
print("accuracy?on?test?data:?",?accuracy_score(y_test,?y_pred))
>>>
accuracy?on?test?data:??0.7276
文檔分類(lèi)
數(shù)據(jù)集共分為4類(lèi),如下:

下面就通過(guò)訓(xùn)練一個(gè)樸素貝葉斯分類(lèi)器,來(lái)判別測(cè)試集中的文檔類(lèi)別。
首先還是處理文本,把文檔和對(duì)應(yīng)的分類(lèi)讀入內(nèi)存
首先寫(xiě)一個(gè)切割文檔的函數(shù)
def?cut_word(file):
????text?=?open(file,?'r',?encoding='gb18030').read()
????stopword?=?[line.strip()?for?line?in?open(r'text/stop/stopword.txt',?encoding='utf-8').readlines()]
????text_segd?=?jieba.cut(text.strip())
????seg_word?=?''
????for?word?in?text_segd:
????????if?word?not?in?stopword:
????????????seg_word?+=?word?+?'?'
????return?seg_word
依然使用 jieba 做分詞,同時(shí)處理停用詞,返回一個(gè)大的字符串
下面再編寫(xiě)導(dǎo)入文件的函數(shù),在函數(shù)中調(diào)用 cut_word 函數(shù)做分詞
def?load_file(file_path,?label):
????file_list?=?os.listdir(file_path)
????words_list,?labels_list?=?[],?[]
????for?f?in?file_list:
????????file?=?file_path?+?'/'?+?f
????????words_list.append(cut_word(file))
????????labels_list.append(label)
????return?words_list,?labels_list
設(shè)想是,總共四個(gè)分類(lèi)文件,依次讀入并設(shè)置標(biāo)簽
依次讀入文件
#?訓(xùn)練數(shù)據(jù)
train_words_list1,?train_labels1?=?load_file('text/train/女性',?'女性')
train_words_list2,?train_labels2?=?load_file('text/train/體育',?'體育')
train_words_list3,?train_labels3?=?load_file('text/train/文學(xué)',?'文學(xué)')
train_words_list4,?train_labels4?=?load_file('text/train/校園',?'校園')train_words_list?=?train_words_list1?+?train_words_list2?+?train_words_list3?+?train_words_list4
train_labels?=?train_labels1?+?train_labels2?+?train_labels3?+?train_labels4#?測(cè)試數(shù)據(jù)
test_words_list1,?test_labels1?=?load_file('text/test/女性',?'女性')
test_words_list2,?test_labels2?=?load_file('text/test/體育',?'體育')
test_words_list3,?test_labels3?=?load_file('text/test/文學(xué)',?'文學(xué)')
test_words_list4,?test_labels4?=?load_file('text/test/校園',?'校園')test_words_list?=?test_words_list1?+?test_words_list2?+?test_words_list3?+?test_words_list4
test_labels?=?test_labels1?+?test_labels2?+?test_labels3?+?test_labels4
這次我們使用 TF-IDF 的方式來(lái)做文本轉(zhuǎn)向量的操作
tf?=?TfidfVectorizer()train_features?=?tf.fit_transform(train_words_list)
test_features?=?tf.transform(test_words_list)
模型預(yù)測(cè)
clf?=?MultinomialNB(alpha=0.001)
clf.fit(train_features,?train_labels)
predicted_labels?=?clf.predict(test_features)#?計(jì)算準(zhǔn)確率
print('準(zhǔn)確率為:', metrics.accuracy_score(test_labels, predicted_labels))
>>>
準(zhǔn)確率為:?0.91
可以看到,準(zhǔn)確率還是相當(dāng)不錯(cuò)的
混淆矩陣
下面再來(lái)介紹一個(gè)概念,混淆矩陣。
我們先來(lái)看下如何打印混淆矩陣
from?sklearn.metrics?import?confusion_matrix
#?混淆矩陣
print(confusion_matrix(test_labels,?predicted_labels,?labels=['女性',?'體育',?'文學(xué)',?'校園']))
>>>
[[?36???1???1???0]
?[??3?106???5???1]
?[??0???1??30???0]
?[??1???3???2??10]]
為了方便理解,我們可以把混淆矩陣改表格的形式
| 女性 | 體育 | 文學(xué) | 校園 | |
|---|---|---|---|---|
| 女性 | 36 | 1 | 1 | 0 |
| 體育 | 3 | 106 | 5 | 1 |
| 文學(xué) | 0 | 1 | 30 | 0 |
| 校園 | 1 | 3 | 2 | 10 |
對(duì)于“女性”這個(gè)測(cè)試分類(lèi),總共有38個(gè)測(cè)試數(shù)據(jù),其中36個(gè)分類(lèi)正確,有3個(gè)測(cè)試數(shù)據(jù)分類(lèi)錯(cuò)誤,分別錯(cuò)誤的分類(lèi)到了體育、文學(xué)和校園下。
同理,對(duì)于“體育”測(cè)試分類(lèi),總共有115個(gè)測(cè)試數(shù)據(jù),其中106個(gè)是正確分類(lèi)的,有3個(gè)錯(cuò)誤的分類(lèi)到了女性當(dāng)中,有5個(gè)錯(cuò)誤的分類(lèi)到文學(xué)當(dāng)中,有1個(gè)錯(cuò)誤的分類(lèi)到校園當(dāng)中。
另外兩個(gè)類(lèi)似。
可以看出在對(duì)角線上的36,106,30,10是正確的分類(lèi),而其余位置則是各數(shù)據(jù)的錯(cuò)誤分類(lèi),可以清楚的通過(guò)混淆矩陣看出哪些分類(lèi)是容易混淆的,比如有5個(gè)體育類(lèi)文檔錯(cuò)誤的分類(lèi)到了文學(xué)當(dāng)中。
這樣可以幫助我們更好的分析數(shù)據(jù)和分類(lèi)器的效果,對(duì)容易混淆的分類(lèi)做一些更加細(xì)致的處理。
完整代碼
https://github.com/zhouwei713/DataAnalyse/tree/master/Naive_Bayes
總結(jié)
本節(jié)我們列舉了兩個(gè)實(shí)戰(zhàn)例子,希望你能從實(shí)戰(zhàn)中更好的體會(huì)樸素貝葉斯的應(yīng)用方法。
同時(shí)我們還知道,自然語(yǔ)言處理是樸素貝葉斯應(yīng)用的最為廣泛的領(lǐng)域,一般的流程為,先分析文本的類(lèi)型、內(nèi)容組成等信息,再對(duì)文本進(jìn)行處理,如果是中文文本可以使用 jieba 工具做分成并打標(biāo)簽,再次通過(guò)詞袋模型或者 TF-IDF 模型來(lái)處理分詞的權(quán)重,進(jìn)行文本向量化,得到特征矩陣,最后就可以構(gòu)建分類(lèi)器,進(jìn)行訓(xùn)練和預(yù)測(cè)了。

