白猫のメモ帳

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

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のメイン機能なはず!

それではまた。

staticイニシャライザとstaticコンストラクタは同じだと思っていました

こんにちは。

年の瀬ですね。
お仕事は納まりましたか。
あれ・・・なんかこれ去年も書いた気がする。

そもそもイニシャライザとかコンストラクタってなんだっけ

Java

staticイニシャライザはJavaの機能。

class Hoge {
    
    static String fuga;
    
    // staticイニシャライザ
    static {
        System.out.println("static initializer");
    }
    
    // イニシャライザ
    {
        System.out.println("initializer");
    }

    // コンストラクタ
    Hoge() {
        System.out.println("constructor ");
    }
}

Javaにはコンストラクタとは別にイニシャライザという機能があって、
文字通りイニシャライズ(初期化)する時に呼ばれます。

staticイニシャライザはクラスにstaticアクセスしたときに呼ばれるため、
staticフィールドにアクセスしたり、インスタンス生成すると呼ばれます。

一方で、staticでないイニシャライザとコンストラクタはクラスのインスタンスを生成したときに呼ばれます。

// static initializer
String fuga = Hoge.fuga;

// static initializer
// initializer
// constructor 
new Hoge();

C#

で、staticコンストラクタはC#の機能。

class Hoge
{

    internal static string fuga;

    // staticコンストラクタ
    static Hoge()
    {
        Console.WriteLine("static constructor");
    }

    // コンストラクタ
    internal Hoge()
    {
        Console.WriteLine("constructor");
    }
}

C#にはイニシャライザという機能はなくて、
クラスにアクセスした際に処理を実行したい場合はstaticコンストラクタを使います。

// static constructor
var fuga = Hoge.fuga;

// static constructor
// constructor
new Hoge();

ここまでの感じでは同じように使っていいように思えます。

継承してみる

継承のあるパターンを確認してみます。

Java

class Parent {
    
    static String hoge;
    
    static {
        System.out.println("Parent static initializer");
    }
    
    {
        System.out.println("Parent initializer");
    }

    Parent() {
        System.out.println("Parent constructor ");
    }
}

class Child extends Parent {
    
    static String fuga;
    
    static {
        System.out.println("Child static initializer");
    }
    
    {
        System.out.println("Child initializer");
    }

    Child() {
        System.out.println("Child constructor ");
    }
}

子クラスから親クラスのstaticフィールドにアクセスすると、親クラスのstaticイニシャライザだけが呼ばれます。

// Parent static initializer
String hoge = Child.hoge;

子クラスのstaticフィールドにアクセスすると、親クラスと子クラスのstaticイニシャライザが呼ばれます。

// Parent static initializer
// Child static initializer
String fuga = Child.fuga;

親クラスのインスタンスを生成すると、親クラスのstaticイニシャライザ、イニシャライザ、コンストラクタが呼ばれます。

// Parent static initializer
// Parent initializer
// Parent constructor 
new Parent();

子クラスのインスタンスを生成すると、全部呼ばれます。順番は見たとおり。

// Parent static initializer
// Child static initializer
// Parent initializer
// Parent constructor 
// Child initializer
// Child constructor 
new Child();

C#

class Parent
{

    internal static string hoge;

    static Parent()
    {
        Console.WriteLine("Parent static initializer");
    }

    internal Parent()
    {
        Console.WriteLine("Parent constructor ");
    }
}

class Child : Parent
{

    internal static string fuga;

    static Child()
    {
        Console.WriteLine("Child static initializer");
    }

    internal Child()
    {
        Console.WriteLine("Child constructor ");
    }
}

子クラスから親クラスのstaticフィールドにアクセスすると、親クラスのstaticコンストラクタだけが呼ばれます。
ここは一緒。

// Parent static constructor
var hoge = Child.hoge;

子クラスのstaticフィールドにアクセスすると、子クラスのstaticコンストラクタだけが呼ばれます。
あれ、なんか挙動が違うぞ・・・。

// Child static constructor
var fuga = Child.fuga;

親クラスのインスタンスを生成すると、親クラスのstaticコンストラクタ、コンストラクタが呼ばれます。
ここは一緒。

// Parent static constructor
// Parent constructor
new Parent();

子クラスのインスタンスを生成すると、全部呼ばれます。
が、なんか順番が違います。
staticコンストラクタは子が先で、コンストラクタは親が先。ややこしい…。

// Child static constructor
// Parent static constructor
// Parent constructor
// Child constructor
new Child();

深くは掘り下げず、そういうもんだと思ってほかのパターンも見ていきます。

クラスジェネリクスをつけてみる

なんかずいぶん長くなってきてしまったので、いらないとこ除外しますね。

Java

class Parent<T> {
    
    static {
        System.out.println("Parent static initializer");
    }

    Parent() {
        System.out.println("Parent constructor ");
    }
}

class Child1 extends Parent<String> {
    
    static {
        System.out.println("Child1 static initializer");
    }

    Child1() {
        System.out.println("Child1 constructor ");
    }
}

class Child2 extends Parent<Integer> {
    
    static {
        System.out.println("Child2 static initializer");
    }

    Child2() {
        System.out.println("Child2 constructor ");
    }
}

Child1とChild2を連続してインスタンス生成すると、Parentのstaticイニシャライザは1回しか呼ばれません。
これは型削除(イレイジャ)という機能によって、コンパイル時にジェネリクスが消されているからでしょう。

// Parent static initializer
// Child1 static initializer
// Parent constructor 
// Child1 constructor 
new Child1();

