なんだか雲行きの怪しい雑記帖

ぐだぐだ日記とメモと,あと不定期更新

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

週アレ(13) LIBLINEARで高速線形SVM

週に一回アレしてアレする記事:十三回目

前回「週アレ(11) C++で始めるLibSVM」ということで,RBFもLINEARもPOLYNOMINALもカーネルに使えるSVMのライブラリとしてLibSVMについての記事でした
そこでサラッとおまけあたりで触れましたが,実はLibSVMはLINEARカーネルを用いるときでもナイーブに内部にサポートベクトルを保持して計算するので,LINEARカーネルなのに計算コストが非常に高いつくりになっています
本来LINEARカーネルであれば識別には
 「識別平面の角度を表すベクトル(入力ベクトルと同次元)」
 +「原点から識別平面までの射影軸上の距離(バイアス値:1次元)」
で事足りますね


そこでLibSVMと同じ作者らがLINEARカーネル(正確には,カーネル無し)用に最適化しているLIBLINEARというライブラリを公開しています
これを使うとLINEARカーネルでも高速に学習・予測が行えるということで,LINEARカーネルに限定できる状況下では積極的に使用すると効率的に時間が使えて幸せですね

LINEARカーネルかあるいはRBFカーネルなど他のカーネルを用いるかどちらがいいのかは議論すべきところですが,研究においては精度的な意味でRBFカーネルを使うのが通常ですし,処理速度を気にしなければならない問題にぶつかったときにLINEARカーネル使うという流れになるので,考えるより先に使ってみるという話が一番現実で多いかもしれません


ということで今回はLIBLINEARの話,LibSVMに比べてマイナーな話題

LIBLINEAR

公式ページ:LIBLINEAR -- A Library for Large Linear Classification

LINEARカーネルを用いたサポートベクトルマシン(SupportVectorMachine)を学習したり予測に使いたい時のためのライブラリ

主な特徴は次
  • 高速な学習・予測

  • 線形分類(Support Vector Classification)および線形回帰(Support Vector Regression)をサポート

  • マルチクラス分類のSVMの学習(one vs restとか※)

  • ロジスティック回帰のサポート

※LibSVMではデフォルトではone-vs-oneなマルチクラス分類が使われますが,LIBLINEARではone-vs-restなマルチクラス識別器が使われるようです
これはone-vs-oneにするとモデルが大きくなっちゃうからという話ですが,今更な気がするのでone-vs-oneなLIBLINEAR使いたい人はLIBLINEAR for One-versus-one Multi-class Classificationとかで公開されています


LibSVMと違う特徴は次
  • LINEARカーネルのみ

  • 事後確率の計算はデフォルトではロジスティック回帰のみサポート

  • 学習後のモデルからサポートベクトルとなっているノードを抽出できない



学習に必要なもの

APIなどの構造は実は非常にLibSVMと似ているため前回の記事を内容がかぶるんですが…;;
私がこの記事を書いている時点では最新バージョンは1.94です

インクルードするヘッダ

一つだけ
    // liblinear
#include <linear.h>


学習パラメータの設定

当たり前ですがLibSVMに比べて必要なパラメータは少なく,シンプルです
struct parameter
{
int solver_type;// !!識別器のタイプL2R_L2LOSS_SVC_DUALなど,ロジスティック回帰の設定もここ!!

/* these are for training only */
double eps;// 学習終了判定の閾値
double C;// !!SVMにおけるコストパラメータ!!

// マルチクラス分類の時につかう各カテゴリのウェイト値とかの指定
int nr_weight;
int *weight_label;
double* weight;

// SVRにおける収束判定の閾値
double p;
};

大抵の問題において設定すべきところは二つです
  • solver_type:識別器のタイプ
  • C:コストパラメータ

識別器のタイプは様々ありますが,特にここでは述べないことにします

学習データの用意

