金融と工学のあいだ

興味関心に関するメモ(機械学習、検索エンジン、プログラミングなど)

golang設定

やりたいこと

設定のメモ

wantedlyの資料

やっぱり1から書いてみる

まずは簡単にサーバを立ててみよう

ファイル構成はこんな感じ

$ tree ../
../
└── goweb
    ├── LICENSE
    ├── README.md
    └── main.go

main.goの中身はこんな感じ。

package main

import (
    "fmt"
    "html"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
    })

    log.Fatal(http.ListenAndServe(":8080", nil))
}

実行してみた

$ go run main.go

ブラウザから見た感じ f:id:kumechann:20170812002319p:plain

routerを使ってみた

  • routerは"github.com/gorilla/mux"と言うものが有名らしい
  • $ go getすると勝手に関連モジュールがdownloadされる
package main

import (
    "fmt"
    "net/http"
    "github.com/gorilla/mux"
)

func main() {
    r := mux.NewRouter()

    // 単純なハンドラ
    r.HandleFunc("/", Index)

    // パスに変数を埋め込み
    r.HandleFunc("/users", UserIndex)

    // パスに変数を埋め込み
    r.HandleFunc("/users/{userId}", UserIndexId)

    // パス変数で正規表現を使用

    // マッチするパスがない場合のハンドラ
    r.NotFoundHandler = http.HandlerFunc(NotFoundHandler)

    // http://localhost:8080 でサービスを行う
    http.ListenAndServe(":8080", r)
}

func Index(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Index!\n"))
}

func UserIndex(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("UserIndex!\n"))
}

func UserIndexId(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    fmt.Fprintf(w, "%s user index\n", vars["userId"])
}

func NotFoundHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Gorilla!\nNot Found 404\n"))
}

JSONを返却する

user.goにuser型の定義を書く

package main

type User struct {
    Id      string
    Name    string
}

type Users []User

UserIndexをJSONを返すように書き直す

func UserIndex(w http.ResponseWriter, r *http.Request) {
    users := Users{
        User{Id: "00000001", Name: "keisuke"},
        User{Id: "00000002", Name: "yusuke"},
    }
    json.NewEncoder(w).Encode(users)
}

最終型

github.com

  • つけた機能
    • メモリ上に配置した仮想DB
    • データのPOST機能
    • error機能

Chainerによるword2vecのチュートリアル(1)

word2vecとは

導入

  • word2vecは単語の分散表現を生成する手法です。単語の意味が近いほど類似度が大きくなるように、各単語に実ベクトルを割り当てる手法です。
  • そもそも単語の意味とはなんでしょうか。
    • 人であれば動物と犬という単語が似ているというのはなんとなく分かります。
    • しかし、word2vecは何の情報を元に、動物と犬は似ているとか、食べ物と犬は似ていないといった意味の類似度を学習すれば良いのでしょうか。

基本的なアイデア

  • word2vecは単語の意味の類似度を単純な情報から学習します。
  • それは文章における単語の並びです。ある単語の意味は、その単語の周囲の単語で決まるというアイデアです。
  • 学習対象の単語をTarget Word、その周囲の単語をContext Wordと呼びます。ウィンドウサイズcに応じてContex Wordの数は変わります。
  • 例として、The cute cat jumps over the lazy dog. という文で説明を行います。
    • 以下の図は全てTarget Wordをcatとした場合のものです。
    • ウィンドウサイズcに応じて、catを学習する際に使用するContext Wordが変わることがわかると思います。
  • f:id:kumechann:20170628152453p:plain

主なアルゴリズム

  • word2vecと呼ばれる手法は実はSkip-gramとCBoWという2つの手法の総称です。
  • 以下で図を使って説明しますが、その時の記号の意味をここで説明します。
    • N:ボキャブラリ数。
    • D:分散表現のベクトルのサイズ。
    • {v_t} :Target Word。サイズは[N,1]。
    • {v_{t+c}}:Context Word。サイズは[N,1]。
    • {L_H}:隠れ層。サイズは[D,1]。
    • {L_O}:出力層。サイズは[N,1]。
    • {W_H}:隠れ層重み行列。サイズは[N,D]。
    • {W_O}:出力層重み行列。サイズは[D,N]。

Skip-gram

  • Target Word {v_t}が与えられた時、Context Word {v_{t+c}}が出現することを予測するように学習する
  • この時、隠れ層重み行列W_Hの各行がそれぞれの単語の分散表現になる。

  • f:id:kumechann:20170628153848p:plain