// Child2 static initializer
// Parent constructor 
// Child2 constructor 
new Child2();

C#

class Parent<T>
{
    static Parent()
    {
        Console.WriteLine("Parent static constructor");
    }

    internal Parent()
    {
        Console.WriteLine("Parent constructor ");
    }
}

class Child1 : Parent<string>
{
    static Child1()
    {
        Console.WriteLine("Child1 static constructor");
    }

    internal Child1()
    {
        Console.WriteLine("Child1 constructor ");
    }
}

class Child2 : Parent<int>
{
    static Child2()
    {
        Console.WriteLine("Child2 static constructor");
    }

    internal Child2()
    {
        Console.WriteLine("Child2 constructor ");
    }
}

Child1とChild2を連続してインスタンス生成すると、Parentのstaticイニシャライザが2回呼ばれます。
C#では型削除は行われないので(内部実装的にはプリミティブは展開して、それ以外は共有している?)、別のクラスとして扱われるのでしょう。

これは別にstaticイニシャライザ/コンストラクタの機能の違いというわけではなく、
あくまで副次的なものだと思いますが、なかなかにややこしいです。

おまけ 自身のインスタンスをstaticに持つ場合

これはJavaC#も挙動は同じなのですが、シングルトンパターンなんかでよくやる、
自分自身のインスタンスをstaticで保持する場合の呼び出し順が難しいのでついでに見ておきます。

class Parent {
    
    static {
        System.out.println("Parent static initializer");
    }

    Parent() {
        System.out.println("Parent constructor ");
    }
}

class Child extends Parent {
    
    static Child singleton = new Child();
    
    static {
        System.out.println("Child static initializer");
    }

    private Child() {
        System.out.println("Child constructor ");
    }
}

なんでstaticがあとにくるんじゃーい。

// Parent static initializer
// Parent constructor 
// Child constructor 
// Child static initializer
Child child = Child.singleton;

ほかにも違いがあるんですかね

普通にコードを書いている上ではそんなに出会うパターンではないとは思うのですが、
なんだかこの辺りって難しいですよね。

誰にでもわかりやすいコードを書けるようにしたいものです。

fastTextをインストールしてみる

こんばんは。

寒さに負けて早くもムートンブーツを出してしまったのですが、
もっと寒くなったらどんな格好をすれば良いのでしょうか。

さて、今日はFacebookの公開している自然言語処理ライブラリ「fastText」を使ってみたいので、
その環境構築をしてみます。

インストールするぞ

とりあえず調べたらコマンドライン実行のための構築手順と、
pythonライブラリのための構築手順がごっちゃになってヒットしたのでしょっぱなから混乱します。
どどどういうことなの・・・私はpythonは書けないのでコマンド実行が良いのですが・・・。

ググる

とりあえず、わかったことはWindows環境よりもLinux環境の方が良さそうということ。
というわけで、MacOSにDockerコンテナを立てて試してみることにします。
こういう時にDockerはミスったらやり直しができるので便利ですね。

というわけで余談ですが、珍しくMacで記事も書いているのでなんだかわたわたします。

とりあえずDockerコンテナを立てよう

Dockerのインストールは良い・・・ですよね?

Docker For Mac | Dockerから落としてきて、
言われた通りに操作すればあら簡単という感じですぐにインストールできます。
Bash On Windows(名前変わるんでしたっけ?)でも同じようなもんだと思います。試してないけど。

まずはイメージの確認。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

当然何もありません。
じゃあまずは空っぽのCentOSのイメージを探して来て・・・とか思いますが、
Dockerさんはrunする時にイメージがなければちゃんと取って来てくれる賢い子なので気にしないことにします。

$ docker run -it --name fasttext centos
Unable to find image 'centos:latest' locally
latest: Pulling from library/centos
85432449fd0f: Pull complete 
Digest: sha256:3b1a65e9a05f0a77b5e8a698d3359459904c2a354dc3b25ae2e2f5c95f0b3667
Status: Downloaded newer image for centos:latest
[root@596e34b5ca1f /]# 

素晴らしく簡単。-itオプションは起動したコンテナにそのままアタッチする呪文です。
あ、そうでした。Dockerがメインじゃないんでした。

Let's 環境構築

プレーンなCentOSコンテナでコマンド実行用のfastTextを使うのに必要なのは以下。

これらをyumで取って来ます。

# yum install -y git make gcc gcc-c++
(なんかいっぱい出る)

あ、コンテナ内は「#」つけますね。

で、cloneしてmake。
コンテナ内だからルートで良いやという適当さ。

# git clone https://github.com/facebookresearch/fastText.git
(なんかちょっと出る)
# cd fastText
# make
(なんかちょっと出る)

これだけでおしまい。簡単。
一応インストールできているかちょっと確認。

# ./fasttext     
usage: fasttext <command> <args>

The commands supported by fasttext are:

  supervised              train a supervised classifier
  quantize                quantize a model to reduce the memory usage
  test                    evaluate a supervised classifier
  predict                 predict most likely labels
  predict-prob            predict most likely labels with probabilities
  skipgram                train a skipgram model
  cbow                    train a cbow model
  print-word-vectors      print word vectors given a trained model
  print-sentence-vectors  print sentence vectors given a trained model
  print-ngrams            print ngrams given a trained model and word
  nn                      query for nearest neighbors
  analogies               query for analogies

大丈夫そうですね。

最小構成がわかるまでに余計なものを色々インストールしてコンテナ作りなおしたのは秘密。

今回はこれでおしまい。
次回はfastTextを使ってみます。