Go
MachineLearning
word2vec
GloVe
WordEmbedding
More than 1 year has passed since last update.

はじめに

この記事はGo4 Advent Calender 2017の10日目の記事です。

Golangを使って自然言語処理(NLP)におけるWord embeddingのモデルをfrom scratchで実装してみたので、そのご紹介をさせていただきます。

What's word embedding?

Word embeddingとは「単語の意味や文法を捉えるように、ベクトル空間に写像する」 ことやそれを実現するモデルを指します。代表的なツールとしてWord2Vecがあり、こちらの単語の方が知っている方もいらっしゃるかもしれません。

単語がベクトルになったので、単語の意味をベクトルの演算で獲得できたりします。以下は有名な例ですね。

King - Man + Woman = Queen

この構造自体を使ったアプリケーションが存在したり、別のNLPのタスク(e.g. Sentiment analysis, Named entity recognition)の入力/補完のために使われたりします。

...といったことができるモデルをGolangで実装してみました。現在はWord2VecとGloVeがあります。

理論的な解説については、Word2Vecは様々な方がご紹介されているのでそちらを、GloVeについては別記事をしたためたので参照していただけると幸いです。

少しGolang以外の事前知識を要求してしまいましたが、ご了承ください。

作ったもの

はじめにレポジトリはこちらになります。

CLIから利用できます。モデル毎(word2vec, glove)にサブコマンドに分かれています。またハイパーパラメータの設定についての詳細はREADMEをご覧ください。

例として:

$ word-embedding word2vec -i text8 -o example/word2vec_sg_ns.txt --model skip-gram --optimizer ns -d 100 -w 5 --verbose

学習データとしてはwhitespaceで区切られたコーパスを与えていただく形式になります。お試し用のdemo.shも用意させていただきました。text8を利用して学習しています。

code-basedでも利用できます > example.go

package main

import (
    "os"

    "github.com/ynqa/word-embedding/builder"
)

func main() {
    b := builder.NewWord2VecBuilder()

    b.SetDimension(10).
        SetWindow(5).
        SetModel("cbow").
        SetOptimizer("ns").
        SetNegativeSampleSize(5).
        SetVerbose()

    m, err := b.Build()

    if err != nil {
        // Failed to build word2vec.
    }

    inputFile, _ := os.Open("text8")

    f, err := m.Preprocess(inputFile)

    if err != nil {
        // Failed to Preprocess.
    }

    // Start to Train.
    m.Train(f)

    // Save word vectors to a text file.
    m.Save("example.txt")
}

こちらの場合のハイパーパラメータの与え方は、gensimのそれよりもDeepLearning4jのようにBuilder patternを利用するようにしました。

学習が完了したら以下のような単語と横に並んだ数値が記述されたファイルが生成されます(一部抜粋)。この数値が単語に対応するベクトルの要素です。

.
.
implies 0.8050302902386232 ...
retardation 0.09584770941636722 ...
fulfilling 0.3856524018054331 ...
rising -0.2590658187029575 ...
sharply -0.010006702028302616 ...
indicating 0.4769265104449138 ...
.
.

Lock-freeなアルゴリズムを利用して非同期に単語ベクトルを学習しているので、冪等性は保証されていません。
また現在はraw textでのみ出力をサポートしていますが、今後はnpy/gobにも対応できたらと思っています。

ただ、これらは人目にとってただの数値の羅列以上の情報が得られないので、distanceというサブコマンドを用意しました。
これはオリジナルのword2vecのコードと同じように、与えられた単語に最もcos類似度が高い順に別の単語を並べてくれます。

$ word-embedding distance -i example/word2vec_sg_ns.txt microsoft
  RANK |   WORD    |  COSINE
+------+-----------+----------+
     1 | ibm       | 0.995206
     2 | pc        | 0.992438
     3 | os        | 0.992154
     4 | cd        | 0.991713
     5 | mac       | 0.989921
     6 | versions  | 0.989613
     7 | dos       | 0.989581
     8 | server    | 0.989553
     9 | computers | 0.989096
    10 | apple     | 0.988126

以上が、このライブラリができることの一連の流れでした。いかがでしょうか。本来はGolangやアルゴリズムのテクニックも色々と書きたかったのですが、このプロジェクトを初めて記事にして公開するので、紹介記事にさせていただきました。

利用したライブラリ/ツール

Golangにおけるpackage manager。以前はglideを利用していたのですが、Golangのv1.9以降ではdepに移行しました。標準でtest/fmtがvendorを無視してくれるようになったので、特に利用せずとも問題がなくなりました。

CLI使う場合にはこれをまず検討しておけばいいと思います。ただそれなりに多機能なので、そこまで大きくないプロジェクトになりそうな場合は、別のものを探してみてもいいかもしれません。

GolangにもBLASがいくつかありますが、パフォーマンスチューニングの部分でまだ制御ができなかった(Cgo/Assemblyが出現するあたり。ベクトルの長さでパフォーマンスが変わる。Cgoが遅い等)ので、今回は学習部分では利用しませんでした。今後は検証を重ねて積極的に取り組んでいきたいところです。ちなみにdistanceによるcos類似度の計算には以下のtensorを利用しています。

どちらもオリジナルなBLASを実装しつつも、OpenBLAS/MKLも利用できるのでとてもいいと思いました。

Future Works

今後もモデルを追加したり、単語ベクトルを生成後に何か操作できるような(それこそベクトル同士の演算がCLI/REPLで行えるといった)機能を作っていこうと思います。また、パフォーマンスチューニングもどんどん行なっていこうと思います。Golangはプロファイリングが実行しやすいので、とても助かります。

最後にご意見/コメントをいただけると助かります。もちろんPRやIssueも受け付けております!