1. 程式人生 > >文字分類---樸素貝葉斯(2)

文字分類---樸素貝葉斯(2)

基於sklearn的文字分類—樸素貝葉斯(2)

本文是文字分類的第二篇,記錄使用樸素貝葉斯進行文字分類任務,資料集下載地址:http://thuctc.thunlp.org/

文字分類的主要內容如下:
- 1.基於邏輯迴歸的文字分類
- 2.基於樸素貝葉斯的文字分類
- 3.使用LDA進行文件降維以及特徵選擇
- 4.基於SVM的文字分類
- 5.基於多層感知機MLPC的文字分類
- 6.基於卷積神經網路詞級別的文字分類以及調參
- 7.基於卷積神經網路的句子級別的文字分類以及調參
- 8.基於Facebook fastText的快速高效文字分類
- 9.基於RNN的文字分類
- 10.基於LSTM的文字分類
- 11.總結

1 資料預處理

其中使用的訓練資料來自清華大學開源的文字分類資料集,原始資料集比較大,提供下載的是提取的小資料,thu_data_500 表示每個類提取500篇文章,thu_data_3000 表示每個類提取3000篇文章,一共14個類別,資料處理的程式碼如下:

import os
import codecs
import jieba
import re

from sklearn.utils import shuffle
category = ['星座', '股票', '房產', '時尚', '體育', '社會', '家居', '遊戲', '彩票', '科技', '教育', '時政'
, '娛樂', '財經']
# 每篇文件保留的文件數量
#per_class_max_docs = 1000

def load_data_to_mini(path, to_path, per_class_max_docs=1000):
    """
    處理清華大學語料庫,將類別和文件處理成fasttext 所需要的格式
    :param path: 
    :param to_path: 
    :return: 
    """
    # 抽取後的語料庫
    corpus = []
    if not os.path.isdir(path):
        print('path error'
) # 列舉當前目錄下的所有子列別目錄 with codecs.open(to_path, 'a') as f: for files in os.listdir(path): curr_path = os.path.join(path, files) print(curr_path) if os.path.isdir(curr_path): count = 0 docs = [] for file in os.listdir(curr_path): count += 1 if count > per_class_max_docs: break file_path = os.path.join(curr_path, file) # 讀取檔案中的內容 with codecs.open(file_path, 'r', encoding='utf-8') as fd: docs.append('__label__' + files + ' ' + ' '.join(jieba.cut(re.sub('[ \n\r\t]+', '', fd.read())))) f.write('__label__' + files + ' ' + ' '.join(jieba.cut(re.sub('[ \n\r\t]+', '', fd.read())))) corpus.append(docs) # 將資料寫到一個新的檔案中 with codecs.open(to_path, 'a') as f: for docs in corpus: for doc in docs: f.write(doc + '\n') return corpus

通過呼叫下面的程式碼,執行小資料集的提取

corpus = load_data_to_mini('../dataset/THUCNews', 'thu_data_500', 500)
../dataset/THUCNews/股票
../dataset/THUCNews/星座
../dataset/THUCNews/遊戲
../dataset/THUCNews/社會
../dataset/THUCNews/教育
../dataset/THUCNews/時尚
../dataset/THUCNews/財經
../dataset/THUCNews/體育
../dataset/THUCNews/娛樂
../dataset/THUCNews/時政
../dataset/THUCNews/彩票
../dataset/THUCNews/房產
../dataset/THUCNews/家居
../dataset/THUCNews/科技

我們看下提取的結果

print('corpus size(%d,%d)' %(len(corpus), len(corpus[0])))
corpus size(14,500)

可以看到,結果一共是14個類,每個類1000篇文件,下面看下corpus裡面的具體內容

