LibSVM

LibSVMとは

簡単な用語の説明

  • 学習データ
    • 学習させるためのテキストデータ
  • 評価データ
    • システムの評価に使うテキストデータ
  • クラス/正例/負例 (Positive/Negative)
    • 何のデータが正解で,何のデータが不正解なのか
    • 基本的には何の数値を使ってもいいが,正例を+1,負例を-1 or 0とすることが多い
  • 素性(値)
    • データとなる特徴量
  • スケーリング
    • 正規化.素性値の最大値と最小値を統一させることで各素性に対する重みが変わることを防ぐ
    • 例: 「年齢」「身長(cm)」「体重(kg)」「一日の平均摂取kcal」の4つをもとに「健康か否か」の二値分類を行う場合. スケーリングを行わないと,値変動の大きい素性が判定を支配してしまう.例の4つの素性の中では,摂取kcalが最も人によって変動する素性値であると考えられる(1500kcalの人もいれば10000kcalの人もいる).したがってSVMは摂取kcalを常に信頼して判定を行う可能性がある. スケーリングを行い,各素性値の最大値と最小値を用いて正規化を行うことにより,すべての素性値を平等に用いて判定を行ってくれる.よって,スケーリングは精度向上のためには欠かせない操作となる.
  • モデルファイル
    • 学習データをトレーニングすると生成されるファイル
    • このファイルを用いて評価を行う
  • クローズド/オープンテスト
    • 学習データの全部,もしくは一部をそのまま評価データとして使う評価方法をクローズドテストといい,対して,学習データには用いていないデータ(未知のデータ)を評価データとして使う評価方法をオープンテストという.
    • 当たり前だが,クローズドテストの結果の方が高い精度となる
    • データ準備の都合上,オープンテストを行うことが困難な場合などには交差検定(Cross Validation)を用いることが多い

簡単な使い方

学習データを用意する(場合によっては評価データも)

  • データの単位は一行ごととなる
  • 書式:
    クラス_1:値_2:値_3:値_4:値...
    (_=スペース or Tab)
  • +1 1:0.97 2:1.4 3:1 4:2 5:1.3
    -1 1:-1.2 2:0.1 3:0.1 4:-2 5:1.1
    ・
    ・
    ・
    • クラスが判定させたいブール値となる.基本的には2値だが,LibSVMは多値分類もできる
    • クラス以降に素性値を入力していく.素性番号は飛び飛びでもOKだが自然数でなければならない
    • SVMを活かすも殺すも素性次第

svm-scaleで各素性値をスケーリングする

  • 学習データと評価データが同じ場合の例(Cross Validation を使う際も含む)
    $ ./svm-scale data.train > data.scale
  • 学習データと評価データが違う場合の例
    $ ./svm-scale -s data.minmax data.train > data.train.scale
    $ ./svm-scale -r data.minmax data.measure > data.measure.scale
    (学習データと評価データのスケールを同じにするため,学習データの最大値と最小値をファイルに保存することで評価データの方にも同じスケーリングを施している)

svm-trainで学習する

  • モデルファイルの作成
    $ ./svm-train data.train.scale
    • これでdata.train.scale.modelという名前のモデルファイルができる
  • 10-Fold Cross Validationを行う
    • 学習データの量が少ないとき,評価データを用意していないとき等に行う評価方法
    • 学習データを10個に分割して,1個を評価データ,残りの9個を学習データとして評価する
    • これを分割数(10回)行い,その平均を結果とする
      crossvalidation.png
      $ ./svm-train -v 10 data.train.scale

svm-predictで評価する

  • 評価データを用意しているときは,Cross Validationを行う必要はない
  • 作成したモデルファイルを利用し実際に予測を行う
    $ ./svm-predict data.measure.scale data.train.scale.model > result.txt
    • result.txt に書きだされた結果が実際に予測された結果となる

バランスを整える

  • 正例と負例の数のバランスがあまりにも違うと,データによっては極端な判定となってしまうことがある(すべて負例に予測される,など).=データの不均衡,過学習
  • 次のような方法で不均衡をなくすことができる
    • バランスがとれるように学習データを増やす
    •   〃            減らす(=アンダーサンプリング)
    • 学習時にクラスごとの重みを調整する(-wN オプション)

