- はじめに -
RustでNLP、機械学習どこまでできるのか試した時のメモ。
Pythonどこまで脱却できるのか見るのも兼ねて。
コードは以下に全部置いてある。
GitHub - vaaaaanquish/rust-text-analysis: rust-text-analysis
- 形態素解析 -
Rustの形態素解析実装を調べると、lindera-morphology/lindera を使うのが有力候補となりそうである。sorami/sudachi.rs や agatan/yoin 、 nakagami/awabi のような実装もあるがメンテは止まっている様子である。
linderaメンテナのブログ。
Rust初心者がRust製の日本語形態素解析器の開発を引き継いでみた - Qiita
neologd
linderaはipadic-neologd含む辞書作成ツール等もRustプロジェクト内で作成されている。まず、ipadic-neologd辞書を取得し、linderaで扱えるようにする。
以下ツールを使う。
github.com
READMEの通りに進める。
# neologdの辞書をlindera用に $ cargo install lindera-ipadic-neologd-builder $ curl -L https://github.com/neologd/mecab-ipadic-neologd/archive/master.zip > ./mecab-ipadic-neologd-master.zip $ unzip -o mecab-ipadic-neologd-master.zip $ ./mecab-ipadic-neologd-master/bin/install-mecab-ipadic-neologd --create_user_dic -p $(pwd)/mecab-ipadic-neologd-master/tmp -y $ IPADIC_VERSION=$(find ./mecab-ipadic-neologd-master/build/mecab-ipadic-*-neologd-* -type d | awk -F "-" '{print $6"-"$7}') $ NEOLOGD_VERSION=$(find ./mecab-ipadic-neologd-master/build/mecab-ipadic-*-neologd-* -type d | awk -F "-" '{print $NF}') $ lindera-ipadic-neologd ./mecab-ipadic-neologd-master/build/mecab-ipadic-${IPADIC_VERSION}-neologd-${NEOLOGD_VERSION} lindera-ipadic-${IPADIC_VERSION}-neologd-${NEOLOGD_VERSION} # lindera-cli で検証 $ cargo install lindera-cli $ echo "すもももももももものうち" | lindera すもも 名詞,一般,*,*,*,*,すもも,スモモ,スモモ も 助詞,係助詞,*,*,*,*,も,モ,モ もも 名詞,一般,*,*,*,*,もも,モモ,モモ も 助詞,係助詞,*,*,*,*,も,モ,モ もも 名詞,一般,*,*,*,*,もも,モモ,モモ の 助詞,連体化,*,*,*,*,の,ノ,ノ うち 名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ EOS $ echo "すもももももももものうち" | lindera -d ./lindera-ipadic-2.7.0-20070801-neologd-20200910 すもももももももものうち 名詞,固有名詞,一般,*,*,*,すもももももももものうち,スモモモモモモモモノウチ,スモモモモモモモモノウチ EOS
neologdが利用可能な状態になっている。ここで作成した./lindera-ipadic-2.7.0-20070801-neologd-20200910なる辞書データは、後でRustスクリプト上で使う。
lindera
CLIではなくRustからneologdを呼ぶ。本体は以下のrepoになる。
github.com
linderaのREADMEに先程作成したneologdの辞書を流す例。
use lindera::tokenizer::Tokenizer; use lindera_core::core::viterbi::Mode; fn main() -> std::io::Result<()> { let mut tokenizer = Tokenizer::new(Mode::Normal, "./lindera-ipadic-2.7.0-20070801-neologd-20200910"); let tokens = tokenizer.tokenize("すもももももももものうち"); for token in tokens { println!("{}", token.text); } Ok(()) }
$ cargo run すもももももももものうち
形態素解析はこれで一旦良さそう。トークナイズ結果は、mecabフォーマット等でも出力できるので、自然にPythonから移行したりできそうである。
- Text Processing、Embedding -
NLPでよく使うような特徴量、Count Vector、TF-IDF、fasttext、BERT、…辺りを扱いたい。
PyTorchのRust Bindingが一番活発に開発されていて、候補として最も良さそう。
github.com
tch-rsに依存した、guillaume-be/rust-bertなどもある。
TF-IDFは、 ferristseng/rust-tfidf や afshinm/tf-idf など野良の実装が見つかるが、メンテも止まっていてあまり使いやすい実装のクレートは現状見当たらなかった。
今回は、facebookresearch/fastTextの公式実装のRust Bindingを実装している以下を使う。
github.com
fasttextの実装も含むのでbuildにcmake必須。macならbrewで入れておく。
$ brew install cmake
extern crate csv; use lindera::tokenizer::Tokenizer; use lindera_core::core::viterbi::Mode; use fasttext::FastText; use fasttext::Args; ... // 一度csvに書き込む let mut tokenizer = Tokenizer::new(Mode::Normal, "./lindera-ipadic-2.7.0-20070801-neologd-20200910"); let mut file = File::create("./data/train_fasttext.csv")?; for (sentence, label) in zip(&train_sentences, &train_labels){ let row = tokenizer.tokenize(&sentence).iter().map(|x| x.text).collect::<Vec<&str>>().join(" "); write!(file, "__label__{}, {}\n", label, row)?; } file.flush()?; // 先程作成した __label__1, bar の形式のcsvを入力としてbinを生成 let mut fasttext_args = Args::default(); fasttext_args.set_input("./data/train_fasttext.csv"); let mut model = FastText::default(); let _ = model.train(&fasttext_args); let _ = model.save_model("./data/fasttext.bin");
公式実装のBindingなので、パラメータは以下を見れば良い。
List of options · fastText
このfasttext model自体supervisedに分類を解いてtrainさせれば、この時点でテキスト分類のpredictはできる。
- XGBoost -
一般的にfasttextだけで問題が解ける場合は少ないので、特徴量を追加してxgboostのようなモデルを挟む事になる。
gbdtに関連したものだと、以下が最も更新されていて良さそう。
github.com
今回は、公式実装のRust Bindingなのでこちらを選ぶ(最終更新が2年前でxgboost 0.8を使っていて作者も音沙汰がない様子ではあるが、PythonやC++から触っている馴染みのxgboostのパラメータやAPIを使いたい)。
github.com
先程のfasttextモデルで文字列をEmbeddingして、DMatrixを作成しBoosterに入れる。更新がなくREADMEに書かれたExampleは動かないので、実装を見比べるか、repo内のExamplesを参考にする。
extern crate xgboost; use xgboost::{DMatrix, Booster}; use xgboost::parameters::{self, tree, learning::Objective}; ... // python binding同様にDMatrixを作る let train_data_size = train_sentences.len(); let test_data_size = test_sentences.len(); let mut train_dmat = DMatrix::from_dense(train_ft_vector_flatten, train_data_size).unwrap(); train_dmat.set_labels(&train_labels).unwrap(); let mut test_dmat = DMatrix::from_dense(test_ft_vector_flatten, test_data_size).unwrap(); test_dmat.set_labels(&test_labels).unwrap(); // parameter群を設定し多クラス分類でtrain let uniq_label = train_labels.unique().len() as u32; let eval_sets = &[(&train_dmat, "train"), (&test_dmat, "test")]; let learning_params = parameters::learning::LearningTaskParametersBuilder::default().objective(Objective::MultiSoftmax(uniq_label)).build().unwrap(); let tree_params = tree::TreeBoosterParametersBuilder::default().eta(0.1).max_depth(6).build().unwrap(); let booster_params = parameters::BoosterParametersBuilder::default().booster_type(parameters::BoosterType::Tree(tree_params)).learning_params(learning_params).build().unwrap(); let training_params = parameters::TrainingParametersBuilder::default().dtrain(&train_dmat).booster_params(booster_params).boost_rounds(5).evaluation_sets(Some(eval_sets)).build().unwrap(); let bst = Booster::train(&training_params).unwrap(); // predict let mut preds = bst.predict(&test_dmat).unwrap();
特徴量化からmodelによるpredictまでができた。
- 実験 -
実際にlivedoorニュースコーパスに対して、ニュース本文からメディアを分類するタスクを実施する。
以下からデータをダウンロードした。
ダウンロード - 株式会社ロンウイット
RUN wget https://www.rondhuit.com/download/ldcc-20140209.tar.gz
RUN tar -zxvf ldcc-20140209.tar.gz
このデータを以下スクリプトで雑にtrain、testデータに分割した。
https://github.com/vaaaaanquish/rust-text-analysis
Dockerに収めたので、以下をbuildすれば、上記データがダウンロードされtrain、testデータが作成される。
github.com
実際にlinderaで形態素解析し、fasttextでtrain、embedding、XGBoostで分類タスクを実施した。
testに対するclassification_reportは以下のようになった。
precision recall f1-score support 0 0.73 0.72 0.72 251 1 0.73 0.58 0.64 147 2 0.91 0.94 0.93 251 3 0.91 0.92 0.92 283 4 0.80 0.77 0.79 251 5 0.84 0.80 0.82 232 6 0.82 0.94 0.88 254 7 0.78 0.78 0.78 263 8 0.81 0.83 0.82 278 micro avg 0.82 0.82 0.82 2210 macro avg 0.81 0.81 0.81 2210 weighted avg 0.82 0.82 0.82 2210 samples avg 0.82 0.82 0.82 2210
悪くないので一旦ここまで。
- おわりに -
割と悪くない所までできた。
LightGBMもcoreのBindingを書いてあげれば使えそうだし、もう少しmetricとかpreprocessingのML関連クレート書いていけば良さそう。
fasttexやPyTorchがWebAssenblyに対応しているので、次の記事ではその検証を書く。
WebAssembly module · fastText
WebAssemblyでの機械学習モデルデプロイの動向 · tkat0.github.io
Python脱却にはまだまだ遠い(クレートやメンテナの数が違いすぎるし、EDAなど型なしでやりたい作業も多い)が、期待した結果が得られたので良かった。