corpus[0][1]
'_星座_ 啥 星座 男是 自信 戀愛 王 ( 圖 ) \u3000 \u3000 每 一場 風花雪月 故事 的 發生 , 都 離不開 追逐 與 被 追逐 、 主動 與 被動 的 關係 。 所以 有人 將 戀愛 比喻 為 雙人 共舞 , 在 欲說還休 間 , 總有 一個 人 的 腳尖 踏過 另 一個 人 的 讓步 。 然而 讓步 的 人 未必 就是 弱勢 一方 , 對方 的 若即若離 , 究竟 是 落花有意 、 流水無情 , 還是 胸有成竹 時 欲擒故縱 的 高姿態 , 你 能否 洞悉 這些 男生 的 心思 呢 ? 現在 , 就讓 摘星 工廠 — 星 吧 為 你 盤點 在 戀愛 中 最 自信 的 星座 男 , 並 分析 他們 常見 的 戀愛 心理 ! \u3000 \u3000 第一名 : 水瓶座 \u3000 \u3000 水瓶座 男生 在 戀愛 時 的 心態 絕對 是 一等一 的 自信 , 有時 這種 自信 甚至 會 成為 戀愛 對手 方 的 困擾 。 這 是因為 , 在 絕對 的 自信心 驅使 下 , 水瓶 男對 想要 追到手 的 女孩 採取 的 攻勢 , 歸結 起來 無非 是 “ 軟磨硬泡 、 死纏 濫 打 ” 八字 真言 。 哪怕 女孩 對 他們 的 窮追不捨 已經 恨 到 連 牙根 都 開始 癢癢 了 , 他們 仍然 視若無睹 ( 或許 是 他們 根本 沒 發現 也 說不定 , 水瓶 男 可是 出名 的 粗 神經 哦 ) , 不死心 地 策劃 著 第一百 零 一次 進攻 。 對 水瓶 男 來說 , 愛情 中 他們 絕對 不 允許 自己 遭遇 失敗 , 而 他們 鍥而不捨 的 後果 無非 兩個 : 或是 女孩 的 芳心 終於 被 打動 , 或是 他們 在 一次次 嘗試 後 覺得 不好玩 了 , 於是 選擇 放棄 。 注意 , 這裡 所說 的 他們 的 心態 是 “ 放棄 ” 而 不是 “ 認輸 ” 。 也就是說 , 無論是 進 還是 退 , 水瓶 男 始終 預設 自己 是 一段 感情 中 的 掌控 者 。 \u3000 \u3000 遭遇 水瓶 男 的 美女 請 注意 : 假如 你 愛 上 水瓶 男 , 卻 又 對 他 的 某些 不良習慣 感到 惱火 , 那 你 絕對 不要 嘗試 說服 他 去 改變 這些 習慣 。 最好 的 方式 是 以柔克剛 , 通過 各種 暗示 讓 水瓶 男 自己 意識 到 這些 問題 非改 不可 , 然後 乖乖 就範 。 要不然 , 小心 水瓶 男 和 你 犯上 犟脾氣 哦 ! \u3000 \u3000 第二名 : 射手座 \u3000 \u3000 射手座 男生 最 喜歡 遊戲 花叢 的 感覺 了 , 這 不是 說 他們 花心 , 而是 說 他們 骨子裡 有 一種 騎士 精神 , 他們 覺得 有 義務 去 照顧 每 一位 被 自己 青睞 的 異性 。 射手 男 的 這種 性格 , 很多 女孩 都 是 既 愛 又 恨 , 因為 她們 中 不少 人 或許 都 曾 經歷 過 射手 男 的 這種 “ 善意 曖昧 ” , 並 在 這 上面 吃 過 苦頭 ! 對於 女孩 們 的 聲討 , 射手 男 自己 也 是 很 委屈 的 : 我 分明 只是 想 做 個 好人 , 想替 那麼 多 可愛 的 美女 做點 什麼 , 分擔 她們 的 憂愁 , 怎麼 到頭來 反而 全成 了 我 的 不是 ? 看來 一定 是 咱 個人 魅力 太高 , 不經意 間 都 能 電到 一票 美女 … … 抱 著 這樣 心態 的 射手 男 , 自信心 怎麼 會 不滿 到 爆棚 呢 ? \u3000 \u3000 遭遇 射手 男 的 美女 請 注意 : 假如 你 確定 射手 男對 你 有意 , 而 不是 單純 的 憐香惜玉 , 那麼 一定 要 趁 他 還 沒 將 你 追到手 , 好好 矯正 一下 他 四處 亂 放電 的 習慣 , 否則 日後 恐怕 你 就 有 得 忙 哦 ! \u3000 \u3000 第三名 : 天蠍座 \u3000 \u3000 天蠍座 的 男生 不會 輕易 鎖定 戀愛 的 進攻 目標 , 因為 他們 的 自尊心 很強 , 面對 一段 感情 , 不乏 患得患失 的 心態 , 所以 假如 他們 確定 出手 , 那 一定 是 有 了 必勝 的 把握 。 因此 , 天蠍 男 在 周圍 人 的 眼中 , 總是 呈現出 完美 情聖 的 姿態 : 他們 能 精確 洞悉 女孩 的 心思 ; 對於 何時 進 、 何時 退 , 時機 都 拿捏 得當 , 並且 很會 營造 各種 浪漫 氣氛 , 無論是 表白 還是 約會 , 總能 搞 得 超有 情調 … … 但 這 一切 表象 的 背後 , 其實 是 天蠍 男 煞費苦心 的 各種 權衡 。 \u3000 \u3000 遭遇 天蠍 男 的 美女 請 注意 : 當天 蠍 男 將 視線 集中 在 你 身上 時 , 並 不 意味著 他 選擇 了 你 , 只能 說明 你 成為 了 他 的 目標 之一 。 這種 時候 , 一定 不要 輕易 淪陷 , 相反 , 一定 要 吊足 他 的 胃口 , 讓 自己 成為 他 心中 的 神祕 女神 , 這樣 才能 激發 天蠍 男 更 強 的 征服 欲 ! \u3000 \u3000 第四名 : 天秤座 \u3000 \u3000 天秤座 男生 通常 氣質 儒雅 、 社交 廣泛 , 因此 身邊 總 少不了 一些 愛慕者 , 而 這些 女孩 也 是 他們 在 情場 上 擁有 絕佳 自信心 的 重要依據 。 說白了 就是 天秤 男 身邊 總是 圍繞 著 數量 可觀 的 紅顏 知己 , 即使 他們 情場失意 , 也 會 很快 在 其中 某個 紅顏 知己 那裡 找到 安慰 。 所以 , 天秤 男 在 追逐 一段 感情 時 , 並 不在意 最終 的 結果 是 成 是 敗 。 反正 就算 門 被 關上 了 , 還 可以 回頭 看看 背後 的 窗子 裡 , 有 哪 一扇 碰巧 剛剛 為 他們 開啟 。 \u3000 \u3000 遭遇 天秤 男 的 美女 請 注意 : 除非 你 確定 自己 不在意 成為 天秤 男 身邊 眾多 花卉 中 的 一株 、 不在意 他 只 將 你 作為 失意 時 傾訴 的 物件 而 非 攜手 到 老 的 伴侶 , 否則 , 建議 你 還是 不要 奢望 靠 “ 友情 漸變 ” 的 方式 來 征服 天秤 男 。 因為 這樣 的 機會 實在 太 渺茫 了 。 天秤 男 身後 究竟 有 多少 紅顏 知己 , 恐怕 只有 他們 自己 知道 , 你 真的 確定 自己 就是 其中 獨一無二 的 那個 嗎 ? \u3000 \u3000 第五名 : 魔羯座 \u3000 \u3000 魔羯座 男生 做事 一向 穩紮穩打 , 對待 感情 問題 也 不 例外 。 對 魔羯 男 來說 , 比較 完美 的 感情 模式 是 “ 40 歲 以前 努力 賺錢 立業 , 40 歲 以後 坐擁 香車 美女 ” 。 雖然 劃分 事業 問題 和 情感 問題 的 年齡 線 因人而異 , 但是 總之 , 在 事業 小有成就 前 , 魔羯 男 即使 對 某個 女孩 表現 出 興趣 , 很 有 可能 也 只是 把 這段 感情 當作 正餐 前 的 開胃 小 甜點 , 本質 上 屬於 實戰 前 的 小規模 演習 。 當然 , 不 排除 練 著 練 著 日久生情 、 越談 越 靠 譜 , 但是 這種 浪漫 的 意外 通常 和 魔羯 男 無緣 。 總體 來講 , 魔羯 男 在 談婚論嫁 時 還是 比較 傳統 的 , 他們 更 傾向 於 在 自己 具備 一定 經濟 實力 的 時候 , 作為 鑽石 王老五 去 被 女孩 們 爭搶 。 試問 這種 心態 的 魔羯 男 走上 情場 時 怎麼 可能 不 自信 呢 ? \u3000 \u3000 遭遇 魔羯 男 的 美女 請 注意 : 儘管 前面 說 了 這麼 多 嚇人 的話 , 但是 “ 先下手為強 ” 這句 古訓 在 對付 魔羯 男 的 時候 仍然 是 適用 的 。 只 需 切記 一點 : 魔羯 男 很 信奉 “ 娶妻 娶 賢 ” 這個 原則 , 假如 你 想 讓 魔羯 男視 你 為 寶 , 一定 要 給 他 留下 賢妻良母 的 印象 , 到時候 不怕 他 不 上鉤 !'