LIBLINEARでは学習データは「problem」構造体で表されます
気づいた方がおられるかもしれませんが,LIBLINEARではLibSVMのAPIはにおいて接頭辞「svm_」をとった名前が多いです
struct problem
{
int l;// 学習データに含まれるノード(ベクトル)の数
int n;// 学習データに含まれるベクトルの次元
double *y;// 学習ノードそれぞれのラベル(カテゴリを表す値)配列:y[0]-y[l-1]まで必要
struct feature_node **x;// 学習ノードの配列(なぜポインタのポインタなのかは後述)

// 全学習データに付加するバイアス値
double bias; /* < 0 if no bias term */
};

一つを除いてどれも割りとそのままなのですかね
ノードの数「l」,ノードのベクトルの次元「n」,学習ノードのラベル配列「y」については特にこれ以上の説明は要らないと思います
「bias」については後述します


ベクトルの表現

これに関しては完全にLibSVMと同じなため,ほぼ省略
ベクトルの一要素は「feature_node」構造体で表されます
struct feature_node
{
int index;// 次元の添字
double value;// indexの次元の値
};

添字と値とを分けることでスパースなベクトルでも効率よく表現できるという話でした

LIBLINEARを使って学習・予測する

とりあえず,前回のLibSVMのときとほぼ同様コードですが,二次元のデータを適当に作ってLINEARで学習してもらってOpenCVで可視化するサンプルをどんする
    // OpenCV使えない場合はコメントアウト
#define ENABLE_OPENCV

// liblinear:これは個人の環境によって適宜
#include "linear.h"

#ifdef ENABLE_OPENCV
// opencv
#include <opencv2/opencv.hpp>
#endif// ENABLE_OPENCV

// その他STLとか
#include <ctime>
#include <iostream>
#include <vector>
#include <memory>
#include <functional>
#include <random>
#include <utility>


// サンプルデータを表すクラス
// 二次元ベクトル:ラベル(ノードが属するクラスのカテゴリ)を持つ
class node
{
public :
node( int label, double x, double y )
: label_( label ), x_( x ), y_( y )
{
}
// member variables
int label_;
double x_, y_;
};


