中文新闻分类,逻辑回归大战朴素贝叶斯

1 问题背景

我们有一堆新闻文本数据,包含“新闻类别”、“新闻内容”两列,希望建立一个模型,来预测新闻内容属于哪个类别。

这是一个文本分类问题,朴素贝叶斯和逻辑回归可以说是文本分类最常用的Baseline模型,今天小树带大家来看看,朴素贝叶斯和逻辑回归效果如何以及它们两个究竟谁更厉害。

操作环境:jupyter notebook + python3
预备知识:中文分词、tf-idf、朴素贝叶斯模型、逻辑回归模型

2 加载相关包

import numpy as np
import pandas as pd
import random
from sklearn.svm import SVC
from sklearn import preprocessing, decomposition, model_selection, metrics, pipeline
from sklearn.model_selection import GridSearchCV
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.decomposition import TruncatedSVD
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import MultinomialNB

3 加载数据并进行数据预处理

(1)加载数据

# load data
data = [[i.split("\t")[0],i.split("\t")[1]] for i in open(u"data/cnews.train.txt","r", encoding="utf-8")]
# 打乱数据
random.shuffle(data)
data[1]
output

(2)分词,标签转换,求tf-idf

# 对新闻内容进行分词
import jieba
jieba.enable_parallel() #并行分词开启
terms = [" ".join([j for j in jieba.cut(i[1])]) 
         for i in data[:1000]]

#接下来用scikit-learn中的LabelEncoder将文本标签(Text Label)转化为数字(Integer)
lbl_enc = preprocessing.LabelEncoder()
y = lbl_enc.fit_transform([i[0] for i in data[:1000]])

from sklearn.feature_extraction.text import TfidfVectorizer
# 初始化TFidf对象,去停用词,加2元语言模型
tfv = TfidfVectorizer(min_df=3,  max_features=None, strip_accents='unicode', analyzer='word',token_pattern=r'\w{1,}', ngram_range=(1, 2), use_idf=1,smooth_idf=1,sublinear_tf=1, stop_words = 'english')

tfv.fit(terms)
X_all = tfv.transform(terms)

(3)观察label分布,划分数据集

len_train = 600
X_train = X_all[:len_train] 
y_train = y[:len_train]
X_test = X_all[len_train:]
y_test = y[len_train:]

print('X_train shape: ', X.shape)
print('y_train shape: ', y_train.shape)
print('X_test shape: ', X_test.shape)
print('y_test shape: ', y_test.shape)

# 观察label的分布: 共有10个类别,类别均衡
label_data = pd.DataFrame({'label':y})
print('\nDistribution of all label:\n', label_data.label.value_counts())
output

从上面的数据可以得到两点结论:

  • 我们的特征有29812个,而训练样本只有600个,特征维度远远大于训练样本数量,因此特征空间是非常稀疏的。
  • 从label的分布来看,各个类别是平衡的。

4 定义评估标准

这是一个典型的多分类问题,采用常用的multi-class log loss进行评估。

def multiclass_logloss(actual, predicted, eps=1e-15):
    """
    对数损失度量(Logarithmic Loss  Metric)的多分类版本。
    :param actual: 包含actual target classes的数组
    :param predicted: 分类预测结果矩阵, 每个类别都有一个概率
    """
    # Convert 'actual' to a binary array if it's not already:
    if len(actual.shape) == 1:
        actual2 = np.zeros((actual.shape[0], predicted.shape[1]))
        for i, val in enumerate(actual):
            actual2[i, val] = 1
        actual = actual2

    clip = np.clip(predicted, eps, 1 - eps)
    rows = actual.shape[0]
    vsota = np.sum(actual * np.log(clip))
    return -1.0 / rows * vsota

好了,万事俱备,开始激动人心的建模时刻!

5 朴素贝叶斯 vs 逻辑回归

step 1 建立一个朴素贝叶斯的baseline模型