可以看到,開頭時label的本文標籤,後面接著的是新聞正文,正文已經使用jieba進行了分詞,詞之間使用空格鍵分開。
下面進行資料的切分,將資料劃分為樣本和標籤,因為讀取的資料是按照類別來分塊的,在後面採用訓練資料和測試資料的時候,會出現問題,所以這裡也需要進行資料的隨機打亂,資料打亂最好不要使用numpy.random.shuffle(),這個效率很低,而且非常容易出現記憶體溢位問題,推薦使用的是pandas或者是sklearn中的shuffle,我使用的是後者。切分的程式碼如下:

def split_data_with_label(corpus):
    """
    將資料劃分為訓練資料和樣本標籤
    :param corpus: 
    :return: 
    """
    input_x = []
    input_y = []

    tag = []
    if os.path.isfile(corpus):
        with codecs.open(corpus, 'r') as f:
            for line in f:
                tag.append(line)

    else:
        for docs in corpus:
            for doc in docs:
                tag.append(doc)
    tag = shuffle(tag)
    for doc in tag:
        index = doc.find(' ')
        input_y.append(doc[:index])
        input_x.append(doc[index + 1 :])

    # 打亂資料,避免在取樣的時候出現類別不均衡現象
    # datasets = np.column_stack([input_x, input_y])
    # np.random.shuffle(datasets)
    # input_x = []
    # input_y = []
    # for i in datasets:
    #     input_x.append(i[:-1])
    #     input_y.append(i[-1:])
    return [input_x, input_y]

這個函式返回兩個值,其中第一個返回值input_x是樣本資料,一共14*1000行,第二個引數input_y和input_x有著相同的行數,每行對應著input_x中新聞樣本的類別標籤.

2.特徵選擇

下面將進行特徵提取,特徵選擇的方法有基本的bag-of-words, tf-idf,n-gran等,我們將對這些方法進行實驗,下面是程式碼:

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.cross_validation import train_test_split
from sklearn.metrics.scorer import make_scorer
from sklearn.naive_bayes import MultinomialNB
from sklearn import metrics