int main( int, char*[] )
{
using namespace std;

// サンプルデータを適当に作る
// 今回は2クラスで,いずれも正規分布に従います
std::vector< node > samples;

// それぞれのクラスで作るサンプルデータの数です
const auto kClass1NodeNum = 100;
const auto kClass2NodeNum = 100;

// それぞれのクラスのラベルです,今回はクラス1は「1」,クラス2は「-1」としました
const auto kClass1Label = 1;
const auto kClass2Label = -1;

// それぞれのクラスのx,y軸のMeanとSigmaを入れて,その正規分布に従う乱数生成器を作ります
auto class1_x_generator = std::normal_distribution<double>( 0.7, 0.2 );
auto class1_y_generator = std::normal_distribution<double>( 0.3, 0.2 );

auto class2_x_generator = std::normal_distribution<double>( 0.3, 0.2 );
auto class2_y_generator = std::normal_distribution<double>( 0.7, 0.2 );

// 実際にサンプルデータを作ります
std::mt19937 eng( static_cast<unsigned int>( time( nullptr ) ) );
for( std::size_t i=0; i<kClass1NodeNum; ++i )
{
samples.push_back( node( kClass1Label, class1_x_generator( eng ), class1_y_generator( eng ) ) );
}
for( std::size_t i=0; i<kClass2NodeNum; ++i )
{
samples.push_back( node( kClass2Label, class2_x_generator( eng ), class2_y_generator( eng ) ) );
}


// liblinearに学習してもらいます
// liblinear用の学習データを作ります
problem prob;
feature_node* prob_vec;
// 各学習データに加えるバイアス次元の値
prob.bias = -1;
// 学習ベクトルの次元
prob.n = 2 + (prob.bias>=0);
// 学習データの数
prob.l = samples.size();
// 各学習データのラベル
prob.y = new double[ prob.l ];
for( std::size_t i=0; i<samples.size(); ++i )
{
prob.y[i] = samples[i].label_;
}
// 各学習データのベクトル
/*
liblinearではlibsvmと同じくスパースな特徴量も効率的に扱えるよう,一つの学習データ(ベクトル)はfeature_nodeの配列で表されます
feature_nodeはベクトル内の一つの軸の値を保持するもので,そのベクトルの軸の番号「index」(1から始まる)と値「value」をメンバに持ちます
二次元ベクトルの場合,軸はXとYで二つなので,二次元ベクトルを表現するのにfeature_node[2]という配列が必要です
しかし,これだとベクトルの終端がどこだか分からないため(liblinearではスパースなベクトルも扱えるため),
実際には最後に”終端”を表すfeature_nodeが必要です(この”終端”を表すfeature_nodeは,indexを「-1」とすることで処理されます)
よって,二次元ベクトル一つを表現するのに必要なのはfeature_node[3]の配列です
*/
prob_vec = new feature_node[ prob.l *(prob.n+1) ];// 全てのベクトルがn次元,かつベクトルの句切れに1個必要なので,学習データ*(n+1)のfeature_nodeが必要
prob.x = new feature_node*[ prob.l ];
for( std::size_t i=0; i<prob.l; ++i )
{
prob.x[i] = prob_vec+i*(prob.n+1);
prob.x[i][0].index = 1;
prob.x[i][0].value = samples[i].x_;
prob.x[i][1].index = 2;
prob.x[i][1].value = samples[i].y_;
if ( prob.bias >= 0 )
{
// bias成分
prob.x[i][2].index = 3;
prob.x[i][2].value = prob.bias;
prob.x[i][3].index = -1;
}
else
{
prob.x[i][2].index = -1;
}
}
// 学習する識別器のパラメータ
parameter param;
param.solver_type = L2R_L2LOSS_SVC_DUAL;// ロジスティック回帰とかの設定もここ
param.C = 1;// SVMにおけるコスト:大きいほどハードマージン

// その他
param.p = 0.1;
param.eps = 1e-3;// 収束判定基準
param.nr_weight = 0;
param.weight_label = nullptr;
param.weight = nullptr;

// 学習!
cout << "Ready to train ..." << endl;
model* model = train( &prob, &param );
cout << "Finished ..." << endl;


// predit training samples
int correct_count =0, wrong_count =0;
cout << "predict training samples ..." << endl;
for( std::size_t i=0; i<samples.size(); ++i )
{
feature_node test[4];
test[0].index = 1;
test[0].value = samples[i].x_;
test[1].index = 2;
test[1].value = samples[i].y_;
if ( prob.bias >= 0 )
{
// bias成分
test[2].index = 3;
test[2].value = prob.bias;
test[3].index = -1;
}
else
{
test[2].index = -1;
}
// libsvmにpredictしてもらう
const auto kPredictedLabel = static_cast<int>( predict( model, test ) );
// 結果カウント
if ( kPredictedLabel == samples[i].label_ )
{
++correct_count;
}
else
{
++wrong_count;
}
}
cout << "done" << endl;
cout << "RESULT : correct=" << correct_count << " : wrong=" << wrong_count << endl;
cout << "Accuracy[%]=" << ( static_cast<double>(correct_count)/static_cast<double>(correct_count+wrong_count)*100.0 ) << endl;


#ifdef ENABLE_OPENCV
// 表示
// 表示するサイズ
const auto kHeight = 640;
const auto kWidth = 640;
// 真っ白なイメージを作る
cv::Mat img( kHeight, kWidth, CV_8UC3 );
img = cv::Scalar( 255, 255, 255 );
// 全てのピクセルで判定する
cout << "Rendering result ..." << endl;
for( std::size_t y=0; y<kHeight; ++y ) for( std::size_t x=0; x<kWidth; ++x )
{
// 現在のピクセルに対応するベクトル作る
feature_node test[4];
test[0].index = 1;
test[0].value = static_cast<double>(x) / static_cast<double>( kWidth );
test[1].index = 2;
test[1].value = static_cast<double>(y) / static_cast<double>( kHeight );
test[2].index = -1;
if ( prob.bias >= 0 )
{
// bias成分
test[2].index = 3;
test[2].value = prob.bias;
test[3].index = -1;
}
else
{
test[2].index = -1;
}
// libsvmにpredictしてもらう
const auto kPredictedLabel = static_cast<int>( predict( model, test ) );
// 結果に依って色分けする
if ( kPredictedLabel == kClass1Label )
{
img.at<cv::Vec3b>( y, x ) = cv::Vec3b( 128, 128, 255 );
}
else
{
img.at<cv::Vec3b>( y, x ) = cv::Vec3b( 255, 128, 128 );
}
}
cout << "Rendering background done ..." << endl;
// サンプルデータを描画する
const auto kClass1Color = cv::Scalar( 0, 0, 255 );
const auto kClass2Color = cv::Scalar( 255, 0, 0 );
for( std::size_t i=0; i<samples.size(); ++i )
{
const auto kColor = ( samples[i].label_==kClass1Label ? kClass1Color : kClass2Color );
cv::circle( img, cv::Point(samples[i].x_*kWidth,samples[i].y_*kHeight), 2, kColor, -1 );
}
cout << "Rendering sample data done ..." << endl;

// 表示してまつ
cv::imshow( "result", img );
cv::waitKey();
#endif// ENABLE_OPENCV

// 後始末
free_and_destroy_model( &model );
delete[] prob.y;
delete[] prob.x;
delete[] prob_vec;

return 0;
}

