1. 程式人生 > >基於NLP自然語言構建的文件自動分類系統(搜狐娛樂)—word2vec模型

基於NLP自然語言構建的文件自動分類系統(搜狐娛樂)—word2vec模型

開發環境 jupyter notebook

1 載入資料

import numpy as np
import pandas as pd

# 檢視訓練資料
train_data = pd.read_csv('data/sohu_train.txt', sep='\t', header=None, 
                         dtype=np.str_, encoding='utf8', names=[u'頻道', u'文章'])
train_data.head() 

# 載入停用詞
stopwords = set()
with open('data/stopwords.txt'
, 'rb') as infile: for line in infile: line = line.rstrip('\n') if line: stopwords.add(line.lower())

2 計算每個文章的詞向量

# 載入訓練好的Word2Vec模型
# 需要 4.0_訓練word2vec模型.ipynb 的執行結果
from gensim.models import Word2Vec
w2v = Word2Vec.load('output_word2vec/model.w2v') 

# 使用文章中所有詞的平均詞向量作為文章的向量
import jieba def compute_doc_vec_single(article): vec = np.zeros((w2v.layer1_size,), dtype=np.float32) n = 0 for word in jieba.cut(article): if word in w2v: vec += w2v[word] n += 1 return vec / n def compute_doc_vec(articles): return np.row_stack([
compute_doc_vec_single(x) for x in articles]) x = compute_doc_vec(train_data[u'文章']) print (x.shape)

3 訓練分類器

from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split 

# 編碼目標變數
y_encoder = LabelEncoder()
y = y_encoder.fit_transform(train_data[u'頻道']) 

# 劃分訓練測試資料,根據y分層抽樣,測試資料佔20%
train_idx, test_idx = train_test_split(range(len(y)), test_size=0.2, stratify=y)
train_x = x[train_idx, :]
train_y = y[train_idx]
test_x = x[test_idx, :]
test_y = y[test_idx] 

4 .訓練與評估

# 訓練邏輯迴歸模型 
from sklearn.linear_model import LogisticRegression
"""
	常用引數說明:
	penalty: 正則項型別,l1還是l2
	C:       正則項懲罰係數的倒數,越大則懲罰越小
	fit_intercept: 是否擬合常數項
	max_iter:      最大迭代次數
	multi_class:   以何種方式訓練多分類模型
	     ovr =     對每個標籤訓練二分類模型
	     multinomial = 直接訓練多分類模型,僅當solver={newton-cg, sag, lbfgs}時支援
	 solver: 用哪種方法求解,可選有{liblinear, newton-cg, sag, lbfgs}
	         小資料liblinear比較好,大資料量sag更快
	         多分類問題,liblinear只支援ovr模式,其他支援ovr和multinomial
	         liblinear支援l1正則,其他只支援l2正則
"""

model = LogisticRegression(multi_class='multinomial', solver='lbfgs')
model.fit(train_x, train_y)
from sklearn.metrics import confusion_matrix, precision_recall_fscore_support 

# 在測試集上計算模型的表現
test_y_pred = model.predict(test_x)

# 計算混淆矩陣
pd.DataFrame(confusion_matrix(test_y, test_y_pred), 
             columns=y_encoder.classes_, 
             index=y_encoder.classes_)
