1 基本概念
文本分类
文本分类(text classification),指的是将一个文档归类到一个或多个类别的自然语言处理任务。文本分类的应用场景非常广泛,包括垃圾邮件过滤、自动打标等任何需要自动归档文本的场合。
文本分类在机器学习中属于监督学习,其流程是:人工标注文档类别、利用语料训练模型、利用模型训练文档的类别。
2 问题
总结文本分类的一般流程;
3 解决思路
3.1 文本的特征提取
特征选择是特征工程中的重要一环,其主要目的是从所有特征中选出相关特征 (relevant feature),或者说在不引起重要信息丢失的前提下去除掉无关特征 (irrelevant feature) 和冗余特征 (redundant feature)。进行特征选择的好处主要有以下几种:
- 降低过拟合风险,提升模型效果
- 提高训练速度,降低运算开销
- 更少的特征通常意味着更好的可解释性
在向量空间模型中,表示文本的特征项可以选择文字、词、短语、甚至“概念”等多种元素,目前常用的特征提取方法有:基于文档频率的特征提取法、信息增益法、 χ2
统计量法、互信息法等。
3.2 卡方特征选择
在文本分类时会有这样一个问题,比如汉语中的虚词“的”,这些词在所有类别的文档中均匀出现,为了消除这些单词的影响,一方面可以用停用词表,另一方面可以用卡方非参数检验来过滤掉与类别相关程度不高的词语。
在统计学上,卡方检验常用于检验两个事件的独立性,如果两个随机事件 A 和 B 相互独立,则两者同时发生的概率P(AB)= P(A)P(B)。如果将词语的出现与类别的出现作为两个随机事件则类别独立性越高的词语越不适合作为特征。如果将某个事件的期望记作 E,实际出现(观测)的频次记作 N,则卡方检验衡量期望与观测的相似程度。卡方检验值越高,则期望和观测的计数越相化也更大程度地否定了独立性。
具体细节可参考宗成庆《统计自然语言处理》第二版13.3.3 χ2\chi^2χ2t统计量
3.3 分类器的选择和训练
理论上讲,在文本特征抽取之后,就进入了常规机器学习分类模型的框架,但作为文本分类也有其特殊性,主要有以下几点:
- 自变量(词条)数量极多;
- 各自变量之间(词条)不可能完全独立;
- 大部分自变量(词条)都是干扰项,对分类没有贡献;
所以在分类模型选择上主要考虑以下几点:
- 速度-文本数据量一般比较大;
- 变量筛选能力-能够从大部分是无效变量的情况下筛选出有效变量;
- 容错性-分类模型是建立在特征抽取的基础上,特征抽取过程本身不可避免的带来部分信息差错;
- 共线容忍度-词条之间不可能相互独立,很多模型都有变量的独立性假设。
基于上面两个方面的考虑,文本分类的模型通常使用朴素贝叶斯、svm两个模型。
关于Naive Bayes、svm数学细节参考《统计学习方法》;
3.4 非常规方法
文本分类不一定需要分词,根据清华大学2016年的工作THUCTC:An Efficient Chinese Text Classifier,将文中相邻两个字符构成的所有二元语法作为“词”,反而可以取得更好的分类准确率;
4 实现
4.1 准备文本分类语料库
数据的目录结构如下:
搜狗门户数据(汽车(1000个txt文档)、教育(1000个txt文档)、健康(1000个txt文档)、军事(1000个txt文档)、体育(1000个txt文档))
from pyhanlp import *
corpus_path = r'/Users/kitty/Work/Projects/text_mining/data/搜狗文本分类语料库迷你版'
# 把数据加载到内存中,查看数据属性
MemoryDataSet = JClass('com.hankcs.hanlp.classification.corpus.MemoryDataSet')
dataSet = MemoryDataSet() # 将数据集加载到内存中
dataSet.load(corpus_path) # 加载data/test/搜狗文本分类语料库迷你版
allClasses = dataSet.getCatalog().getCategories() # 获取标注集
print("标注集:%s" % (allClasses))
for document in dataSet.iterator():print("第一篇文档的类别:" + allClasses.get(document.category))break
运行结果:
标注集:[教育, 汽车, 健康, 军事, 体育]
第一篇文档的类别:教育
4.2 准备分词器
BigramTokenizer = JClass('com.hankcs.hanlp.classification.tokenizers.BigramTokenizer')
HanLPTokenizer = JClass('com.hankcs.hanlp.classification.tokenizers.HanLPTokenizer')
BlankTokenizer = JClass('com.hankcs.hanlp.classification.tokenizers.BlankTokenizer')
tokenizer = BigramTokenizer()
4.3 准备训练集
FileDataSet = JClass('com.hankcs.hanlp.classification.corpus.FileDataSet')
training_corpus = FileDataSet().setTokenizer(tokenizer).load(corpus_path, "UTF-8", 0.9)
4.4 卡方特征选择
这个方法和相应的参数都被隐藏的模型中,用户只需知道分词后需要进行卡方特征选择。
4.5 准备分类器
NaiveBayesClassifier = JClass('com.hankcs.hanlp.classification.classifiers.NaiveBayesClassifier')
LinearSVMClassifier = JClass('com.hankcs.hanlp.classification.classifiers.LinearSVMClassifier')
model_class = LinearSVMClassifier
4.6 训练模型
IOUtil = SafeJClass('com.hankcs.hanlp.corpus.io.IOUtil')
model_path = r'/Users/kitty/anaconda3/envs/nlp/lib/python3.6/site-packages/pyhanlp/static/data/test/'
def train_or_load_classifier(model_class, model_path, training_corpus):classifier = model_class()model_path += classifier.getClass().getSimpleName() + '.ser'if os.path.isfile(model_path):print(model_path)return model_class(IOUtil.readObjectFrom(model_path))classifier.train(training_corpus)model = classifier.getModel()IOUtil.saveObjectTo(model, model_path)return model_class(model)classifier = train_or_load_classifier(model_class, model_path, training_corpus)
4.7 评测训练效果
精细评测:对于每个分类都有一套P、R、F1P、R、F_1P、R、F1;
整体评测:衡量模型在所有类目上的整体性能,则可以利用这些指标在文档颗粒度进行微平均,具体如下:
P‾=∑ci∈CTP∑ci∈CTP+∑ci∈CFPP‾=∑ci∈CTP∑ci∈CTP+∑ci∈CFNF1‾=2×P‾×R‾P‾+R‾\begin{aligned} \overline{P} &= \frac{\sum_{c_i \in C}TP}{\sum_{c_i \in C}TP + \sum_{c_i \in C}FP} \\ \overline{P} &= \frac{\sum_{c_i \in C}TP}{\sum_{c_i \in C}TP + \sum_{c_i \in C}FN} \\ \overline{F_1} &= \frac{2 \times \overline{P} \times \overline{R}}{\overline{P} + \overline{R}} \end{aligned} PPF1=∑ci∈CTP+∑ci∈CFP∑ci∈CTP=∑ci∈CTP+∑ci∈CFN∑ci∈CTP=P+R2×P×R
下面实现采用后一种方式:
Evaluator = JClass('com.hankcs.hanlp.classification.statistics.evaluations.Evaluator')def evaluate(classifier, corpus_path, tokenizer):testing_corpus = MemoryDataSet(classifier.getModel()).load(corpus_path, "UTF-8", -0.1)result = Evaluator.evaluate(classifier, testing_corpus)print(classifier.getClass().getSimpleName() + "+" + tokenizer.getClass().getSimpleName())print(result)evaluate(classifier, corpus_path, tokenizer)
运行结果:
LinearSVMClassifier+BigramTokenizerP R F1 A 93.27 97.00 95.10 98.00 教育98.02 99.00 98.51 99.40 汽车98.97 96.00 97.46 99.00 健康98.00 98.00 98.00 99.20 军事
100.00 98.00 98.99 99.60 体育97.65 97.60 97.63 97.60 avg.
data size = 500, speed = 7936.51 doc/s
4.8 {NB, SVM} ❌ {中文分词、二元语法}
中文文本分类的确不需要分词,不分词直接用元语法反而能够取得更高的准确率。只不过由于二元语法数量比单词多,导致参与运算的特征更多,相应的分类速度减半。
线性支持向量机的分类准确率更高,而且分类速度更快,推荐使用。
5 参考文献
- 何晗《自然语言处理入门》;
- 宗成庆《统计自然语言处理》;
- 李航《统计学习方法》;
6 需要解决的问题
- 当文档被转化为向量后,就彻底脱离了语言、句子等语料的约束,进入机器学习的范畴了,所以文本特征选择是关键的一步,尤其在传统机器学习中。总结文本特征选择的常用方法。