さて,上記のコードを実行してみるとおおよそ良好な結果が(LibSVMに比べて)高速に得られます
linear_nobias.png
画面の左下と右上に違うクラスのクラスタがあり,その中間を横切るように識別平面が引かれています
画面内の左上が(0,0),右下が(1,1)に対応してます


さて,では次はちょっと違った学習データセットを入れてみましょう

        // それぞれのクラスのx,y軸のMeanとSigmaを入れて,その正規分布に従う乱数生成器を作ります
auto class1_x_generator = std::normal_distribution<double>( 0.7, 0.2 );
auto class1_y_generator = std::normal_distribution<double>( 0.3, 0.2 );

auto class2_x_generator = std::normal_distribution<double>( 0.3, 0.2 );
auto class2_y_generator = std::normal_distribution<double>( 0.7, 0.2 );

の部分を次のように書き換えてください
        // それぞれのクラスのx,y軸のMeanとSigmaを入れて,その正規分布に従う乱数生成器を作ります
auto class1_x_generator = std::normal_distribution<double>( 0.3, 0.2 );
auto class1_y_generator = std::normal_distribution<double>( 0.3, 0.2 );

auto class2_x_generator = std::normal_distribution<double>( 0.7, 0.2 );
auto class2_y_generator = std::normal_distribution<double>( 0.7, 0.2 );

結果は次
linear_nobias_hardprob.png
なんと非常によろしくない結果がでてきました
Accuracyも先ほどに比べ非常に低くなっています

「学習データの分布を【左下と右上】から【左上と右下】に変えただけなのに…」

実はこの辺で学習データの際に述べた「bias」が関連してきます

バイアスとなる次元

LIBLINEARではバイアスを使わない場合(bias<0に設定した場合),求めるモデルは「識別平面の角度」のみとなります
つまり,「識別平面は必ず原点を通る」ことになります

