文本分类

文本分类

一、文本分类

文本分类( text classification),又称文档分类( document classification),指的是将一个文档归类到一个或多个类别中的自然语言处理任务。文本分类的应用场景非常广泛,涵盖垃圾邮件过滤、垃圾评论过滤、自动标签、情感分析等任何需要自动归档文本的场合。

文本的类别有时又称作标签,所有类别组成了标注集,文本分类输出结果一定属于标注集。

文本分类是一个典型的监督学习任务,其流程离不开人工指导: 人工标注文档的类别,利用语料训练模型,利用模型预测文档的类别。

二、文本分类语料库

文本分类语料库的标注过程相对简单,只需收集一些文档, 人工指定每篇文档的类别即可。另外,许多新闻网站的栏目是由编辑人工整理的,如果栏目设置符合要求,也可以用爬虫爬取下来作语料库使用。其中,搜狗实验室就提供了这样一份语料库。搜狗文本分类语料库迷你版
当语料库就绪时,文本分类的流程一般分为特征提取和分类器处理两大步。

三、文本分类的特征提取

在机器学习中,我们需要对具体对象提取出有助于分类的特征,才能交给某个分类器进行分类。这些特征数值化后为一个定长的向量(数据点),用来作为分类器的输入。在训练时,分类器根据数据集中的数据点学习出决策边界。在预测时,分类器根据输人的效据点落在决策边界的位置来决定类别。

我们依然使用词袋向量作为特征向量,词袋向量是词语颗粒度上的频次或 TF-IDF 向量,先进行分词处理

1.分词
HanLP 允许为数据集的构造函数指定一个分词器 ITokenizer,用来实现包括分词在内的预处理逻辑。

实现应用场景
HanLPTokenizer中文文本,使用NotionalTokenizer分词并过滤停用词
BlankTokenizer英文文本,使用空格分词
BigramTokenizer中文文本,将相邻字符作为二元语法输出

2.卡方特征选择

在文本分类时会有这样一个问题,比如汉语中的虚词“的”,这些词在所有类别的文档中均匀出现,为了消除这些单词的影响,一方面可以用停用词表,另一方面可以用卡方非参数检验来过滤掉与类别相关程度不高的词语。

在统计学上,卡方检验常用于检验两个事件的独立性,如果两个随机事件 A 和 B 相互独立,则两者同时发生的概率P(AB)= P(A)P(B)。如果将词语的出现与类别的出现作为两个随机事件则类别独立性越高的词语越不适合作为特征。如果将某个事件的期望记作 E,实际出现(观测)的频次记作 N,则卡方检验衡量期望与观测的相似程度。卡方检验值越高,则期望和观测的计数越相化也更大程度地否定了独立性。

一旦确定了哪些特征有用,接下来就可以将文档转化为向量了。

3.词袋向量
我们提取的是 TF 特征,统计出每个特征及其频次。以特征的 id 作为下标,频次作为数值,假设一共有 n 个特征,一篇文档就转化为 n 维的词袋向量。沿用机器学习文献的习惯,将词袋向量记作 x,向量的第 i 维记作 X1。将类别记
作 y,其中 K 为类别总数。则语料库(训练数据集) T 可以表示为词袋向量 x 和类别 y 所构成的二元组的集合:

$$T={\left(x^{(1)}, y_{1}\right),\left(x^{(2)}, y_{2}\right), \cdots,\left(x^{(N)}, y_{N}\right)}$$

在不进行特征选择的前提下,如果以词语作为特征,则 n 大约在 10 万量级;如果以字符二元语法作为特征,则 n 大约在 50 万量级。数十万维的向量运算开销不容小觑,一般利用卡方特征选择,可以将特征数量减小到10% ~ 20%左右。

当文档被转化为向量后,就可以利用机器学习进行训练了。

四、朴素贝叶斯分类器

在各种各样的分类器中,朴素贝叶斯法( naive Bayes)可算是最简单常用的一种生成式模型。朴素贝叶斯法基于贝叶斯定理将联合概率转化为条件概率,然后利用特征条件独立假设简化条件概率的计算。

1.朴素贝叶斯法原理

朴素贝叶斯法的目标是通过训练集学习联合概率分布 P(X,Y),由贝叶斯定理可以将联合概率转换为先验概率分布与条件概率分布之积:

$$p(X=x, Y=c_{k})=p(Y=c_{k}) p(X=x \mid Y=c_{k})$$

首先计算先验概率分布 P(Y=Ck),通过统计每个类别下的样本数:

$$p(Y=c_{k})=\frac{\operatorname{count}(Y=c_{k})}{N}$$

然后计算 P(X=x|Y=Ck),这个难以估计,因为 x 的量级非常大,可以从下式看出来:
$$p(X=\boldsymbol{x} \mid Y=c_{k})=p(X_{1}=\boldsymbol{x}), \cdots, X_{n}=\boldsymbol{x}_{n} \mid Y={c}_{k}),k=1,2, \cdots, K$$

该条件概率分布的参数数量是指数级的,难以估计。为此朴素贝叶斯法“朴素”的假设了所有特征是条件独立的:

$$\begin{aligned}
p\left(X=\boldsymbol{x} \mid Y=c_{k}\right) & =p\left(X_{1}=\boldsymbol{x}_{1}, \cdots, X_{n}=\boldsymbol{x}_{n} \mid Y=c_{k}\right) \\
& =\prod_{i=1}^{n} p\left(X_{i}=\boldsymbol{x}_{i} \mid Y=c_{k}\right)
\end{aligned}$$

于是,又可以利用极大似然来进行估计:

$$p\left(X_{i}=\boldsymbol{x}_{i} \mid Y=c_{k}\right)=\frac{\operatorname{count}\left(X_{i}=\boldsymbol{x}_{i}, y_{i}=c_{k}\right)}{\operatorname{count}\left(y_{i}=c_{k}\right)}$$

预测时,朴素贝叶斯法依然利用贝叶斯公式找出后验概率 P(Y=Ck|X=x) 最大的类别 Ck 作为输出 y:

$$y=\arg \max {c_{k}} p\left(Y=c_{k} \mid X=x\right)$$

将贝叶斯公式带入上式得:

$$y=\arg \max {c_{k}} \frac{p\left(X=\boldsymbol{x} \mid Y=c_{k}\right) p\left(Y=c_{k}\right)}{p(X=\boldsymbol{x})}$$
最终,由于分母与 Ck 无关,可以省略掉,然后将独立性假设带入,得到最终的分类预测函数:

$$y=\arg \max {c_{k}} p\left(Y=c_{k}\right) \prod_{i=0}^{n} p\left(X_{i}=x_{i} \mid Y=c_{k}\right)$$

五、支持向量机

朴素贝叶斯法实现简单,但由于特征独立性假设过于强烈,有时会影响准确性,支持向量机分类器更加健壮。

支持向量机( Support Vector Machine, SVM)是一种二分类模型,其学习策略在于如何找出一个决策边界,使得边界到正负样本的最小距离都最远。这种策略使得支持向量机有别于感知机,能够找到一个更加稳健的决策边界。支持向量机最简单的形式为线性支持向量机,其决策边界为一个超平面,适用于线性可分数据集。

(原理还没弄懂-。-)

线性支持向量机文本分类器

from pyhanlp.static import STATIC_ROOT, download


import zipfile
import os
from pyhanlp.static import download, remove_file, HANLP_DATA_PATH

def test_data_path():
    """
    获取测试数据路径,位于$root/data/test,根目录由配置文件指定。
    :return:
    """
    data_path = os.path.join(HANLP_DATA_PATH, 'test')
    if not os.path.isdir(data_path):
        os.mkdir(data_path)
    return data_path

## 验证是否存在 MSR语料库,如果没有自动下载
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


sogou_corpus_path = ensure_data('搜狗文本分类语料库迷你版', 'http://file.hankcs.com/corpus/sogou-text-classification-corpus-mini.zip')


## ===============================================
## 以下开始 支持向量机SVM
def install_jar(name, url):
    dst = os.path.join(STATIC_ROOT, name)
    if os.path.isfile(dst):
        return dst
    download(url, dst)
    return dst


install_jar('text-classification-svm-1.0.2.jar', 'http://file.hankcs.com/bin/text-classification-svm-1.0.2.jar')
install_jar('liblinear-1.95.jar', 'http://file.hankcs.com/bin/liblinear-1.95.jar')
from pyhanlp import *

LinearSVMClassifier = SafeJClass('com.hankcs.hanlp.classification.classifiers.LinearSVMClassifier')
IOUtil = SafeJClass('com.hankcs.hanlp.corpus.io.IOUtil')


def train_or_load_classifier():
    model_path = sogou_corpus_path + '.svm.ser'
    if os.path.isfile(model_path):
        return LinearSVMClassifier(IOUtil.readObjectFrom(model_path))
    classifier = LinearSVMClassifier()
    classifier.train(sogou_corpus_path)
    model = classifier.getModel()
    IOUtil.saveObjectTo(model, model_path)
    return LinearSVMClassifier(model)


def predict(classifier, text):
    print("《%16s》\t属于分类\t【%s】" % (text, classifier.classify(text)))
    # 如需获取离散型随机变量的分布,请使用predict接口
    # print("《%16s》\t属于分类\t【%s】" % (text, classifier.predict(text)))


if __name__ == '__main__':
    classifier = train_or_load_classifier()
    predict(classifier, "C罗获2018环球足球奖最佳球员 德尚荣膺最佳教练")
    predict(classifier, "潜艇具有很强的战略威慑能力与实战能力")
    predict(classifier, "研究生考录模式亟待进一步专业化")
    predict(classifier, "如果真想用食物解压,建议可以食用燕麦")
    predict(classifier, "通用及其部分竞争对手目前正在考虑解决库存问题")

运行结果如下:

《C罗获2018环球足球奖最佳球员 德尚荣膺最佳教练》    属于分类    【体育】
《潜艇具有很强的战略威慑能力与实战能力》    属于分类    【军事】
《 研究生考录模式亟待进一步专业化》    属于分类    【汽车】
《如果真想用食物解压,建议可以食用燕麦》    属于分类    【健康】
《通用及其部分竞争对手目前正在考虑解决库存问题》    属于分类    【汽车】

参考资料

HanLP

留下评论