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

6

我刚接触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)。但显然,这会很麻烦,因为我必须维护和持久化除序列化分类器之外的第二个文件。有人看到更简单的方法吗?

你的代码似乎可以工作,如果你删除概率估计部分(即删除“probability = 1”,将predict_probability更改为只是predict,并删除pred_probability)。 - dmcer
@dmcer,太棒了。相反,只要对于每个标签至少有两个样本,我似乎可以保留概率估计值。奇怪的是为什么仅对于每个标签一个样本不起作用。如果您将评论发布为答案,则我将将其标记为被接受的答案。 - Cerin
2个回答

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)

带有概率估计的预测确实会生效,如果您将训练集中的数据加倍(即将每个数据点包含两次)。但是,我找不到任何一种参数化模型的方法,使得只使用原始的四个训练点就可以进行多类预测并输出概率。


3
如果您对另一种处理方式感兴趣,可以尝试以下方法。理论上更为严谨,但不够直接。
提到均值和标准差时,似乎是在指涉您认为数据以某种方式分布的情况。例如,您观察到的数据服从高斯分布。然后,您可以使用对称Kullback-Leibler_divergence作为这些分布之间的距离度量。然后可以使用类似k-nearest neighbour的算法进行分类。
对于两个概率密度函数p和q,当且仅当p和q相同时,KL(p,q)= 0。但是,KL不是对称的-因此为了得到合适的距离度量,您可以使用
distance(p1,p2) = KL(p1,p2) + KL(p1,p2)
对于高斯分布,KL(p1, p2) = { (μ1 - μ2)^2 + σ1^2 - σ2^2 } / (2.σ2^2) + ln(σ2/σ1). (我从这里偷来的,你也可以找到一个演示:)
简而言之:
给定一个(mean, std, class)元组的训练集D和一个新的p=(mean, std)对,找到D中距离(d, p)最小的q,并返回该类。
对我来说,这种方法比具有多个内核的SVM方法更好,因为分类方式不那么随意。

谢谢。我想可能有比SVM更适合正态/高斯分布的算法。但是,我还打算将这些高斯特征与其他任意特征一起使用,因此使用专门的距离度量的k-nn算法不太合适。 - Cerin
实际上有办法从类标签中学习这样的距离度量。也许你想看看Sam Roweis关于邻域组件分析的工作。 - bayer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接