我的问题和疑问在下文中加粗显示。
我通过遵循Accord.NET文档页面上的示例,如这个,成功使用了Accord.NET的支持向量机。然而,当使用KernelSupportVectorMachine和OneclassSupportVectorLearning进行训练时,训练过程产生了较大的误差值和错误的分类结果。
下面的最小示例展示了我的意思。它生成一个密集的训练点集,然后训练一个SVM来将点分类为集群的内点或外点。训练集群只是一个以原点为中心的0.6 x 0.6的正方形,训练点以0.1的间隔排列:
static void Main(string[] args){ // 模型和训练参数 double kernelSigma = 0.1; double teacherNu = 0.5; double teacherTolerance = 0.01; // 生成输入点云,一个以0,0为中心的0.6 x 0.6的正方形。 double[][] trainingInputs = new double[49][]; int inputIdx = 0; for (double x = -0.3; x <= 0.31; x += 0.1) { for (double y = -0.3; y <= 0.31; y += 0.1) { trainingInputs[inputIdx] = new double[] { x, y }; inputIdx++; } } // 生成内点和外点测试点。 double[][] outliers = { new double[] { 1E6, 1E6 }, // 非常远的外点 new double[] { 0, 1E6 }, // 非常远的外点 new double[] { 100, -100 }, // 远的外点 new double[] { 0, -100 }, // 远的外点 new double[] { -10, -10 }, // 仍然是远的外点 new double[] { 0, -10 }, // 仍然是远的外点 }; double[][] inliers = { new double[] { 0, 0 }, // 集群中间 new double[] { .15, .15 }, // 集群角落的一半处 new double[] { -0.1, 0 }, // 舒适地在集群内部 new double[] { 0.25, 0 } // 接近集群内部边缘 }; // 构建内核、模型和训练器,然后进行训练。 Console.WriteLine($"训练模型的参数如下:"); Console.WriteLine($" kernelSigma = {kernelSigma.ToString("#.##")}"); Console.WriteLine($" teacherNu={teacherNu.ToString("#.##")}"); Console.WriteLine($" teacherTolerance={teacherTolerance}"); Console.WriteLine(); var kernel = new Gaussian(kernelSigma); var svm = new KernelSupportVectorMachine(kernel, inputs: 1); var teacher = new OneclassSupportVectorLearning(svm, trainingInputs) { Nu = teacherNu, Tolerance = teacherTolerance }; double error = teacher.Run(); Console.WriteLine($"训练完成 - 误差为 {error.ToString("#.##")}"); Console.WriteLine(); // 测试训练后的分类器。 Console.WriteLine("测试外点:"); foreach (double[] outlier in outliers) { WriteResultDetail(svm, outlier); } Console.WriteLine(); Console.WriteLine("测试内点:"); foreach (double[] inlier in inliers) { WriteResultDetail(svm, inlier); }}private static void WriteResultDetail(KernelSupportVectorMachine svm, double[] coordinate){ string prettyCoord = $"{{ {string.Join(", ", coordinate)} }}".PadRight(20); Console.Write($"分类: {prettyCoord} 结果: "); // 分类坐标,打印结果。 double result = svm.Compute(coordinate); if (Math.Sign(result) == 1) { Console.Write("内点"); } else { Console.Write("外点"); } Console.Write($" ({result.ToString("#.##")})\n");}
以下是合理参数集的输出结果:
训练模型的参数如下: kernelSigma = .1 teacherNu=.5 teacherTolerance=0.01训练完成 - 误差为 222.4测试外点:分类: { 1000000, 1000000 } 结果: 内点 (2.28)分类: { 0, 1000000 } 结果: 内点 (2.28)分类: { 100, -100 } 结果: 内点 (2.28)分类: { 0, -100 } 结果: 内点 (2.28)分类: { -10, -10 } 结果: 内点 (2.28)分类: { 0, -10 } 结果: 内点 (2.28)测试内点:分类: { 0, 0 } 结果: 内点 (4.58)分类: { 0.15, 0.15 } 结果: 内点 (4.51)分类: { -0.1, 0 } 结果: 内点 (4.55)分类: { 0.25, 0 } 结果: 内点 (4.64)
括号中的数字是SVM为该坐标给出的分数。使用Accord.NET的SVM(以及一般情况下),负分数表示一个类别,正分数表示另一个类别。在这里,所有结果都是正分数。内点被正确分类,但外点(即使是非常远的外点)也被分类为内点。
请注意,以前我使用Accord.NET训练模型时,训练误差总是非常接近于零,但在这里误差超过了200。
这是另一组参数的输出结果:
训练模型的参数如下: kernelSigma = .3 teacherNu=.8 teacherTolerance=0.01训练完成 - 误差为 1945.67测试外点:分类: { 1000000, 1000000 } 结果: 内点 (20.96)分类: { 0, 1000000 } 结果: 内点 (20.96)分类: { 100, -100 } 结果: 内点 (20.96)分类: { 0, -100 } 结果: 内点 (20.96)分类: { -10, -10 } 结果: 内点 (20.96)分类: { 0, -10 } 结果: 内点 (20.96)测试内点:分类: { 0, 0 } 结果: 内点 (44.52)分类: { 0.15, 0.15 } 结果: 内点 (41.62)分类: { -0.1, 0 } 结果: 内点 (43.85)分类: { 0.25, 0 } 结果: 内点 (40.53)
同样,训练误差非常高,所有分数都是正的。
模型确实从训练中得到了一些东西——内点和外点的分数是不同的。但为什么这个简单的场景没有像应该的那样给出正负符号不同的结果呢?
附注:这里有一个类似的程序,它测试了许多训练和模型参数的组合,这里是它的输出。同样,所有结果都是正的分类分数,高误差值,以及错误分类的外点。
回答:
在问题中提出的问题已在Accord.NET的3.7.0版本中得到解决。还添加了一个与您的示例相似的单元测试,在提交be81aab中完成。