from time import time
def feature_extractor(input_x, case='tfidf', n_gram=(1,1)):
    """
    特徵抽取
    :param corpus: 
    :param case: 不同的特徵抽取方法
    :return: 
    """
    if n_gram == (1,1):
        if case.lower() == 'tfidf':
            return TfidfVectorizer().fit_transform(input_x)
        elif case.lower() == 'bagofwords':
            return CountVectorizer().fit_transform(input_x)
    else:
        if case.lower() == 'tfidf':
            return TfidfVectorizer(ngram_range=n_gram).fit_transform(input_x)
        elif case.lower() == 'bagofwords':
            return CountVectorizer(ngram_range=n_gram).fit_transform(input_x)

接下來將進行訓練資料和測試資料的切分,現在不進行更好的交叉驗證等技術,僅僅簡單的以一定的比例劃分訓練資料和測試資料。使用sklearn中提供的工具,具體程式碼如下:

def split_data_to_train_and_test(corpus, indices=0.2, random_state=10, shuffle=True):
    """
    將資料劃分為訓練資料和測試資料
    :param corpus: [input_x]
    :param indices: 劃分比例
    :random_state: 隨機種子
    :param shuffle: 是否打亂資料
    :return: 
    """
    input_x, y = corpus

    # 切分資料集
    x_train, x_dev, y_train, y_dev = train_test_split(input_x, y, test_size=indices, random_state=10)
    print("Vocabulary Size: {:d}".format(input_x.shape[1]))
    print("Train/Dev split: {:d}/{:d}".format(len(y_train), len(y_dev)))
    return x_train, x_dev, y_train, y_dev

函式返回四個值,分別是訓練資料的樣本,訓練資料的標籤,測試資料樣本,測試資料真實標籤,下面呼叫樸素貝葉斯進行分類。

樸素貝葉斯是一種生成模型,其是貝葉斯分類器的naive方法,其naive表現在其採用了”屬性條件假定性假設”,對於已知類別,假設所有屬性相互獨立,也就是說,假設每個屬性獨立的對分類結果發生影響。

在文字分類任務中,naive bayes假定文件中的每個詞都是獨立的,當前詞與其上下文是無關的,每個詞即文件中的每個特徵單獨的對分類結果作貢獻。

這裡主要是進行相關的實驗,不在理論上展開太多,下面採用樸素貝葉斯分類器進行文件分類,具體程式碼如下:

def fit_and_predicted(train_x, train_y, test_x, test_y):
    """
    訓練與預測
    :param train_x: 
    :param train_y: 
    :param test_x: 
    :param test_y: 
    :return: 
    """
    clf = MultinomialNB().fit(train_x, train_y)
    predicted = clf.predict(test_x)
    print(metrics.classification_report(test_y, predicted))
    print('accuracy_score: %0.5fs' %(metrics.accuracy_score(test_y, predicted)))

上面函式呼叫MultinomialNB(),其是假設樣本符合多項式分佈的貝葉斯,此外sklearn還提供了naive_bayes.GaussianNB和naive_bayes.BernoulliNB。其區別可以從名稱中看出來。

講述完了使用naive bayes 進行文字分類的流程,下面將進行實際的程式碼執行階段了。

# 1. 載入語料
corpus = split_data_with_label('thu_data_2000')

2.1 bag-of-words

input_x, y = corpus
# 2. 特徵選擇
input_x = feature_extractor(input_x, 'bagofwords')
# 3.切分訓練資料和測試資料
train_x, test_x, train_y, test_y = split_data_to_train_and_test([input_x, y])
Vocabulary Size: 279691
Train/Dev split: 22411/5603
# 4. 訓練以及測試
t0 = time()
print('\t\t使用 bag-of-words 進行特徵選擇的樸素貝葉斯文字分類\t\t')
fit_and_predicted(train_x, train_y, test_x, test_y)
print('time uesed: %0.4fs' %(time() - t0))
        使用 bag-of-words 進行特徵選擇的樸素貝葉斯文字分類        
             precision    recall  f1-score   support

       _體育_       0.94      0.98      0.96       402
       _娛樂_       0.82      0.93      0.87       369
       _家居_       0.91      0.82      0.86       390
       _彩票_       0.99      0.93      0.96       408
       _房產_       0.92      0.89      0.90       402
       _教育_       0.92      0.91      0.91       380
       _時尚_       0.90      0.90      0.90       416
       _時政_       0.89      0.87      0.88       417
       _星座_       0.93      0.97      0.95       399
       _遊戲_       0.94      0.87      0.90       386
       _社會_       0.81      0.88      0.85       420
       _科技_       0.86      0.78      0.82       413
       _股票_       0.83      0.83      0.83       402
       _財經_       0.85      0.93      0.89       399

avg / total       0.89      0.89      0.89      5603

accuracy_score: 0.89184s
time uesed: 0.4997s

2.2 TF-IDF

