あかすくぱるふぇ

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

2016年11月

Chainerでcaffemodelを読み込んで使えるらしいです。素晴らしい!
Chainerでcaffemodelを読み込んで画像を分類する

ただ、例のごとく、Python初心者には何をやっているのかよく分からなかったので解析してみました。
ソースコードは上記リンクのものを利用させていただき、Python初心者が躓きそうなところをピックアップして解説していきます。

def load(self, path):
self.func = caffe.CaffeFunction(path)

def _predict_class(self, x):
y, = self.func(inputs={'data': x}, outputs=['loss3/classifier'], disable=['loss1/ave_pool', 'loss2/ave_pool'], train=False)
return F.softmax(y)
まずは一番肝になる部分から。
CaffeFunction()は、caffemodelファイルを読みこんで、Chainerで呼び出せる関数を生成します。
func()の引数は、(入力データ, 出力層, 出力に関連しない無視する層, 学習フェーズかどうか)です。
層の名前は読み込むcaffemodelによって異なるので、調べる必要があります。
第三引数は指定しなくても結果は変わりません(無視することによって処理時間が短くなるとか?)。

def load_label(self, path):
self.categories = np.loadtxt(path, str, delimiter="\n")
カテゴリ名を記載したファイルの読み込みです。
この例では、categoriesは1000要素の配列で、各要素はカテゴリ名となります。

def predict(self, image_path, rank=10):
x = chainer.Variable(self.load_image(image_path), volatile=True)
y = self._predict_class(x)
result = zip(y.data.reshape((y.data.size,)), self.categories)
return sorted(result, reverse=True)
result = zip(a, b)は(a, b)というセットを要素として持つ配列resultを生成します。
そして、sorted(result)は、resultを(zip(a, b)のaについて)ソートする関数です。
この処理によって、カテゴリを確率の高いものから順に並べた結果を得ることができます。

def print_prediction(self, image_path, rank=10):
prediction = self.predict(image_path, rank)
for i, (score, label) in enumerate(prediction[:rank]):
print '{:>3d} {:>6.2f}% {}'.format(i + 1, score * 100, label)
enumerate()はインデックスと要素を同時に得るための関数です。
この例では、iにインデックス、(score, label)にprediction[i]が入ります。
また、{:>3d}や{:>6.2f}はオプション付きの書式であり、>は右詰めのオプションです。

以上です。

本記事では、Python初心者のためにCIFAR-10データのファイル入力について解説します。
CIFAR-10というのは、一般物体認識のベンチマーク用画像データセットです。
http://www.cs.toronto.edu/~kriz/cifar.html

本記事を書くきっかけとなったのは、以下の記事を参考にChainerでCNNを試したことです。
実行はうまくいったのですが、Python初心者なので、CIFAR-10のファイル入力の理解に手間取りました。
ChainerによるCIFAR-10の一般物体認識 (1)

まずは、ソースコードを載せます。
このソースコードは上記サイトと以下のサイトのコードを合体して、改変したものです。
http://aidiary.hatenablog.com/entry/20151014/1444827123
#coding: utf-8
import numpy as np
import matplotlib.pyplot as plt

def unpickle(f):
import cPickle
fo = open(f, 'rb')
d = cPickle.load(fo)
fo.close()
return d

def load_cifar10(datadir):
train_data = []
train_target = []

# 訓練データをロード
for i in range(1, 6):
d = unpickle("%s/data_batch_%d" % (datadir, i))
train_data.extend(d["data"])
train_target.extend(d["labels"])

# テストデータをロード
d = unpickle("%s/test_batch" % (datadir))
test_data = d["data"]
test_target = d["labels"]

# データはfloat32、ラベルはint32のndarrayに変換
train_data = np.array(train_data, dtype=np.float32)
train_target = np.array(train_target, dtype=np.int32)
test_data = np.array(test_data, dtype=np.float32)
test_target = np.array(test_target, dtype=np.int32)

# 画像のピクセル値を0-1に正規化
train_data /= 255.0
test_data /= 255.0

return train_data, test_data, train_target, test_target

# CIFAR-10データをロード
print "load CIFAR-10 dataset"
X_train, X_test, y_train, y_test = load_cifar10("data")

# 画像を (nsample, channel, height, width) の4次元テンソルに変換
X_train = X_train.reshape((len(X_train), 3, 32, 32))
X_test = X_test.reshape((len(X_test), 3, 32, 32))

# 画像を表示
plt.imshow(X_train[0].reshape(3, 32, 32).transpose(1, 2, 0))
plt.show()
では、解説していきます。
def unpickle(f):
import cPickle
fo = open(f, 'rb')
d = cPickle.load(fo)
fo.close()
return d
ここでは、PythonのcPickleを利用しています。
cPickleはPythonオブジェクトとバイト列との相互変換を行うライブラリです。
この例では、load()によってファイル入力を行い、Pythonのdict型オブジェクトであるdを生成しています。
dict型オブジェクトは、以下のサイトに記されているように、キーと値が紐づけられたオブジェクトです。
http://www.pythonweb.jp/tutorial/dictionary/index8.html

# 訓練データをロード
for i in range(1, 6):
d = unpickle("%s/data_batch_%d" % (datadir, i))
train_data.extend(d["data"])
train_target.extend(d["labels"])
6個のファイルに分かれた訓練データをロードします。
"%s/data_batch_%d" % (datadir, i)は、%sにdatadir、%dにiを代入して、ファイル名文字列となります。
このファイル名のファイルからデータをロードし、train_dataとtrain_targetに追加していきます。
d["key"]はdict型オブジェクトdから"key"という名前のキーに紐づけられた値を参照します。
"data"キーは画像データ、"labels"キーは正解ラベルの値と紐づけられています。

# 画像を (nsample, channel, height, width) の4次元テンソルに変換
X_train = X_train.reshape((len(X_train), 3, 32, 32))
X_test = X_test.reshape((len(X_test), 3, 32, 32))
データをChainerのデータ形式(画像数, チャンネル数, 高さ, 幅)に変換します。

以上です。

Chainerをインストールし、mnistの実行ができた!
……が、mnistには色々な要素が詰まっていて、なかなか解読できません(以下の記事に続いて3度目。。)
Pythonと機械学習の初心者がChainerで最初に解読したコード
get_mnist()で取得したデータをchainer1.11以前のmnistに入力する

そこで本記事では、mnistのサンプルコードからエッセンスのみを抽出してみました。
元にしたのは上記2つ目の記事に載せたコードです。
エッセンスを抽出したコードを以下に載せます。
import numpy as np
from chainer import cuda, Variable, FunctionSet, optimizers, datasets
import chainer.functions as F

n_epoch = 100
n_units = 1000 # 中間層
pixel_size = 28

# MNISTの画像データDL
train, test = datasets.get_mnist()

# データの詰め替え
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)

# MLP モデル
# 入力784, 出力10
model = FunctionSet(l1 = F.Linear(784, n_units),
l2 = F.Linear(n_units, n_units),
l3 = F.Linear(n_units, 10))

# NNの構造
def forward(x_data, y_data, train = True):
x, t = Variable(x_data), Variable(y_data)
#h1 = F.dropout(F.relu(model.l1(x)), train = train)
#h2 = F.dropout(F.relu(model.l2(h1)), train = train)
h1 = F.relu(model.l1(x))
h2 = F.relu(model.l2(h1))
y = model.l3(h2)

# 多クラス分類なのでsoftmax関数の交差エントロピー関数を誤差関数とする
return F.softmax_cross_entropy(y, t)

# optimizerの設定
optimizer = optimizers.Adam()
optimizer.setup(model)

# Learning loop
for epoch in range(1, n_epoch + 1):
print("epoch", epoch)

model.zerograds()
loss.backward()
optimizer.update()

# 訓練データの誤差と正解精度を表示
loss = forward(x_train, y_train)
print("train mean loss = {0}".format(loss.data))

# evaluation
# テストデータで誤差と正解精度を算出し汎化性能を確認
loss = forward(x_test, y_test)

# テストデータの誤差と正解精度を表示
print("test mean loss = {0}".format(loss.data))
これなら理解できそう!
というわけで、解説していきます。
model = FunctionSet(l1 = F.Linear(784, n_units),
l2 = F.Linear(n_units, n_units),
l3 = F.Linear(n_units, 10))
ネットワーク構造。
784個の入力(28×28の画像)から10個の出力(0~9のクラス分類)を生成します。
def forward(x_data, y_data, train = True):
x, t = Variable(x_data), Variable(y_data)
#h1 = F.dropout(F.relu(model.l1(x)), train = train)
#h2 = F.dropout(F.relu(model.l2(h1)), train = train)
h1 = F.relu(model.l1(x))
h2 = F.relu(model.l2(h1))
y = model.l3(h2)

# 多クラス分類なのでsoftmax関数の交差エントロピー関数を誤差関数とする
return F.softmax_cross_entropy(y, t)
順伝播関数。
コメントにも書いていますが、「多クラス分類なのでsoftmax関数の交差エントロピー関数を誤差関数」とします。
ドロップアウトは過適合を防ぐ仕組みであり、無くても動きます。
model.zerograds()
loss.backward()
optimizer.update()
元のコードではbatchsizeごとに処理をしていますが、これは「ミニバッチ」という効率化のためのテクニックであり、必須ではありません。
ただし、元のコードではbatchsize×n_epoch回重みを更新しますが、上記コードではn_epoch回しか更新しないため、その分n_epochを大きい値に設定する必要があります。

以上です。
やっとmnistを理解することができました。
早くCNNとかを使えるようになって、実践的な例を試してみたいです。

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)

以上です。

↑このページのトップヘ