JubatusのRustクライアントを書きました

  • 1
    いいね
  • 0
    コメント

msgpack-rustmsgpack-rpc-rustを使ったライブラリの試作として、Jubatusクライアントを書いてみました。

Rustのバージョンは1.13.0です。

現時点ではClassifierとRecommenderとにしか対応していません。

リポジトリ

jubatus-rust-client

使い方

JubatusのドキュメントにあるClassifierの例をRust実装で試してみます。

他のクライアントの例と同様、事前にshogun.jsonを作成し、jubaclassifierでサーバを起動しておきます。

まずは cargo new でプロジェクト作成します。

$ cargo new --bin jubaclassify
$ cd jubaclassify

Cargo.tomlに以下を追加。rand crateはトレーニングデータをシャッフルするために使います。

Cargo.toml
[dependencies]
jubatus = { git = "https://github.com/hhatto/jubatus-rust-client.git" }
rand = "0.3"

少し長いですが、 src/main.rs は以下です。

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-rustmsgpack-rpc-rustの組み合わせを選びました。

その他改善点のまとめ

  • rmp::value::Value <-> Rust構造体 それぞれの変換をもう少しエレガントにできないか?
    • 今のところ対象の構造体に from_msgpack_valueto_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ユーザのみなさん、是非とも使ってみてください。

この投稿は Rust Advent Calendar 201613日目の記事です。