clf_nb = MultinomialNB()   # 多项式朴素贝叶斯
clf_nb.fit(X_train, y_train)
predictions = clf_nb.predict_proba(X_test)

print ("logloss: %0.3f " % multiclass_logloss(y_test, predictions))

logloss: 1.111

朴素贝叶斯在测试集上的logloss为1.111, 效果不太好,没关系,我们把这个作为baseline。

step 2 建立一个逻辑回归模型来对比看看

clf_lr = LogisticRegression(C=1.0,solver='lbfgs',multi_class='multinomial')
clf_lr.fit(X_train, y_train)
predictions = clf_lr.predict_proba(X_test)

print ("logloss: %0.3f " % multiclass_logloss(y_test, predictions))

logloss: 1.223

看起来似乎逻辑回归比朴素贝叶斯效果还差喔。

step 3 我们来调参吧

# 创建评分函数
mll_scorer = metrics.make_scorer(multiclass_logloss, greater_is_better=False, needs_proba=True)

(1)朴素贝叶斯调参

调参指南:朴素贝叶斯调参一般调alpha,平滑参数,其值越小,越容易过拟合,值越大,容易造成欠拟合。

下面建立一个pipeline来调参。

nb_model = MultinomialNB()

# 创建pipeline 
clf = pipeline.Pipeline([('nb', nb_model)])

# 搜索参数设置
param_grid = {'nb__alpha': [0.001, 0.01, 0.1, 1, 10, 100]}

# 网格搜索模型(Grid Search Model)初始化
model = GridSearchCV(estimator=clf, param_grid=param_grid, scoring=mll_scorer,
                                 verbose=10, n_jobs=-1, iid=True, refit=True, cv=6)

# fit网格搜索模型
model.fit(X_train, y_train)
print("Best score: %0.3f" % model.best_score_)
print("Best parameters set:")
best_parameters = model.best_estimator_.get_params()
for param_name in sorted(param_grid.keys()):
    print("\t%s: %r" % (param_name, best_parameters[param_name]))

Best parameters set: nb__alpha: 0.1

我们发现alpha = 0.1时,模型效果最好,用这个参数重新跑跑看

# 使用最优参数建模
clf_nb = MultinomialNB(alpha=0.1)
clf_nb.fit(X_train, y_train)
predictions = clf_nb.predict_proba(X_test)

print ("logloss: %0.3f " % multiclass_logloss(y_test, predictions))

logloss: 0.243

朴素贝叶斯调参后logloss 从1.111 直接降到了0.243,惊不惊喜,意不意外!只是简单的调参就收获了如此大的性能提升,而且0.243这个表现说明朴素贝叶斯的效果还是杠杠的哟。

下面来看看逻辑回归调参之后能否反超朴素贝叶斯!

(2)逻辑回归调参

调参指南:逻辑回归调参常调正则项C以及penalty,由于这里我们使用了lgfgs solver,它只支持L2 penalty,因此我们只调C。注意,C是正则化系数的倒数,其值越大,正则项的权重越小,模型越容易过拟合。

lr_model = LogisticRegression(solver='lbfgs',multi_class='multinomial')

# 创建pipeline 
clf = pipeline.Pipeline([('lr', lr_model)])

# 搜索参数设置
param_grid = {'lr__C': [0.1, 1.0, 10]}

# 网格搜索模型(Grid Search Model)初始化
model = GridSearchCV(estimator=clf, param_grid=param_grid, scoring=mll_scorer,
                                 verbose=10, n_jobs=-1, iid=True, refit=True, cv=6)

# fit网格搜索模型
model.fit(X_train, y_train)
print("Best score: %0.3f" % model.best_score_)
print("Best parameters set:")
best_parameters = model.best_estimator_.get_params()
for param_name in sorted(param_grid.keys()):
    print("\t%s: %r" % (param_name, best_parameters[param_name]))

Best parameters set: lr__C: 10

用这个最优参数重新训练模型:

clf_lr = LogisticRegression(C=10, solver='lbfgs',multi_class='multinomial')
clf_lr.fit(X_train, y_train)
predictions = clf_lr.predict_proba(X_test)

print ("logloss: %0.3f " % multiclass_logloss(y_test, predictions))

logloss: 0.546

调参后逻辑回归的表现也变好了很多,但是跟朴素贝叶斯比,还是相去甚远,看起来好像还是朴素贝叶斯更厉害嘛!逻辑回归还有翻盘的机会吗?

我们来观察一下模型的结果:

print('train accuracy: ',clf_lr.score(X_train, y_train))
print('test accuracy: ',clf_lr.score(X_test, y_test))

train accuracy: 1.0
test accuracy: 0.9675

在测试集上准确率很高啊!奇怪了,我们打印一个预测的结果来看看:

predictions[0] 

array([0.02830099, 0.03794662, 0.03657158, 0.02690717, 0.02604819,
0.04728005, 0.02332729, 0.73275343, 0.02299653, 0.01786817])

测试集的第一个数据真实label是7,而模型预测为7的概率为0.73,由于我们使用交叉熵损失作为评估,所以看起来模型的表现不太好。尽管特征很多,样本量很小,我们还想让模型拟合得更好!

那怎么办?我有一个大胆的想法...

我们重新调逻辑回归的正则项参数C,加大加大,人有多大胆,地有多大产!!!说白了,我就是不要正则化了!

lr_model = LogisticRegression(solver='lbfgs',multi_class='multinomial')

# 创建pipeline 
clf = pipeline.Pipeline([('lr', lr_model)])

# 搜索参数设置
param_grid = {'lr__C': [1000,10000,100000,1000000, 10000000, 100000000]}

# 网格搜索模型(Grid Search Model)初始化
model = GridSearchCV(estimator=clf, param_grid=param_grid, scoring=mll_scorer,
                                 verbose=10, n_jobs=-1, iid=True, refit=True, cv=6)

# fit网格搜索模型
model.fit(X_train, y_train)
print("Best score: %0.3f" % model.best_score_)
print("Best parameters set:")
best_parameters = model.best_estimator_.get_params()
for param_name in sorted(param_grid.keys()):
    print("\t%s: %r" % (param_name, best_parameters[param_name]))

Best parameters set: lr__C: 1000000

这路子有点野...看看它行不行先!

clf_lr = LogisticRegression(C=1000000, solver='lbfgs',multi_class='multinomial')
clf_lr.fit(X_train, y_train)
predictions = clf_lr.predict_proba(X_test)

print ("logloss: %0.3f " % multiclass_logloss(y_test, predictions))

logloss: 0.125

内心诚惶诚恐,但数据显示很牛逼!好了,贝叶斯你跪下叫爸爸吧。。。

。。。。。。

贝叶斯表示这路子太野我不服,能不能来点正常的套路?!

那降维打击了解一下?

#使用SVD(奇异值分解)进行降维,components设为180,对于SVM来说,SVD的components的合适调整区间一般为120~200 
svd = decomposition.TruncatedSVD(n_components=180)
svd.fit(X_train)
xtrain_svd = svd.transform(X_train)
xtest_svd = svd.transform(X_test)

#对从SVD获得的数据进行缩放
scl = preprocessing.StandardScaler()
scl.fit(xtrain_svd)
xtrain_svd_scl = scl.transform(xtrain_svd)
xtest_svd_scl = scl.transform(xtest_svd)

# 看看降维后LR的效果
clf_lr = LogisticRegression(C=100, solver='lbfgs',multi_class='multinomial')
clf_lr.fit(xtrain_svd_scl, y_train)
predictions = clf_lr.predict_proba(xtest_svd_scl)

print ("logloss: %0.3f " % multiclass_logloss(y_test, predictions))

logloss: 0.144

贝叶斯,卒。至此,逻辑回归大获全胜!


参考资料:

[1] 网易云课堂AI工程师(自然语言处理)-文本分类
[2] 【NLP文本分类】各种文本分类算法集锦,从入门到精通

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352