input_x, y = corpus
# 2. 特徵選擇
input_x = feature_extractor(input_x, 'tfidf')
# 3.切分訓練資料和測試資料
train_x, test_x, train_y, test_y = split_data_to_train_and_test([input_x, y])
Vocabulary Size: 279691
Train/Dev split: 22411/5603
# 4. 訓練以及測試
t0 = time()
print('\t\t使用 TF-IDF 進行特徵選擇的樸素貝葉斯文字分類\t\t')
fit_and_predicted(train_x, train_y, test_x, test_y)
print('time uesed: %0.4fs' %(time() - t0))
        使用 TF-IDF 進行特徵選擇的樸素貝葉斯文字分類      
             precision    recall  f1-score   support

       _體育_       0.93      0.99      0.96       402
       _娛樂_       0.85      0.89      0.87       369
       _家居_       0.94      0.83      0.88       390
       _彩票_       0.99      0.94      0.96       408
       _房產_       0.91      0.88      0.89       402
       _教育_       0.86      0.92      0.89       380
       _時尚_       0.90      0.91      0.91       416
       _時政_       0.91      0.83      0.87       417
       _星座_       0.91      0.98      0.94       399
       _遊戲_       0.93      0.89      0.91       386
       _社會_       0.84      0.85      0.84       420
       _科技_       0.90      0.74      0.81       413
       _股票_       0.80      0.88      0.84       402
       _財經_       0.85      0.93      0.89       399

avg / total       0.89      0.89      0.89      5603

accuracy_score: 0.89024s
time uesed: 0.4860s

可以看出使用TF-IDF和簡單的使用詞袋模型效果相當, 下面我們在tf-idf作為特徵選擇的基礎上增加文字的n-gram特徵。

2.3 n_gram 抽取unigram和bigram

input_x, y = corpus
# 2. 特徵選擇
input_x = feature_extractor(input_x, 'tfidf', n_gram=(1,2))
# 3.切分訓練資料和測試資料
train_x, test_x, train_y, test_y = split_data_to_train_and_test([input_x, y])
Vocabulary Size: 5027849
Train/Dev split: 22411/5603
# 4. 訓練以及測試
t0 = time()
print('\t 使用 n_gram(unigram,bigram) 進行特徵選擇的樸素貝葉斯文字分類\t\t\n')
fit_and_predicted(train_x, train_y, test_x, test_y)
print('time uesed: %0.4fs' %(time() - t0))
     使用 n_gram(unigram,bigram) 進行特徵選擇的樸素貝葉斯文字分類     

             precision    recall  f1-score   support

       _體育_       0.92      0.99      0.95       402
       _娛樂_       0.85      0.91      0.88       369
       _家居_       0.96      0.82      0.89       390
       _彩票_       0.99      0.94      0.96       408
       _房產_       0.90      0.90      0.90       402
       _教育_       0.86      0.92      0.89       380
       _時尚_       0.91      0.90      0.91       416
       _時政_       0.90      0.84      0.87       417
       _星座_       0.92      0.98      0.95       399
       _遊戲_       0.93      0.90      0.91       386
       _社會_       0.83      0.87      0.85       420
       _科技_       0.91      0.75      0.82       413
       _股票_       0.82      0.85      0.83       402
       _財經_       0.81      0.94      0.87       399

avg / total       0.90      0.89      0.89      5603

accuracy_score: 0.89291s
time uesed: 4.3272s

2.4 n_gram 抽取unigram、bigram和trigram

input_x, y = corpus
# 2. 特徵選擇
input_x = feature_extractor(input_x, 'tfidf', n_gram=(1,3))
# 3.切分訓練資料和測試資料
train_x, test_x, train_y, test_y = split_data_to_train_and_test([input_x, y])
Vocabulary Size: 12286931
Train/Dev split: 22411/5603
# 4. 訓練以及測試
t0 = time()
print('\t\t使用 n-gram(unigram、bigram和trigram) 進行特徵選擇的樸素貝葉斯文字分類\t\t')
fit_and_predicted(train_x, train_y, test_x, test_y)
print('time uesed: %0.4fs' %(time() - t0))
        使用 n-gram(unigram、bigram和trigram) 進行特徵選擇的樸素貝葉斯文字分類      
             precision    recall  f1-score   support

       _體育_       0.92      0.99      0.95       402
       _娛樂_       0.86      0.91      0.88       369
       _家居_       0.97      0.83      0.90       390
       _彩票_       0.99      0.94      0.96       408
       _房產_       0.90      0.90      0.90       402
       _教育_       0.86      0.93      0.89       380
       _時尚_       0.92      0.90      0.91       416
       _時政_       0.90      0.83      0.87       417
       _星座_       0.93      0.98      0.95       399
       _遊戲_       0.93      0.90      0.91       386
       _社會_       0.83      0.88      0.85       420
       _科技_       0.91      0.76      0.83       413
       _股票_       0.83      0.83      0.83       402
       _財經_       0.80      0.94      0.86       399

avg / total       0.90      0.89      0.89      5603

accuracy_score: 0.89291s
time uesed: 9.5657s

2.5 n_gram 抽取unigram、bigram、trigram和4-gram

