前面介绍了LIBSVM和LIBLINEAR的优化算法,下面简单总结一下不同算法的应用场景吧:
- 所有线性问题都是用LIBLINEAR,而不要使用LIBSVM。
- LIBSVM中的不同算法,如C-SVM和nu-SVM在模型和求解上并没有本质的区别,只是做了一个参数的变换,所以选择自己习惯的就好。
- LIBLINEAR的优化算法主要分为两大类,即求解原问题(primal problem)和对偶问题(dual problem)。求解原问题使用的是TRON的优化算法,对偶问题使用的是Coordinate Descent优化算法。总的来说,两个算法的优化效率都较高,但还是有各自更加擅长的场景。对于样本量不大,但是维度特别高的场景,如文本分类,更适合对偶问题求解,因为由于样本量小,计算出来的Kernel Matrix也不大,后面的优化也比较方便。而如果求解原问题,则求导的过程中要频繁对高维的特征矩阵进行计算,如果特征比较稀疏的话,那么就会多做很多无意义的计算,影响优化的效率。相反,当样本数非常多,而特征维度不高时,如果采用求解对偶问题,则由于Kernel Matrix过大,求解并不方便。反倒是求解原问题更加容易。
多分类问题
LIBSVM和LIBLINEAR都支持多分类(Multi-class classification)问题。所谓多分类问题,就是说每一个样本的类别标签可以超过2个,但是最终预测的结果只能是一个类别。比如经典的手写数字识别问题,输入是一幅图像,最后输出的是0-9这十个数字中的某一个。
LIBSVM与LIBLINEAR但实现方式却完全不同。LIBSVM采取的one vs one的策略,也就是所有的分类两两之间都要训练一个分类器。这样一来,如果存在k个class,理论上就需要训练 k(k?1)/2个分类器。实际上,libsvm在这一步也进行了一定的优化,利用已有分类的关系,减少分类器的个数。尽管如此,LIBSVM在多分类问题上还是要多次训练分类器。但是,考虑到前面说的LIBSVM的优化方法,随着样本数量的增加,训练的复杂度会非线性的增加。而通过1VS1的策略,可以保证每一个子分类问题的样本量不至于太多,其实反倒是方便了整个模型的训练。
而LIBLINEAR则采取了另一种训练策略,即one vs all。每一个class对应一个分类器,副样本就是其他类别的所有样本。由于LIBLINEAR能够和需要处理的训练规模比LIBSVM大得多,因此这种方式要比one vs one更加高效。此外,LIBLINEAR还实现了基于Crammer and Singer方法的SVM多分类算法,在一个统一的目标函数中学习每一个class对应的分类器。
输出文件
一般来说,我们使用LIBLINEAR或者LIBSVM,可以直接调用系统的训练与预测函数,不需要直接去接触训练得到的模型文件。但有时候我们也可能需要在自己的平台实现预测的算法,这时候就不可避免的要对模型文件进行解析。
由于LIBLINEAR与LIBSVM的训练模型不同,因此他们对应的模型文件格式也不同。LIBLINEAR训练结果的格式相对简单,例如:
1 solver_type L2R_L2LOSS_SVC_DUAL
2 nr_class 3
3 label 0 1 2
4 nr_feature 5
5 bias -1
6 w
7 -0.4021097293855418 0.1002472498884907 -0.1619908595357437
8 0.008699468444669581 0.2310109611908343 -0.2295723940247394
9 -0.6814324057724231 0.4263611607497726 -0.4190714505083906
10 -0.1505088594898125 0.2709227166451816 -0.1929294695905781
11 2.14656708009991 -0.007495770268046003 -0.1880325536062815
上面的solver_type
表示求解算法,w
以下表示求解得到的模型权重。其中每一列对应一个class的分类器,而每一行对应特征的一个维度。其中nr_class
表示求解的个数,nr_feature
表示特征的维度,bias
表示模型的bias,可以人工设置权重。这里容易产生误解的是label
这个字段,表示的是每一个用户训练文件中label对应w的列数。比如在上面的模型中,用户指定编号为0的分类器对应w的第一列。但是上面的对应关系并不是一定存在的,比如在二分类场景中,用将整样本标为1,负样本标为0,但在模型训练中,LIBLINEAR会按照自己的编号系统进行训练,因而有可能出现负样本在前,正样本在后的情形。这时候,就必须要根据label 1 0
将LIBLIENAR内部的编号体系与真实的用户标签进行对应。当然,后来LIBLINEAR和LIBSVM做了一些优化,在二分类时,如果正负样本标签分别是-1
和+1
,那么可以始终保证正样本出现在w的第一列。但是这个机制也不是完全靠谱,比如说在LIBLINEAR的Spark实现代码中,就没有实现这个特性,曾经把我整的很惨。因此在这里还是需要十分注意。
LIBSVM的训练结果格式就更复杂一些,例如:
1 kernel_type rbf
2 gamma 0.0769231
3 nr_class 3
4 total_sv 140
5 rho -1.04496 0.315784 1.03037
6 label 1 0 -1
7 nr_sv 2 2 1
8 SV
9 0 1 1:0.583333 2:-1 3:0.333333 4:-0.603774 5:1 6:-1 7:1 8:0.358779 9:-1 10:-0.483871 12:-1 13:1
10 0 0.6416468628860974 1:0.125 2:1 3:0.333333 4:-0.320755 5:-0.406393 6:1 7:1 8:0.0839695 9:1 10:-0.806452 12:-0.333333 13:0.5
11 0 1 1:0.333333 2:1 3:-1 4:-0.245283 5:-0.506849 6:-1 7:-1 8:0.129771 9:-1 10:-0.16129 12:0.333333 13:-1
12 0.2685466895842373 0 1:0.583333 2:1 3:1 4:-0.509434 5:-0.52968 6:-1 7:1 8:-0.114504 9:1 10:-0.16129 12:0.333333 13:1
13 0 1 1:0.208333 2:1 3:0.333333 4:-0.660377 5:-0.525114 6:-1 7:1 8:0.435115 9:-1 10:-0.193548 12:-0.333333 13:1
上面参数的意义都比较直接,需要注意的是SV后面就是训练出的模型参数,以支持向量的方式进行存储。nr_sv给出了每一个支持向量所对应的模型,比如“2 2 1”就表示前两行是标签为1类的支持向量,其后面两行是标签为0类的支持向量,最后一行是标签为-1类的支持向量。而具体每一行支持向量,在上面的模型中,由于存在三类,所以每一个支持向量有可能都会存在于两个分类器中,所以前两列的数分别对应了对剩下两个分类作为支持向量时候的α值,后面才是真正的支持向量。