~ 體育 健康 女人 娛樂 房地產 教育 文化 新聞 旅遊 汽車 科技 財經
體育 385 0 4 3 2 1 1 0 2 0 1 1
健康 0 314 22 0 0 5 6 27 2 0 6 18
女人 7 13 323 19 2 2 17 4 3 3 7 0
娛樂 1 1 15 308 0 3 60 3 2 1 6 0
房地產 1 3 4 3 357 0 0 13 4 1 0 14
教育 0 5 4 5 0 335 6 32 3 1 5 4
文化 1 3 25 67 2 7 232 30 15 2 13 3
新聞 8 14 13 7 22 26 25 227 12 4 16 26
旅遊 1 5 15 1 5 5 15 15 312 6 11 9
汽車 0 3 5 0 1 0 0 7 5 365 0 14
科技 2 9 4 3 2 3 12 20 8 3 319 15
財經 3 4 2 2 29 1 1 40 2 12 25 279
# 計算各項評價指標
def eval_model(y_true, y_pred, labels):

    # 計算每個分類的Precision, Recall, f1, support
    p, r, f1, s = precision_recall_fscore_support(y_true, y_pred)
    # 計算總體的平均Precision, Recall, f1, support
    tot_p = np.average(p, weights=s)
    tot_r = np.average(r, weights=s)
    tot_f1 = np.average(f1, weights=s)
    tot_s = np.sum(s)
    res1 = pd.DataFrame({
        u'Label': labels,
        u'Precision': p,
        u'Recall': r,
        u'F1': f1,
        u'Support': s
    })
    res2 = pd.DataFrame({
        u'Label': [u'總體'],
        u'Precision': [tot_p],
        u'Recall': [tot_r],
        u'F1': [tot_f1],
        u'Support': [tot_s]
    })
    res2.index = [999]
    res = pd.concat([res1, res2])
    return res[[u'Label', u'Precision', u'Recall', u'F1', u'Support']] 

eval_model(test_y, test_y_pred, y_encoder.classes_) #檢視訓練結果 

檢視前五條

~ Label Precision Recall F1 Support
0 體育 0.941320 0.9625 0.951792 400
1 健康 0.839572 0.7850 0.811370 400
2 女人 0.740826 0.8075 0.772727 400
3 娛樂 0.736842 0.7700 0.753056 400
4 房地產 0.845972 0.8925 0.868613 400

5 模型儲存

# 儲存模型到檔案
import dill
import pickle
model_file = os.path.join(output_dir, u'model.pkl')
with open(model_file, 'wb') as outfile:
    pickle.dump({
        'y_encoder': y_encoder,
        'lr': model
    }, outfile)

6 載入模型對新文件進行預測

from gensim.models import Word2Vec
import dill
import pickle
import jieba

# 把預測相關的邏輯封裝在一個類中,使用這個類的例項來對新文件進行分類預測
class Predictor(object):
    
    def __init__(self, w2v_model_file, lr_model_file):
        self.w2v = Word2Vec.load(w2v_model_file)
        with open(lr_model_file, 'rb') as infile:
            self.model = pickle.load(infile)
    
    def predict(self, articles):
        x = self._compute_doc_vec(articles)
        y = self.model['lr'].predict(x)
        y_label = self.model['y_encoder'].inverse_transform(y)
        return y_label
    
    def _compute_doc_vec(self, articles):
        return np.row_stack([compute_doc_vec_single(x) for x in articles])

    def _compute_doc_vec_single(self, article):
        vec = np.zeros((w2v.layer1_size,), dtype=np.float32)
        n = 0
        for word in jieba.cut(article):
            if word in w2v:
                vec += w2v[word]
                n += 1
        return vec / n
# 載入新文件資料
new_data = pd.read_csv('data/sohu_test.txt', sep='\t', header=None, 
                       dtype=np.str_, encoding='utf8', names=[u'頻道', u'文章'])
new_data.head() 

# 載入模型
predictor = Predictor('output_word2vec/model.w2v', model_file)

# 預測前10篇的分類
new_y_pred = predictor.predict(new_data[u'文章'][:10])

# 對比預測
pd.DataFrame({u'預測頻道': new_y_pred, u'實際頻道': new_data[u'頻道'][:10]})

輸出:
|      | 實際頻道 | 預測頻道 |
| ---- | --------| -------- |
| 0    | 娛樂     | 娛樂     |
| 1    | 娛樂     | 體育     |
| 2    | 娛樂     | 娛樂     |
| 3    | 娛樂     | 文化     |
| 4    | 娛樂     | 女人     |
| 5    | 娛樂     | 新聞     |
| 6    | 娛樂     | 娛樂     |
| 7    | 娛樂     | 娛樂     |
| 8    | 娛樂     | 娛樂     |
| 9    | 娛樂     | 娛樂     |