微信公衆號:碼農充電站pro
我的主頁:https://codeshellme.github.iohtml
上篇介紹了樸素貝葉斯的原理,本篇來介紹如何用樸素貝葉斯解決實際問題。python
樸素貝葉斯最擅長的領域是文本分析,包括:git
要對文本進行分類,首先要作的是如何提取文本的主要信息,如何衡量哪些信息是文本中的主要信息呢?github
咱們知道,一篇文檔是由若干詞彙組成的,也就是文檔的主要信息是詞彙。從這個角度來看,咱們就能夠用一些關鍵詞來描述文檔。正則表達式
這種處理文本的方法叫作詞袋(bag of words)模型,該模型會忽略文本中的詞語出現的順序以及相應的語法,將文檔看作是由若干單詞組成的,且單詞之間互相獨立,沒有關聯。算法
要想提取文檔中的關鍵詞,就得先對文檔進行分詞。分詞方法通常有兩種:shell
停用詞是一些很是廣泛使用的詞語,對文檔分析做用不大,在文檔分析以前須要將這些詞去掉。好比:微信
另外分詞階段,還須要處理同義詞,不少時候一件東西有多個不一樣的名字。好比「番茄」和「西紅柿」,「鳳梨」和「菠蘿」等。app
中文分詞與英文分詞是不一樣的,咱們分別介紹一個著名的分詞包:機器學習
哪些關鍵詞對一個文檔纔是重要的?好比能夠經過單詞出現的次數,次數越多就表示越重要。
更合理的方法是計算單詞的TF-IDF
值。
單詞的TF-IDF
值能夠描述一個單詞對文檔的重要性,TF-IDF
值越大,則越重要。
Term Frequency
,即詞頻(單詞出現的頻率),也就是一個單詞在文檔中出現的次數,次數越多越重要。
一個單詞的詞頻TF = 單詞出現的次數 / 文檔中的總單詞數
Inverse Document Frequency
,即逆向文檔詞頻,是指一個單詞在文檔中的區分度。它認爲一個單詞出如今的文檔數越少,這個單詞對該文檔就越重要,就越能經過這個單詞把該文檔和其餘文檔區分開。
一個單詞的逆向文檔頻率 IDF = log(文檔總數 / 該單詞出現的文檔數 + 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
值較高的單詞,做爲文檔的特徵屬性。
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
:有三種取值:
content
。analyzer
:有三種取值,分別是:
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)
好比咱們有以下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. ] ])
sklearn 庫中的 naive_bayes 模塊實現了 5 種樸素貝葉斯算法:
naive_bayes.BernoulliNB
類:伯努利樸素貝葉斯的實現。
naive_bayes.CategoricalNB
類:分類樸素貝葉斯的實現。naive_bayes.GaussianNB
類:高斯樸素貝葉斯的實現。
naive_bayes.MultinomialNB
類:多項式樸素貝葉斯的實現。
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
的含義爲平滑參數:
我準備了一個實戰案例,目錄結構以下:
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。實際應用中,訓練一個模型須要大量的數據,也就會花費不少時間。
爲了方便使用,能夠將訓練好的模型存儲到磁盤上,在使用的時候,直接加載出來就可使用。
可使用 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()
函數用於對新聞標題進行預測,返回標題的類型。本篇文章介紹瞭如何利用樸素貝葉斯處理文本分類問題:
TfidfVectorizer
計算單詞權重。
fit_transform
方法提取訓練集特徵。transform
方法提取測試集特徵。MultinomialNB
類訓練模型,這裏給出了一個實戰項目,供你們參考。(本節完。)
推薦閱讀:
歡迎關注做者公衆號,獲取更多技術乾貨。