フリーランチ食べたい

機械学習/データ解析/フロントエンド/バックエンド/インフラ

【Rust】bostonデータセットで重回帰モデルの学習/評価を行う

TL;DR

  • Rustの勉強として、データセットを読み込み重回帰モデルを構築してみました。
  • Pythonでは良く行っている処理なのですが、Rustで書くと詰まるポイントも多くその分勉強になりました。

f:id:ikedaosushi:20190216192112p:plain

環境

Rust

$ rustc --version
rustc 1.32.0 (9fda7c223 2019-01-16)

crate

  • csv = "1"
  • rusty-machine = "0.5.4"

学習にはrusty-machineというcrateを用いました。

github.com

ただ、MatrixやVectorなどの構造体はrulinalgというcrateに切り出されているのでAPIを調べる際にはこちらのドキュメントを中心に見ることになります。

github.com

Rustでの機械学習に関しては、他にもrustlearnというcrateもあるようですが、rusty-machineの方がメジャーなようです。

github.com

データ

scikit-learnのdatasetから使えるbostonデータセットを用いました。

scikit-learn.org

実装

それぞれの部分をごく簡単に説明します。 プロジェクト全体は下のリポジトリに置きました。

github.com

CSV読み込み

ファイルのパスを渡し、vecと行数を返してくれる関数です。

今回使う rusty_machine::linalg::Matrix は1次元配列から行列を作るので多次元配列にはしていません。その代わり行数を返してインスタンス化する際に使っています。

fn read_csv(filename: &str) -> (Vec<f64>, usize) {
    let mut vec: Vec<f64> = Vec::new();
    let mut line = 0;

    let file = File::open(filename).unwrap();
    let mut rdr = csv::Reader::from_reader(file);
    for result in rdr.records() {
        let record = result.unwrap();
        line = line + 1;
        for item in record.iter() {
            vec.push(item.parse().unwrap());
        }
    }
    (vec, line)
}

train_test_split

scikit-learnのtrain_test_splitに当たる関数はまだ実装されていないようだったので、自分で実装してみました。ratioの割合に応じてtrain/test、そしてX/yに分割しています。今回はtargetとなる変数が最後に

Matrixから特定の行や列を取ってきたい場合は、 select_rowsselect_cols にイテレータを渡して使います。学習の際の目的変数(y)はMatrix型は受け付けてくれず、Vector型しか対応していないので少し煩わしいですが、変換を行っています。

fn train_test_split(data: Matrix<f64>, ratio: f64) -> (Matrix<f64>, Vector<f64>, Matrix<f64>, Vector<f64>) {
    let boundary = (data.rows() as f64 * ratio).ceil() as usize;
    let train_iter: Vec<usize> = (0..boundary).collect();
    let test_iter: Vec<usize> = (boundary+1..data.rows()).collect();
    let train = data.select_rows(train_iter.iter());
    let test = data.select_rows(test_iter.iter());

    let x_iter: Vec<usize> = (0..(data.cols()-2) as usize).collect();
    let x_train = train.select_cols(x_iter.iter());
    let tmp_y_train: Vec<f64> = train.select_cols([data.cols()-1].iter()).iter().map(|v| v.clone()).collect();
    let y_train = Vector::new(tmp_y_train);

    let x_test = test.select_cols(x_iter.iter());
    let tmp_y_test: Vec<f64> = test.select_cols([data.cols()-1].iter()).iter().map(|v| v.clone()).collect();
    let y_test = Vector::new(tmp_y_test);

    (x_train, y_train, x_test, y_test)
}

学習/評価

上述した、関数群を使い準備を行い、線形回帰モデルをインスタンス化し、学習/推論しています。

fn regression() {
    // データ読み込み
    let filename = "data/house.csv";
    let (data, nrow) = read_csv(filename);
    let ncol = data.len() / nrow;
    let data = Matrix::new(nrow, ncol, data);

    // Split train/test
    let ratio = 0.99;
    let (x_train, y_train, x_test, y_test) = train_test_split(data, ratio);

    // Initiate model
    let mut reg = LinRegressor::default();

    // Train
    reg.train(&x_train, &y_train).unwrap();

    // Predict
    let y_pred = reg.predict(&x_test).unwrap();

    // Print
    for (v_pred, v_test) in y_pred.iter().zip(y_test.iter()) {
        println!("pred: {:.1}, true: {}", v_pred, v_test);
    }
}

結果

出力結果は次のようになり、無事学習できているようです。

pred: 20.3, true: 20.6
pred: 25.1, true: 23.9
pred: 23.7, true: 22
pred: 19.3, true: 11.9

さいごに

  • まだRustの書き方にあまり慣れていないので、改善点などあれば指摘していただければ幸いです。
  • 普段書いてない言語を勉強するのは楽しいですね!これからも少しずつ勉強していきたいと思います。