Introduction

以前、LIBSVMという台湾生まれのSVMライブラリのC#ラッパーを作成しました。
今回、同じ台湾生まれのLIVLINEARのC#ラッパーLibLinear.Netを作成しました。

LIVLINEARのライブラリも.NETラッパーは少ない感じです。

  • libsvm.clr
    • C++/CLIでLIBLINEARを再ビルドして.NETから呼べるようにしています。

めぼしいのはこれ位でした。
LibSvm.Netと同じく、

  • XMLコメントほぼ全て英語と日本語で実装していますので、IntelliSenseがバリバリ
  • 100%C#の.NETStandard準拠
  • Linuxで動く

そして、前回と同じサンプルをLibLinear.Netで実装したものを持ってきました。

Get Started

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

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

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

Sample

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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
using System;
using System.Diagnostics;
using System.Linq;
using LibLinearDotNet;
using Microsoft.Extensions.CommandLineUtils;

namespace Pendigits
{

internal class Program
{

private static void Main(string[] args)
{
var app = new CommandLineApplication(false);
app.Name = nameof(Pendigits);
app.Description = "The exsample program for pendigits";
app.HelpOption("-h|--help");

var quietArgument = app.Argument("quiet", "Suppress output of LIBLINEAR");
var outputOption = app.Option("-o|--output", "output path for trained model", CommandOptionType.SingleValue);
var solverOption = app.Option("-s|--solver", "solver", CommandOptionType.SingleValue);
var biasOption = app.Option("-b|--bias", "bias", CommandOptionType.SingleValue);

app.OnExecute(() =>
{
if (quietArgument.Value != null)
LibLinear.SetPrintFunction(null);

var biasStr = "-1";
if (biasOption.HasValue())
biasStr = biasOption.Value();

if (!double.TryParse(biasStr, out var bias))
{
app.ShowHelp();
return -1;
}

var solverStr = "1";
if (solverOption.HasValue())
solverStr = solverOption.Value();

if (!int.TryParse(solverStr, out var solver))
{
app.ShowHelp();
return -1;
}

if (Enum.GetValues(typeof(SolverType)).Cast<int>().All(s => s != solver))
{
app.ShowHelp();
return -1;
}

var sol = (SolverType)solver;

double eps = 0;
switch (sol)
{
case SolverType.L2RegularizedLogisticRegression:
case SolverType.L2RegularizedL2LossSupportVectorClassification:
eps = 0.01;
break;
case SolverType.L2RegularizedL2LossSupportVectorRegression:
eps = 0.001;
break;
case SolverType.L2RegularizedL2LossSupportVectorClassificationDual:
case SolverType.L2RegularizedL1LossSupportVectorClassificationDual:
case SolverType.MulticlassSupportVectorMachineCrammerSinger:
case SolverType.L2RegularizedLogisticRegressionDual:
eps = 0.1;
break;
case SolverType.L1RegularizedL2LossSupportVectorClassification:
case SolverType.L1RegularizedLogisticRegression:
eps = 0.01;
break;
case SolverType.L2RegularizedL2LossSupportVectorRegressionDual:
case SolverType.L2RegularizedL1LossSupportVectorRegressionDual:
eps = 0.1;
break;
}

var output = outputOption.Value();

// Load training data and test data set
using (var train = Problem.FromFile("pendigits", bias))
using (var test = Problem.FromFile("pendigits.t", bias))
{
// Configure parameter
var param = new Parameter
{
SolverType = sol,
C = 1d,
Epsilon = eps,
P = 0.1,
WeightLabel = new int[0],
Weight = new double[0]
};

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

// Train training data
var sw = new Stopwatch();
sw.Start();
using (var model = LibLinear.Train(train, param))
{
sw.Stop();

if (!string.IsNullOrWhiteSpace(output))
Model.Save(output, model);

var correct = 0;
var total = 0;
var x = test.X;
var y = test.Y;

double error = 0;
double sump = 0;
double sumt = 0;
double sumpp = 0;
double sumtt = 0;
double sumpt = 0;

for (var i = 0; i < test.Length; i++)
{
// Get vector from test data
var array = x[i];

// Get classification result (returns label)
var target = y[i];
var predict = LibLinear.Predict(model, array);
if (Math.Abs(predict - target) < double.Epsilon)
correct++;

error += (predict - target) * (predict - target);
sump += predict;
sumt += target;
sumpp += predict * predict;
sumtt += target * target;
sumpt += predict * target;

total++;
}

if (model.IsRegressionModel)
{
var mse = error / total;
var scc = (total * sumpt - sump * sumt) * (total * sumpt - sump * sumt) /
((total * sumpp - sump * sump) * (total * sumtt - sumt * sumt));
Console.WriteLine($"Mean squared error: {mse:f5}%, Squared correlation coefficient: {scc:f6}, Elapsed: {sw.ElapsedMilliseconds}ms");
}
else
{
var accuracy = correct / (double)total * 100;
Console.WriteLine($"Accuracy: {accuracy:f4}% ({correct}/{total}), Elapsed: {sw.ElapsedMilliseconds}ms");
}
}
}

return 0;
});

app.Execute(args);
}

}

}

このサンプルは、訓練データとテストデータの分類または回帰を実行します。
実行のために、

をダウンロードします。
これは手書き数字のデータセットです。MNISTと似ているのかな? 引数として、

  • ソルバー (オプション)
    • 例: -s=4
  • バイアス (オプション)
    • 例: -b=3
  • 学習済みファイルの出力先 (オプション)
    • 例: -o=trained.model

を指定できます。
分類と回帰によって実行結果が異なります。

分類

1
2
3
4
D:\\Works\\OpenSource\\LibSvmDotNet\\example\\Pendigits> dotnet run -c Release optimization finished, #iter = 1000  
WARNING: reaching max number of iterations Using -s 2 may be faster (also see FAQ)
Objective value = -10.592368 nSV = 1404 \* . . . WARNING: reaching max number of iterations Using -s 2 may be faster (also see FAQ)
Objective value = -3.640437 nSV = 592 Accuracy: 79.7027% (2788/3498), Elapsed: 1737ms

回帰

1
2
3
4
5
6
7
D:\Works\OpenSource\LibSvmDotNet\example\Pendigits> dotnet run -c Release "-s=11"
iter 1 act 1.445e+05 pre 1.623e+05 delta 1.690e+01 f 2.026e+05 |g| 1.405e+07 CG 1
cg reaches trust region boundary
iter 2 act 1.430e+04 pre 1.418e+04 delta 6.758e+01 f 5.808e+04 |g| 1.746e+06 CG 2
iter 3 act 5.313e+03 pre 5.281e+03 delta 6.758e+01 f 4.378e+04 |g| 2.849e+05 CG 9
iter 4 act 6.203e+01 pre 6.202e+01 delta 6.758e+01 f 3.847e+04 |g| 2.140e+04 CG 11
Mean squared error: 6.60231%, Squared correlation coefficient: 0.215259, Elapsed: 13ms

Conclusion

またも、正直車輪の再発明な気がしないでもないですが… LibLinearのメリットは、大規模データに対してLibSvmよりも高速に実行できるところがウリのようです。
このあたりの高速性も紹介していきたいです。

Source Code

https://github.com/takuya-takeuchi/LibLinearDotNet/tree/master/example/Pendigits