External Memory

プログラミング周辺知識の備忘録メイン

重み初期設定と活性化関数による学習速度の改善

前回の3層ニューラルネットワーク構造の学習の検討において
学習速度の速度の問題が目についた。
http://taka74k4.hatenablog.com/entry/2017/08/03/120000


今回は学習速度の改善を試みた。

過適合による学習速度の低下ではなく、始めから学習速度は遅かった。
現状では速度の向上には以下が思いつく。

1 重みの初期値の変更
2 より適当な活性化関数の選択
3 最適化法と学習率の最適化

1~3について順番に検証を行った。

1について

重みの初期化には前回、
標準偏差を入力パラメータ数の平方の逆数をとった正規分布を用いた。
この手法は、シグモイド関数系統に用いられる手法であって
softmaxなどに用いる必要性はない。
正規分布の鋭さを抑える理由はシグモイド関数σ(z)においてz>1 or z<0の時に
傾きがなだらかになる影響で重みを変化させてもσ(z)はあまり変化せず
学習速度が遅くなるからである。

重みの初期値はhidden layerへの出力に影響すると思われる。
hidden layerへの出力の関数として前回使用したのはsoftmax関数である。
softmax関数は確率分布を出力し、分布を鋭くするような性質を持つ。
ここでは重みやバイアスの差を際立たせるような役目である。

いろいろ試行錯誤をしたが、
結局すべて0に設定したとき以外は分布の変化に対し学習速度に影響はなかった。

2について

hiddenlayerへの出力の関数としてsoftmax関数を用いたが、
より軽量で効果が期待できるといわれるReLUを使った。
ReLUは以下のような単純な関数である。

relu(x) = max(0,x)

結果、重みの分布の標準偏差を大きめにすることで、
2000回ほどで正答率が約0.98で飽和した。
学習速度と精度ともに大幅に向上したことになる。
ただ標準偏差が10.0以上になると徐々に極小付近で振動するようになった。
3.0程度がちょうどよかった。

なぜそうなったのか。
標準偏差が小さい場合、学習速度は極端に遅かった。
また、分布の平均値は0以上ならそれほど違いはないようである。

softmax関数の出力は1以下であるのに比べて、ReLUは1以上の値を出力する。
標準偏差が小さい場合に学習速度が遅かったことと合わせて、
最終出力の確率分布が鋭いことが学習速度に影響を与えているように見える。
入力のパラメータ数が少ない上に、
分布が平坦だとlayerごとに特徴が表れにくいからだろうか。
またsoftmax関数のクロスエントロピーの性質から分布がある程度鋭いほうが、
この場合においては学習速度は速くなりそうである。

以下は参考で標準偏差の違いによる重みの初期値である。

print(sess.run(tf.truncated_normal([2, 5],stddev=1.0  / math.sqrt(float(2)))))
[[ 0.02702628  0.91012442 -0.18702209 -0.03638476 -0.67407352]
 [-0.39508435 -0.0171926   0.02808492 -0.95571172 -0.11896304]]

print(sess.run(tf.truncated_normal([2, 5],stddev=1.0)))
[[-1.10803401  0.15547203 -1.11678505  0.81495434 -0.77232492]
 [ 0.86105257 -0.29577333 -0.5726105  -0.10565408 -1.52466965]]

print(sess.run(tf.truncated_normal([2, 5],stddev=3.0 )))
[[ 2.23127007 -1.23616624  5.98013353  0.65966594 -3.75138664]
 [ 3.99544954 -3.38144922  0.53547996  1.00201726  2.26863289]]

print(sess.run(tf.truncated_normal([2, 5],stddev=10.0 )))
[[  5.31843233   3.73229957  -8.37697124   6.94522333  -1.52599192]
 [ 14.96617889   8.58791733   1.42042267  -1.91340804 -12.14058113]]

3について

活性化関数にReLUを用いた場合にさらに成果を上げるために、
簡単な最適化法と学習率の検討を行った。

使用した勾配降下法はAdagrad、GradientDescent、Adadeltaである。
この3つを選んだ理由は私がまだこの3つしか知らないからである。
Adadeltaはもともと使っていたAdagradと比べて、
学習速度の低下は起こりにくい性質を持つので少し期待はしていた。

Adagradを使用した場合、学習率は0.5程度で良好な結果が得られた。
学習率がそれより小さいと学習速度が下がり、
大きいと正答率が落ちた、勾配の和の影響を受けているようである。

GradientDescentを使用した場合は極小値付近で振動する傾向がみられた。

Adadeltaの場合は3.0-5.0程度の係数を掛けることでAdagradと似たような結果が得られた。

結局、さらに学習効果を向上させることはできなかった。

Pythonコード

import tensorflow as tf
import math
import random

N = 10000
R = 9 #label Range
times = 10000
HIDDEN_UNITS = 100

abmlist = [random.uniform(0,1) for i in range(N)] 
 
d_list = [random.uniform(0.1,1) for i in range(N)]

apmlist = [ [0 for i in range(R)] for j in range(N)]

for i in range(N):
    #星の等級式 apm = abm + 5 * log(distance)- 5
    apmlist[i][round(abmlist[i]*3+5*math.log10(d_list[i]*10))]=1

x = tf.placeholder(tf.float32,[None,2])
y_ = tf.placeholder(tf.float32,[None,R])

with tf.name_scope('hidden1'):

    
    weights1 = tf.Variable(
        tf.truncated_normal([2, HIDDEN_UNITS],
                            stddev=3.0),
        name='weights')

    biases1 = tf.Variable(tf.zeros([HIDDEN_UNITS]),
                         name='biases')
    hidden1 = tf.nn.relu(tf.matmul(x, weights1) + biases1)
 
  
with tf.name_scope('output'):
    
    weights_o = tf.Variable(
        tf.truncated_normal([HIDDEN_UNITS, R],
                            stddev=1.0 / math.sqrt(float(HIDDEN_UNITS))),
        name='weights')
    
    biases_o = tf.Variable(tf.zeros([R]),
                         name='biases')
    logits = tf.matmul(hidden1, weights_o) + biases_o
 

cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels = y_,logits = logits))
train_step = tf.train.AdagradOptimizer(0.5).minimize(cross_entropy)
 
sess = tf.InteractiveSession()
tf.global_variables_initializer().run()

testlist = [[i,j] for (i,j) in zip(abmlist[-500:],d_list[-500:])]
labellist = apmlist[-500:]
        
for i in range(times):
    s = (i * 100)%(N-500)
    sess.run(train_step,feed_dict={x:[[j,k] for (j,k) in zip(abmlist[s:s+100],d_list[s:s+100])],y_:apmlist[s:s+100]})
    if i%1000 ==0:
        correct_prediction = tf.equal(tf.arg_max(logits,1),tf.arg_max(y_,1))
        accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
        print(sess.run(accuracy,feed_dict={x:testlist,y_:labellist}))

最適化法やハイパーパラメータ類を少し変えただけでかなり挙動が変化する上、
法則性を見つけるのも困難である。
うまくいいパラメータを探し出すのは骨の折れる作業である。

初期の検討から比較すると訓練回数が大幅に低減し、正答率も向上した。

題材として星の等級をモデルにした検証は深追いせずここまでとする。
深層学習のモデルとしてはあまり適当だとは思えないからである。

MNISTやCIFARなどでいろいろ試すことになる予定。