これを避けるため,「原点から識別平面までの距離(垂線の長さ)」を意図的に付加させる必要があります
これがバイアスとなる次元で,具体的には「全ての学習ベクトルに一定の値を持つ次元を加える」操作に対応します
コンピュータグラフィックスなどで2次元や3次元でのベクトルを扱う際,扱う次元より一つ多い次元の行列を使うことがあります
これは「回転」や「スケーリング」に加え「並行移動」を許すための次元拡張ですが,今回のバイアス値はそれと同じ意味を持ちます


この「全ての学習ベクトルに加える一定の値」が学習データにおける「bias」であり,「bias」が0以上の時は学習データの”見せかけの”次元は「<学習ベクトルの次元>+1」となります
 (この辺の処理が「prob.n = 2 + (prob.bias>=0);」ら辺です)

ただ,バイアスの次元も使おうとすると学習の時も予測の時もそのバイアス値が必要になるため注意が必要です
そういう意味でLIBLINEARはちょっと注意が必要ですね
liblinearに付属しているツール「train」では「-B 1」を指定しないとバイアスが有効になりません
デフォルトではバイアスは無効になっているため,「やっぱりLINEARカーネルではだめだ!」と諦めてしまう人が多いような


さて,実際にバイアスの次元を付加した時,上記の問題が本当に解消されるのかどうかみてみましょう
        // 各学習データに加えるバイアス次元の値
prob.bias = -1;

となっているところを
        // 各学習データに加えるバイアス次元の値
prob.bias = 1;

と書き換えてください

そうすると次のような図になります
linear_bias_hardprob.png
無事まともそうな識別平面が得られました

終わりに

というわけでLINEARにカーネルの場合に限り高速に学習・予測ができるSVMのライブラリLIBLINEARの話でした

対象となる学習データに依るのですが,RBFカーネルを使った時とLINEARカーネルを使った時とで精度が大きく違う場合と,精度があまり変わらない場合があります
後者の場合は積極的にLIBLINEARとかのライブラリを使えば,時間が効率的に使えるのではないでしょうか
RBFカーネルを使うよりは,特徴量を工夫するなど線形識別器でも十分な性能にできるような工夫もまた良いと思いますよ!


おまけ1:LibSVMと学習・予測時間を比べてみる

「高速!高速!」と言っておきながらそれの証左が全くないので,おまけに突っ込んでおきました
「libsvm」と「liblinear」を使った同じ学習データに対する学習・予測の違いです

    // liblinear
#include "linear.h"

// libsvm
#include "svm.h"

// その他STLとか
#include <ctime>
#include <iostream>
#include <vector>
#include <memory>
#include <functional>
#include <random>
#include <utility>


// 時間計測系
#ifdef _MSC_VER
#include <Windows.h>
class eclock
{
public :
eclock()
{
QueryPerformanceFrequency( &freq_ );
QueryPerformanceCounter( &start_ );
}
double msec() const
{
LARGE_INTEGER current;
QueryPerformanceCounter( &current );
return ( static_cast<double>( current.QuadPart-start_.QuadPart ) / static_cast<double>( freq_.QuadPart ) * 1000.0 );
}
private :
LARGE_INTEGER freq_, start_;
};
#else
// こっちはコンパイルしかチェックしてないので動かないかも;;
#include <unistd.h>
#include <sys/time.h>
class eclock
{
public :
static double getms()
{
struct timeval timeval;
gettimeofday( &timeval, NULL );
return static_cast< double >( timeval.tv_sec )*1000.0 + static_cast<double>( timeval.tv_usec )/1000.0;
}
eclock()
: s_( getms() )
{
}
double msec() const
{
const auto kC = getms();
return kC -s_;
}
private :
double s_;
};
#endif


// サンプルデータを表すクラス
// 二次元ベクトル:ラベル(ノードが属するクラスのカテゴリ)を持つ
class node
{
public :
node( int label, double x, double y )
: label_( label ), x_( x ), y_( y )
{
}
// member variables
int label_;
double x_, y_;
};


