msgpack-rustとmsgpack-rpc-rustを使ったライブラリの試作として、Jubatusクライアントを書いてみました。
Rustのバージョンは1.13.0です。
現時点ではClassifierとRecommenderとにしか対応していません。
リポジトリ
使い方
JubatusのドキュメントにあるClassifierの例をRust実装で試してみます。
他のクライアントの例と同様、事前にshogun.json
を作成し、jubaclassifier
でサーバを起動しておきます。
まずは cargo new
でプロジェクト作成します。
$ cargo new --bin jubaclassify
$ cd jubaclassify
Cargo.tomlに以下を追加。rand
crateはトレーニングデータをシャッフルするために使います。
[dependencies]
jubatus = { git = "https://github.com/hhatto/jubatus-rust-client.git" }
rand = "0.3"
少し長いですが、 src/main.rs
は以下です。
extern crate jubatus;
extern crate rand;
use jubatus::classifier::client::ClassifierClient;
use jubatus::classifier::types::LabeledDatum;
use jubatus::common::datum::Datum;
use rand::{thread_rng, Rng};
fn train(client: &mut ClassifierClient) {
let mut train_data = vec![];
for n in vec!["家康", "秀忠", "家光", "家綱", "綱吉", "家宣", "家継", "吉宗", "家重", "家治", "家斉", "家慶", "家定", "家茂"] {
let mut d = Datum::new();
d.add_string("name", n);
train_data.push(LabeledDatum {
label: "徳川".to_string(),
data: d,
});
}
for n in vec!["尊氏", "義詮", "義満", "義持", "義量", "義教", "義勝", "義政", "義尚", "義稙", "義澄", "義稙", "義晴", "義輝", "義栄"] {
let mut d = Datum::new();
d.add_string("name", n);
train_data.push(LabeledDatum {
label: "足利".to_string(),
data: d,
});
}
for n in vec!["時政", "義時", "泰時", "経時", "時頼", "長時", "政村", "時宗", "貞時", "師時", "宗宣", "煕時", "基時", "高時", "貞顕"] {
let mut d = Datum::new();
d.add_string("name", n);
train_data.push(LabeledDatum {
label: "北条".to_string(),
data: d,
});
}
// training data must be shuffled on online learning!
let mut rng = thread_rng();
rng.shuffle(&mut train_data);
// run train
client.train(train_data);
}
fn predict(client: &mut ClassifierClient) {
// predict the last shogun
for n in vec!["慶喜", "義昭", "守時"] {
let mut d = Datum::new();
d.add_string("name", n);
let mut res = client.classify(vec![d]);
// get the predicted shogun name
let ref shogun_name = res[0].iter_mut().max_by_key(|x| (x.score * 100.) as i64).unwrap().label;
println!("{} {}", shogun_name, n);
}
}
fn main() {
let host = "127.0.0.1:9199";
let name = "test";
let mut client = ClassifierClient::new(host, name);
train(&mut client);
predict(&mut client);
}
ビルドして実行してみます。
$ cargo build
$ cargo run
徳川 慶喜
足利 義昭
北条 守時
無事、例の通りに実行できました。
コード生成処理について
Jubatusクライアントはjeneratorと呼ばれるJubatusが提供するコードジェネレータを使用して生成されています。
jubatus-rust-clientはjubatus/jubatusをフォークしたhhatto/jubatusのsupport-rust-client-for-jeneratorブランチを使ってコード生成しています。
現状はjubatus-rust-client向けに生成するコードは100%自動生成では無く、
一部手で修正している箇所があります。今後100%自動生成にすべく改修する予定です。
(そもそも私がOCamlをほとんど書いたことないので、かなりゆっくりな対応になりそうですが。)
使用したcrateについて
RustでMessagePackを扱うcrateとしてはmneumann/rust-msgpackも存在しているのですが、1年近く変更が無くメンテナンスされてなさそうなのと、msgpack-rpc用のシリアライザ/デシリアライザが用意されてそうなのですが、通信部分が提供されていないので簡単に使えなさそうだったので、msgpack-rustとmsgpack-rpc-rustの組み合わせを選びました。
その他改善点のまとめ
- rmp::value::Value <-> Rust構造体 それぞれの変換をもう少しエレガントにできないか?
- 今のところ対象の構造体に
from_msgpack_value
とto_msgpack_value
を生やしてそれを使って変換している -
serde
crateあたりでなんとかできそうなできなさそうな...
- 今のところ対象の構造体に
-
jenerator
のコード(rust.ml
)をもう少しエレガントにできないか?- 力技で書いたところが多い
- HashMapや多段のVector(
Vec<Vec<Struct>>
みたいなの)をうまく処理できるようなコードを生成できていない - Struct.from_msgpack_value() 内でrmp::Value::Array.as_array()使って値を順番に取り出すのだが、インデックス番号が0固定になっている
- テスト書く
最後に
jubatus-rust-client作ってみましたの共有と使い方の紹介でした。
Jubatusユーザのみなさん、Rustユーザのみなさん、是非とも使ってみてください。