基於NLP自然語言構建的文件自動分類系統(搜狐娛樂)—word2vec模型
阿新 • • 發佈:2018-12-11
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 | 娛樂 | 娛樂 |