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 yl = 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.7490442113518283Ac 0.9708852073326144.SGD() の代わりに .AdaDelta() を使うと 1000 回のままでも Ac 0.8820703852563474直接の入力がユークリッド空間上の点でなく、整数型で表現されたIDであることがある. 例えば入力が \(n\) 通りのラベルである場合. ラベル同士に従属関係がないのならば、これらを一つの実数に変換することは難しい. こういったものは 1-of-k ベクトルといった表現方法が取られる. これは、 \(n \leq 2^k\) なる最小の自然数を \(k\) とするとき、 ラベル一つを \(k\) 次元のベクトルに変換する. 手順は次の通りである.
次元は \(k\) に増えるが、異なるラベルは線形独立な異なるベクトルで表現できる.
L.EmbedIDL.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こんな風に使う.