白猫のメモ帳

C#とかJavaとかJavaScriptとかHTMLとか機械学習とか。

fastTextでニュースをカテゴリごとに分類する

こんばんは。

暑かったり暑くなかったり、晴れだったり雨だったり。
日によって全然気候が違いますね。関東もそろそろ梅雨入りでしょうか。

さて、fastTextによる単語のベクトル表現を試してからまたずいぶんと間が空いてしまいましたが、
今回は分類をやってみようかと思います。

分類するものがないんだよなーとか思っていたんですが、
そういえばだいぶ前にベイジアンフィルタでニュースの分類したときのデータが
どこかに残っていたような気がするな、と思って探してみたら発見できたのでこれを使います。

shironeko.hateblo.jp

前処理

fastTextに分類してもらうためには書式を変換してあげる必要があります。

学習データが

__label__【ラベル】, 【半角スペースで分かち書きした文】

という書式。

__label__IT・科学, アップル iPhone 不正 ユーザー 指紋 収集 特許 出願

こんな感じ。
前の記事見てもらうとわかるかもしれませんが、名詞しか抜き出していないのでそのまま使います。
ちなみに「__label__」の部分は引数の設定で変更することもできます。

で、分類データが

【半角スペースで分かち書きした文】

という書式。こっちはただの分かち書きだけ。

国内 映画 ランキング 君の名は。 会心 V スタート 後妻業 女 4 位 タートルズ 10 位

こんな感じ。
2年近く前のニュースなので、なんだか懐かしい。

学習

前処理が終わったら学習モデルを作ります。

# /fastText/fasttext supervised -input learn.txt -output model -epoch 1000

エポック数だけ1000にしてみましたが、それ以外はデフォルトです。

Read 0M words
Number of words:  4051
Number of labels: 5
Progress: 100.0% words/sec/thread:  350035 lr:  0.000000 loss:  0.263343 ETA:   0h 0m

こんな感じの表示が出て、2秒くらいで終わりました。はやーい。
まぁ、コーパス数800なのでこんなもんですかね。

分類

あっという間に学習が終わったので、分類してみます。

/fastText/fasttext predict-prob model.bin predict.txt 3

ほんとに一瞬で終わります。

__label__エンタメ, 0.999987
__label__IT・科学, 0.99884
__label__エンタメ, 0.999866
__label__スポーツ, 0.999957
__label__エンタメ, 0.999686
__label__経済, 0.999839
__label__エンタメ, 0.99961
__label__エンタメ, 0.999815
__label__スポーツ, 1
__label__経済, 0.999296

あれ、なんかラベルがちょっとイメージと違うけどまぁ分類はできているんでしょう。たぶん。

結果

○__label__スポーツ, 0.999846 大江香織、藤本麻子が“破格”の韓国ツアー出場)
○__label__IT・科学, 0.999927 「ねこ休み美術館」
○__label__スポーツ, 0.999587 【MLB】3Aでストライク率70% ロッテ戦力外の中後、11戦無失点で防御率0.00継続
○__label__スポーツ, 0.999922 元時天空 髪は失っても親方で「成長したい」
○__label__スポーツ, 0.999848 熾烈な優勝争い続くパ ソフトバンク&日本ハム、カギ握る残りの対戦は?
○__label__エンタメ, 0.999742 【ディズニー】ジェラトーニが街中に涼しげアート!期間限定夏デコレーション
○__label__ライフ, 0.9997 医師が考える。新しいタバコ「iQOS(アイコス)」は健康的なのか?
○__label__エンタメ, 0.999801 「ブラクロ」がジャンプのアニフェスでアニメに!参加受付もスタート
○__label__エンタメ, 0.999793 俳優イ・サンヨプ、コン・ヒョンジュと破局後の近況報告
○__label__スポーツ, 0.999718 フットゴルフ日本代表、第1回アジアカップで上位独占 初代アジア王者に

おお、いきなり100%だ。

×__label__エンタメ, 0.999997 伝説の映画「バトルシップ」 まさかの極上爆音上映が決定!
○__label__経済, 0.706788 エコ運転教習のファインモータースクール、エコドライブ全国大会で3年連続表彰台
×__label__エンタメ, 0.94055 アトピー性皮膚炎ってどんな病気? 乳児湿疹とアトピー性皮膚炎の違いとは?
○__label__スポーツ, 0.997004 バルサ、29日中にもP・アルカセル獲得を発表へ すでにメディカルチェックをパス
×__label__IT・科学, 0.742081 拡張現実開発プラットフォームの新バージョンをリリース
×__label__エンタメ, 0.815593 【鈴鹿8耐】あの感動をもう一度、公式ブルーレイ&DVD 917日発売
○__label__経済, 0.889665 「電力事業者」の新設法人数が初の減少、「太陽光」が4割減
○__label__エンタメ, 0.981648 まゆゆが舞妓さんに! メロメロの豊川悦司「ファンクラブに入りたい」
○__label__スポーツ, 0.939998 広島松山8号2ラン「自分のスイングできました」
○__label__スポーツ, 0.764349 3基のパワーユニットを確保したハミルトン、今後はアップグレードの恩恵得られず

