文本分类
文本分类(text classification),指的是将一个文档归类到一个或多个类别的自然语言处理任务。文本分类的应用场景非常广泛,包括垃圾邮件过滤、自动打标等任何需要自动归档文本的场合。
文本分类在机器学习中属于监督学习,其流程是:人工标注文档类别、利用语料训练模型、利用模型训练文档的类别。
总结文本分类的一般流程;
特征选择是特征工程中的重要一环,其主要目的是从所有特征中选出相关特征 (relevant feature),或者说在不引起重要信息丢失的前提下去除掉无关特征 (irrelevant feature) 和冗余特征 (redundant feature)。进行特征选择的好处主要有以下几种:
在文本分类时会有这样一个问题,比如汉语中的虚词“的”,这些词在所有类别的文档中均匀出现,为了消除这些单词的影响,一方面可以用停用词表,另一方面可以用卡方非参数检验来过滤掉与类别相关程度不高的词语。
在统计学上,卡方检验常用于检验两个事件的独立性,如果两个随机事件 A 和 B 相互独立,则两者同时发生的概率P(AB)= P(A)P(B)。如果将词语的出现与类别的出现作为两个随机事件则类别独立性越高的词语越不适合作为特征。如果将某个事件的期望记作 E,实际出现(观测)的频次记作 N,则卡方检验衡量期望与观测的相似程度。卡方检验值越高,则期望和观测的计数越相化也更大程度地否定了独立性。
具体细节可参考宗成庆《统计自然语言处理》第二版13.3.3 χ 2 \chi^2 χ2t统计量
理论上讲,在文本特征抽取之后,就进入了常规机器学习分类模型的框架,但作为文本分类也有其特殊性,主要有以下几点:
所以在分类模型选择上主要考虑以下几点:
关于Naive Bayes、svm数学细节参考《统计学习方法》;
文本分类不一定需要分词,根据清华大学2016年的工作THUCTC:An Efficient Chinese Text Classifier,将文中相邻两个字符构成的所有二元语法作为“词”,反而可以取得更好的分类准确率;
数据的目录结构如下:
搜狗门户数据(汽车(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
运行结果:
标注集:[教育, 汽车, 健康, 军事, 体育]
第一篇文档的类别:教育
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()
FileDataSet = JClass('com.hankcs.hanlp.classification.corpus.FileDataSet')
training_corpus = FileDataSet().setTokenizer(tokenizer).load(corpus_path, "UTF-8", 0.9)
这个方法和相应的参数都被隐藏的模型中,用户只需知道分词后需要进行卡方特征选择。
NaiveBayesClassifier = JClass('com.hankcs.hanlp.classification.classifiers.NaiveBayesClassifier')
LinearSVMClassifier = JClass('com.hankcs.hanlp.classification.classifiers.LinearSVMClassifier')
model_class = LinearSVMClassifier
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)
精细评测:对于每个分类都有一套 P 、 R 、 F 1 P、R、F_1 P、R、F1;
整体评测:衡量模型在所有类目上的整体性能,则可以利用这些指标在文档颗粒度进行微平均,具体如下:
P ‾ = ∑ c i ∈ C T P ∑ c i ∈ C T P + ∑ c i ∈ C F P P ‾ = ∑ c i ∈ C T P ∑ c i ∈ C T P + ∑ c i ∈ C F N F 1 ‾ = 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+BigramTokenizer
P 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
中文文本分类的确不需要分词,不分词直接用元语法反而能够取得更高的准确率。只不过由于二元语法数量比单词多,导致参与运算的特征更多,相应的分类速度减半。
线性支持向量机的分类准确率更高,而且分类速度更快,推荐使用。