C++:文本分类器

原文地址:http://palydawn.blog.163.com/blog/static/18296905620124171155256。

1. 序言

最近一直在做文本分类的实验,查阅了很多文章和资料后,大概清楚了文本分类的整体流程。根据查阅到的资料,编写了一个简单的文本分类程序,对这些工作,在这篇文章中做个总结。

2. 文本分类过程

2.1 实验样本选择

训练样本和测试样本使用的是"tc-corpus-answer.rar"中的txt文件,很早之前下载的,忘了具体是什么了,好像是人民日报的语料库,在里面挑选了4种类型(C7-History, C19-Computer, C32-Agriculture, C39-Sports)的文档用来做训练和测试。

表1 测试样本和训练样本数

2.2 文本分类流程图

图1左边部分是对训练样本的处理过程。

图1右边部分是对测试样本的处理过程。

图1 文本分类过程

2.3 训练样本处理

2.3.1 训练样本分词

分词的目的是将文档分割成一个个的单词。实验中用的分词器是中科院的"ICTCLAS",号称是最好的汉语分词器,分词率9x.xx%(具体多少忘了,反正标的很高),测试了一下,效果不是他说的那么好(9x.xx%肯定是达不到的),不过网上貌似也没找到其他的分词器可以用了(可能有我不知道吧,懒得查了,就用这个)。"ICTCLAS"的分词效果虽然不如宣传的那样好,但是做实验用足够了。郁闷的是从官网上下下来的最新的"ICTCLAS50_Windows_32_C.rar"中包含的API文档和头文件"ICTCLAS50.h"中的接口不一样,很明显文档时很久以前的,把更新了的程序和古老的文档一起打包发布,不知道是什么原因。在官方网站上看到,"ICTCLAS"号称是开源的,下下来之后才发现,只有一个头文件,提供了几个简单的接口,用起来很不方便、不灵活。

2.3.2 去"停用词"

分词完成之后就要去除"停用词"了。所谓的"停用词"是那些没有意义的词语,他们对文本分类没有贡献,比如说各种标点符号,今后、今天、今年、今後这样的词语等。去掉这些没有作用的词语可以减少文本特征向量的维数,从而减少不必要的运算量。从网上下了几个停用词表合并起来使用,仍然出现了一些不想看到的东西,比如各种全角符号,全角数字,全角字母,还有一些数字和标点符号的混合体,要把这些全部去掉仅仅靠停用词表(停用词表枚举的情况是有限的,而这种乱七八糟的组合是无限的)是不够的。

狠了狠心,干脆把含有非汉字的单词全部去掉。图2列举的是常用汉字的GBK编码范围。对照这表里面的范围,把带有非汉字的词语全都过滤掉。接着对照着停用词表去停用词。通常的方法是对一篇分完词的文档,遍历每一个单词,对每一个单词,遍历一遍停用词表,查看此单词是否在停用词表中,在停用词表中就去掉,不在就保留。停用此表大概有2000个单词,假设平均一篇文档有1000个单词(没有细算,不需要太准确),一篇文档的去停用词工作需要计算2000*1000次,本实验中用的测试样本有3000多个,这样算起来运算就是2000*1000*3000次,才刚刚开始计算量就有点大了。

图2 常用汉字的编码范围

使用布隆过滤器去来去停用词是一件美好的事情,先把2000个停用词通过hash运算映射到一个bitmap中,再遍历每一个文档,对每一个文档遍历每一个单词,将每一个单词做hash运算,映射到相同的bitmap,检查bitmap中对应的位值是否为1即可。程序里面使用的是8个hash函数,故使用布隆过滤器的运算次数为2000*8 +1000 * 8 * 3000 ,而原始的方法运算次数为2000*1000*3000,运算次数大幅减少。布隆过滤器大幅减少运算次数的代价是一定的误判率,即,有些单词不在停用词表中却被误以为是停用词而被忽略,不过在bitmap很大、hash函数的个数最优的时候,误判的概率是非常小的,在我的程序里面,用了16万位的bitmap,8个hash函数,2000个停用词,每个停用词占用8个bit,一共才占用16000个bit,只有总容量的十分之一,错误率应该是很低的,2000个停用词误判不会超过10个(参照某一篇文献里面的布隆过滤器误判统计和分析,10个还是非常保守的估计),更何况,对于几千维、上万维的文本特征向量来说,这点小误差根本不重要。(关于布隆过滤器网上有很多资料)。

2.3.3 提取初步特征

提取初步特征就是对所有训练文档分词去停用词后的结果做并集操作,提取出一个特征集合,包含所有在分词去停用词后的样本中出现的单词。布隆过滤器是做大集合操作的优秀工具,最近非常喜欢用它,这里又用上了。最终得到的初步特征有35808维。如图3所示。

图3 初步属性特征

2.3.4 互信息计算、特征属性选择

这么多的特征用来做训练,运算的时间将会是很漫长的,这个时候降维是必要的。常用的降维方法有粗糙集属性约减、计算互信息,取互信息值最大的若干项等,粗糙集降维比较复杂,最近才开始看,这个实验里面用的是计算互信息,取互信息最大的5000个单词作为最终的特征属性。互信息的公式很容易实现。按从大到小的顺序排序之后取前5000个词就可以了。互信息计算结果如图4所示。

 

图4 互信息计算结果

2.3.5 文档特征向量(权重为tf-idf值)计算

确定了文档的特征属性之后,采用tf-idf作为文档属性值的权重,tf-idf的公式也很容易计算。遍历每一个文档,计算其特征向量,每个文档都用一个5000维的向量表示,按照所得到的文本特征向量按照libsvm的格式要求保存到文件。

计算得到的训练样本的文本特征向量如图5所示。可以看到,这些文本向量是很稀疏的。

不管是互信息计算还是tf-idf值都需要词频信息,而词频的统计又是一个很耗时的过程,统计35808维的特征词在每一个文档中出现的频率,总的运算次数为35808 * 1000 * 3000,在去停用词的时候,原始方法的慢勉强还是可以接受的,这个时候就不行了,在我的机器上每篇文档的词频统计目测需要一到两秒,3000篇就需要等很久了,这个时候解决的办法还是布隆过滤器,加速效果十分明显。

图5 训练样本的特征向量

2.3.6 样本训练

到这里所有的训练样本已经用特征向量表示了,接着就用libsvm做训练。SVM是一个很有前途的分类器,tlibsvm用起来也很方便。3000个5000维的特征向量,训练了一天多,交叉检验的准确率只有59.958%,训练结果如图6所示。在我参考的那篇论文里面,取1000维的特征向量,正确率、召回率都在90.XX%以上,靠!

图6 libsvm训练结果

2.4 测试样本处理

测试样本的处理的过程中也需要分词,去停用词,使用已经得到的5000维特征属性进行tf-idf的计算,计算出测试文档的特征向量,这些操作和训练样本处理时是相同的。使用libsvm训练的结果对测试样本进行分类,正确率69.7905%。

图5 测试结果

测试结果如图5所示,正确率69.7905%(474/680),比想象中的要好,训练的时候才59.958%,当然跟所有的文献里面比起来都显得低了。

各个类别的分类正确率如表2所示,有效的样本数是指排除掉文本特征向量中属性值全部为0的文档后剩下的数量。

表2 各种类型的测试结果

在表2中可以看到每一类测试样本中都有一些无效的(即文本特征向量的属性值全部为0)。这种情况在训练样本中也有,这里暂时不考虑这个问题。训练样本较多的C19-Computer、C32-Agriculture、C39-Sports分类准确率比训练样本较少的C7-History要高很多。

3. 需要解决的问题

目前只是实现了一个最简单的文本分类算法,效果很不好,还有很多需要改进的地方。

  1. 降维。选用互信息值最高的属性作为特征属性似乎是一种十分拙劣的手法,查看了以下互信息的排序结果,5000名以后的很多词语其实是很有价值的,但是忽略了,5000以内的很多词语排名靠前只是好像是因为出现的次数很少,很多都是没有意义的。(5000这个维度也是随便取得,没什么根据)。目前正在学习粗糙集的有关知识,查看了很多论文,感觉这个才是主流的降维方法。
  2. 特征向量中所有属性值都为0的情况。一共有35808 个特征属性,值提取其中的5000维作为特征属性,显然可能会出现某些文档中没有出现一个特征属性的情况,现在的处理办法是直接忽视掉,这样的处理方式的到的分类成功率肯定是不准确的。
  3. 特征属性权重的计算方式。除了tf-idf之外还有很多其他的方法可是使用,下一步会尝试几种其他的方法,并做对比分析。
  4. 训练。使用libsvm训练虽然很方便,但是也可以试一下其他的训练方法,如神经网络、决策树等。可能会有更好的结果。

 

这篇文章主要总结的是文本分类的过程,其中的运算时间、准确率等都只是一个概数,并没有进行太准确的测试和分析,其中的公式也一句带过。因为刚刚开始接触文本分类,实验主要的目的在于对文本分类流程有一个完整深刻的理解(比这么粗糙的系统做精确的分析和测试也没什么意义)。等到整个文本分类系统趋于完善了再做一个定量的分析和测试。

4. 后记

(虽然这只是一个小程序,里面的算法理论都是在别人论文里面找到的,我只是写代码实现,但是其中布隆过滤器用来去停用词,提取初步的特征,统计词频都是因为程序运行太慢我自己想出来的,灵感来自于前一段时间用python写的网络爬虫里面url的去重,看了十几篇论文,都没人把布隆过滤器和文本分类结合起来,(*^__^*) 嘻嘻……,这也能算一点微创新吧!)

----------------------------------------------------------------------------------------------------------

很久之前写的程序,里面有个bug当时没发现,IG值计算错误,导致分类成功率底,纠正之后分类正确率确实可以到90%以上。

其他文章:《libvsm回归详细操作步骤》 http://blog.sina.com.cn/s/blog_5980835e0100drwx.html。
  基于libsvm的中文文本分类原型:http://blog.csdn.net/marising/article/details/5844063。

你可能感兴趣的:(C&C++)