input_x, y = corpus
# 2. 特徵選擇
input_x = feature_extractor(input_x, 'tfidf', n_gram=(1,4))
# 3.切分訓練資料和測試資料
train_x, test_x, train_y, test_y = split_data_to_train_and_test([input_x, y])
Vocabulary Size: 19909967
Train/Dev split: 22411/5603
# 4. 訓練以及測試
t0 = time()
print('\t\t使用 n-gram(unigram、bigram、trigram和4-gram) 進行特徵選擇的樸素貝葉斯文字分類\t\t')
fit_and_predicted(train_x, train_y, test_x, test_y)
print('time uesed: %0.4fs' %(time() - t0))
        使用 n-gram(unigram、bigram、trigram和4-gram) 進行特徵選擇的樸素貝葉斯文字分類       
             precision    recall  f1-score   support

       _體育_       0.92      0.99      0.95       402
       _娛樂_       0.85      0.91      0.88       369
       _家居_       0.96      0.83      0.89       390
       _彩票_       0.99      0.94      0.96       408
       _房產_       0.90      0.90      0.90       402
       _教育_       0.86      0.93      0.89       380
       _時尚_       0.92      0.90      0.91       416
       _時政_       0.90      0.83      0.87       417
       _星座_       0.93      0.98      0.95       399
       _遊戲_       0.93      0.90      0.92       386
       _社會_       0.83      0.88      0.85       420
       _科技_       0.91      0.75      0.82       413
       _股票_       0.84      0.83      0.84       402
       _財經_       0.79      0.94      0.86       399

avg / total       0.90      0.89      0.89      5603

accuracy_score: 0.89327s
time uesed: 14.8933s

2.6 n_gram 抽取unigram、bigram、trigram、4-gram和5-gram

input_x, y = corpus
# 2. 特徵選擇
input_x = feature_extractor(input_x, 'tfidf', n_gram=(1,5))
# 3.切分訓練資料和測試資料
train_x, test_x, train_y, test_y = split_data_to_train_and_test([input_x, y])
Vocabulary Size: 27610364
Train/Dev split: 22411/5603
# 4. 訓練以及測試
t0 = time()
print('\t\t使用 n-gram(unigram、bigram、trigram、4-gram和5-gram) 進行特徵選擇的樸素貝葉斯文字分類\t\t')
fit_and_predicted(train_x, train_y, test_x, test_y)
print('time uesed: %0.4fs' %(time() - t0))
        使用 n-gram(unigram、bigram、trigram、4-gram和5-gram) 進行特徵選擇的樸素貝葉斯文字分類        
             precision    recall  f1-score   support

       _體育_       0.92      0.99      0.95       402
       _娛樂_       0.85      0.91      0.88       369
       _家居_       0.96      0.83      0.89       390
       _彩票_       0.99      0.94      0.96       408
       _房產_       0.91      0.90      0.90       402
       _教育_       0.86      0.93      0.89       380
       _時尚_       0.92      0.90      0.91       416
       _時政_       0.91      0.83      0.86       417
       _星座_       0.93      0.98      0.95       399
       _遊戲_       0.93      0.90      0.91       386
       _社會_       0.83      0.88      0.85       420
       _科技_       0.91      0.75      0.82       413
       _股票_       0.84      0.83      0.83       402
       _財經_       0.79      0.94      0.86       399

avg / total       0.90      0.89      0.89      5603

accuracy_score: 0.89220s
time uesed: 22.0319s

2.7 n_gram 僅僅只用bigram

input_x, y = corpus
# 2. 特徵選擇
input_x = feature_extractor(input_x, 'tfidf', n_gram=(2,2))
# 3.切分訓練資料和測試資料
train_x, test_x, train_y, test_y = split_data_to_train_and_test([input_x, y])
Vocabulary Size: 4748158
Train/Dev split: 22411/5603
# 4. 訓練以及測試
t0 = time()
print('\t\t僅僅使用 bigram 進行特徵選擇的樸素貝葉斯文字分類\t\t')
fit_and_predicted(train_x, train_y, test_x, test_y)
print('time uesed: %0.4fs' %(time() - t0))
        僅僅使用 bigram 進行特徵選擇的樸素貝葉斯文字分類        
             precision    recall  f1-score   support

       _體育_       0.92      0.99      0.95       402
       _娛樂_       0.85      0.91      0.88       369
       _家居_       0.96      0.83      0.89       390
       _彩票_       0.99      0.94      0.96       408
       _房產_       0.91      0.90      0.90       402
       _教育_       0.86      0.93      0.89       380
       _時尚_       0.92      0.90      0.91       416
       _時政_       0.91      0.83      0.86       417
       _星座_       0.93      0.98      0.95       399
       _遊戲_       0.93      0.90      0.91       386
       _社會_       0.83      0.88      0.85       420
       _科技_       0.91      0.75      0.82       413
       _股票_       0.84      0.83      0.83       402
       _財經_       0.79      0.94      0.86       399

avg / total       0.90      0.89      0.89      5603

accuracy_score: 0.89220s
time uesed: 20.1179s

可以看到在N元語法的特徵增加後,分類效果有提升效果,但是在當n=5的時候,效果有一定的下降(不應該是一個結論,只是在資料集上的一個個例,因在吳軍老師的數學之美中說明,N越大,效果是越好的)。同時也應該看到,隨著N的增大,特徵的數量也在顯著的增長,同時訓練時間也在逐漸增加,在應用中,應該在效率和結果直接有一個較好的選擇才會使得最後的工作高效簡潔。

下面將進行樸素貝葉斯的調參工作,機器學習很多時候都是在進行引數的調整工作,一個好的引數可以讓模型產生更好的效果。

3. 使用交叉驗證