とおもったら6割。安定しませんね。

○__label__IT・科学, 0.922675 東大、「水」ベースのリチウムイオン伝導性液体を発見
○__label__経済, 0.715266 セダンのトランクに積める折りたたみ自転車、あさひが発売
×__label__スポーツ, 0.882106 個人間カーシェアのエニカ、1周年記念イベント 917日
○__label__スポーツ, 0.950249 田口 調整に自信「世界戦では割といい方」
×__label__エンタメ, 0.634755 怖いと思う災害1位は地震、「住んでいる地域・家は危険だと思う」32.7%、保険クリニック調べ
○__label__ライフ, 0.999923 【高校受験2017】大阪府公立高、体験入学や説明会一覧…茨木11/9・北野10/8
×__label__経済, 1.00001 都市開発:5 とび職 正確な鉄骨組み、段取り勝負
○__label__エンタメ, 0.804691 俳優イ・サンヨプ、コン・ヒョンジュと破局後の近況報告
○__label__スポーツ, 0.999977 インテルデビュー戦を白星で飾れなかった伊代表MF「勝つためにここへ来たから残念」)
○__label__エンタメ, 0.42318 エースコック、主力商品のスーパーカップ1・5倍シリーズをリニューアル

7割。やっぱり学習データが足りていなさそうですね。

まとめ

fastとつくだけあってとても処理が速いし、使い方もとてもわかりやすいです。
しっかりと分類するためにはデータの量がもっと必要かとは思いますが、
その実力の片鱗くらいは見ることができたかなという感じです。

そのうちにもっと分類に適したデータを集めることができたら、また試してみたいですね。
それでは。

総称型メソッドの型指定

こんにちは。

新年度が始まって二週間がたちました。
新しい環境で頑張っている方はとてもお疲れのことでしょう。
新しくなくても眠たいです。春眠春眠・・・。

この前コードを書いていてふと気づいたこと。
JavaC#で総称型メソッドの挙動が違う。

総称型メソッド

まずそもそも総称型メソッドってなんだって話です。
ジェネリクスメソッドとかジェネリックメソッドとか呼んだりもします。

public static String firstOrDefault(List<String> list, String defaultValue) {
    return list != null && list.size() > 0 ? list.get(0) : defaultValue;
}

例えばこんなメソッドを作ったとして(StreamAPI使えとか無粋なことは言わないこと)、
Listで同じことをしたい場合、メソッドのオーバーロードをしなくてはいけません。

public static Integer firstOrDefault(List<Integer> list, Integer defaultValue) {
    return list != null && list.size() > 0 ? list.get(0) : defaultValue;
}

ひい。めんどくさい。
ということでこんな風にします。

public static <T> T firstOrDefault(List<T> list, T defaultValue) {
    return list != null && list.size() > 0 ? list.get(0) : defaultValue;
}

第一引数に「List<String>」、第二引数に「String」を渡せばちゃんと動きます。
第一引数に「List<Integer>」、第二引数に「Integer」を渡してもちゃんと動きます。
第一引数に「List<String>」、第二引数に「Integer」を渡したらコンパイルエラーになります。
えらい。

引数の前についてる<T>がポイント。
ここでなんかよくわからんけど、なんでも良い型「T」っていうのが定義されます。

public static <T extends Number> T firstOrDefault(List<T> list, T defaultValue) {
    return list != null && list.size() > 0 ? list.get(0) : defaultValue;
}

こんな風にすると、Numberを継承した何か。
になるので、IntegerはOKだけどStringはNGになります。

C#的にはこんな感じ。

public static T FirstOrDefault<T>(IList<T> list, T defaultValue) where T : struct
{
    return list != null && list.Count > 0 ? list[0] : defaultValue;
}

で、このTってコンパイラ的にはどうやって決めてるのっていうのが今日のお話。

明示的に指定

class Util {
    public static <T> List<T> newArrayList() {
        return new ArrayList<>();
    }
}

例えばこんな定義をしたとして、

Util.<String>newArrayList();

こんな風にメソッド呼び出し時に明示的にTの型を指定してあげることができます。

引数の型で指定

class Util {
    public static <T> List<T> newArrayList(Class<T> c) {
        return new ArrayList<>();
    }
}

次にこんな風に定義を変えてみると、

Util.newArrayList(String.class);

ジェネリクスの指定なしに呼び出すことができました。
これは引数の型情報からTの型がわかるからです。

戻り値の型で指定