Continuous Bag of Words (CBoW)

  • Context Word {v_{t+c}}が与えられた時、Target Word {v_t}が出現することを予測するように学習する
  • この時、出力層重み行列{W_O}の各列がそれぞれの単語の分散表現になる。

  • f:id:kumechann:20170628153915p:plain

Skip-gramの詳細

  • チュートリアルでは、以下の観点からSkip-gramをメインで扱います。
    1. 学習アルゴリズムがCBoWに比べて理解しやすい
    2. 単語数が増えても精度が落ちにくく、スケールしやすい

具体例を使った説明

  • 上の例と同じように、ボキャブラリ数Nは10、分散表現のベクトルのサイズDは2とします。
  • 犬という単語をTarget Word、動物という単語をContext Wordとし学習する様子を説明します。
  • Context Wordは複数あるはずなので下記工程をContext Word分繰り返します。

    1. 犬という単語の局所表現は[0 0 1 0 0 0 0 0 0 0]であり、それをTarget Wordとして入力します。
    2. この時、隠れ層重み行列{W_H}の3行目が隠れ層{L_H}になります。
      • ちなみに、ここの値が学習後には犬という単語の分散表現になります。
    3. 出力層重み行列{W_O}と隠れ層{L_H}をかけた結果が出力層{L_O}となります。
    4. 出力層の各要素の値を制限するために、出力層{L_O}にsoftmax関数を適用し{W_O}を計算する
      • 最終的には出力層とContext Wordの誤差を出し、その誤差をネットワークに逆伝播することでパラメータの更新を行う必要があります。
      • しかし、出力層の各要素の値は範囲が制限されていないため-∞〜+∞までの値をとります。Context Wordの局所表現は、[1 0 0 0 0 0 0 0 0 0]のように各要素は0か1の値しか取りません。
      • 出力層の各要素の値を0〜1に制限するため、各要素の値を0〜1の範囲に制限する関数softmaxを適用します。
    5. {W_O}とanimalの局所表現[1 0 0 0 0 0 0 0 0 0]の誤差を計算し、その誤差をネットワークに逆伝播させてパラメータを更新する
  • f:id:kumechann:20170628155336p:plain

Chainerによる実装方法

実装方法

  • 基本的にchainerを使用する場合にはこのような形でimportをします。
    • functionsをF、linksをLのような形でimportすると使いやすいです。
  • 次にskip-gramのネットワーク構造の定義です。
    • コンストラクタ__init__に、ボキャブラリ数n_vocab、分散ベクトルのサイズn_units、損失関数loss_funcを渡すようになっています。
      • init_scope()内でParameterの初期化を行っています。
      • ここで、self.embed内の重み行列Wが隠れ層重み行列{W_H}にあたります。
      • 注意してもらいたいのが、Skip-gramの場合、Target WordとContext Wordを1対1で対応するため、Context WordとTarget Wordを入れ替えても問題がなく、Context WordとTarget Wordを入れ替えて学習させています。(CBoWモデルとコードの整合性が取りやすいからです。)
    • 関数呼び出し__call__は、Target WordのIDx、Context WordのIDcontextを受取り損失関数loss_funcで誤差を返します。
      • e = self.embed(context)でcontextに対応する分散表現を取得しています。
      • batch_size分のTarget WordのIDxをContext Wordの数だけbroad castします。
      • x[batch_size * n_context,], e[batch_size * n_context, n_units]になります。
  • 損失関数の定義です。実質的には、skip-gramのネットワーク構造の定義をしています。
    • xに対して重み行列による線形写像self.out(x) (self.out:= L.Linear(n_in, n_out, initialW=0)(x))を計算した後、F.softmax_cross_entropyを計算します。
    • ここで、線形写像self.out(x)は出力層重み行列{W_O}F.softmax_cross_entropyはsoftmax関数と損失計算部分に該当します。
  • Iteratorの定義
    • コンストラクタ__init__に、単語idのリストによる文書データセットdataset、ウインドウサイズwindow、ミニバッチサイズbatch_sizeを渡すようになっています。
      • この中で、文書中の単語の位置をシャッフルしたリストself.orderを作成しています。それは学習する時に、文書の最初から最後まで順番に学習するのではなく、文書からランダムに単語を選択し学習するようにするためです。ウィンドウサイズ分だけ最初と最後を切り取った単語の位置がシャッフルされて入っています。
      • 例:文書データセットdataset中の単語数が100個、ウインドウサイズwindowが5だった場合、self.orderは5から94までの数字がシャッフルされたnumpy.arrayになる。
    • イテレータの定義__next__は、コンストラクタのパラメータに従ってミニバッチサイズ個のTarget Word centerとContext Word contextを返します。
      • position = self.order[i:i_end]で、単語の位置をシャッフルしたリストself.orderからbatch_size分のTarget Wordのインデックスpositionを生成します。(positionは後でself.dataset.takeによってTarget Word centerに変換されます。)
      • offset = np.concatenate([np.arange(-w, 0), np.arange(1, w + 1)])で、ウインドウを表現するオフセットoffsetを作成しています。
      • pos = position[:, None] + offset[None, :]によって、それぞれのTarget Wordに対するContext Wordのインデックスposを生成します。(posは後でself.dataset.takeによってContext Word contextに変換されます。)
  • main関数
    • データ取得
      • trainvalにはそれぞれtrainingデータ、validationデータが入っています。単語のidの列で文書を表現しています。

          >>> train
          array([ 0,  1,  2, ..., 39, 26, 24], dtype=int32)
          >>> val
          array([2211,  396, 1129, ...,  108,   27,   24], dtype=int32)
        
      • trainingデータtrainに含まれる最大のid+1の値がボキャブラリ数n_vocabになります。

    • 損失関数作成
    • モデル作成
    • Optimizer作成、Trainer作成、実行を下記コードで行っています。
      • 注意してほしいのは、CPUでもGPUでも動くように、GPUの場合にだけGPU上のメモリにデータを転送する関数convertが引数converterとして渡されています。
      • また、trainer.extendによってログ出力などの機能拡張が行われています。

