本文是文本分类的第二篇,记录使用朴素贝叶斯进行文本分类任务,数据集下载地址:http://thuctc.thunlp.org/
文本分类的主要内容如下:
- 1.基于逻辑回归的文本分类
- 2.基于朴素贝叶斯的文本分类
- 3.使用LDA进行文档降维以及特征选择
- 4.基于SVM的文本分类
- 5.基于多层感知机MLPC的文本分类
- 6.基于卷积神经网络词级别的文本分类以及调参
- 7.基于卷积神经网络的句子级别的文本分类以及调参
- 8.基于Facebook fastText的快速高效文本分类
- 9.基于RNN的文本分类
- 10.基于LSTM的文本分类
- 11.总结
其中使用的训练数据来自清华大学开源的文本分类数据集,原始数据集比较大,提供下载的是提取的小数据,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中新闻样本的类别标签.
下面将进行特征提取,特征选择的方法有基本的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')
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
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特征。
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
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
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
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
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的增大,特征的数量也在显著的增长,同时训练时间也在逐渐增加,在应用中,应该在效率和结果直接有一个较好的选择才会使得最后的工作高效简洁。
下面将进行朴素贝叶斯的调参工作,机器学习很多时候都是在进行参数的调整工作,一个好的参数可以让模型产生更好的效果。
上面的实验中,我们只是简单的选取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])}
朴素贝叶斯的参数比偶较少,根据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%,效果是明显的。
本文记录了使用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,只是作为最有参数选寻找的个例,不应该用作一般性结论。