HELLO CYBERNETICS

深層学習、機械学習、強化学習、信号処理、制御工学、量子計算などをテーマに扱っていきます

【ミニバッチ学習のコード】ニューラルネットとTensorFlow入門のためのオリジナルチュートリアル3

 

 

follow us in feedly

f:id:s0sem0y:20170707094814j:plain

 

 

はじめに

前回までのチュートリアルは、それぞれ

 

1.TensorFlowの書き方や考え方と共にニューラルネットの構築を学びました。

s0sem0y.hatenablog.com

 

2.構築したニューラルネットの学習をさせる方法を学びました。

s0sem0y.hatenablog.com

 

 

これで最低限のことができるようにはなったのですが、今回学ぶことは

 

ミニバッチ学習の考え方

ミニバッチ学習の実装

Accuracyとlossの表示の際の注意点

 

の3つになります。これらは学習を実際に行う上で必須となるものなので、必ず抑えるようにしましょう。

 

学習の際のデータの与え方

バッチ学習、ミニバッチ学習、オンライン学習

まず、学習とは

 

学習データ:\bf x

教師データ:\bf t

ニューラルネットのパラメータ:\bf w

 

として、ニューラルネットワーク

 

f({\bf x;w})

 

を構築したときに、\bf tf({\bf x;w})の相違の度合いを

 

損失関数:L({\bf t},f({\bf x;w}))=L({\bf t},{\bf x};{\bf w})

 

で与え、

 

\displaystyle {\rm optimizer}: {\bf w} \rightarrow {\bf w} - ε\frac{ \partial L({\bf w})}{\partial {\bf w}}

 

という写像によって、\bf wを更新していき、損失関数を最小化することです。これにより上手いf({\bf x;w})が獲得されることを期待するわけです。

 

以下から、学習と言った場合にはこの勾配法による損失関数の最小化のことだと考えてください。(ここでL({\bf x,t;w})という表記は、\bf x,tがプレースホルダーであり、\bf wが学習したいパラメータであることを強調するために用いています)、

 

 

バッチ学習

 

学習データ\bf xN個あるときに、N個のデータを全て用いて、それぞれのデータでの損失lの平均を計算し、それをデータ全体の損失Lと考え、

 

\displaystyle  L({\bf t, x;w}) = \frac{1}{N}\sum_{i=1}^N l({\bf t_i,x_i;w})

 

と定義して学習に臨むのがバッチ学習です。この場合、学習が進む方向はデータ全体の損失が考慮されているため、どのデータも上手く扱えるような平均的な方向ということになります。

 

一般的に学習は安定しており、かつ、ニューラルネットに入れたデータは事実上計算も同時に行えるため、以下2つの方法に比べ高速です。

 

前回のチュートリアルで使ったのはこのバッチ学習になります。

 

オンライン学習(確率的勾配法)

 

一方で、\bf N個のデータ{\bf x_1,x_2,...,x_N}からランダムに1つ\bf x_iを選び出し、そのデータ1つに対する損失lをそのままLに用いて

 

\displaystyle  L({\bf t, x;w}) = l({\bf t_i,x_i;w})

 

と損失関数を定義して学習に臨むのが、オンライン学習、あるいは「確率的勾配法」と言います。

 

このとき進む学習の方向は、そのデータのみ上手く扱える方向になります。従って他のデータにとってはむしろよくない方向へ進む可能性もあります。

 

\bf x_iをランダムに選ぶため、学習の方もランダムになるため「確率的勾配法」と言うわけです。実際にはデータを1つずつ入れて、最終的には全てのデータを学習させていくことになりますが、この場合\bf x_iを使ってから\bf x_jを使う、あるいはその逆では学習の振る舞いは変わるでしょう。

 

通常、この学習はあまり安定しません。

 

損失関数は非常に複雑な形をしているため、学習の進む方向の順序がランダムであれば、学習毎に違う結果が得られる可能性があります。しかし、

 

それが返って、偽物の解への収束や、学習の停滞を防いでくれるとも考えられます。 

 

またバッチ学習は全てのデータを同時に計算できますが、

 

オンライン学習では、全てのデータについて学習を1回ずつ行うために、データ数N回の順伝播、逆伝播、更新、を行う必要があり、学習に掛かるコストは大きいと言えます。

 

ミニバッチ学習

 

全体を考慮したバッチ学習と、確率的勾配法の間を取ったのがミニバッチ学習であり、このとき学習データ\bf xN個あるときに、ランダムなn (\leq N)個のデータを使い

 

