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
を前提とする.
pickle
ではなく Chainer.serializer
でモデルを保存np.array
np.array(list)
.reshape((n, m))
で \(n \times m\) 行列に変換する.reshape((n, ))
でベクトルに変換する[]
で要素にアクセスVariable
np.array
をさらに包んだものVariable
に対する諸々の演算が定義されている.data
で中の np.array
にアクセス
.grad
とかいろいろ実は持ってて最適化の際に用いられるnp.array(list, dtype=np.float32).reshape((1, 2))
と表現する
list
は適当な数の PythonリストChainer.Variable
で包むnp.array([ y ], dtype=np.int32).reshape((1,))
y
は \(y \in \mathcal{Y}\) の \(y\)Chainer.Variable
で包む"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) ))
Ac 0.7490442113518283
Ac 0.9708852073326144
.SGD()
の代わりに .AdaDelta()
を使うと 1000 回のままでも Ac 0.8820703852563474
直接の入力がユークリッド空間上の点でなく、整数型で表現された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
こんな風に使う.