樸素貝葉斯分類-實戰篇-如何進行文本分類

微信公衆號:碼農充電站pro
我的主頁:https://codeshellme.github.iohtml

上篇介紹了樸素貝葉斯的原理,本篇來介紹如何用樸素貝葉斯解決實際問題。python

樸素貝葉斯最擅長的領域是文本分析,包括:git

  • 文本分類
  • 情感分析
  • 垃圾郵件處理

要對文本進行分類,首先要作的是如何提取文本的主要信息,如何衡量哪些信息是文本中的主要信息呢?github

1,對文檔分詞

咱們知道,一篇文檔是由若干詞彙組成的,也就是文檔的主要信息是詞彙。從這個角度來看,咱們就能夠用一些關鍵詞來描述文檔。正則表達式

這種處理文本的方法叫作詞袋(bag of words)模型,該模型會忽略文本中的詞語出現的順序以及相應的語法,將文檔看作是由若干單詞組成的,且單詞之間互相獨立,沒有關聯。算法

要想提取文檔中的關鍵詞,就得先對文檔進行分詞。分詞方法通常有兩種:shell

  • 第一種是基於字符串匹配。就是掃描字符串。若是發現字符串的子串和詞相同,就算匹配成功。
    • 匹配規則通常有「正向最大匹配」,「逆向最大匹配」,「長詞優先」等。
    • 該類算法的優勢是隻需基於字典匹配,算法簡單;缺點是沒有考慮詞義,處理歧義詞效果不佳。
  • 第二種是基於統計和機器學習。須要人工標註詞性和統計特徵,對中文進行建模。
    • 先要訓練分詞模型,而後基於模型進行計算機率,取機率最大的分詞做爲匹配結果。
    • 常見的序列標註模型有隱馬爾科夫模型條件隨機場

停用詞是一些很是廣泛使用的詞語,對文檔分析做用不大,在文檔分析以前須要將這些詞去掉。好比:微信

  • 中文停用詞:「你,我,他,它,的,了」 等。
  • 英文停用詞:「is,a,the,this,that」 等。
  • 停用詞文件:停用詞通常保存在文件中,須要自行讀取。

另外分詞階段,還須要處理同義詞,不少時候一件東西有多個不一樣的名字。好比「番茄」和「西紅柿」,「鳳梨」和「菠蘿」等。app

中文分詞與英文分詞是不一樣的,咱們分別介紹一個著名的分詞包:機器學習

  • 中文分詞:jieba 分詞比較經常使用,其中包含了中文的停用詞等。
  • 英文分詞:NTLK 比較經常使用,其中包含了英文的停用詞等。

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
  • 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):可理解爲先 fittransform

在上面三個方法中:

  • t 表示 TfidfVectorizer 對象。
  • raw_docs 參數是一個可遍歷對象,其中的每一個元素表示一個文檔。

fit_transformtransform 的用法

  • 通常在擬合轉換數據時,先處理訓練集數據,再處理測試集數據。
  • 訓練集數據會用於擬合模型,而測試集數據不會用於擬合模型。因此:
    • 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']

不知道你有沒有發現,這些特徵詞中不包含ia ?你能解釋一下是爲何嗎?

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 種樸素貝葉斯算法:

  1. naive_bayes.BernoulliNB 類:伯努利樸素貝葉斯的實現。
    • 適用於離散型數據,適合特徵變量是布爾變量,符合 0/1 分佈,在文檔分類中特徵是單詞是否出現
    • 該算法以文件爲粒度,若是該單詞在某文件中出現了即爲 1,不然爲 0。
  2. naive_bayes.CategoricalNB 類:分類樸素貝葉斯的實現。
  3. naive_bayes.GaussianNB 類:高斯樸素貝葉斯的實現。
    • 適用於特徵變量是連續型數據,符合高斯分佈。好比說人的身高,物體的長度等,這種天然界物體
  4. naive_bayes.MultinomialNB 類:多項式樸素貝葉斯的實現。
    • 適用於特徵變量是離散型數據,符合多項分佈。在文檔分類中特徵變量體如今一個單詞出現的次數,或者是單詞的 TF-IDF 值等。
  5. 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 是文件路徑。

在上邊的例子用,咱們須要存儲兩個對象,分別是:

  • tfTF-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 存儲模型,方便模型的使用。

(本節完。)


推薦閱讀:

樸素貝葉斯分類-理論篇-如何經過幾率解決分類問題

決策樹算法-理論篇-如何計算信息純度

決策樹算法-實戰篇-鳶尾花及波士頓房價預測


歡迎關注做者公衆號,獲取更多技術乾貨。

碼農充電站pro

相關文章
相關標籤/搜索