実行方法

$ pwd
/root2chainer/chainer/examples/word2vec
$ $ python train_word2vec.py --test  # test modeで実行。全データで学習したいときは--testを消去
GPU: -1
# unit: 100
Window: 5
Minibatch-size: 1000
# epoch: 20
Training model: skipgram
Output type: hsm

n_vocab: 10000
data length: 100
epoch       main/loss   validation/main/loss
1           4233.75     2495.33               
2           1411.14     4990.66               
3           4233.11     1247.66               
4           2821.66     4990.65               
5           4231.94     1247.66               
6           5642.04     2495.3                
7           5640.82     4990.64               
8           5639.31     2495.28               
9           2817.89     4990.62               
10          1408.03     3742.94               
11          5633.11     1247.62               
12          4221.71     2495.21               
13          4219.3      4990.56               
14          4216.57     2495.16               
15          4213.52     2495.12               
16          5616.03     1247.55               
17          5611.34     3742.78               
18          2800.31     3742.74               
19          1397.79     2494.95               
20          2794.1      3742.66

リファレンス

単語の分散表現のチュートリアル

単語の意味の表現

なぜ単語の意味を表現する必要があるのか

  1. 表現することで機械に処理させることができるから
    • 機械に処理を行わせるためには、処理したい対象を数値列として表現しなくてはなりません。例えば、私たちが普段見ている画像も各画素のRGBの値として表現しており、その数値列を用いて記録を行ったり、描画したりしています。
    • 自然言語においても、同様のことが言えます。自然言語の意味を機械に処理させたい場合には、その意味を数値列で表現しなくてはなりません。
  2. 単語が意味を成す最小の構成単位だから
    • 自然言語の場合、意味を成す最小の構成単位は「単語」です。意味の最小構成単位である単語を表現できれば、その組み合わせである文やパラグラフ、文章の意味を表現することはそこまで難しくないのでは?(本当は難しいのですが…)と思えるでしょう。
    • 上記の理由から、単語の意味を表現するモチベーションは高いです。

