Introduction

2018/04/30 更新
記事の中で実際に使用しているライブラリが間違っていました。libsvm.net ではなく libsvm.clr でした。
そのため、記事の説明を libsvm.clr に変更しています。

機械学習といえば猫も杓子もディープラーニングって感じですが、ディープラーニングを使うにはちょっと重すぎるという場合に、まだまだいけるのが Support Vector Machine (SVM) です。
.NETでSVMを扱えるライブラリはそこまで多くありません。

  • Accord.NET
  • KMLib
  • LibSVMsharp
  • libsvm.net
  • libsvm.clr

位が簡単に見つかるところです。
dlibでもsvmは扱えますが、ラップ処理が終わっていませんのでdlib.netでは扱えません。
(テンプレートを.NETを移植するのは本当に辛いです) 今回は、libsvm.clrを使います。
libsvm.clrはlibsvmをC++/CLIでビルドし直したライブラリです。
libsvmはwikipediaによれば、

LIBSVMおよびLIBLINEARは広く使われるオープンソースの機械学習ライブラリである。両方とも国立台湾大学で開発され、C言語APIを用いたC++で記述されている。

とあります。
2000年あたりから開発されているようで、現在でも開発が続いています。
(最終更新は2016年12月!!)

ソースは下記になります

Get Started

libsvm.clrを使うには下記の作業が必要です。

  1. Nugetからlibsvm.clrのインストール

以上!!!

1
Install-Package libsvm.clr.vs2015.x64

で完了です。

Sample

SVMといえば、2値判定が有名ですが、多値判定も可能です。
今回は多値判定をテストします。

ただし、libsvmは遅いです。これは今後解説しますが、データが線形で分類できるなら、liblinearの使用をお勧めします。
公式ページにも

When to use LIBLINEAR but not LIBSVM There are some large data for which with/without nonlinear mappings gives similar performances. Without using kernels, one can quickly train a much larger set via a linear classifier. Document classification is one such application. In the following example (20,242 instances and 47,236 features; available on LIBSVM data sets), the cross-validation time is significantly reduced by using LIBLINEAR: LIBSVMではなくLIBLINEARを使用するとき 非線形マッピングが有無に関わらず、近似したパフォーマンスが得られる巨大なデータがあります。カーネルを利用しない場合、線形分類器を通じて非常に大きなデータセットを高速に訓練できます。ドキュメント分類はそのアプローチの一つです。下記のサンプル (20,242インスタンスと47,236の特徴量; LIBSVMで利用可能なデータセット) において、LIBLINEARを使用することで交差検定の時間を大幅に減らしています。
(注:345.569sが2.944sと170倍の性能をたたき出しています!!)

とあります。

まず、学習データについて。
libsvm.clr、というかlibsvmは、学習データをテキスト形式で用意します。
各学習対象データを整数または小数のベクトルで表現します。
例えば、

1
0.334343 0.7080 -23222 0 54544

というデータがある場合、

1
1:0.334343 2:0.7080 3:-23222 5:54544

と表現します。
libsvmにおいて、0という値は無視することができます。
そのために、ベクトル内の各値の先頭に1から始める要素番号を付与することで、0をスキップしたことを認識することができます。
1行1データとして学習データを生成します。
そして、大事なことですが、labelは整数または小数で指定します。さもなくばクラッシュします。文字列なんか使えません。

今回のサンプルでは、テストデータの生成も含んでいます。
内容は、

  • 0以上10未満の小数を 0 から 9 の整数に分類

というもの。
例えば、0.5455は0、8.9843は8というような感じです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using LibSvm;

namespace SupportVectorMachineTest
{

internal class Program
{

private static void Main()
{
// ランダムにデータを作成
var r = new Random();
const int trainCount = 500;
const int testCount = 100;

var temp = Path.GetTempPath();
var trainDic = new StringBuilder();
var testDic = new StringBuilder();
var testDicAns = new List<int>();

for (var l = 0; l < 10; l++)
{
for (var i = 0; i < trainCount; i++)
{
// 0 以上 10未満の小数を生成
var v = r.NextDouble() + l;
trainDic.AppendLine($"{l} 1:{v}");
}
for (var i = 0; i < testCount; i++)
{
// 0 以上 10未満の小数を生成
var v = r.NextDouble() + l;
testDic.AppendLine($"{l} 1:{v}");
testDicAns.Add(l);
}
}

var tempTrainPath = Path.Combine(temp, "train");
var tempTestPath = Path.Combine(temp, "test");

using (var fs = new FileStream(tempTrainPath, FileMode.Create, FileAccess.Write, FileShare.Write))
using (var sw = new StreamWriter(fs, Encoding.ASCII))
sw.Write(trainDic.ToString());

using (var fs = new FileStream(tempTestPath, FileMode.Create, FileAccess.Write, FileShare.Write))
using (var sw = new StreamWriter(fs, Encoding.ASCII))
sw.Write(testDic.ToString());

// データセットの読み込み
var train = Svm.ReadProblem(tempTrainPath);
var test = Svm.ReadProblem(tempTestPath);

// パラメータの設定
var param = new Parameter();
param.SvmType = SvmType.C_SVC;
param.KernelType = KernelType.RBF;
param.Gamma = 0.05d;
param.C = 5d;
param.CacheSize = 100;
param.Degree = 3;
param.Coef0 = 0;
param.Nu = 0.5;
param.Eps = 1e-3;
param.p = 0.1;
param.Shrinking = 1;
param.Probability = 0;
param.WeightLabel = new int[0];
param.Weight = new double[0];

var message = Svm.CheckParameter(train, param);
if (!string.IsNullOrWhiteSpace(message))
{
Console.WriteLine($"Error: {message} for train problem");
return;
}

message = Svm.CheckParameter(test, param);
if (!string.IsNullOrWhiteSpace(message))
{
Console.WriteLine($"Error: {message} for test problem");
return;
}

// 学習データとパラメータから学習
var model = Svm.Train(train, param);

var correct = 0;
var total = 0;
for (var i = 0; i < test.Count; i++)
{
// 学習データのベクトル
var x = test.x[i];

// 分類結果 (ラベルが返ってくる)
var ret1 = (int)Svm.Predict(model, x);
if (ret1 == testDicAns[i])
correct++;

total++;
}

Console.WriteLine($"Accuracy: {correct / (double)total * 100}%");
}

}

}

ソースを見てもそこまで難しいことはしていません。
そして、実行すると下記のような出力を行います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
D:\Works\OpenSource\Demo\SVM1\SupportVectorMachineTest\bin\Release> .\SupportVectorMachineTest.exe
*
optimization finished, #iter = 84
nu = 0.166000
obj = -636.290437, rho = 0.028434
nSV = 166, nBSV = 166
*
.
.
.
optimization finished, #iter = 79
nu = 0.156000
obj = -580.913665, rho = 0.001248
nSV = 156, nBSV = 156
Total nSV = 1442
Accuracy: 99.8%

10クラスで学習データが5000、テストデータが1000で、毎回99.0%以上の精度を叩き出します。
100%で欲しかったところですが、まぁまぁでしょう。

Conclusion

簡単にSVMを試すことができて良い感じではあります。
ただ、機械学習系のライブラリは、ここ最近Pythonのが手軽な感じが否定できません。
もう少し日本語のドキュメントやC#のライブラリが充実してくれると良いのですが….

Source Code

https://github.com/takuya-takeuchi/Demo/tree/master/MachineLearning/SVM/SVM1