使用LibSVM计算与均值/标准差对的最近匹配
我刚接触支持向量机(SVM),现在想用Python的libsvm库来对一个包含均值和标准差的样本进行分类。不过,我得到的结果完全不靠谱。
这个任务不适合用SVM吗,还是我在使用libsvm时出了错?下面是我用来测试的简单Python脚本:
#!/usr/bin/env python
# Simple classifier test.
# Adapted from the svm_test.py file included in the standard libsvm distribution.
from collections import defaultdict
from svm import *
# Define our sparse data formatted training and testing sets.
labels = [1,2,3,4]
train = [ # key: 0=mean, 1=stddev
{0:2.5,1:3.5},
{0:5,1:1.2},
{0:7,1:3.3},
{0:10.3,1:0.3},
]
problem = svm_problem(labels, train)
test = [
({0:3, 1:3.11},1),
({0:7.3,1:3.1},3),
({0:7,1:3.3},3),
({0:9.8,1:0.5},4),
]
# Test classifiers.
kernels = [LINEAR, POLY, RBF]
kname = ['linear','polynomial','rbf']
correct = defaultdict(int)
for kn,kt in zip(kname,kernels):
print kt
param = svm_parameter(kernel_type = kt, C=10, probability = 1)
model = svm_model(problem, param)
for test_sample,correct_label in test:
pred_label, pred_probability = model.predict_probability(test_sample)
correct[kn] += pred_label == correct_label
# Show results.
print '-'*80
print 'Accuracy:'
for kn,correct_count in correct.iteritems():
print '\t',kn, '%.6f (%i of %i)' % (correct_count/float(len(test)), correct_count, len(test))
这个问题看起来比较简单。我本以为如果它训练出来知道均值2.5对应标签1,那么当它看到均值2.4时,应该返回标签1作为最可能的分类结果。然而,每个核函数的准确率都是0%。这是为什么呢?
另外,有没有办法隐藏libsvm在终端输出的那些冗长的训练信息?我查过libsvm的文档和代码,但找不到关闭这个功能的方法。
还有,我本想在我的稀疏数据集中用简单的字符串作为键(比如{'mean':2.5,'stddev':3.5})。可惜libsvm只支持整数。我试着用字符串的长整型表示(比如'mean' == 1109110110971110),但libsvm似乎把这些都截断成普通的32位整数。我看到的唯一解决办法是维护一个单独的“键”文件,把每个字符串映射到一个整数('mean'=0,'stddev'=1)。但显然这样很麻烦,因为我还得维护和保存第二个文件,和序列化的分类器一起。有没有人觉得有更简单的方法呢?
2 个回答
如果你想尝试一种不同的方法,可以这样做。这种方法在理论上更合理,但操作起来没有那么简单。
提到均值和标准差,似乎你是在说一些你认为是以某种方式分布的数据。比如,你观察到的数据是高斯分布的。然后你可以使用对称的Kullback-Leibler散度来衡量这些分布之间的距离。接着,你可以用类似k近邻算法的方法来进行分类。
对于两个概率密度p和q,只有当p和q完全相同时,KL(p, q)才等于0。不过,KL并不是对称的——所以为了得到一个合适的距离度量,你可以使用
distance(p1, p2) = KL(p1, p2) + KL(p2, p1)
对于高斯分布,KL(p1, p2)的计算公式是{ (μ1 - μ2)² + σ1² - σ2² } / (2.σ2²) + ln(σ2/σ1)。这个公式我从这里抄来的,你也可以在那找到一些偏差的内容 :)
简单来说:
给定一个训练集D,里面有(均值, 标准差, 类别)的组合,以及一个新的p = (均值, 标准差)对,找到D中与p的距离最小的q,并返回那个类别。
我觉得这种方法比使用多个核的支持向量机(SVM)要好,因为分类的方式没有那么随意。
这个问题似乎是因为把多类别预测和概率估计结合在一起导致的。
如果你把代码设置成不进行概率估计,它实际上是可以正常工作的,比如:
<snip>
# Test classifiers.
kernels = [LINEAR, POLY, RBF]
kname = ['linear','polynomial','rbf']
correct = defaultdict(int)
for kn,kt in zip(kname,kernels):
print kt
param = svm_parameter(kernel_type = kt, C=10) # Here -> rm probability = 1
model = svm_model(problem, param)
for test_sample,correct_label in test:
# Here -> change predict_probability to just predict
pred_label = model.predict(test_sample)
correct[kn] += pred_label == correct_label
</snip>
做了这个修改后,我得到了:
--------------------------------------------------------------------------------
Accuracy:
polynomial 1.000000 (4 of 4)
rbf 1.000000 (4 of 4)
linear 1.000000 (4 of 4)
如果你在训练集中把数据重复一遍,概率估计的预测是可以正常工作的(也就是说,每个数据点都包含两次)。不过,我找不到任何方法来调整模型,使得在只有原始四个训练点的情况下,多类别预测和概率估计能够一起正常工作。