int main( int, char*[] )
{
using namespace std;

// サンプルデータを適当に作る
// 今回は2クラスで,いずれも正規分布に従います
std::vector< node > samples;

// それぞれのクラスで作るサンプルデータの数です
const auto kClass1NodeNum = 10000;
const auto kClass2NodeNum = 10000;

// それぞれのクラスのラベルです,今回はクラス1は「1」,クラス2は「-1」としました
const auto kClass1Label = 1;
const auto kClass2Label = -1;

// それぞれのクラスのx,y軸のMeanとSigmaを入れて,その正規分布に従う乱数生成器を作ります
auto class1_x_generator = std::normal_distribution<double>( 0.3, 0.2 );
auto class1_y_generator = std::normal_distribution<double>( 0.3, 0.2 );

auto class2_x_generator = std::normal_distribution<double>( 0.7, 0.2 );
auto class2_y_generator = std::normal_distribution<double>( 0.7, 0.2 );

// 実際にサンプルデータを作ります
std::mt19937 eng( static_cast<unsigned int>( time( nullptr ) ) );
for( std::size_t i=0; i<kClass1NodeNum; ++i )
{
samples.push_back( node( kClass1Label, class1_x_generator( eng ), class1_y_generator( eng ) ) );
}
for( std::size_t i=0; i<kClass2NodeNum; ++i )
{
samples.push_back( node( kClass2Label, class2_x_generator( eng ), class2_y_generator( eng ) ) );
}


// liblinearに学習してもらいます
// liblinear用の学習データを作ります
problem prob;
feature_node* prob_vec;
prob.bias = 1;
prob.n = 2 + (prob.bias>=0);
prob.l = samples.size();
prob.y = new double[ prob.l ];
for( std::size_t i=0; i<samples.size(); ++i )
{
prob.y[i] = samples[i].label_;
}
prob_vec = new feature_node[ prob.l *(prob.n+1) ];
prob.x = new feature_node*[ prob.l ];
for( std::size_t i=0; i<prob.l; ++i )
{
prob.x[i] = prob_vec+i*(prob.n+1);
prob.x[i][0].index = 1;
prob.x[i][0].value = samples[i].x_;
prob.x[i][1].index = 2;
prob.x[i][1].value = samples[i].y_;
if ( prob.bias >= 0 )
{
// bias成分
prob.x[i][2].index = 3;
prob.x[i][2].value = prob.bias;
prob.x[i][3].index = -1;
}
else
{
prob.x[i][2].index = -1;
}
}
// 学習する識別器のパラメータ
parameter param;
param.solver_type = L2R_L2LOSS_SVC_DUAL;// ロジスティック回帰とかの設定もここ
param.C = 1;// SVMにおけるコスト:大きいほどハードマージン

// その他
param.p = 0.1;
param.eps = 1e-3;// 収束判定基準
param.nr_weight = 0;
param.weight_label = nullptr;
param.weight = nullptr;

// 学習!
model* linear_model;
cout << "liblinear : Ready to train ..." << endl;
{
eclock e;
linear_model = train( &prob, &param );
cout << "elapsed : " << e.msec() << "msec" << endl;
}
cout << "liblinear : Finished ..." << endl;


// liblsvmに学習してもらいます
// libsvm用の学習データを作ります
svm_problem svmprob;
svm_node* svmprob_vec;
svmprob.l = samples.size();
svmprob.y = new double[ svmprob.l ];
for( std::size_t i=0; i<samples.size(); ++i )
{
svmprob.y[i] = samples[i].label_;
}
svmprob_vec = new svm_node[ svmprob.l *3 ];
svmprob.x = new svm_node*[ svmprob.l ];
for( std::size_t i=0; i<prob.l; ++i )
{
svmprob.x[i] = svmprob_vec+i*3;
svmprob.x[i][0].index = 1;
svmprob.x[i][0].value = samples[i].x_;
svmprob.x[i][1].index = 2;
svmprob.x[i][1].value = samples[i].y_;
svmprob.x[i][2].index = -1;
}
// 学習する識別器のパラメータ
svm_parameter svmparam;
svmparam.svm_type = C_SVC;// SVCとかSVRとか
svmparam.kernel_type = LINEAR;// RBF(放射基底関数)カーネルとかLINEAR(線形)カーネルとかPOLY(多項式)カーネルとか
svmparam.C = 1;// SVMにおけるコスト:大きいほどハードマージン

// その他
svmparam.coef0 = 0;
svmparam.cache_size = 100;
svmparam.eps = 1e-3;
svmparam.shrinking = 1;
svmparam.probability = 0;

// その他状況(svm_typeやカーネル)に応じて必要なもの
svmparam.degree = 3;
svmparam.nu = 0.5;
svmparam.p = 0.1;
svmparam.nr_weight = 0;
svmparam.weight_label = nullptr;
svmparam.weight = nullptr;

// 学習!
svm_model* svmmodel;
cout << "libsvm : Ready to train ..." << endl;
{
eclock e;
svmmodel = svm_train( &svmprob, &svmparam );
cout << "elapsed : " << e.msec() << "msec" << endl;
}
cout << "libsvm : Finished ..." << endl;


// predit training samples
{
// liblinear
int correct_count =0, wrong_count =0;
cout << "liblinear : predict training samples ..." << endl;
{
eclock e;
for( int i=0; i<prob.l; ++i )
{
// liblinearにpredictしてもらう
const auto kPredictedLabel = static_cast<int>( predict( linear_model, prob.x[i] ) );
// 結果カウント
if ( kPredictedLabel == static_cast<int>( prob.y[i] ) )
{
++correct_count;
}
else
{
++wrong_count;
}
}
cout << "elapsed : " << e.msec() << "msec" << endl;
}
cout << "done" << endl;
cout << "liblinear : RESULT : correct=" << correct_count << " : wrong=" << wrong_count << endl;
cout << "liblinear : Accuracy[%]=" << ( static_cast<double>(correct_count)/static_cast<double>(correct_count+wrong_count)*100.0 ) << endl;
}
{
// libsvm
int correct_count =0, wrong_count =0;
cout << "libsvm : predict training samples ..." << endl;
{
eclock e;
for( int i=0; i<svmprob.l; ++i )
{
// libsvmにpredictしてもらう
const auto kPredictedLabel = static_cast<int>( svm_predict( svmmodel, svmprob.x[i] ) );
// 結果カウント
if ( kPredictedLabel == static_cast<int>( svmprob.y[i] ) )
{
++correct_count;
}
else
{
++wrong_count;
}
}
cout << "elapsed : " << e.msec() << "msec" << endl;
}
cout << "done" << endl;
cout << "libsvm : RESULT : correct=" << correct_count << " : wrong=" << wrong_count << endl;
cout << "libsvm : Accuracy[%]=" << ( static_cast<double>(correct_count)/static_cast<double>(correct_count+wrong_count)*100.0 ) << endl;
}


// 後始末
free_and_destroy_model( &linear_model );
delete[] prob.y;
delete[] prob.x;
delete[] prob_vec;

svm_free_and_destroy_model( &svmmodel );
delete[] svmprob.y;
delete[] svmprob.x;
delete[] svmprob_vec;

return 0;
}


私の環境だとランダムで回したらこんな感じでした:

liblinear: 学習=84.9922msec : 予測=33.872msec : Accuracy=92.15%
libsvm: 学習=4212.59msec : 予測=2984.13msec : Accuracy=92.175%

※私の環境:Win7x64 MSVS2013 -O2 IntelCore-i5-1.8GHz

精度はほぼ変わらないですが速度に超えられない壁ぐらいの大きな違いがありますね
関連記事
スポンサーサイト

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://fe0km.blog.fc2.com/tb.php/102-cc96c590
この記事にトラックバックする(FC2ブログユーザー)

FC2Ad

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。