上面的實驗中,我們只是簡單的選取20%的資料作為測試集和80%的資料作為訓練集,這樣做是存在偶然性結構的, 即可能劃分資料集不能表示真實的資料分佈,導致模型訓練引數的泛化性不好,採用交叉驗證可以避免資料集劃分導致的問題,下面,就進行該實驗,實驗在上一步的基礎上使用TF-IDF和unigram,bigram和trigram來進行特徵選擇。


def train_and_test_with_CV(corpus, cv=5, alpha=1, fit_prior=True):
    """

    """
    input_x, y = corpus
#     scoring = {'prec_macro': 'precision_macro',
#                'rec_micro': make_scorer(recall_score, average='macro')}
    scoring = ['precision_macro', 'recall_macro', 'f1_macro']
    clf = MultinomialNB(alpha=alpha, fit_prior=fit_prior)
    scores = cross_validate(clf, input_x, y, scoring=scoring,
                            cv=cv, return_train_score=True)
    sorted(scores.keys()) 
    return scores
input_x, y = corpus
# 2. 特徵選擇
input_x = feature_extractor(input_x, 'tfidf')
scores = train_and_test_with_CV([input_x, y])
scores
{'fit_time': array([ 0.69856882,  0.6891861 ,  0.68457079,  0.68122745,  0.68401599]),
 'score_time': array([ 0.24055672,  0.25055385,  0.24642444,  0.24583435,  0.25062966]),
 'test_f1_macro': array([ 0.93190598,  0.93358814,  0.92900074,  0.93620104,  0.93139325]),
 'test_precision_macro': array([ 0.93411186,  0.93509947,  0.93082131,  0.93790787,  0.93312355]),
 'test_recall_macro': array([ 0.93178571,  0.93357143,  0.92892857,  0.93607143,  0.93142857]),
 'train_f1_macro': array([ 0.95534592,  0.95516529,  0.95665886,  0.95573948,  0.95629695]),
 'train_precision_macro': array([ 0.95629235,  0.95618146,  0.95767379,  0.9566414 ,  0.95725075]),
 'train_recall_macro': array([ 0.95526786,  0.95508929,  0.95660714,  0.95571429,  0.95625   ])}

交叉驗證的K=10的時候

scores = train_and_test_with_CV([input_x, y],cv=10)
scores
{'fit_time': array([ 0.86708903,  0.85473442,  0.85248995,  0.8252821 ,  0.93414092,
         1.118325  ,  1.41779876,  1.2739253 ,  1.98447776,  1.11306906]),
 'score_time': array([ 0.16501474,  0.16674805,  0.17412877,  0.15616584,  0.14272356,
         0.21593046,  0.44325757,  0.30753231,  0.19881511,  0.20148587]),
 'test_f1_macro': array([ 0.93355446,  0.93725727,  0.9367952 ,  0.93744957,  0.9319552 ,
         0.93147271,  0.94146465,  0.93213457,  0.93504439,  0.93282066]),
 'test_precision_macro': array([ 0.93583195,  0.93947505,  0.93829325,  0.93885285,  0.9343669 ,
         0.93272889,  0.94303357,  0.93393932,  0.93704557,  0.93441742]),
 'test_recall_macro': array([ 0.93357143,  0.93714286,  0.93678571,  0.9375    ,  0.93178571,
         0.93142857,  0.94142857,  0.93214286,  0.935     ,  0.93285714]),
 'train_f1_macro': array([ 0.9565462 ,  0.95530877,  0.95550728,  0.95550059,  0.9569892 ,
         0.95626531,  0.95577014,  0.95573623,  0.95608533,  0.95600703]),
 'train_precision_macro': array([ 0.95739641,  0.95630651,  0.95651889,  0.95645792,  0.95790256,
         0.95727949,  0.95675378,  0.95657705,  0.9570335 ,  0.95699902]),
 'train_recall_macro': array([ 0.95650794,  0.9552381 ,  0.95543651,  0.95543651,  0.95694444,
         0.95619048,  0.95571429,  0.95571429,  0.95603175,  0.95595238])}

4 尋找最好的引數

樸素貝葉斯的引數比偶較少,根據sklearn的文件可以看出,其引數主要是平滑項引數alpha、是否需要依靠樣本去學習類別先驗fit_prior和給定類別先驗class_prio的給定.

下面對這些引數做相關實驗。

from sklearn.grid_search import GridSearchCV
def train_and_predicted_with_graid(corpus, cv, param_grid):
    input_x, y = corpus

    scoring = ['precision_macro', 'recall_macro', 'f1_macro']
    clf = MultinomialNB()
    grid = GridSearchCV(clf, param_grid, cv=cv, scoring='accuracy')

    scpres = grid.fit(input_x, y)

    print('parameters:')
    best_parameters = grid.best_estimator_.get_params()
    for param_name in sorted(best_parameters):
        print('\t%s: %r' %(param_name, best_parameters[param_name]))
    return scores
k_alpha = [0, 1,2,4,10]
fit_prior= [True, False]
param_grid = dict(alpha=k_alpha, fit_prior=fit_prior)
print(param_grid)
scores = train_and_predicted_with_graid([input_x, y], 5, param_grid)
parameters:
    alpha: 0
    class_prior: None
    fit_prior: True


