DockerでC++のsvmの開発環境を構築する(2)

#はじめに
dockerでの開発環境の準備は，[前回](https://qiita.com/sey323/items/40d37d93a66ac76619f1)の記事を参考に．

今回の記事では，libsvmの学習とテストを行うC++のプログラムの解説を行います．


libsvmを利用するときの，ソースコードは以下のリンクに
(https://github.com/sey323/svm-sample/blob/master/src/libsvm_sample.cpp)

liblinearもほとんどlibsvmと同じ使い方なので，そちらは記事にするか未定です．
liblinearは線形svmに強いらしい...
#学習データの準備
学習データは`TRAIN_DATA`構造体の配列に格納する．ライブラリの制約で，教師ラベルと素性データはdouble型らしい．

```./src/libsvm_sample.cpp
//学習データ保存用の構造体
typedef struct TRAIN_DATA{
  int train_num;//学習に用いるデータ数
  double label;//教師用ラベル
  int data_num;//学習に用いる素性データ数
  double *data;//素性データの配列
}TRAIN_DATA;
```

サンプルの学習データは[こちら](https://github.com/sey323/svm-sample/blob/master/data/data.train.csv)にある．１列目が教師ラベル，２列目以降が学習に用いる素性データのベクトル，いわば特徴量と呼ばれるもの．
今回は適当に，ラベル0のものは0~0.5まで，ラベル1のものを0.5~1の乱数で素性データを生成した．
#学習
学習は，libsvm_data_makeクラスで行う．`TRAIN_DATA`を渡すことで，それをsvmで教師あり学習したモデルが返される．

```./src/libsvm_sample.cpp
/*
*特徴量から学習データの作成
*/
svm_model *libsvm_data_make( const struct TRAIN_DATA *t_data ){
  svm_problem prob;
  svm_node* prob_vec;

  //素性データ数
  int train_num = t_data[0].train_num;
  int data_num = t_data[0].data_num;

  cout << "learning datas :" << train_num << endl;

  //学習データの数
  prob.l = train_num;
  //学習データの数だけラベルを用意
  prob.x = new svm_node *[ prob.l ];
  //各学習データのラベル
  prob.y = new double[ prob.l ];
  prob_vec = new svm_node[  prob.l *  ( data_num + 1)  ];// 学習データの枚数 * 素性データ数　+ 特徴点のクトルの終端コード)
  for( int i = 0 ; i < prob.l; i++){
    //ラベルをふる
    prob.y[ i ] = t_data[i].label;
    prob.x[ i ] = prob_vec + i * ( data_num + 1);

    //データの数だけ繰り返す
    for( int j = 0; j < data_num; j++){
      prob.x[ i ][ j ].index = j;
      prob.x[ i ][ j ].value = t_data[ i ].data[ j ];
    }
    //末尾は-1
    prob.x[ i ][ data_num ].index = -1;
  }

  // 学習する識別器のパラメータ
  svm_parameter param;
  param.svm_type =  C_SVC;// SVCとかSVRとか
  param.kernel_type = LINEAR;// RBF（放射基底関数）カーネルとかLINEAR（線形）カーネルとかPOLY（多項式）カーネルとか
  param.C = 8096;// SVMにおけるコスト：大きいほどハードマージン
  param.gamma = 0.1;// カーネルとかで使われるパラメータ

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

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

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

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

  return model;
}
```
libsvmでは`svm_train( &prob, &param )`のように，データとパラメータのポインタを渡して`svm_train`クラスで学習するらしい．

`svm_paramerter`の構造体でsvmのパラメータを調整するようだが，リファレンスにある初期値の通りにしてある．こちらの調整次第では学習効率が大きく変わるので，データに応じて適時調整してもらいたい．

# 検定
検定は`libsvm_predict`で行う．引数に，分類をしたいデータを格納した`TRAIN_DATA`構造体と，上で学習した学習済みモデルの両方のポインタをとる．

返り値は検定した結果のラベル(int)である．

```./src/libsvm_sample.cpp
/*
*libsvmによる検定
*/
int libsvm_predict( const struct TRAIN_DATA *t_data , const svm_model *model ){
  int data_num = t_data->data_num;
  svm_node test[ data_num ];

  cout << "predict training samples ..." << endl;

  for( int i = 0 ; i < data_num; i++){
    test[i].index = i;
    test[i].value = t_data->data[i];
  }
  test[data_num].index = -1;

  // libsvmによるpredict
  const auto result = static_cast<int>( svm_predict( model, test ) );

  return result;
}

```

#最後に
python全盛期の昨今，c++でsvmをやる人はほとんどおらず，資料がほとんどない状態で動かすのはかなり苦戦しました．(c++もあんまり理解しきれてない( ;∀;))

同様の悩みを抱えている人に，少しでも手助けになれればと思います．