文本分类是NLP领域非常常见的应用场景,在现实生活中有着非常多的应用,例如舆情监测、新闻分类等等。在文本分类中,常见的分类算法有SVM、KNN、决策树、神经网络等等。本文简单介绍了中文文本分类的应用实践。
现有的文本分类方法主要分为两大类,分别是基于传统机器学习的方法和基于深度学习的方法。基于传统机器学习的文本分类方法主要是是对文本进行预处理、特征提取,然后将处理后的文本向量化,最后通过常见的机器学习分类算法来对训练数据集进行建模,传统的文本分类方法中,对文本的特征提取质量对文本分类的精度有很大的影响。基于深度学习的方法则是通过例如CNN、LSTM等深度学习模型来对数据进行训练,无需人工的对数据进行特征抽取,对文本分类精度影响更多的是数据量以及训练的迭代次数。本文主要介绍一下传统机器学习算法在中文文本分类中的应用。
新闻数据获取
本文中的数据集来源于网易新闻,数据通过爬虫获取,共包括体育、健康、军事娱乐等类别在内的9大类,每类2000篇,总计18000篇新闻数据。
这里简单介绍一下爬虫的方法,以网易科技类新闻为例,我们要获取科技新闻数据的第一步就是要知道新闻的url是什么,那么我们如何去获取每一篇新闻的url呢?首先点开网易新闻的科技类首页,在进入科技新闻首页之后,我们会发现位于网页中间有一块下拉刷新的新闻展示位:
可以猜测在我们执行下拉操作的时候,浏览器会向服务器发送一次请求数据的操作,在获取到数据之后展示在页面上。如果我们可以模拟浏览器向服务器发送请求数据的操作,就能获取到新闻数据的链接,抱着这样的想法,我们按F12查看浏览器获取到的数据,我们需要寻找的是一条包含刷新新闻数据的请求:
然后我们如愿的找到了这么一条请求,返回的数据是一个json类型的list,里面包含了需要刷新的新闻的title、url、时间等信息,这条请求的url是http://tech.163.com/special/00097UHL/tech_datalist_02.js?callback=data_callback,通过观察这条url我们可以发现一些规律,tech表示这是科技新闻的请求操作,02表示这是请求的第二页数据,抱着这样的猜想我们继续对页面进行下拉操作,可以获得第三页的请求数据:
第三页的请求url为http://tech.163.com/special/00097UHL/tech_datalist_03.js?callback=data_callback,我们发现如上述的猜想一致,可以通过在url中修改类别参数和页码参数方便地获取到数据的真实url地址。
爬取新闻文本的最后一步就是根据新闻的url地址将新闻的文本内容内容提取出来保存文件,这里通过分析网页的结构:
可以发现新闻的文本内容装在post_text节点中,标题和新闻文本均post_text节点的p子节点中,这里我们可以通过BeautifulSoup来解析html文档数,并轻易的获取新闻文本,解析代码如下:
req = requests.get(url,headers)
soup = BeautifulSoup(req.text,"html.parser")
title = soup.title.string
content = soup.find('div', class_='post_text').text.strip()
由于不同的类别的新闻页面的网页结构有所差别,所以在进行文本提取的时候需要根据页面结构的真实情况去具体分析。通过上述的爬虫操作,最后我们获得了9大类的新闻文本数据:
文本特征提取
文本特征的抽取是文本挖掘的一个基本问题,它是从文本数据中抽取出能够体现文本信息特征的单元,从而将其从无结构的文本数据转换为计算机可以识别的结构化数据。在文本挖掘中用于表达文本的基本单元叫特征项,特征项具有如下的特点:
1. 特征项要具有表达文本信息的能力
2. 特征项要具有区分文本的能力
3. 特征项的数量不能太多
在文本挖掘中常常使用字、词、短语作为特征项。而词与字相比,具有更加强大的表达能力,同时词与短语相比,切分难度又要相对小很多,因此在实际应用中大多使用词作为特征项,也称为特征词。特征词可以用来标识文档,计算文档与文档之间的相似性。文本的特征抽取在对文档进行分类、聚类,提取文档的关键词等方面有着非常重要的作用。现有的一些常见的文本特征提取算法有TF-IDF、TextRank,卡方检验等。这里简单介绍一下TF-IDF,其他的算法有兴趣的同学可以自行查阅相关资料。
TF-IDF 是一种基于统计的特征抽取方法,可以计算一个词语对于文档库中的一篇文档的重要性程度。 TF-IDF的核心思想是:一个词如果在一篇文档中出现的频次很高,那么从一定程度上可以说明这个词在这篇文档中具有比较重要的作用,可以表达文档的特征。同时一个词如果在其他文章数出现的次数较少,说明这个词在文档中具有很好的区分度,这也满足了上述特征项特点中的前两条。
TF是指词频,表示一个词在文档中出现的频次,为了防止不同文档长度对于词频的影响,这里的 TF 是经过归一化的,用公式可以表示为:$tf_{i,j}=\frac{n_{i,j}}{\sum_kn_{k,j}}$,式子中的$n_{i,j}$表示$t_i$在文件$d_j$中出现的次数,$\sum_kn_{k,j}$表示文件$d_j$中所有词出现的总次数。
IDF(inverse document frequency) 是指逆向文件频率,这个指标用于衡量一个词的普遍重要性程度,由总文件数目比上包含该词的文件数目,然后在对这个值求对数得到,具体公式表示为:$idf_i=\log\frac{left| D right|}{left| {j:t_i \in d_j right|}}$,式中$left| D right|$表示总文档数,${j:t_i \in d_j right|}$表示包含该词语的文件数,最后将TF与IDF相乘就可以得到TF-IDF值。
TF-IDF算法手动实现起来也很简单,但python下还是提供了一些现成的实现方法,比如方法jieba.analyse.extract_tags,我们可以通过如下的代码轻易的提取出文本中最重要的前N个词:
Import jieba.analyse
#以上对Context进行分词操作
keywords = jieba.analyse.extract_tags(context,topK=N)
for word in keywords:
print word
在了解了通过TF-IDF算法或者TextRank等其他算法来提取文本关键词的方法之后,我们怎么来提取与类别相关的关键词呢?我们这里提供了一种比较简单有效的方法,首先,依次对每个类别中的每篇新闻提取关键词,将每篇新闻中权重最高的N个词放入一个map中,如果map中已经出现过该词,则对其权重加1,遍历完该类别所有的新闻之后,我们就可以得到一个<k,v>键值对,k是词语,v是关键词出现的次数,然后我们对这个键值对按value从高到低排序,取出value值最大的M个词放入关键词集中。当遍历完所有类别的新闻之后,我们就可以得到一个由多个类别的关键词构成的特征词集。
在通过上述处理之后,我们可以通过这个特征词集去对一篇未分类的新闻文本进行特征词提取,第一步是通过特征提取算法提取出权重最高的N个特征词,然后通过上文中得到的特征词集对这N个特征词进行过滤,只保留在特征词集中出现过的特征词。
词向量化
词向量化是将词从符号型的文本数据转换为向量的过程,常见的词向量化方法有离散表示以及分布式表示两种。
离散表示 (One-hot Representation) 本质上就是使用一个高维的向量来表示一个词,向量的维度等于词典的大小,每个向量只有一个维度的值是 1,其他维度的值均为 0,向量中值为 1 的那个位置即是词语在词典中的位置。例如:
南京”表示为 [0 0 0 1 0 0 0 0 0 0 0…]
北京”表示为 [1 0 0 0 0 0 0 0 0 0 0…]
这种词向量化的方法属于稀疏表达方式,这种表达方式最大的好处就是简单,但是它也有两个非常明显的缺点:
1. 容易造成维度灾难,因为每个词的向量维度等于词典大小,即每个词向量化之后长度以万计,而这会大大地增加计算的复杂度,对效率造成很大的负面影响,同时也需要浪费很多的存储资源。
2. 不能体现出词向量与词向量之间的相关性,丢失了很多重要的信息,例如“北京”、“南京”、“可乐”这些词,“北京”与“南京”之间的相似度要比“北京”与“可乐”之间的相似度更高,但是在离散表示方法中无法体现出这样的关系。
分布式表达的核心思想是对语言进行建模,将语言中的每个词映射到一个固定长度的向量空间中,这里的长度要远远小于离散表达向量的维度规模。每一个词向量都对应了向量空间中的一个点,而且在这个向量空间中引入了距离的概念,向量空间中两个点之间的距离表示两个词之间的相似度。现在业内比较流行的分布式表达方式是google2013年开源的word2vec工具,Word2vec 简单来说就是对给定的语料数据建立一个神经网络语言模型,而词向量作为神经网络的输入,是通过神经网络学习语言模型时产生的副产品,词向量在训练过程中随着神经网络的更新不断调整直到收敛。
Genism.models是Python下非常好用的word2vec工具,我们可以通过如下的代码来实现对文本的建模以及词向量化:
from gensim.models import word2vec
#对文本进行建模
model = word2vec.Word2Vec(train_data,min_count=1,size=100)
model.save('model/News2vec_model') #存储模型
#将词转换为词向量
model = gensim.models.Word2Vec.load(model_path)
wordvec = model[word]
上述两种向量化方法均可以将新闻文本转换为文本特征项向量,采用离散表达方式时,特征向量的维度等于特征词集的大小,即如果一篇文章中有第i个词,则将向量中第i个词置1,如果没有这个词则置0;而采用分布式表达时,我们可以简单的将所有词的词向量做加权均值来作为新闻的文本特征向量,这种方法看起来很简单却又非常有效。
模型训练及分类
常见的机器学习方法有线性模型、决策树、神经网络、支持向量机、强化学习以及最近热度非常高的深度学习等,本文主要简单介绍一下KNN和支持向量机在文本分类中的应用。
K 近邻算法 (KNN) [24] 是一种典型的非参数模型算法,这是一种非常基本的机器学习方法。该算法的基本思想就是在一个训练集大小为 N 的样本集中,求待分类样本与训练集中样本之间距离,并统计与待分类点距离最近的 K 个样本点的类别,最后将待分类点的类别划分为 K 个样本点所属类别最多的那个类别。当 K = 1 时,该算法即为最近邻算法。下面直接给出使用KNN进行模型训练及分类的代码:
import numpy as np
from sklearn import neighbors
traindata,trainlabel=loadTraindata(i)
testdata,testlabel= loadTestdata(i)
# 训练模型
# clftree = tree.DecisionTreeClassifier(max_depth=7) #可限制最大深度
clf = neighbors.KNeighborsClassifier()
# 拟合模型
clf.fit(traindata, trainlabel)
#测试集上的预测
result=clf.predict(testdata)
count=0
for i in range(len(result)):
if(result[i]!=testlabel[i]):
count++
print("准确率:" + count/len(result))
9大类的分类效果如下:
我们可以发现KNN在模型训练上耗时非常的少,且在绝大部分类别上的分类准确率都可以达到不错的效果。
支持向量机(SVM)是一种非常重要且应用广泛的传统机器学习方法,最早是由 Vladimir N. Vapnik 于 1963 年提出,现在流行的版本是由 Corinna Cortes 和Vapnik 于 1993 年提出。在深度学习方法流行起来之前, SVM 通常被认为是近十几年来最有效最成功的机器学习算法之一。假设存在训练样本数据集$T={(x_1,y_1),(x_2,y_2,...,(x_3,y_3)},y_i \in {-1,1}$,样本$(x_i,y_i)$ 散落在样本空间中,SVM 要解决的问题就是在样本空间中找到一个最优的超平面来将正负两类样本划分开,效果如图所示:
关于这个最优的超平面如何求解,感兴趣的同学可以去学习一下原理并试着自己推导一下公式,我们这里直接给出相关的代码
import numpy as np
from sklearn.svm import SVC
traindata,trainlabel=loadTraindata(i)
testdata,testlabel= loadTestdata(i)
# 训练模型
# clftree = tree.DecisionTreeClassifier(max_depth=7) #可限制最大深度
clf = SVC(C=1.0,kernel="rbf")
# 拟合模型
clf.fit(data, label)
#测试集上的预测
result=clf.predict(testdata)
count=0
for i in range(len(result)):
if(result[i]!=testlabel[i]):
count++
print("准确率:" + count/len(result))
9大类的分类结果如下
和KNN相比,SVM在模型训练上更加耗时,但是预测速度和分类的效果都要更胜一筹。
总结
本文简单介绍了传统机器学习在中文文本分类上的应用,包括了数据的爬取、预处理以及模型训练和预测,限于篇幅,很多概念原理和公式推理都没有给出,感兴趣的同学可以自行查阅。除了上述介绍的基于传统机器学习的方法,现在得益于数据量的大规模增涨以及计算能力的提升,深度学习在文本处理上也有了广泛的应用,且有着非常好的效果,比如CNN、LSTM等。但是不同算法之间各有优势,我们需要根据问题的具体应用场景和规模,来选择合适的方法来解决问题。