/usr/local/lib/python3.5/dist-packages/sklearn/naive_bayes.py:472: UserWarning: alpha too small will result in numeric errors, setting alpha = 1.0e-10
  'setting alpha = %.1e' % _ALPHA_MIN)
print(scores)
{'test_recall_macro': array([ 0.93357143,  0.93714286,  0.93678571,  0.9375    ,  0.93178571,
        0.93142857,  0.94142857,  0.93214286,  0.935     ,  0.93285714]), 'test_precision_macro': array([ 0.93583195,  0.93947505,  0.93829325,  0.93885285,  0.9343669 ,
        0.93272889,  0.94303357,  0.93393932,  0.93704557,  0.93441742]), 'train_recall_macro': array([ 0.95650794,  0.9552381 ,  0.95543651,  0.95543651,  0.95694444,
        0.95619048,  0.95571429,  0.95571429,  0.95603175,  0.95595238]), 'fit_time': array([ 0.86708903,  0.85473442,  0.85248995,  0.8252821 ,  0.93414092,
        1.118325  ,  1.41779876,  1.2739253 ,  1.98447776,  1.11306906]), 'train_f1_macro': array([ 0.9565462 ,  0.95530877,  0.95550728,  0.95550059,  0.9569892 ,
        0.95626531,  0.95577014,  0.95573623,  0.95608533,  0.95600703]), 'test_f1_macro': array([ 0.93355446,  0.93725727,  0.9367952 ,  0.93744957,  0.9319552 ,
        0.93147271,  0.94146465,  0.93213457,  0.93504439,  0.93282066]), 'train_precision_macro': array([ 0.95739641,  0.95630651,  0.95651889,  0.95645792,  0.95790256,
        0.95727949,  0.95675378,  0.95657705,  0.9570335 ,  0.95699902]), 'score_time': array([ 0.16501474,  0.16674805,  0.17412877,  0.15616584,  0.14272356,
        0.21593046,  0.44325757,  0.30753231,  0.19881511,  0.20148587])}

使用最佳引數進行訓練

scores = train_and_test_with_CV([input_x, y], cv=10, alpha=0)
print(scores)
{'test_recall_macro': array([ 0.98714286,  0.98607143,  0.98642857,  0.98607143,  0.98178571,
        0.98464286,  0.98535714,  0.98214286,  0.97714286,  0.98392857]), 'test_precision_macro': array([ 0.98735865,  0.98622353,  0.98659011,  0.98618267,  0.98201582,
        0.9849389 ,  0.98558907,  0.9825292 ,  0.97748478,  0.9840529 ]), 'train_recall_macro': array([ 0.99809524,  0.99781746,  0.99785714,  0.99789683,  0.9975    ,
        0.9975    ,  0.9977381 ,  0.99801587,  0.99785714,  0.99781746]), 'fit_time': array([ 0.81418514,  0.80592179,  0.81234241,  0.79919314,  0.8071866 ,
        0.79807925,  0.79640722,  0.77489328,  0.80264211,  0.7880013 ]), 'train_f1_macro': array([ 0.9980958 ,  0.99781816,  0.99785777,  0.99789754,  0.99750104,
        0.99750157,  0.99773862,  0.99801643,  0.99785776,  0.99781855]), 'test_f1_macro': array([ 0.98716361,  0.98606902,  0.98643996,  0.98603623,  0.98180131,
        0.98463883,  0.98534904,  0.98216567,  0.9771135 ,  0.98393848]), 'train_precision_macro': array([ 0.99810511,  0.99782832,  0.99787039,  0.99790794,  0.99751509,
        0.99751977,  0.99775029,  0.99802555,  0.99786749,  0.99783064]), 'score_time': array([ 0.17398071,  0.14726663,  0.16023517,  0.14582086,  0.17289758,
        0.14939237,  0.15340734,  0.14639425,  0.15420914,  0.15345049])}

可以看到,訓練得到的結果相對於在沒有進行最優引數調整的時候提高了約5%,效果是明顯的。

5. 總結

本文記錄了使用sklearn,採用樸素貝葉斯進行文字分類任務,在使用簡單的bag-of-word,tf-idf 作為引數選擇,為了增加特徵,保留句子中的部分語義資訊,
,我們還進行了n-gram操作,在特徵選擇階段,我們發現,使用tf-idf的特徵表示方法比簡單的詞袋模型要好,添加了n-gram特徵後,效果也有一定的提升;

在選取好了特徵後,我們對資料集進行交叉驗證,發現cv=10相對cv=5的時候有細微的提升,但是效果不明顯,說明本資料集在cv=5的時候已經夠用了,不需要再繼續使用CV=10增加計算量;

最後,我們進行了最佳引數的尋找,由於naive bayes 分類器的引數較少,調參起來相對簡單,在選用了最佳的引數後,我們得出了相對最優的結果,在測試集上P,R,F值幾乎都達到了98%以上。
但是分析我們的最佳引數,其中平滑項引數我們選取的是0,在模型中說明是不需要進行資料的平滑處理,但是經驗而言,當資料變大,在開放的資料中,平滑項是必不可少的,此處的0,只是作為最有引數選尋找的個例,不應該用作一般性結論。