機械による単語と意味の表現方法

  • ここで今すぐに単語の意味の表現の話にうつるべきですが、そもそもどのように単語を機械的に扱ってきたか振り返ろうと思います。
  • 例として、「動物」、「食べ物」、「犬」、「猫」、「猿」、「人間」、「トマト」、「りんご」、「バナナ」、「肉」の計10単語を扱います。
    1. idによる表現
      • これは最もシンプルな方法であり、単純に単語それぞれにユニークな番号を割り当てていくだけです。

        • 例:左から右にidを0から割り当てていくと、id=0は動物、id=1は食べ物、・・・、id=9は肉となる。

            words = [
                    "animal",
                    "food",
                    "dog",
                    "cat",
                    "monkey",
                    "human",
                    "tomato",
                    "apple",
                    "banana",
                    "meat"
                    ]
          
            print(words[0])  # animal
            print(words[2])  # dog
          
      • この方法のメリットは、誰でもすぐに思いつく方法であり、とても単純の方法であることです。

        1. 1つの数字(id)でどの単語か表現ができる
        2. 機械としても、1つの数値で単語を表現できるのでメモリ効率が良い
      • しかし、この方法には大きな問題があります。それはここで単語に割り当てられるidと単語の意味には何の関係もないことです。
        • 例:「動物(id=0)」に対して「食べ物(id=1)」と「犬(id=2)」だと食べ物の方がidが近いが、それは動物という単語に対して「食べ物」の方が「犬」より意味が近いことを表現しているわけではない
      • 特にこの問題は機械学習などの数値解析的手法を適用するときに明らかになります。(例えば、数直線状に上記単語がidの位置に配置されている図を想像して、それを意味のまとまりごとに分断する線を引けるか、考えてみるといいかもしれません。)
    2. 局所表現(local representation)
      • idによる表現は確かに単純ですが、大きな問題を抱えています。この問題を解決するために局所表現、特にその代表的な表現方法「one-hot表現」があります。
      • 全単語数の長さのvectorをそれぞれの単語に用意し、idの位置の値だけ1にし、残りを0にします。

        • 例:[1 0 0 0 0 0 0 0 0 0]は動物、[0 1 0 0 0 0 0 0 0 0]は食べ物、・・・、[0 0 0 0 0 0 0 0 0 1]は肉となります。

            words = [
                    "animal",
                    "food",
                    "dog",
                    "cat",
                    "monkey",
                    "human",
                    "tomato",
                    "apple",
                    "banana",
                    "meat"
                    ]
          
            representations = {
                    "animal":[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    "food":[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
                    "dog":[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
                    "cat":[0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
                    "monkey":[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
                    "human":[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
                    "tomato":[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
                    "apple":[0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
                    "banana":[0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
                    "meat":[0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
                    }
          
            print(representations[words[0]])  # [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
            print(representations[words[2]])  # [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
          
      • この方法のメリットは以下になります。

        1. idによる表現よりやや複雑になったが、全単語数を数えた後、順に1つずつone-hot表現を単語に割り当てるだけで良いので単純に生成できる
        2. 全ての単語が空間上で等距離に配置され、単語の距離が近い・遠いという状態がなくなり、idによる表現で問題になっていたことが無視できる
      • しかし、この方法も依然として大きな問題を抱えています。
        1. 全ての単語を等距離に配置してしまったため、似たような意味の単語も全く異なった単語として扱われる
          • 例:動物は[1 0 0 0 0 0 0 0 0 0]、犬は[0 0 1 0 0 0 0 0 0 0]であるが、この2単語の距離は他の単語と較べても変わらず、その他の単語と同様完全に数値的に独立した単語として扱われてしまう。
        2. 全単語数に応じてone-hot表現の次元数が線形に増えてしまう
        3. あらかじめ全単語数を知っていないとone-hot表現を作成できない
          • 例:1度one-hot表現を作った後、単語を追加すると新たにone-hot表現を全単語に対してつくりなおさないといけない
    3. 分散表現(distributed representation)、またな単語埋め込み(word embeddings)
      • idによる表現も局所表現も問題を抱えています。その共通する問題として、単語の意味を適切に表現できていないということが挙げられると思います。
      • この問題に対応するため、分散表現、もしくは単語埋め込みと呼ばれる手法が生まれました。
      • 分散表現は、単語を高次元の実数ベクトルで表現する手法です。この時、それぞれの単語は意味が近いほど距離が近くなるように実数ベクトルが割り当てられていきます。

        • 例:今回はグラフ化できるように各単語の次元数を2にしました。

            import matplotlib.pyplot as plt
          
            words = [
                    "animal",
                    "food",
                    "dog",
                    "cat",
                    "monkey",
                    "human",
                    "tomato",
                    "apple",
                    "banana",
                    "meat"
                    ]
          
            representations = {
                    "animal":[-0.3, 0.2],
                    "food":[0.4, -0.1],
                    "dog":[-0.6, 0.4],
                    "cat":[-0.5, 0.5],
                    "monkey":[-0.5, 0.1],
                    "human":[0.0, 0.2],
                    "tomato":[0.6, -0.3],
                    "apple":[0.5, -0.5],
                    "banana":[0.3, -0.5],
                    "meat":[0.1, -0.6]
                    }
          
            print(representations[words[0]])  # [-0.3, 0.2]
            print(representations[words[2]])  # [-0.6, 0.4]
          
            for w in words:
                plt.scatter(representations[w][0], representations[w][1])
                plt.annotate(w, xy=(representations[w][0], representations[w][1]))
            plt.show()
          
      • f:id:kumechann:20170626182224p:plain

      • この方法のメリットは以下が挙げられます
        1. もし単語の意味にしたがって正しく実数ベクトルが割り当てられていた場合、別々の単語でも意味が近い単語であれば近い実数ベクトルを持つことになり、数値計算機械学習アルゴリズムが同じように扱ってくれることです。
          • 例:グラフを見てみるとおそらく簡単に意味ごとのグループを作ることができるでしょう。k-means等のアルゴリズムを使えば機械も同様にグループを作ることができます。
        2. 実ベクトルの次元数は全単語数とは関係ないので、全単語数に対して線形に実ベクトルの次元数が増えることもないし、あとから単語を追加しても実ベクトルの次元数が増えることもない
      • しかし、この方法にも実は決定的な問題があります。
        • それは単語の意味にしたがって実数ベクトルを単語に割り当てることです。
        • 一見するとそんな方法は簡単に思いつかないような気がしますし、最近まで実応用ができるほど精度が高い、かつ高速な方法はありませんでした。
      • そこをブレイクスルーしたのがword2vecです。次の記事からはword2vecの詳細について扱います。
      • Chainerによる単語の分散表現のチュートリアル - 金融と工学のあいだ

CS224n「Lecture2 Word Vector Representations: word2vec」


Lecture 2 | Word Vector Representations: word2vec

Word meaning

離散表現の問題

  • 細かいニュアンスが消えてしまう
  • 人手で作らないといけない
  • 単語種長のone-hot vectorが必要になる
  • 近い意味の単語が全く異なる語として扱われる

分散表現

  • ある単語に近い単語はその単語の意味を表現する f:id:kumechann:20170507000225p:plain
  • 単語の意味が固定長のみつベクトルで表現される
    • 意味の近い単語は類似度が高くなるように学習されている f:id:kumechann:20170507000509p:plain

word2vecとは

単語tからのprediction

f:id:kumechann:20170507001215p:plain - 全ての単語t=1,…,Tに対して、距離mにあるた単語の確率が最大化されるように学習を行う

objective functionとは

  • objective function = loss function = cost function

詳細

  • 確率のモデリングにはSoftmax関数を用いる
    • Softmaxは実数を0~1の値(確率)にマップできるf:id:kumechann:20170507002142p:plain

参考文献

CS224n「Lecture1 Intro to NLP and Deep Learning」


Lecture 1 | Natural Language Processing with Deep Learning

  • CS224nのvideoが公開されたのでこちらにする。

自然言語処理とは?

自然言語のレベル

  • 音声 → 音素
  • 文書 → token化
  • 構文解析 → 意味解析

自然言語の特徴

  1. 離散
    • 脳内では連続的な信号である
  2. シンボリック
  3. 分類的

DL

DLと他の機械学習の違い

  • 自動で適切な特徴量を取得する

なぜDLをつかうの?

  • 特徴量を設計しなくて良い
  • ユニバーサル
  • 現在進行系で改善されている

なぜ成功したの?

なぜNLPは難しいの?

  • 複雑
  • プログラミングと違ってあいまい
  • コンテキストによって意味が異なる

Deep NLP

  • Deep LearningとNLPをくみあわせたもの
  • 以下で大きな成果を出した
    1. レベル:speech, words, syntax, semantics
    2. ツール:speech, entities, parsing
    3. アプリ:機械翻訳、意味解析、会話エージェント、QAシステム

リンク

CS224n「Lecture1 Intro to NLP and Deep Learning」


CS224D Lecture 1 - 29th Mar 2016 - Introduction.mp4

Deep Learningを研究する理由

  • 下記によりパフォーマンスが上がった

DLが効果を発揮した分野

Phonology・Morphology・Word・Syntax・Semantics

  • Phonology:ある言語の音の体系およびその音素の分析と分類の研究
  • Morphology:形態。容認可能な単語を形成するための規則の研究。
  • word
  • syntax
  • semantics
  • DLは上記すべてvectorとして表現する。
  • その他にも
    • Sentiment Analysis
    • Question Answering
    • Machine Translation

リンク

Chainerで「CS224d: Deep Learning for Natural Language Processing」

勉強の仕方