🔙 Back to Top

Sun May 22 10:30:45 JST 2016

chainer の勉強

wget https://github.com/pfnet/chainer/archive/v1.8.2.tar.gz # the latest url
tar xf v1.8.2.tar.gz
cd chainer-v1.8.2/docs
make html
open html/build/index.html

以上

以下、(私が詰まった) 最低限の必要知識

import numpy as np
import chainer
from chainer import cuda, Function, gradient_check, Variable, optimizers, serializers, utils
from chainer import Link, Chain, ChainList
import chainer.functions as F
import chainer.links as L

を前提とする.

環境

線形分類器

ネットワーク (Chainer.Chain)

"Core Concept" にもあるように、まずはネットワークを定義する

class MyNet(Chain):
    def __init__(self):
        super(MyNet, self).__init__(
            l = L.Linear(2, 2) # (dim(x), #Y)
        )
    def __call__(self, x):
        y = self.l(x)
        return y

l = L.Linear(n, m) とは \(n \times m\) 行列 \(W\) と長さ \(m\) のバイアスなベクトル \(b\) による線形分類1つを表現する (\(y = Wx+b\)). \(b\) は明示しなければ始めゼロベクトルであるが、\(W\) は始めはランダムな数によって初期化されるっぽい.

モデル、最適化ソルバ

model = L.Classifier(MyNet())
opt = optimizers.SGD()
opt.setup(model)

この時から我々は model および opt のみを操作すればよい. Chainer.optimizers は中に様々な最適化アルゴリズムをもっており、 例えばここでは SGD を選択している. opt.setup はよくわからんけど、不思議なことに model のための最適化を行うための構造をそこでセットする. 適当な数値微分を行うのだろうか.

人工データ

訓練

    x = Variable(
            np.array([x0, x1], dtype=np.float32)
            .reshape((1, 2))
            )
    t = Variable(
            np.array([ y ], dtype=np.int32)
            .reshape((1,))
            )
    opt.update(model, x, t)

これを適当にループでやる.

import sys, random
for i in range(1000):
    x0 = random.random()
    x1 = random.random()
    y = 0 if x0+x1<1.0 else 1
    # 上のコード

こんな感じ

テスト

実際に予測してみるには model.predictor を使う.

x = Variable(
        np.array([x0, x1], dtype=np.float32)
        .reshape((1, 2))
        )

について、model.predictor(x) はやはり Variable なのだが、 その .data をみると、確信度 (confidence) を配列になっている. y 番目の要素が \(y (\in \mathcal{Y})\) の確信度に対応している.

最も大きい数が入ってるインデックスを選べば、最尤になる. np.argmax でこの操作は実現できる. 次のコードを参考に.

訓練したのち、 網羅的に

for i in range(101):
    for j in range(101):
        x0 = i / 100.0
        x1 = j / 100.0
        y = 0 if x0+x1<1.0 else 1

について、実際に予測してみて、答え (y) との一致度 (Accuracy) を図ることにする.

correct = 0
wrong = 0

for i in range(101):
    for j in range(101):
        x0 = i / 100.0
        x1 = j / 100.0
        y = 0 if x0+x1<1.0 else 1
        x = Variable(
                np.array([x0, x1], dtype=np.float32)
                .reshape((1, 2))
                )
        y2 = np.argmax(model.predictor(x).data)

        if y == y2:
            correct += 1
        else:
            wrong += 1

print("Ac {}".format( correct / (correct + wrong) ))

結果

1-of-k 表現

直接の入力がユークリッド空間上の点でなく、整数型で表現されたIDであることがある. 例えば入力が \(n\) 通りのラベルである場合. ラベル同士に従属関係がないのならば、これらを一つの実数に変換することは難しい. こういったものは 1-of-k ベクトルといった表現方法が取られる. これは、 \(n \leq 2^k\) なる最小の自然数を \(k\) とするとき、 ラベル一つを \(k\) 次元のベクトルに変換する. 手順は次の通りである.

次元は \(k\) に増えるが、異なるラベルは線形独立な異なるベクトルで表現できる.

L.EmbedID

L.EmbedID 関数は、難しいことを考えなくても 1-of-k 表現的なことをする. L.EmbedID(n, k) によって \(n\) 個のID列を \(k\) の長さのベクトルにする. 適度に大きい \(k\) を設定すれば良い (思考停止).

class MyNet(Chain):
    def __init__(self):
        super(MyNet, self).__init__(
            embed = L.EmbedID(1, 10) # k=10
            l = L.Linear(10, 2) # (k, #Y)
        )
    def __call__(self, x):
        x = self.embed(x) # ID(x) -> k-vector(x)
        y = self.l(x) # y = Wx+b
        return y

__call__ 関数によってネットワークの計算手順を定義する. 入力 x はラベルID (np.int32) 一つだとする. これを長さ \(k=10\) のベクトルに変換してから、先ほどと同じ線形分類に投げる.

はじめの \(x\)\(\mathbb{R}^2\) から ID にした都合上、コードの上の x の表現がちょっとだけ変わる.

  id = 1
  x = Variable( xp.array([ id ]).astype(xp.int32).reshape((1,1)) )
  model.predictor(x).data

こんな風に使う.