実際に判別に使ってみる

  • テーマ: 「気象庁の過去の天気からその日が晴れたかどうかを予測」 [#uf423d42]
  • 「あれ?今日の天気って実際どうだったっけ?」という疑問に応えるための枠組み(いらん)
  • データの提供元:http://www.data.jma.go.jp/gmd/risk/obsdl/
  • 2008年4月1日~2014年3月31日まで,札幌市のデータを学習データとして利用
  • 素性は適当に選択した4種類: 平均気温,降水量合計,降雪量合計,平均風速

ダウンロードしたcsvデータの整形

  • 判定に必要ない「均質情報」や「品質情報」「現象なし情報」を事前にExcelなどで取り除く
  • 以下のスクリプト (test.pl) を回して学習データを作成
    test.pl data.csv > data.train
    #! perl
    
    # 気象庁からダウンロードしたCSVファイルを編集して
    # 学習データにする
    
    use strict;
    use warnings;
    
    # クラスにしたい列番号 (0~)
    my $class_num = 5;
    
    # 天気の列番号(あれば)
    my @tenki_id = (5);
    
    ########################
    
    my @data;
    my %convlist = (
      '晴' => '+1', '曇' => '-1', '雨' => '-1', '雪' => '-1'
      ); # 天気から数値に変換する用
    
    open(FILE, "<", shift) or die("file open error");
    
    while(<FILE>) {
      next if($. <= 5);
      push(@data, [split(/,/)]); # コンマで分割した無名配列を追加
    }
    
    close(FILE);
    
    # 天気の整形 (データ数の関係から,雲後晴 などを 雲 にし,数値に変換)
    foreach my $id (@tenki_id) {
      foreach(@data) {
        my @temp = keys %convlist; # 正規表現内に展開する用
        $" = '|';  # その際の区切り文字はorにしたい"
        $$_[$id] =~ /(@temp)/; # /晴|曇|雨|雪/
        $$_[$id] = $convlist{"$1"};
      }
    }
    
    # 出力
    foreach(@data) {
      print "$$_[$class_num]";     # はじめにクラスを出力
      for my $i (1 .. @$_ - 1) {   # ここでは日付は用いないので 1個目からloopスタート
        next if($i == $class_num); # クラスはもう出力したのでskip
        print " $i:$$_[$i]";       # 素性番号と値を出力
      }
      print "\n";
    }
  • 完成!
    -1 1:3.1 2:0 3:0 4:9.7
    +1 1:4.8 2:0 3:0 4:6.2
    -1 1:5.2 2:0 3:0 4:4.5
    -1 1:7.2 2:0 3:0 4:2.5
    -1 1:6.1 2:1 3:0 4:3.6
    +1 1:8.3 2:0 3:0 4:2.1
    -1 1:9.9 2:0 3:0 4:2.7
    -1 1:10.7 2:0 3:0 4:4.5
    +1 1:10.7 2:0 3:0 4:3.6
    -1 1:10.1 2:0 3:0 4:2.5
    • といったデータ列.左から,天気(0~4),平均気温,降水量合計,降雪量合計,平均風速

性能評価を行ってみる

  • 10-fold cross validation
    svm-train -v 10 data.train
  • すると以下のような結果が
    Cross Validation Accuracy = 67.4121%
    Positive (+1) class:
     precision = 0.570738 (472 / 827)
        recall = 0.56799 (472 / 831)
      F1 value = 0.569361
    Negative (-1) class:
     precision = 0.736804 (1005 / 1364)
        recall = 0.738971 (1005 / 1360)
      F1 value = 0.737885
  • 大体6割程度の確率で晴れかどうかが予測できそうだ!

実際にここ一週間(2014年6月4日~11日)の天気を予測してみる

  • 学習データには含まれていない未知(open)なデータ
  • 気象庁によると:
    気温,降水量,降雪量,平均風速
    23.9,0,0,2.5,晴
    21.8,0,0,7.8,曇
    18.8,0,0,7.4,曇
    18.0,0,0.6.8,曇
    18.1,9.5,0,4.6,雨
    19.7,5.5,0,5.6,雨
    19.6,3.0,0,4.7,雨
    18.7,10.5,5.9,曇
  • test.plでSVMが読めるデータ形式にすると
    +1 1:23.9 2:0 3:0 4:2.5
    -1 1:21.8 2:0 3:0 4:7.8
    -1 1:18.8 2:0 3:0 4:7.4
    -1 1:18 2:0 3:0 4:6.8
    -1 1:18.1 2:9.5 3:0 4:4.6
    -1 1:19.7 2:5.5 3:0 4:5.6
    -1 1:19.6 2:3 3:0 4:4.7
    -1 1:18.7 2:10.5 3:0 4:5.9
  • 早速上記データを評価データ (week.m)として,SVMしてみる
    svm-train data.train
    svm-predict week.m data.train.model data.result
    > Accuracy = 100% (8/8) (classification)
    • 何やら完全正解したみたい
    • 生成された予測結果ファイルdata.resultを見てみると
      1
      -1
      -1
      -1
      -1
      -1
      -1
      -1
      確かに一致している!SVMすごい!!

そういえばスケーリングは?精度上がるんじゃないの?

  • 忘れていました.やってみます
    svm-scale data.train > data.train.scale
    svm-train -v 10 data.train.scale
  • クロスバリデーションの結果
    Cross Validation Accuracy = 62.0721%
    Positive (+1) class:
     precision = -1.#IND (0 / 0)
        recall = 0 (0 / 831)
      F1 value = -1.#IND
    Negative (-1) class:
     precision = 0.620721 (1360 / 2191)
        recall = 1 (1360 / 1360)
      F1 value = 0.765981
    • +1が一個も検出されていない?!確かに-1の判別性能は上がったけど・・・
      • データの不均衡が原因かもしれない(+1=831個,-1=1360個)

データのアンダーサンプリング

  • ということで,-1の数も831個に統一してみる.適当にデータ中から-1を (1360-831) 個だけ削除
    • このデータで再度スケーリングとクロスバリデーション
      Cross Validation Accuracy = 64.4404%
      Positive (+1) class:
       precision = 0.592736 (767 / 1294)
          recall = 0.922984 (767 / 831)
        F1 value = 0.721882
      Negative (-1) class:
       precision = 0.826087 (304 / 368)
          recall = 0.365824 (304 / 831)
        F1 value = 0.507089
    • 微妙・・・

パラメータチューニング

  • ただしまだ諦めてはいけない.SVMは最適なパラメータ(Cとγ)が与えられて初めて効果を発揮する
    • この2つのパラメータは学習データによって異なり,基本的には自力で探す必要がある
  • でも,LibSVMには自動で探してくれる便利なPythonスクリプトがバインドされている.のでこれを使う
    $ ./libsvm/tools/grid.py -v 10 data.train.scale
    • 学習データによってはかなり時間がかかるので,寝てる時などに行う
  • 寝てる間に以下のような画像が生成される
    grid.png
  • これに基づいて再度学習と評価
    svm-train -c 32768 -g 0.5 -v 10 data.train.scale
    # warning ... と怒られたときは「-h 0」オプションを使うとよい
    • とりあえずCが大きいため学習に時間がかなりかかる.結果は
      Cross Validation Accuracy = 67.4489%
      Positive (+1) class:
       precision = 0.627641 (713 / 1136)
          recall = 0.858002 (713 / 831)
        F1 value = 0.724962
      Negative (-1) class:
       precision = 0.775665 (408 / 526)
          recall = 0.490975 (408 / 831)
        F1 value = 0.601326
    • 若干!!若干上がった!!

まとめ

  • 学習データによってはスケーリングの効果が薄い場合がある
  • その時も諦めずにパラメータ調整を行って最高精度を目指してみる

2値のクロスバリデーションでPrecision(精度)などを表示させるパッチ

トップ   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS