这一次我们需要利用HanLP进行文本分类与情感分析。同时这也是pyhanlp用户指南的倒数第二篇关于接口和Python实现的文章了,再之后就是导论,使用技巧汇总和几个实例落。真是可喜可贺啊。
文本分类
在HanLP中,文本分类与情感分析都是使用一个分类器,朴素贝叶斯分类器。或许这个分类器还算是比较一般,不过从最终结果来看效果还是很可以的。
因为底层采用词袋模式,所以当文本较大时可能会是内存开效果大,不过没关系,作者预先写了一个特征检测的方法。使用卡方检测,利用阈值来过滤特征,减少内存的开销。
原作者只给了文本分类的例子,这里我们对原来的例子稍加改造,使其更适用分类任务。
语料库
本文语料库特指文本分类语料库,对应IDataSet接口。而文本分类语料库包含两个概念:文档和类目。一个文档只属于一个类目,一个类目可能含有多个文档。比如搜狗文本分类语料库迷你版.zip,下载前请先阅读搜狗实验室数据使用许可协议。
数据格式
分类语料的根目录.目录必须满足如下结构:
根目录
├── 分类A
│ └── 1.txt
│ └── 2.txt
│ └── 3.txt
├── 分类B
│ └── 1.txt
│ └── ...
└── ...
文件不一定需要用数字命名,也不需要以txt作为后缀名,但一定需要是文本文件.
分词
目前,本系统中的分词器接口一共有两种实现: BigramTokenizer and HanLPTokenizer。
但文本分类是否一定需要分词?答案是否定的。 我们可以顺序选取文中相邻的两个字,作为一个“词”(术语叫bigram)。这两个字在数量很多的时候可以反映文章的主题(参考清华大学2016年的一篇论文《Zhipeng Guo, Yu Zhao, Yabin Zheng, Xiance Si, Zhiyuan Liu, Maosong Sun. THUCTC: An Efficient Chinese Text Classifier. 2016》)。这在代码中对应BigramTokenizer. 当然,也可以采用传统的分词器,如HanLPTokenizer。 另外,用户也可以通过实现ITokenizer来实现自己的分词器,并通过IDataSet#setTokenizer来使其生效
特征提取
特征提取指的是从所有词中,选取最有助于分类决策的词语。理想状态下所有词语都有助于分类决策,但现实情况是,如果将所有词语都纳入计算,则训练速度将非常慢,内存开销非常大且最终模型的体积非常大。
本系统采取的是卡方检测,通过卡方检测去掉卡方值低于一个阈值的特征,并且限定最终特征数不超过100万。
预测
classify方法直接返回最可能的类别的String形式,而predict方法返回所有类别的得分(是一个Map形式,键是类目,值是分数或概率),categorize方法返回所有类目的得分(是一个double数组,分类得分按照分类名称的字典序排列),label方法返回最可能类目的字典序。
线程安全性
类似于HanLP的设计,以效率至上,本系统内部实现没有使用任何线程锁,但任何预测接口都是线程安全的(被设计为不储存中间结果,将所有中间结果放入参数栈中)。
from pyhanlp import SafeJClass
import zipfile
import os
from pyhanlp.static import download, remove_file, HANLP_DATA_PATH
# 设置路径,否则会从配置文件中寻找
HANLP_DATA_PATH = "/home/fonttian/Data/CNLP"
"""
获取测试数据路径,位于$root/data/textClassification/sogou-mini,
根目录由配置文件指定,或者等于我们前面手动设置的HANLP_DATA_PATH。
"""
DATA_FILES_PATH = "textClassification/sogou-mini"
def test_data_path():
data_path = os.path.join(HANLP_DATA_PATH, DATA_FILES_PATH)
if not os.path.isdir(data_path):
os.mkdir(data_path)
return data_path
def ensure_data(data_name, data_url):
root_path = test_data_path()
dest_path = os.path.join(root_path, data_name)
if os.path.exists(dest_path):
return dest_path
if data_url.endswith('.zip'):
dest_path += '.zip'
download(data_url, dest_path)
if data_url.endswith('.zip'):
with zipfile.ZipFile(dest_path, "r") as archive:
archive.extractall(root_path)
remove_file(dest_path)
dest_path = dest_path[:-len('.zip')]
return dest_path
NaiveBayesClassifier = SafeJClass('com.hankcs.hanlp.classification.classifiers.NaiveBayesClassifier')
IOUtil = SafeJClass('com.hankcs.hanlp.corpus.io.IOUtil')
sogou_corpus_path = ensure_data('搜狗文本分类语料库迷你版',
'http://hanlp.linrunsoft.com/release/corpus/sogou-text-classification-corpus-mini.zip')
def train_or_load_classifier(path):
model_path = path + '.ser'
if os.path.isfile(model_path):
return NaiveBayesClassifier(IOUtil.readObjectFrom(model_path))
classifier = NaiveBayesClassifier()
classifier.train(sogou_corpus_path)
model = classifier.getModel()
IOUtil.saveObjectTo(model, model_path)
return NaiveBayesClassifier(model)
def predict(classifier, text):
print("《%16s》 属于分类 【%s】" % (text, classifier.classify(text)))
# 如需获取离散型随机变量的分布,请使用predict接口
# print("《%16s》 属于分类 【%s】" % (text, classifier.predict(text)))
if __name__ == '__main__':
classifier = train_or_load_classifier(sogou_corpus_path)
predict(classifier, "C罗压梅西内马尔蝉联金球奖 2017=C罗年")
predict(classifier, "英国造航母耗时8年仍未服役 被中国速度远远甩在身后")
predict(classifier, "研究生考录模式亟待进一步专业化")
predict(classifier, "如果真想用食物解压,建议可以食用燕麦")
predict(classifier, "通用及其部分竞争对手目前正在考虑解决库存问题")
print("
我们这里再用训练好的模型连测试一下新的随便从网上找来的几个新闻标题
")
predict(classifier, "今年考研压力进一步增大,或许考研正在变成第二次高考")
predict(classifier, "张继科被刘国梁连珠炮喊醒:醒醒!奥运会开始了。")
predict(classifier, "福特终于开窍了!新车1.5T怼出184马力,不足11万,思域自愧不如")
《C罗压梅西内马尔蝉联金球奖 2017=C罗年》 属于分类 【体育】
《英国造航母耗时8年仍未服役 被中国速度远远甩在身后》 属于分类 【军事】
《 研究生考录模式亟待进一步专业化》 属于分类 【教育】
《如果真想用食物解压,建议可以食用燕麦》 属于分类 【健康】
《通用及其部分竞争对手目前正在考虑解决库存问题》 属于分类 【汽车】
我们这里再用训练好的模型连测试一下新的随便从网上找来的几个新闻标题
《今年考研压力进一步增大,或许考研正在变成第二次高考》 属于分类 【教育】
《张继科被刘国梁连珠炮喊醒:醒醒!奥运会开始了。》 属于分类 【体育】
《福特终于开窍了!新车1.5T怼出184马力,不足11万,思域自愧不如》 属于分类 【汽车】
从我们最后自己增加的几个新闻标题来看,分类器的效果相当的好。这多亏了word2vec。
情感分析
我们对于情感分析的实现与之前的文本分类具有高度的相似性,同时刚刚也提到了,实际上他们就是用的一个分类器。而在python的实现中,他们则几乎一模一样。
也正是因为如此,所以只要我们拥有同样格式的语料,那么我们可以使用这个分类器做任何我们需要的文本分类
语料来源
可以利用文本分类在情感极性语料上训练的模型做浅层情感分析。目前公开的情感分析语料库有:中文情感挖掘语料-ChnSentiCorp,语料发布者为谭松波。
"""
获取测试数据路径,位于$root/data/textClassification/sogou-mini,
根目录由配置文件指定,或者等于我们前面手动设置的HANLP_DATA_PATH。
ChnSentiCorp评论酒店情感分析
"""
DATA_FILES_PATH = "sentimentAnalysis/ChnSentiCorp"
if __name__ == '__main__':
ChnSentiCorp_path = ensure_data('酒店评论情感分析',
'这里是找不到数据时/默认去下载的地址/不过这里我们不需要/所以随便写点什么就好了/')
classifier = train_or_load_classifier(ChnSentiCorp_path)
predict(classifier, '距离川沙公路较近,但是公交指示不对,如果是"蔡陆线"的话,会非常麻烦.建议用别的路线.房间较为简单.')
predict(classifier, "商务大床房,房间很大,床有2M宽,整体感觉经济实惠不错!")
predict(classifier, "标准间太差 房间还不如3星的 而且设施非常陈旧.建议酒店把老的标准间从新改善.")
predict(classifier, "服务态度极其差,前台接待好象没有受过培训,连基本的礼貌都不懂,竟然同时接待几个客人")
print("
我们这里再用训练好的模型连测试一下我自己编的‘新的’的文本
")
predict(classifier, "服务态度很好,认真的接待了我们,态度可以的!")
predict(classifier, "有点不太卫生,感觉不怎么样。")
《距离川沙公路较近,但是公交指示不对,如果是"蔡陆线"的话,会非常麻烦.建议用别的路线.房间较为简单.》 属于分类 【正面】
《商务大床房,房间很大,床有2M宽,整体感觉经济实惠不错!》 属于分类 【正面】
《标准间太差 房间还不如3星的 而且设施非常陈旧.建议酒店把老的标准间从新改善.》 属于分类 【负面】
《服务态度极其差,前台接待好象没有受过培训,连基本的礼貌都不懂,竟然同时接待几个客人》 属于分类 【负面】
我们这里再用训练好的模型连测试一下我自己编的‘新的’的文本
《服务态度很好,认真的接待了我们,态度可以的!》 属于分类 【正面】
《 有点不太卫生,感觉不怎么样。》 属于分类 【负面】