使用LibSVM计算与均值/标准差对的最近匹配

6 投票
2 回答
1264 浏览
提问于 2025-04-15 21:09

我刚接触支持向量机(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 个回答

3

如果你想尝试一种不同的方法,可以这样做。这种方法在理论上更合理,但操作起来没有那么简单。

提到均值和标准差,似乎你是在说一些你认为是以某种方式分布的数据。比如,你观察到的数据是高斯分布的。然后你可以使用对称的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)要好,因为分类的方式没有那么随意。

5

这个问题似乎是因为把多类别预测和概率估计结合在一起导致的。

如果你把代码设置成不进行概率估计,它实际上是可以正常工作的,比如:

<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)

如果你在训练集中把数据重复一遍,概率估计的预测是可以正常工作的(也就是说,每个数据点都包含两次)。不过,我找不到任何方法来调整模型,使得在只有原始四个训练点的情况下,多类别预测和概率估计能够一起正常工作。

撰写回答