微信公眾號:碼農充電站pro
個人主頁:https://codeshellme.github.io
上篇介紹了樸素貝葉斯的原理,本篇來介紹如何用樸素貝葉斯解決實際問題。
樸素貝葉斯最擅長的領域是文字分析,包括:
- 文字分類
- 情感分析
- 垃圾郵件處理
要對文字進行分類,首先要做的是如何提取文字的主要資訊,如何衡量哪些資訊是文字中的主要資訊呢?
1,對文件分詞
我們知道,一篇文件是由若干詞彙組成的,也就是文件的主要資訊是詞彙。從這個角度來看,我們就可以用一些關鍵詞來描述文件。
這種處理文字的方法叫做詞袋(bag of words)模型,該模型會忽略文字中的詞語出現的順序以及相應的語法,將文件看做是由若干單片語成的,且單詞之間互相獨立,沒有關聯。
要想提取文件中的關鍵詞,就得先對文件進行分詞。分詞方法一般有兩種:
- 第一種是基於字串匹配。就是掃描字串。如果發現字串的子串和詞相同,就算匹配成功。
- 匹配規則一般有“正向最大匹配”,“逆向最大匹配”,“長詞優先”等。
- 該類演算法的優點是隻需基於字典匹配,演算法簡單;缺點是沒有考慮詞義,處理歧義詞效果不佳。
- 第二種是基於統計和機器學習。需要人工標註詞性和統計特徵,對中文進行建模。
- 先要訓練分詞模型,然後基於模型進行計算概率,取概率最大的分詞作為匹配結果。
- 常見的序列標註模型有隱馬爾科夫模型和條件隨機場。
停用詞是一些非常普遍使用的詞語,對文件分析作用不大,在文件分析之前需要將這些詞去掉。比如:
- 中文停用詞:“你,我,他,它,的,了” 等。
- 英文停用詞:“is,a,the,this,that” 等。
- 停用詞檔案:停用詞一般儲存在檔案中,需要自行讀取。
另外分詞階段,還需要處理同義詞,很多時候一件東西有多個不同的名字。比如“番茄”和“蕃茄”,“鳳梨”和“菠蘿”等。
中文分詞與英文分詞是不同的,我們分別介紹一個著名的分詞包:
2,計算單詞權重
哪些關鍵詞對一個文件才是重要的?比如可以通過單詞出現的次數,次數越多就表示越重要。
更合理的方法是計算單詞的TF-IDF
值。
2.1,單詞的 TF-IDF 值
單詞的TF-IDF
值可以描述一個單詞對文件的重要性,TF-IDF
值越大,則越重要。
- TF:全稱是
Term Frequency
,即詞頻(單詞出現的頻率),也就是一個單詞在文件中出現的次數,次數越多越重要。- 計算公式:
一個單詞的詞頻TF = 單詞出現的次數 / 文件中的總單詞數
- 計算公式:
- IDF:全稱是
Inverse Document Frequency
,即逆向文件詞頻,是指一個單詞在文件中的區分度。它認為一個單詞出現在的文件數越少,這個單詞對該文件就越重要,就越能通過這個單詞把該文件和其他文件區分開。- 計算公式:
一個單詞的逆向文件頻率 IDF = log(文件總數 / 該單詞出現的文件數 + 1)
- 為了避免分母為0(有些單詞可能不在文件中出現),所以在分母上加1
- 計算公式:
IDF 是一個相對權重值,公式中log 的底數可以自定義,一般可取2,10,e 為底數。
假設我們現在有一篇文章,文章中共有2000 個單詞,“中國”出現100 次。假設全網共有1 億篇文章,其中包含“中國”的有200 萬篇。現在我們要求“中國”的TF-IDF值。
計算過程如下:
TF(中國) = 100 / 2000 = 0.05
IDF(中國) = log(1億/(200萬+1)) = 1.7 # 這裡的log 以10 為底
TF-IDF(中國) = 0.05 * 1.7 = 0.085
通過計算文件中單詞的TF-IDF
值,我們就可以提取文件中的特徵屬性,就是把TF-IDF
值較高的單詞,作為文件的特徵屬性。
2.2,TfidfVectorizer 類
sklearn 庫的 feature_extraction.text
模組中的 TfidfVectorizer 類,可以計算 TF-IDF
值。
TfidfVectorizer
類的原型如下:
TfidfVectorizer(*,
input='content',
encoding='utf-8',
decode_error='strict',
strip_accents=None,
lowercase=True,
preprocessor=None,
tokenizer=None,
analyzer='word',
stop_words=None,
token_pattern='(?u)\b\w\w+\b',
ngram_range=(1, 1),
max_df=1.0,
min_df=1,
max_features=None,
vocabulary=None,
binary=False,
dtype=<class 'numpy.float64'>,
norm='l2',
use_idf=True,
smooth_idf=True,
sublinear_tf=False)
常用的引數有:
input
:有三種取值:- filename
- file
- content:預設值為
content
。
analyzer
:有三種取值,分別是:- word:預設值為
word
。 - char
- char_wb
- word:預設值為
stop_words
:表示停用詞,有三種取值:english
:會載入自帶英文停用詞。None
:沒有停用詞,預設為None
。List
型別的物件:需要使用者自行載入停用詞。- 只有當引數
analyzer == 'word'
時才起作用。
token_pattern
:表示過濾規則,是一個正規表示式,不符合正規表示式的單詞將會被過濾掉。- 注意預設的
token_pattern
值為r'(?u)\b\w\w+\b'
,匹配兩個以上的字元,如果是一個字元則匹配不上。 - 只有引數
analyzer == 'word'
時,正則才起作用。
- 注意預設的
max_df
:用於描述單詞在文件中的最高出現率,取值範圍為[0.0~1.0]
。- 比如
max_df=0.6
,表示一個單詞在 60% 的文件中都出現過,那麼認為它只攜帶了非常少的資訊,因此就不作為分詞統計。
- 比如
mid_df
:單詞在文件中的最低出現率,一般不用設定。
常用的方法有:
t.fit(raw_docs)
:用raw_docs
擬合模型。t.transform(raw_docs)
:將raw_docs
轉成矩陣並返回,其中包含了每個單詞在每個文件中的 TF-IDF 值。t.fit_transform(raw_docs)
:可理解為先fit
再transform
。
在上面三個方法中:
t
表示TfidfVectorizer
物件。raw_docs
引數是一個可遍歷物件,其中的每個元素表示一個文件。
fit_transform
與 transform
的用法
- 一般在擬合轉換資料時,先處理訓練集資料,再處理測試集資料。
- 訓練集資料會用於擬合模型,而測試集資料不會用於擬合模型。所以:
fit_transform
用於訓練集資料。transform
用於測試集資料,且transform
必須在fit_transform
之後。- 如果測試集資料也用
fit_transform
方法,則會造成過擬合。
下圖表達的很清晰明瞭:
所以一般的使用步驟是:
# x 為 DictVectorizer,DictVectorizer 等類的物件
# 用於特徵提取
x = XXX()
train_features = x.fit_transform(train_datas)
test_features = x.transform(test_datas)
2.3,一個例子
比如我們有如下3 個文件(docs
的每個元素表示一個文件):
docs = [
'I am a student.',
'I live in Beijing.',
'I love China.',
]
我們用 TfidfVectorizer
類來計算TF-IDF 值:
from sklearn.feature_extraction.text import TfidfVectorizer
t = TfidfVectorizer() # 使用預設引數
用 fit_transform()
方法擬合模型,反回矩陣:
t_matrix = t.fit_transform(docs)
用 get_feature_names()
方法獲取所有不重複的特徵詞:
>>> t.get_feature_names()
['am', 'beijing', 'china', 'in', 'live', 'love', 'student']
不知道你有沒有發現,這些特徵詞中不包含
i
和a
?你能解釋一下是為什麼嗎?
用vocabulary_
屬性獲取特徵詞與ID
的對應關係:
>>> t.vocabulary_
{'am': 0, 'student': 6, 'live': 4, 'in': 3, 'beijing': 1, 'love': 5, 'china': 2}
用 矩陣物件的toarray()
方法輸出 TF-IDF
值:
>>> t_matrix.toarray()
array([
[0.70710678, 0. , 0. , 0. , 0. , 0. , 0.70710678],
[0. , 0.57735027, 0. , 0.57735027, 0.57735027, 0. , 0. ],
[0. , 0. , 0.70710678, 0. , 0. , 0.70710678, 0. ]
])
3,sklearn 樸素貝葉斯的實現
sklearn 庫中的 naive_bayes 模組實現了 5 種樸素貝葉斯演算法:
naive_bayes.BernoulliNB
類:伯努利樸素貝葉斯的實現。- 適用於離散型資料,適合特徵變數是布林變數,符合 0/1 分佈,在文件分類中特徵是單詞是否出現。
- 該演算法以檔案為粒度,如果該單詞在某檔案中出現了即為 1,否則為 0。
naive_bayes.CategoricalNB
類:分類樸素貝葉斯的實現。naive_bayes.GaussianNB
類:高斯樸素貝葉斯的實現。- 適用於特徵變數是連續型資料,符合高斯分佈。比如說人的身高,物體的長度等,這種自然界物體。
naive_bayes.MultinomialNB
類:多項式樸素貝葉斯的實現。- 適用於特徵變數是離散型資料,符合多項分佈。在文件分類中特徵變數體現在一個單詞出現的次數,或者是單詞的 TF-IDF 值等。
naive_bayes.ComplementNB
類:補充樸素貝葉斯的實現。- 是多項式樸素貝葉斯演算法的一種改進。
每個類名中的NB 字尾是 Naive Bayes 的縮寫,即表示樸素貝葉斯。
各個類的原型如下:
BernoulliNB(*, alpha=1.0, binarize=0.0, fit_prior=True, class_prior=None)
CategoricalNB(*, alpha=1.0, fit_prior=True, class_prior=None)
GaussianNB(*, priors=None, var_smoothing=1e-09)
MultinomialNB(*, alpha=1.0, fit_prior=True, class_prior=None)
ComplementNB(*, alpha=1.0, fit_prior=True, class_prior=None, norm=False)
構造方法中的alpha
的含義為平滑引數:
- 如果一個單詞在訓練樣本中沒有出現,這個單詞的概率就會是 0。但訓練集樣本只是整體的抽樣情況,不能因為沒有觀察到,就認為整個事件的概率為 0。為了解決這個問題,需要做平滑處理。
- 當 alpha=1 時,使用的是 Laplace 平滑。Laplace 平滑就是採用加 1 的方式,來統計沒有出現過的單詞的概率。這樣當訓練樣本很大的時候,加 1 得到的概率變化可以忽略不計。
- 當 0<alpha<1 時,使用的是 Lidstone 平滑。對於 Lidstone 平滑來說,alpha 越小,迭代次數越多,精度越高。一般可以設定 alpha 為 0.001。
4,構建模型
我準備了一個實戰案例,目錄結構如下:
naive_bayes\
├── stop_word\
│ └── stopword.txt
├── test_data\
│ ├── test_economy.txt
│ ├── test_fun.txt
│ ├── test_health.txt
│ └── test_sport.txt
├── text_classification.py
└── train_data\
├── train_economy.txt
├── train_fun.txt
├── train_health.txt
└── train_sport.txt
其中:
stop_word
目錄中是中文停用詞。train_data
目錄中是訓練集資料。test_data
目錄中是測試集資料。text_classification.py
:是Python 程式碼,包括以下步驟:- 中文分詞
- 特徵提取
- 模型訓練
- 模型測試
這些資料是一些新聞資料,每條資料包含了新聞型別和新聞標題,型別有以下四種:
- 財經類
- 娛樂類
- 健康類
- 體育類
我們的目的是訓練一個模型,該模型的輸入是新聞標題,模型的輸出是新聞型別,也就是想通過新聞標題來判斷新聞型別。
來看下資料的樣子,每類資料抽取了一條:
財經---11月20日晚間影響市場重要政策訊息速遞
娛樂---2020金雞港澳臺影展曝片單 修復版《蝶變》等將映
健康---全面解析耳聾耳鳴,讓你不再迷茫它的危害
體育---中國軍團1人已進32強!趙心童4-1晉級,丁俊暉顏丙濤將出戰
可以看到,每條資料以---
符號分隔,前邊是新聞型別,後邊是新聞標題。
下面來看下程式碼:
import os
import sys
import jieba
import warnings
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn import metrics
warnings.filterwarnings('ignore')
if sys.version.startswith('2.'):
reload(sys)
sys.setdefaultencoding('utf-8')
def load_file(file_path):
with open(file_path) as f:
lines = f.readlines()
titles = []
labels = []
for line in lines:
line = line.encode('unicode-escape').decode('unicode-escape')
line = line.strip().rstrip('\n')
_lines = line.split('---')
if len(_lines) != 2:
continue
label, title = _lines
words = jieba.cut(title)
s = ''
for w in words:
s += w + ' '
s = s.strip()
titles.append(s)
labels.append(label)
return titles, labels
def load_data(_dir):
file_list = os.listdir(_dir)
titles_list = []
labels_list = []
for file_name in file_list:
file_path = _dir + '/' + file_name
titles, labels = load_file(file_path)
titles_list += titles
labels_list += labels
return titles_list, labels_list
def load_stopwords(file_path):
with open(file_path) as f:
lines = f.readlines()
words = []
for line in lines:
line = line.encode('unicode-escape').decode('unicode-escape')
line = line.strip('\n')
words.append(line)
return words
if __name__ == '__main__':
# 載入停用詞
stop_words = load_stopwords('stop_word/stopword.txt')
# 載入訓練資料
train_datas, train_labels = load_data('train_data')
# 載入測試資料
test_datas, test_labels = load_data('test_data')
# 計算單詞權重
tf = TfidfVectorizer(stop_words = stop_words, max_df = 0.5)
train_features = tf.fit_transform(train_datas)
test_features = tf.transform(test_datas)
# 多項式貝葉斯分類器
clf = MultinomialNB(alpha = 0.001).fit(train_features, train_labels)
# 預測資料
predicted_labels = clf.predict(test_features)
# 計算準確率
score = metrics.accuracy_score(test_labels, predicted_labels)
print score
說明:
load_stopwords
函式用於載入停用詞。load_data
函式用於載入訓練集和測試集資料。- 使用
fit_transform
方法提取訓練集特徵。 - 使用
transform
方法提取測試集特徵。 - 這裡使用的是多項式貝葉斯分類器---
MultinomialNB
,平滑引數設定為0.001。 - 用
fit
方法擬合出了模型。 - 用
predict
方法對測試資料進行了預測。 - 最終用
accuracy_score
方法計算了模型的準確度,為 0.959。
5,如何儲存模型
實際應用中,訓練一個模型需要大量的資料,也就會花費很多時間。
為了方便使用,可以將訓練好的模型儲存到磁碟上,在使用的時候,直接載入出來就可以使用。
可以使用 sklearn 中的 joblib 模組來儲存和載入模型:
joblib.dump(obj, filepath)
方法將obj
儲存到filepath
指定的檔案中。obj
是要儲存的物件。filepath
是檔案路徑。
joblib.load(filepath)
方法用於載入模型。filepath
是檔案路徑。
在上邊的例子用,我們需要儲存兩個物件,分別是:
tf
:TF-IDF 值模型。cfl
:樸素貝葉斯模型。
儲存程式碼如下:
from sklearn.externals import joblib
>>> joblib.dump(clf, 'nb.pkl')
['nb.pkl']
>>> joblib.dump(tf, 'tf.pkl')
['tf.pkl']
使用模型程式碼如下:
import jieba
import warnings
from sklearn.externals import joblib
warnings.filterwarnings('ignore')
MODEL = None
TF = None
def load_model(model_path, tf_path):
global MODEL
global TF
MODEL = joblib.load(model_path)
TF = joblib.load(tf_path)
def nb_predict(title):
assert MODEL != None and TF != None
words = jieba.cut(title)
s = ' '.join(words)
test_features = TF.transform([s])
predicted_labels = MODEL.predict(test_features)
return predicted_labels[0]
if __name__ == '__main__':
# 載入模型
load_model('nb.pkl', 'tf.pkl')
# 測試
print nb_predict('東莞市場採購貿易聯網資訊平臺參加部委首批聯合驗收')
print nb_predict('留在中超了!踢進生死戰決勝一球,武漢卓爾保級成功')
print nb_predict('陳思誠全新系列電影《外太空的莫扎特》首曝海報 黃渤、榮梓杉演父子')
print nb_predict('紅薯的好處 常吃這種食物能夠幫你減肥')
其中:
load_model()
函式用於載入模型。nb_predict()
函式用於對新聞標題進行預測,返回標題的型別。
6,總結
本篇文章介紹瞭如何利用樸素貝葉斯處理文字分類問題:
- 首先需要對文字進行分詞,常用的分詞包有:
- 使用
TfidfVectorizer
計算單詞權重。- 使用
fit_transform
方法提取訓練集特徵。 - 使用
transform
方法提取測試集特徵。
- 使用
- 使用
MultinomialNB
類訓練模型,這裡給出了一個實戰專案,供大家參考。 - 使用 joblib 儲存模型,方便模型的使用。
(本節完。)
推薦閱讀:
歡迎關注作者公眾號,獲取更多技術乾貨。