能動学習で効率的に教師データを作るツールをGoで書いた

みなさん、教師データ作ってますか?!

機械学習のツールも多くなり、データがあれば簡単に機械学習で問題を解ける環境が整ってきました。しかし、データ作成は重要ながらも未だに大変な作業です。最近、私もいくつかのドメインで教師データを作る機会があったので、能動学習を使ってコマンドラインから簡単に教師データ作成(アノテーション)ができるツールを作ってみました。

今回は能動学習で教師データを作る意義と、作ったツールの使い方について簡単に書きます。

問題設定

例があったほうが説明しやすいので、問題設定を先に。

あなたはエンジニアが集まるSlackチャンネルに、最近人気の技術エントリを定期的に投稿するbotを作りたくなったとします。URLが与えられたときに、それが技術エントリかどうかを判定する機械学習の分類器があると便利ですね。これを実現するために、あなたはtwitterに流れてくるURLやはてなブックマークの新着エントリのURLから教師データの候補となるURLのリストを10万件取得しました。しかし、あなたは仕事も忙しいので、教師データはせいぜい300件程度しか付ける時間はなさそうです。同じ300件の教師データを作るならば、なるべく精度が高くなるような300件を選んで教師データを作りたい、というのが今回の問題設定です。

能動学習を使って教師データを効率的にアノテーション

優先的にアノテーションすべき300件のデータを能動学習に選んでもらいましょう。以下が説明用の図です。赤が正例(技術エントリ)、青が負例(それ以外のエントリ)を表わしています(2次元で書いていますが、実際は数千から数万次元程度です)。正例/負例を分離する境界のことを分離平面と呼びますが、分離平面の近くにいる事例や遠くにいる事例が存在します。

f:id:syou6162:20170517160045p:plain

分離平面から近い/遠いは以下のことを表わしています。

  • 分離平面の近くにいる事例
    • 分類器があまり自信のない事例
    • 「iPhone Swift」や「エンジニア 転職」のような事例
  • 分離平面から遠くにいる事例
    • 分類器が自信のある事例
    • 右上や左下に固まっているもの

Webデータでは実際には左下のような事例が非常に多く、これらをずっとアノテーションしていると鬱々としてきます。そういったものはなるべく見ず、分離平面に近い事例を中心的にアノテーションすることは、精度向上の観点だけでなくアノテーターの精神的な観点からも重要です。

能動学習には様々なアルゴリズムがあります。今回実装したものはかなりシンプルなアルゴリズムで、分離平面から最も近い(自信のない)事例からアノテーションして教師データに追加し、再学習するというものです。シンプルなのでいくらでも改善の余地がありますが、これだけでも大きな効果が出る場合があります。過去に実験したエントリがあるので、気になる人はそちらを参照してください。

go-active-learningを試してみる

ダウンロード/インストール

githubにコードを置いています。git cloneしてmakeすれば簡単に使えます。

% git clone https://github.com/syou6162/go-active-learning.git
% cd go-active-learning
% make build

もしくはgo getでもOK。

% go get github.com/syou6162/go-active-learning

能動学習でアノテーション

考え方が分かって、ツールもビルドできたところで実際に試してみましょう。まず、少量の教師データとまだアノテーションされていないURLが入ったファイルを用意します。今回ははてなのエンジニアのBlogを正例(2列目が1のもの)、nhkニュースなどを負例(2列目が-1のもの)としました。アノテーションされていないURLとしては「それどこ」や「メシ通」を入れています。

% cat tech_input_example.txt
http://www.yasuhisay.info/entry/2017/05/09/080000   1
http://www.songmu.jp/riji/entry/2016-12-26-yapc-hokkaido.html   1
http://blog.yuuk.io/entry/web-operations-isucon 1
http://hakobe932.hatenablog.com/entry/2016/11/25/141348 1
http://motemen.hatenablog.com/entry/2016/10/gofind  1
http://www3.nhk.or.jp/news/ -1
https://www.facebook.com/   -1
http://r.gnavi.co.jp/g-interview/   -1
https://cookpad.com/    -1
http://suumo.jp/town/   -1
http://www.rakuten.co.jp/   -1
https://www.hotpepper.jp/mesitsu/entry/bbshin/17-00009  -1
http://srdk.rakuten.jp/
https://www.hotpepper.jp/mesitsu/entry/kekkojin/17-00138

go-active-learningを使って、アノテーションしてみましょう。

% ./go-active-learning annotate --input-filename tech_input_example.txt --output-filename additionaly_annotated_examples.txt --open-url
Loading cache...
Label this example (Score: 0.000): http://srdk.rakuten.jp/ (それどこ)

「メシ通」のラーメンの記事(入力ファイルの一番下のもの)より自信がないということで、「それどこ」の記事がアノテーションする最初のエントリとして提示されました。技術エントリではないのでnを押して負例としてアノテーションします(pは正例としてアノテーション)。hを押すと、可能なコマンドの一覧が見れます。sを押すと、output-filenameで指定したファイルにこれまでアノテーションした事例が保存されます。アノテーションされ、教師データに追加された事例も含めて再学習が行なわれ、再度最も自信がない事例が提示される…といった感じでアノテーションを進めていきます。

現状、テキストが入力で二値分類でしか動かないですが、簡単なgoのコードなので多値分類への拡張や(特徴量抽出を頑張れば)画像への応用もできるんじゃないかなと思います。

おまけ: Go言語を使って得られた効用/感想

Goで書いていて、これはよかったなぁと思ったことをいくつか挙げてみます。

  • マルチプラットフォームで動くシングルバイナリになるので、他の人にもアノテーションをお願いしやすい
    • 他職種の人にあれこれインストールして、、、だと頼みにくい
    • kagomeがself-containedになっているのが大きい
  • 再学習が早い
    • 再学習は時間がかかるので10事例毎にしようかなと思っていたが、1事例毎にやっても全然問題にならない速さだった
  • たくさんのURLを取得しにいくのにgoroutine便利
    • goroutine使わないとアノテーションまで大分待たないといけない
    • 簡単に並列化できるので、待ち時間が減る
  • Goでも本文抽出のライブラリがあったので、そんなに困らなかった