MATHGRAM

主に数学とプログラミング、時々趣味について。

Rustで機械学習、はじめラスタ。

卒論終了の目処が立ちました。働き始めるまでに記事数を2倍にするという若干無謀なツイートをしたことを後悔しながらもどんどん記事書いていこうかと思います。

さて今回はrustで機械学習をやってみよう!というものですが、僕のrust歴は4時間程度ですw

rustのチュートリアルを実行してみたのとrusty-machineのソースコードを軽く読んだくらいです。ですので、勉強&自分用まとめにrusty-machineのexampleを紹介するのみとなっています。

現在の僕のスキルは、python : c++ : rails = 8 : 1 : 1 くらいなので2ヶ月後くらいには python : rust = 6 : 4 くらいにしたいです。(c++&railsは忘却の彼方だろうなぁ・・・)

本記事の流れ

一応、前回の記事と今回の記事でゼロからでも学習&予測までができるように書いているつもりです。不明な点がありましたらコメント下さい。

  • 環境設定
  • rusty-machineの簡単な紹介
  • rusty-machineで符号(プラスかマイナスか)を学習する
  • まとめ

手元の環境は以下の通りです。

  • macOS Sierra 10.12.1
  • rust 1.14.0
  • rusty-machine 0.5.3

環境設定

rustのinstall方法などは前回の記事を参考にして下さい。直接installするのではなくバージョンマネージャを使ったインストール方法を紹介しています。今回の記事でも前回rsvmで作った環境と同じものを使っています。

rusty-machine

今回使うのは、rusty-machineというパッケージです。pythonでいうscikit-learnのようなもので、回帰やSVMニューラルネットなどの機械学習の分野における基本的なものはほとんど実装されています。今のところscikit-learnに比べて抽象化されておらず、まだまだ更新されそうだなぁという印象を持っています。そもそもrustがこれから(らしい)ですもんね。

公式ドキュメントによると、

  • Linear Regression
  • Logistic Regression
  • Generalized Linear Models
  • K-Means Clustering
  • Neural Networks
  • Gaussian Process Regression
  • Support Vector Machines
  • Gaussian Mixture Models
  • Naive Bayes Classifiers
  • DBSCAN

実装済みらしいです。まだ僕は全部見てませんが。これから色々試していきますね。

符号を学習してみる

さて、早速使って見ましょう。ランダムで生成された整数がプラスなのかマイナスなのかをSVMを使って判定する、という簡単な問題を解いてみようと思います。 exampleの全文は以下のようになっています。

extern crate rusty_machine;

use rusty_machine::learning::svm::SVM;
// Necessary for the training trait.
use rusty_machine::learning::SupModel;
use rusty_machine::learning::toolkit::kernel::HyperTan;

use rusty_machine::linalg::Matrix;
use rusty_machine::linalg::Vector;

// Sign learner:
//   * Model input a float number
//   * Model output: A float representing the input sign.
//       If the input is positive, the output is close to 1.0.
//       If the input is negative, the output is close to -1.0.
//   * Model generated with the SVM API.
fn main() {
    println!("Sign learner sample:");

    println!("Training...");
    // Training data
    let inputs = Matrix::new(11, 1, vec![
                             -0.1, -2., -9., -101., -666.7,
                             0., 0.1, 1., 11., 99., 456.7
                             ]);
    let targets = Vector::new(vec![
                              -1., -1., -1., -1., -1.,
                              1., 1., 1., 1., 1., 1.
                              ]);

    // Trainee
    let mut svm_mod = SVM::new(HyperTan::new(100., 0.), 0.3);
    // Our train function returns a Result<(), E>
    svm_mod.train(&inputs, &targets).unwrap();

    println!("Evaluation...");
    let mut hits = 0;
    let mut misses = 0;
    // Evaluation
    //   Note: We could pass all input values at once to the `predict` method!
    //         Here, we use a loop just to count and print logs.
    for n in (-1000..1000).filter(|&x| x % 100 == 0) {
        let nf = n as f64;
        let input = Matrix::new(1, 1, vec![nf]);
        let out = svm_mod.predict(&input).unwrap();
        let res = if out[0] * nf >= 0. { // ここ>=に直してます!!
            hits += 1;
            true
        } else if nf == 0. {
            hits += 1;
            true
        } else {
            misses += 1;
            false
        };

        println!("{} -> {}: {}", Matrix::data(&input)[0], out[0], res);
    }

    println!("Performance report:");
    println!("Hits: {}, Misses: {}", hits, misses);
    let hits_f = hits as f64;
    let total = (hits + misses) as f64;
    println!("Accuracy: {}", (hits_f / total) * 100.);
}

