SVM类库研究(牛mm精简版)
俺的SVM,牛mm说太口语化,另外加的都是句号,说我语文没有学好。于是她给我改写了一下。我发现我真的很喜欢句号,句号表示一句话终结了。很显然,我喜欢把每件事
情都终结了。
不多说了,因为牛mm可能不希望别人知道她真名,所以我只好先用牛mm代替了。因为她很牛,而且又是mm,所以就叫牛mm。原文:
http://blog.csdn.net/cctt_1/archive/2008/11/17/3321185.aspx
废话完毕,下面粘贴改进后的版本:
第一步,资料和代码查找。在 Google 上搜索各种 SVM 相关代码和使用方法,经过可行性试验和筛选,选定了 libsvm 的 c
版本,使用 vs2005 编译生成 svm-train.exe ,svm-predict.exe, 和 svm-scale.exe
。关于这些类库文件的使用方法,可以参照 http://www.csie.ntu.edu.tw/~piaip/ docs/svm/ 。
第二步,确定文件存储格式。通过下载和阅读 heart.scale 了解到,其文件格式是
< 类号 > < 词号 > : < 词个数 > < 词号 > : < 词个数 >…
但是这里有一点要注意:最后一行数据必须敲一个回车。就是说以空行做结束。否则程序就不会终止。另外偶然发现 svm 可以直接处理字符串,文件格式是: 类号
字符串。我心想这个不正适合我的文本分类吗?而且我根本就不需要做分词就可将整个 content 当作一个字符串。我试了下作者给的其他的 svm
版本都不支持。我看了下这个作者给的 svm 的源代码 libsvm-2.88-string
中其实是含有这段代码的,但是没有使用宏进行编译,所以要使用这个 libsvm 来处理字符串,需要对代码进行宏编译。
第三步,宏编译? -D … 后面还有一大堆看不懂的参数。作者给了 makefile 文件,可以选择两种编译环境:一是 windows
环境;二是 linux 等操作系统。考虑到数据文件大小在 3G 左右, linux 下并没有足够大的空间来存放这些文件,所以只有在
windows 下了。下载一个 windows 下的 g++ ,安装后,打开作者所给的 makefile 文件,去掉 CXX?=g++
中的 ?. 然后生成成功。
第四步,研究 java 如何调用 libsvm 的 exe 文件。找了几段网上的代码,发现均不满足要求;于是写了
http://blog.csdn.net/cctt_1/archive/2008/11/14/3300789.aspx
。不过这篇文章里只是说
exe 运行正确的情况下可以正常退出;如果 exe 抛出异常,那么程序就会终止。所以你还有在得到一个类似 error 的输入输出流,这个也是
Process 的函数。然后反复试验就知道是把 error 流放在前面还是正常输入输出放在前面了。
第五步,初步测试。使用十几个 content 做测试,发现效果不错;然后用了 26000 多个 content
再做测试,发现运行了几个小时依旧没有结果。其实之前就不抱有什么期望,因为 libsvm 对字符串的处理是采用子串匹配算法,效率比较低。
第六步,重新使用正常的 libsvm ,采用类号 词号:词个数 … 作为文件存储方式。这时看到了 libsvm-2.88 有 java
的代码,于是采用之;但是在 libsvm 中抛了 Outofmemory 异常。我按网上的做法,将 eclipse 的 eclipse.ini
文件中的 xmx 的大小改为 1024m ,重启之,还是不行。于是再搜索,结果还是没有啥新发现,于是还是使用了调用 exe
的方式。(后来才知道这里的 1024m 是对于 eclipse 来说的。如果 eclipse 运行程序的话,要在 Run… 这个选项中的
jvm 参数中设置 -xmx1024m 才可以。)
第七步,一切 ok ,进行测试,发现正确率偏低达到 28% 。后来看到有 scale 的 exe
,想着可以找到训练的最好参数。但是我看着我的硬盘一点点的被吃掉,那个生成 scale 的程序还没有终止。一个 30M 的小文件耗费了我 6G
左右的硬盘 … 而且还没有将 scale 生成完毕, scale 方法就此作罢。如果只有几百个 content 使用 -c
的参数竟然达到了 100% ;但是交叉训练还是 25% 左右。
第八步,检查了自己的程序,发现竟然词号那里有错误。每统计一个文件,重新生成了一个词号 …
这怎么可能是对的,重新改正,生成新的数据文件。再跑一遍,发现这次 svm-train.exe 抛异常了,竟然是 status_access_valid
。查了下是共享内存出了问题,但是我这个是单线程的,于是把几乎所有能关的程序全部关闭再跑,依旧同样的错误。给作者发信, 作者竟然很快给我回信了,说
libsvm 可以跑出结果呀,又把他的 log 给了我。
第九步,和同组成员一起分析原因。为啥人家就能跑出来,自己却跑不出来呢?再看 libsvm 的异常信息,上面有 stack 的信息,再分析了下作者给的
log ,发现我没有跑出来的那个 log ,其迭代次数达到了 3000 多次;而之前的最高次数也就是 1000 次。难道是 stack
溢出?于是使用 vs 打开源代码,将工程中的 properties->system->stack commit size 调为 10 ,意为
10M ,再各个编译、运行,果然成功。
第十步,训练 26000 个 content 需要 1 个小时,但是需要训练的 content 数量是 26000*7
以上的。以实际经验来看 svm 的训练不是线性的,而是指数或多项式级的;所以训练如此大的文件,我的机器我的时间是根本不允许的。
SVM 虽然部署完毕,但是在运行过程仍然存在着种种问题,下面是一些想法:
1 )如何解决正确率不高,训练时间长的问题。因为没有时间这样去跑,所以得另想它法,能不能将几篇文档整合?于是将全部的文档按照 doc 个数为 n
进行整合,然后最后再除以 n. 所以现在维数据不是词个数(整数),而是浮点数,然后将测试文档放入预测,发现全为 0
了,根本预测不出。可能是因为这样除 n 不具有一般意义,比如一个词只出现 1 次,但是再除以 n ,那就是 0.0001
或者更小;而预测文档中此词就是 1 ,所以根本不可能匹配。另外一种方法:如果 n 个文档不匹配,那么如果是全部的文档,是否符合统计的规律?结果测试说
明,还是不匹配,原因相同。这里其实还有一个想法,就是将这个统计得到的训练文档中维数据整理成整数,如果此时维数据是 0.0001 ,还有的维数据是
0.002 ,那么如果真实测试文档中有这两个词,那么必定至少是 1 。如果每个维数据都乘以一个代价常数,变为一个大于等于 1
的维数据,这样做就有可能得到正确的预测。另外还要考虑到有些词是偶尔出现在此类中,也就是基本与此类无关。那么的它的维数据中可能有更小的例如
0.000001. 那么这样的数据怎么考虑?还有如果全部的维数据都乘以一个相同的代价常数。那么当维数据 0.0001 变为 1 时,(假设)维数据
1 (表示这个词与此类有极大的关联),将变为 10000 ,在一篇文档中不可能有一个词个数是 10000
的。即使这个词在每个文档中都出现,所以我们还是不能这样直接乘以一个相同代价的常数。这里因为时间关系,并没有做一个代价函数。
2 )试图采用所有的数据进行训练。 26000 个 doc
不一定能说明什么问题,当数据量极大的时候,或许能产生一个好结果;但是这是我们希望发生的事情。借用了别人实验室中的机器,发现虽然他的机器是 4 核 4G
的,但是跑 svm 依旧很慢。原因在于 svm 是一个单线程单进程的程序,所以和一个核去跑本质上没有啥区别。另外也发现一个问题,就是 libsvm
在测试 2.57G 的数据时, crash
了,这真的不是内存或者是其他的错误,因为产生的向量维数太大,以至于申请一个向量空间时访问到了操作系统的保护段数据,被操作系统拒绝了,所以可能要进行 “
缩水 ” 。
我这里说的“缩水”是一种自己的算法,我认为每个文档在训练集中的地位不是一样的。那么可能有些文档没有必要存在于训练集中,或者说每个文档存在于训练集中的概率是不
同的,所以我这里做一个“缩水”,将大文档尽可能的排除,设为 20% 概率保留;小文档尽可能的保留,设为 80% ;中等大小的文档 50%
保留。这样做完后,只剩下 500M 左右的数据。我希望这些数据可能保持原来的特性,并且训练速度要提高,可惜结果还是一样的差。
3 )想到可以查询相关论文资料。(这一步感觉应该提前去做。竟然因为时间关系忘记了,因为一直以为这是一个工程的活,和研究没有太多的关联。谁知道在数据量如此大
时候,这些工程的方法都不起作用。)找到很多论文,发现论文之间的一致性:降维,找特征值。可是这个矩阵太大了。另外还有一种方法,就是在这个类中找出很多感觉上一定
相关的词(人肉的方法),然后用这些词进行筛选。当然这个方法是可行的,可惜没有时间和精力去每个类找这么多相关的词。
我觉得 svm 不得不暂时放弃了。毕竟数据量太大的时候,效果很不好,这也是各个 svm
论文中提到的一点;而且至今没有很好的解决方案。另外即使是降维,最后还是要调节参数,看了各个文献,发现调参数也是很有讲究的 ( 使用人工智能等等方法 )
。
最后的总结: svm 的确是可以运行了,但是训练时间太长;另外数据集太大, svm 显得太笨重;还有正确率必须用参数调优,而调优的前提是必须生成
scale 文件。大数据量所生成的 scale 文件太大了,所以就不可能去调优(对于我的机器而言)。这样比较起来还是朴素贝叶斯较好,简单、而且速度快。