\displaystyle L({\bf t, x;w}) = \frac{1}{n}\sum_{i=1}^n l({\bf t_i,x_i;w})

 

を損失関数と定義し、学習を行います。

 

実際は多くの場面でこの方法が用いられており、「確率的勾配法」と言う時には、もはやこちらのミニバッチ学習を指すことのほうが今では多いです。

 

バッチ学習とオンライン学習の両方の特徴を兼ね備えています。ミニバッチの数は意外と学習に影響を与えるケースも多く、これを決定する効率的かつ一般的な方法は特にありません。よく使われるのは、2^n個です(計算機の都合上、メモリアクセス効率が良い)。私の経験上、分類クラス数が多いほど、ミニバッチサイズを小さくすることが有効に働くように思います(まあ、あまり当てにしないで)。

 

 

 

ミニバッチ学習のコードサンプル

データを単に小分けにして入れる

この場合は、全データを総なめするまで学習を繰り返すコードを書くだけで非常にシンプルに実装できます。

 

まず以下のコード(1万回学習を行う)に関して、

num_epoch = 10000
for epoch in range(num_epoch):
sess.run(train_step, feed_dict = {X: train_x, t: train_t})

 

訓練データと教師データをスライスしたものを入れればよく、以下のように実装ができます。

 

num_epoch = 10000
num_data = train_x.shape[0]
batch_size = 32
for epoch in range(num_epoch):
for idx in range(0, num_data, batch_size):
batch_x = train_x[idx: idx + batch_size if idx + batch_size < num_data else num_data]
batch_t = train_t[idx: idx + batch_size if idx + batch_size < num_data else num_data]
sess.run(train_step, feed_dict = {X: batch_x, t: batch_t})

 

ここでスライスは、データ数が100個でバッチサイズが32の場合には

0〜31

32〜63

64〜95

66〜99

という形でデータが取り出されることになります。このケースでは最後のバッチサイズだけ4になってしまっていますので、データ数を割り切れるバッチサイズにしておいても良いでしょう(そうすれば条件文なんて要らなくなる)。ただ、データ数が訳わからん数(もしかしたら素数)の場合もあるので、ひとまずはこういう実装を使えばいいと思います。

 

ここで残念なお知らせですが、これは普通「確率的勾配法」とは言いません。

 

まずミニバッチ学習とはオンライン学習(データを1つずつランダムに入れる)とバッチ学習のいいとこ取りが狙いでした。にも関わらず。

 

今回の実装では、例えば1個目のミニバッチに入る0〜31のデータは毎回同じになっています。その上、ニューラルネットに入力されるミニバッチの順番自体も固定されています。

 

冷静に考えれば、この実装のどこにも乱数なんてありません。意外とやりがちなミスなので注意してください。

 

 

本当のミニバッチ学習(確率的勾配法)

 

従って、ミニバッチに入る32個のデータをそもそもランダムにしなければいけません。これを実現するためにバッチデータに詰め込むデータのインデックスをシャッフルする方法が以下。

 

num_epoch = 10000
num_data = train_x.shape[0]
batch_size = 32
for epoch in range(num_epoch):
sff_idx = np.random.permutation(num_data)
for idx in range(0, num_data, batch_size):
batch_x = train_x[sff_idx[idx: idx + batch_size
if idx + batch_size < num_data else num_data]]
batch_t = train_t[sff_idx[idx: idx + batch_size
if idx + batch_size < num_data else num_data]]
sess.run(train_step, feed_dict = {X: batch_x, t: batch_t})

 

データが100個{\bf x_0,x_1,...,x_100}とあった場合に、ミニバッチに選出される32個のデータはbf {x_12,x_31,x_8,...}などとランダムに選ばれます。そうして全てのデータを総なめした後、再び同様のランダム選出が行われるため、毎回ミニバッチの中身が変わり、真の確率的勾配学習が出来ます。

 

 

(ミニバッチという言葉の意味を考えれば、バッチデータを細かく区切るだけの方法があってもいいかもしれません。ある意味、確率的要素が無くなり学習を安定させられることに違いはありませんので。そうであれば、まずミニバッチからではなく普通のバッチ学習から始めるようにしてください。それで上手く行くならそれが一番良いので。)

 

全体のコード

前回のチュートリアルでも触れた学習に用いていないデータのlossとaccuracyを学習中に表示する実装も踏まえ、ミニバッチ学習の全体コードは以下となります。(学習のコードの部分においては、コードが横に膨れてくるようになるため、TensorFlowが推奨する2スペースによるインデントがおすすめです)