上から順に見ていきましょう。

extern crate rusty_machine;

use rusty_machine::learning::svm::SVM;
// Necessary for the training trait.
use rusty_machine::learning::SupModel;
use rusty_machine::learning::toolkit::kernel::HyperTan;

use rusty_machine::linalg::Matrix;
use rusty_machine::linalg::Vector;

この部分で使うモジュールを宣言しています。pythonでいうimportみたいなものです。 (説明が全てpythonに例えたもので申し訳ないっす・・・。)

次にメイン文。

 let inputs = Matrix::new(11, 1, vec![
                             -0.1, -2., -9., -101., -666.7,
                             0., 0.1, 1., 11., 99., 456.7
                             ]);
 let targets = Vector::new(vec![
                              -1., -1., -1., -1., -1.,
                              1., 1., 1., 1., 1., 1.
                              ]);

inputデータはMatrix型で与えます。この例では11行1列の行列に正負の実数が与えられています。それに対してtargetデータはVector型で作ります。負の数には−1を、正の数には1を与えることで符号の違いを学習できるように教師データを作っています。 

次は最も重要なSVMインスタンス化です。

// Trainee
let mut svm_mod = SVM::new(HyperTan::new(100., 0.), 0.3);
// Our train function returns a Result<(), E>
svm_mod.train(&inputs, &targets).unwrap();

少しだけrustの文法にちょっと触れておくと、mutというのはミュータブルを意味します。mutを宣言することでこの変数は後から値を代入することができるようになります。

本命のSVMの引数はカーネルとラムダの2つです。カーネルやラムダはSVMで使われる一般的なパラメータ名だと思うのでこの値がどのような意味を持つかなどの説明は省略させていただきます。ここではカーネルにハイパボリックタンジェントを使っています。実際のところ今回のデータは、何もしなくても直線で分離できるくそ簡単なデータなのでカーネルとかの設定はいらないと思うんですけどね。まぁ例なのであまり細かいことは気にせずいきましょう。

SVMインスタンス化したら、train関数を使って学習しましょう。何も難しいことはなく、引数にはinputsのMatrixとtargetsのVectorを与えてあげればOKです。&の意味や、unwrap()の意味はrust自体の文法なので、チュートリアルなどを見て下さい。

最後にfor文の中で未知データに対して予測をしています。 手元の環境で試した結果がこちらです。

Sign learner sample:
Training...
Evaluation...
-1000 -> -1: true
-900 -> -1: true
-800 -> -1: true
-700 -> -1: true
-600 -> -1: true
-500 -> -1: true
-400 -> -1: true
-300 -> -1: true
-200 -> -1: true
-100 -> -1: true
0 -> 1: true
100 -> 1: true
200 -> 1: true
300 -> 1: true
400 -> 1: true
500 -> 1: true
600 -> 1: true
700 -> 1: true
800 -> 1: true
900 -> 1: true
Performance report:
Hits: 20, Misses: 0
Accuracy: 100

こんな感じでちゃんと判別できてますね。実はexampleをそのまま動かすと、0が−1の予測されてtrueになってしまってたんですよね。簡単なバグだったのでちょっと修正しておきました。修正部分はコメントで書いてありますので、上のコードを参照して下さい。

以上でSVMのexampleは終了です!

まとめ

Rustのライブラリrusty-machineを使ってSVMを試しました!
正直なんとな〜くrustを勉強し始めたのでまだ将来性とかrustの強みとかを分かりきってないので、もっと勉強してrustを使いこなせるようになっていきたいです。

それでは!

p.s. rustが"ラスタ"って読まないのは知ってるよ。