class Util {
    public static <T> List<T> newArrayList() {
        return new ArrayList<>();
    }
}

メソッド定義を元に戻してみて、

List<String> list = Util.newArrayList();

戻り値側で型を決めてしまえば、明示的に指定しなくてもOKになりました。
やったね。

が、これをC#でやろうとするとエラーになります。

class Util
{
    public static IList<T> newList<T>()
    {
        return new List<T>();
    }
}

こんな定義をして、

IList<string> list = Util.newList<string>();  // OK
IList<string> list = Util.newList();  // NG

試してみると、戻り値側で型が判定できても明示的に指定しないとコンパイルエラーになってしまいます。

いいとこどりしたい

とまぁ、JavaからC#に移ってきた私としてはちょっと不満ではあるのですが、
C#ジェネリックにはJavaにはない素晴らしい機能があります。

class Util
{
    public static T newInstance<T>() where T : new()
    {
        return new T();
    }
}

そう、newができるのです。
これは型削除されてしまうJavaにはできない芸当です。
(可変長引数とか使って無理やり頑張るとできるとかできないとか・・・)

いいとこどりしたいなー。
できるようにならないかなー。なんて。

fastTextで単語のベクトル演算

こんにちは。

随分と空いてしまいました。
3月なのに今年最初のエントリってどういうことじゃい…。

以前、fastTextのインストールをしましたが、実際に使っていなかったので、
今回は適当な文章から単語のベクトルを学習させて、その演算を試してみます。

ちなみにfastTextは分類によく利用されるため、
分散表現を使ってどうこうするのはあんまりポピュラーではないです。
が、せっかく機能としてあるので使ってみます。

何ができるの?

「パリ」 - 「フランス」 + 「日本」 = 「東京」
とか
「王様」 - 「男」 + 「女」 = 「女王」
みたいなやつです。

これはfastText固有の機能というわけじゃなくて、
fastTextの元になっている(元っていうのも違う気がするけれど)、word2vecで有名な機能です。

学習する

詳しい説明は世の中にたくさんあるので、さっくりと端折ります。
今回学習させるデータはWikipedia日本語版の全データです。

https://dumps.wikimedia.org/jawiki/

この辺とかから取れます。

これを分かち書きするのですが、その辺りはお好きな言語とライブラリで頑張りましょうということで、
これについても端折ります。

アンパサンド ( ampersand , &) と は 「 … と … 」 を 意味 する 記号 で ある 。 英語 の " and " に 相当 する ラテン語 の " et " の 合 字 で 、 " etc ." ( et cetera = and so forth ) を "& c ." と 記述 する こと が ある の は その ため 。 Trebuchet MS フォント で は 、 10 px と 表示 さ れ " et " の 合 字 で ある こと が 容易 に わかる 。

こんな感じで半角スペース区切りで分かち書きします。

で、こんな感じで学習させます。

# /fastText/fasttext skipgram -input wakachi.txt -output model

「skipgram」と言うのは単語表現のモードです。
他には「cbow」を指定できます。

github.com

他にもパラメタが色々あるのですが、面倒なのでそのままで試してみました。

・・・が、表現するベクトルの次元を指定する「dim」と、学習の繰り返し回数を指定する「epoch」くらいは
ちゃんと指定しておいたほうが良いと思います。
(ちなみにDockerの設定も特にいじっていなかったので、メモリ2Gしか使わなくてやたら時間が掛かりました)

試す

例として挙げた足し算引き算みたいのを試すには「analogies」と言うコマンドを使います。

# /fastText/fasttext analogies model.bin
Pre-computing word vectors... done.
Query triplet (A - B + C)? 

「triplet」・・・なるほど。
で、対話型で順番に入れると、

Query triplet (A - B + C)? 王様
男
女
魔女 0.704916
奥様 0.665025
待ち遠しい 0.65906
魔法使い 0.655222
花嫁 0.652865
妖精 0.645881
王さま 0.643353
美女 0.642074
ゴチソウノォト 0.640036
シンデレラ 0.639036

ほら完璧・・・ってなんか違くない?
魔女・・・魔女かぁ、あながち間違いでもないかもなー。

Query triplet (A - B + C)? パリ
フランス
日本
東京 0.724273
都内 0.689351
大阪 0.61897
京都 0.612206
ソウル 0.605822
昭和女子大 0.605477
台北 0.603844
横浜 0.60244
渋谷 0.599425
神楽坂 0.596325

こっちはあってそうですね。

まとめ

ある程度の文章量を分かち書きしてfastTextに突っ込むとベクトル演算ができるのがわかりました。
Wikipedia全文とかだと、次元数が足りていない感じなので200くらいにした方がいいのかな?

そのうちに今度はカテゴライズをやってみようと思います。
こっちこそがfastTextのメイン機能なはず!

それではまた。