TL;DR
- Rustの勉強として、データセットを読み込み重回帰モデルを構築してみました。
- Pythonでは良く行っている処理なのですが、Rustで書くと詰まるポイントも多くその分勉強になりました。
環境
Rust
$ rustc --version rustc 1.32.0 (9fda7c223 2019-01-16)
crate
csv = "1"
rusty-machine = "0.5.4"
学習にはrusty-machineというcrateを用いました。
ただ、MatrixやVectorなどの構造体はrulinalgというcrateに切り出されているのでAPIを調べる際にはこちらのドキュメントを中心に見ることになります。
Rustでの機械学習に関しては、他にもrustlearnというcrateもあるようですが、rusty-machineの方がメジャーなようです。
データ
scikit-learnのdatasetから使えるbostonデータセットを用いました。
実装
それぞれの部分をごく簡単に説明します。 プロジェクト全体は下のリポジトリに置きました。
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_rows
、 select_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の書き方にあまり慣れていないので、改善点などあれば指摘していただければ幸いです。
- 普段書いてない言語を勉強するのは楽しいですね!これからも少しずつ勉強していきたいと思います。