あかすくぱるふぇ

同人サークル「あかすくぱるふぇ」のブログです。

機械学習

Chainerをインストールし、mnistの実行ができた!
……が、chainer1.11以降のmnistは処理の中身が隠蔽されてしまい、何をやっているのかさっぱり分かりません。
「それなら以前のmnistを試してみよう」と実行してみると、fetch_mldata()で落ちてしまいます。

そこで、本記事では、chainer1.11以降のget_mnist()で取得したデータの形式を変換することで、chainer1.11以前のmnistに入力する方法を紹介します。

元のコードは以下のページのものを利用させていただきました。
http://turgure.hatenablog.com/entry/2016/08/04/010219

修正は以下の2か所です。

・1か所目
修正前
# MNISTの画像データDL
print("fetch MNIST dataset")
mnist = fetch_mldata('MNIST original', data_home=".")
# mnist.data : 70,000件の28x28=784次元ベクトルデータ
mnist.data = mnist.data.astype(np.float32)
mnist.data /= 255 # 正規化

# mnist.target : 正解データ
mnist.target = mnist.target.astype(np.int32)
修正後
train, test = chainer.datasets.get_mnist()
get_mnist()は、学習用データとテスト用データを別々に取得できます。

・2か所目
修正前
# 学習用データN個,検証用データを残りの個数に設定
N = 60000
x_train, x_test = np.split(mnist.data, [N])
y_train, y_test = np.split(mnist.target, [N])
N_test = y_test.size
修正後
N = np.array(train).size / 2
x_train, y_train, x_test, y_test = [], [], [], []
for i in range(0, N):
x_train.append([train[i][0].astype(np.float32)])
y_train.append(train[i][1].astype(np.int32))
N_test = np.array(test).size / 2
for i in range(0, N_test):
x_test.append([test[i][0].astype(np.float32)])
y_test.append(test[i][1].astype(np.int32))
x_train = np.array(x_train)
y_train = np.array(y_train)
x_test = np.array(x_test)
y_test = np.array(y_test)
詰め替え作業です。
もっと効率的なやり方があると思うのですが、Python初心者なもので。。。
注意するのは画像データ(x_train, x_test)と正解ラベル(y_train, y_test)の次元数です。
画像データは2次元(784×データ数)であり、正解ラベルは1次元(データ数)です。
なので、画像データの方だけappend()の中身を[]で括っています。

修正は以上です。
これを実行しながら解析していけば、chainer1.11以降でもmnistの中身を理解できるはず!
(自分はまだ理解できていない。。)

Chainerをインストールし、mnistの実行ができた!
……が、Pythonと機械学習の初心者である私にはmnistの中身を解読することができませんでした。。

そんな私が最初に解読したコードを載せます。
以下のサイトのコードを微修正したものです。
http://qiita.com/carat_yoshizaki/items/bfe559d1bdd434be03ed
import chainer
import math
import numpy as np
import chainer.functions as F
import chainer.links as L

class MyChain(chainer.Chain):
def __init__(self):
super(MyChain, self).__init__(
l1 = L.Linear(1, 100),
l2 = L.Linear(100, 30),
l3 = L.Linear(30, 1)
)

def predict(self, x):
h1 = F.relu(self.l1(x))
h2 = F.relu(self.l2(h1))
return self.l3(h2)

x, y = [], []
for i in np.linspace(-3,3,100):
x.append([i])
y.append([math.sin(i)])

x = chainer.Variable(np.array(x, dtype=np.float32))
y = chainer.Variable(np.array(y, dtype=np.float32))

model = MyChain()

def forward(x, y, model):
t = model.predict(x)
loss = F.mean_squared_error(t, y)
return loss

optimizer = chainer.optimizers.Adam()
optimizer.setup(model)

for i in range(0,1000):
model.zerograds()
loss = forward(x, y, model)
loss.backward()
optimizer.update()
print loss.data
以下、説明していきます。
import chainer
import math
import numpy as np
import chainer.functions as F
import chainer.links as L
C言語で言うところのinclude文。
asは別名をつける場合に使います。
class MyChain(chainer.Chain):
def __init__(self):
super(MyChain, self).__init__(
l1 = L.Linear(1, 100),
l2 = L.Linear(100, 30),
l3 = L.Linear(30, 1)
)

def predict(self, x):
h1 = F.relu(self.l1(x))
h2 = F.relu(self.l2(h1))
return self.l3(h2)
ネットワークを定義したクラス。
class MyChain(chainer.Chain)はMyChainクラスがChainクラスを継承していることを表します。
必須ではないが、基本的にはChainクラスを継承してネットワーククラスを定義するようです。

__init__はコンストラクタ。
selfはPythonのメンバ関数では必ず一つ目の引数として指定するものらしいです。C++で言うところのthis。
superは親クラスを表します。

l1~l3はネットワークのリンク構造を表しています。
Linear(a, b)は、a個の入力をb個の出力に変換する1層のネットワークを示しており、数式で表すとu=Wx+bとなります。
ここで、xは入力(a列のベクトル)、Wは重み行列(a列b行の行列)、bはバイアス(b列のベクトル)、uは出力(b列のベクトル)です。
この例は3層のネットワークであり、1個の入力→100個の出力→30個の出力→1個の出力という形で、1個の入力から1個の出力を生成します。

reluは活性化関数LeLU。
x, y = [], []
for i in np.linspace(-3,3,100):
x.append([i])
y.append([math.sin(i)])

x = chainer.Variable(np.array(x, dtype=np.float32))
y = chainer.Variable(np.array(y, dtype=np.float32))
ここでは正解データを生成しています。
linspace(a, b, c)は[a, b]をc分割してサンプリングする関数、append()はリストへの追加を行う関数です。
下2行のようにnumpyのarrayを経由してChainer用のリストを生成するのが作法のようです。
model = MyChain()

def forward(x, y, model):
t = model.predict(x)
loss = F.mean_squared_error(t, y)
return loss

optimizer = chainer.optimizers.Adam()
optimizer.setup(model)
モデル、順伝播関数、最適化モデルの設定をしています。
モデルは先ほど定義したMyChainクラス。
順伝播関数は順伝播を行い、そのコストを返します。コスト関数には平均二乗誤差を用いています。
最適化モデルにはAdamというアルゴリズムを採用しています(説明省略)。
for i in range(0,1000):
model.zerograds()
loss = forward(x, y, model)
loss.backward()
optimizer.update()
print loss.data
学習処理です。
zerograds()で勾配を0に初期化し、forward()で損失関数を計算します。
lossはVariable型であり、backward()を呼ぶと、誤差逆伝搬法によって勾配を計算します。
そして、optimizer.update()によって、重みとバイアスを更新します。
なお、この一連の処理は以下のように置き換えることができます。
optimizer.update(forward, x, y, model)

以上です。

↑このページのトップヘ