Introduction

直前の記事であろうことか説明とサンプルで使っているライブラリを間違えるというドジを踏みました。
謹んでお詫びします。

で、**Support Vector Machine (SVM)**のライブラリをたくさん調べたのですが、以前話したようにそこまで多くありません。
なので、自分で作ってみました。

本当は、LibSvm.Net て名前で登録したかったんですが、既に存在するために泣く泣くこうなりました。
まぁ、githubのリポジトリのurlもこうだから、悪くないと言えば悪くないのですが…

ところで、世の中に存在する.NETのSVMライブラリのほとんどが LIBSVM のラッパーであるのと同じく、自分で作ったのもラッパーです。
もう少し違いを説明します。

  • Accord.NET
    • 有名どころです。SVM以外の機械学習も可能です。SVMは独自の実装の模様。ただ、LIBSVMのモデルファイルを読めたりします。
  • KMLib
    • GPUを使える模様。が、最終更新が5年前。CUDA 3.0…
  • LibSVMsharp
    • 最新のLIBSVMに対応。libsvm.dllを直接読み込みます。
  • libsvm.net
    • 最終更新が3年前。また、Javaのラッパー IKVM を経由しているため速度面で不利。
  • libsvm.clr
    • C++/CLIでLIBSVMを再ビルドして.NETから呼べるようにしています。

この中で立ち位置が近いのは、

  • LibSVMsharp
  • libsvm.net
  • libsvm.clr

です。
LibSvm.Netとの違いを説明すると、

  • LibSVMsharp
    • LibSVMsharpは、Predict関数を呼ぶ度にモデルファイルをアンマネージド領域にコピーしています。なので、何度も予測処理をかけたりする場合は、LibSvm.Netは効率よく動作します。
    • 一応、何度も呼び出さないようにする方法もありますが、ポインタを受け渡しするという開発者泣かせの方式。
  • libsvm.net
    • Javaなんか経由しません。LibSvm.Netは100%C#でP/Invokeで直接LIBSVMを呼び出します。
  • libsvm.clr
    • C++/CLIなので、LinuxやMacで使えません。LibSvm.Netは100%C#の.NETStandard準拠です。他の依存ライブラリもありません。Linuxで動くことも確認しています。

何より、XMLコメントほぼ全て英語と日本語で実装していますので、IntelliSenseがバリバリ効きます!!

使い勝手は他とどっこいどっこいですが、前回と同じサンプルをLibSvm.Netで実装したものを持ってきました。

Get Started

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

1.NugetからLibSvm.Netのインストール
2.LIBSVMのソースをダウンロード
3.LIBSVMのソースをビルド

ちょっと面倒ですが、どれも手順はしっかりしています。
LIBSVMのビルド手順はLibSvm.NetのWikiでも説明していますので参考にしてください。

Sample

前回と同じですが、LibSvm.Netで再実装したソースです。
というか、LibSvm.Netのリポジトリにあるサンプルです。

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
112
113
114
115
116
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using LibSvmDotNet;

namespace Classification10
{

internal class Program
{

private static void Main()
{
// Create random data
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++)
{
// Create decimal value 0 or greater but less than 10
var v = r.NextDouble() + l;
trainDic.AppendLine($"{l} 1:{v}");
}
for (var i = 0; i < testCount; i++)
{
// Create decimal value 0 or greater but less than 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());

// Load training data and test data set
using (var train = Problem.FromFile(tempTrainPath))
using (var test = Problem.FromFile(tempTestPath))
{
// Configure parameter
var param = new Parameter
{
SvmType = SvmType.CSVC,
KernelType = KernelType.RBF,
Gamma = 0.05d,
C = 5d,
CacheSize = 100,
Degree = 3,
Coef0 = 0,
Nu = 0.5,
Epsilon = 1e-3,
P = 0.1,
Shrinking = true,
Probability = false,
WeightLabel = new int[0],
Weight = new double[0]
};

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

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

// Train training data
using (var model = LibSvm.Train(train, param))
{
var correct = 0;
var total = 0;
var x = test.X;
for (var i = 0; i < test.Length; i++)
{
// Get vector from test data
var array = x[i];

// Get classification result (returns label)
var ret1 = (int)LibSvm.Predict(model, array);
if (ret1 == testDicAns[i])
correct++;

total++;
}

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

}

}

前回と違い、ModelとProblemがIDisposableな事くらいですかね、違いは。
ちなみに、libsvm.net、libsvm.clrはProblem、Model、Parameter、Nodeの全てが構造体です。
対して、LibSvm.NetはNodeだけが構造体です。古い記事ですが、.NETにおける構造体とクラスの使い分けの基準となるサイズの分水嶺は16バイトです。

Nodeは16バイトで境界値の真上ですが、Modelは184バイト、Paramterは104バイトです。
Modelファイルは予測処理を行う場合、頻繁に受け渡しする値です。libsvm.clrとか大丈夫?

実行すると下記のような出力を行います。ここも同じですね。
強いて言うなら、.NET Coreのコンソールになっているくらいです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
D:\Works\OpenSource\LibSvmDotNet\example\Classification10> dotnet run -c Release
*
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 = -581.389855, rho = -0.017062
nSV = 156, nBSV = 156
Total nSV = 1406
Accuracy: 99.5%

Conclusion

正直車輪の再発明な気がしないでもないですが、コメントを書くに当たりSVMの資料を漁るに漁ったので単語の意味とか朧気だったのが、ある程度わかるようになっています。
日本語に直すの大変ですよ、本当に。

Source Code

https://github.com/takuya-takeuchi/LibSvmDotNet/tree/master/example/Classification10