import numpy as np
import tensorflow as tf

def weight(shape = []):
initial = tf.truncated_normal(shape, stddev = 0.01)
return tf.Variable(initial)

def bias(dtype = tf.float32, shape = []):
initial = tf.zeros(shape, dtype = dtype)
return tf.Variable(initial)

def loss(t, f):
cross_entropy = tf.reduce_mean(-tf.reduce_sum(t * tf.log(f)))
return cross_entropy

def accuracy(t, f):
correct_prediction = tf.equal(tf.argmax(t, 1), tf.argmax(f, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
return accuracy

Q = 4
P = 4
R = 3

sess = tf.InteractiveSession()

X = tf.placeholder(dtype = tf.float32, shape = [None, Q])
t = tf.placeholder(dtype = tf.float32, shape = [None, R])

W1 = weight(shape = [Q, P])
b1 = bias(shape = [P])
f1 = tf.matmul(X, W1) + b1
sigm = tf.nn.sigmoid(f1)

W2 = weight(shape = [P, R])
b2 = bias(shape = [R])
f2 = tf.matmul(sigm, W2) + b2
f = tf.nn.softmax(f2)

loss = loss(t, f)
acc = accuracy(t, f)

optimizer = tf.train.GradientDescentOptimizer(learning_rate = 0.001)
train_step = optimizer.minimize(loss)

with tf.Session() as sess:
init = tf.global_variables_initializer()
sess.run(init)


from sklearn import datasets
iris = datasets.load_iris()
shuffle = np.random.permutation(150)
train_x = iris.data[shuffle[:100]]
train_t = iris.target[shuffle[:100]]
train_t = np.eye(3)[train_t]
test_x = iris.data[shuffle[100:]]
test_t = iris.target[shuffle[100:]]
test_t = np.eye(3)[test_t]

num_epoch = 10000
num_data = train_x.shape[0]
batch_size = 16
for epoch in range(num_epoch):

sff_idx = np.random.permutation(num_data)

for idx in range(0, num_data, batch_size):
batch_x = train_x[sff_idx[idx: idx + batch_size
if idx + batch_size < num_data else num_data]]
batch_t = train_t[sff_idx[idx: idx + batch_size
if idx + batch_size < num_data else num_data]]
sess.run(train_step, feed_dict = {X: batch_x, t: batch_t})

if epoch % 100 == 0:
train_loss = sess.run(loss, feed_dict = {X: train_x, t: train_t})
train_acc = sess.run(acc, feed_dict = {X: train_x, t: train_t})
test_loss = sess.run(loss, feed_dict = {X: test_x, t: test_t})
test_acc = sess.run(acc, feed_dict = {X: test_x, t: test_t})
print('epoch:{} \n \
tr_loss:{}\n \
tr_acc:{} \n \
tes_loss:{} \n \
tes_acc:{}'.format(epoch,
train_loss,
train_acc,
test_loss,
test_acc))

 

ミニバッチ学習のコードを追加したところまでは良いでしょう。これにより、毎回選ばれるデータの組が変わり、確率的勾配法を実現できるようになりました。

 

青い字になっている部分は、ミニバッチ学習でlossやaccuracyを見る時の注意点です。ここではプレースホルダーには全てのデータを入れるようにしてください。

 

間違ってミニバッチデータを入れたりしないように。あくまで見たいのは全てのデータに対する学習の進捗状況です(あまり間違える人いないようにも思いますが)。

 

 

※今回のIrisデータはデータ数が少ないため、シャッフルして訓練データと検証用のデータに分けたときに、学習するクラスが極端に偏る可能性もあります。その結果学習が上手くいかないケースもあるので注意してください。

 

 

 

最後に

可視化したい

数値で表示すれば大抵の場合は事足りますが、学習の最中にもグラフでlossの増減を見張りたいということがあるかもしれません。そうすることで、過学習が起こり始めた地点を視覚的に確認することができるので便利です。ココらへんはmatplotlibなどの使い方の話になるので解説は割愛しますが、サンプルコードを載せておきます。

 

 

このコードではリアルタイムで学習の曲線を見ることが出来ます(ただし学習終了後にグラフ消えます。with構文で学習を書いているため)。

 

Accuracyを見張るコードを追加したければ、loss_list_trと同様に値を格納したリストを作り、plt.figure()をもう1つ追加するか、あるいはsubplotを使うと良いでしょう。