A certain engineer "COMPLEX"

.NETで機械学習を試してみる LibLinear.Net編 第1回

Introduction


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

LibLinearDotNet - .NET wrapper for LIBLINEAR written in C#

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

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

めぼしいのはこれ位でした。

LibSvm.Netと同じく、

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

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

LibLinearDotNet - .NET wrapper for LIBLINEAR written in C#

Get Started


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

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

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

Sample



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);
}

}

}

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

  • https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/multiclass/pendigits
  • https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/multiclass/pendigits.t

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

引数として、

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

を指定できます。

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

分類


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

回帰


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

コメントを残す

メールアドレスが公開されることはありません。

%d人のブロガーが「いいね」をつけました。