External Memory

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

以前の機械学習検討の改善(hidden layer追加など)

前回の
TensorFlowの機械学習を利用した擬似星の等級の計算 - External Memory
でTensorFlowを用いた星の等級の見積りを試しで行ったが、
正答率があまり良くなかった。


今回はhidden layerを追加して前回の2層から3層に変更して見積りを行った。
それぞれの層の次元が2-100-9ニューラルネットワーク構造とした。
またお試しとしてドロップアウト、L2正則化要素も比較検討の手段として盛り込んだ。
これらは過適合を改善したい場合に用いる手法である。


ニューラルネットワークの構造について以下のWebページを見つけた。
The Neural Network Zoo - The Asimov Institute

ネットワーク構造を検討するときに役立つかもしれないし、
学習効果の改善という意味で
引き出しが多くなることはそれほど悪いことでもないと思う。

上記URLに今回行うような構造は見当たらないが(敢えて言えばSAE)
イメージとしては2つのパラメータからより多くの性質を取り出し、
9つに分類させるような感じです。

以下のgithubにあるtensorflowソースコードサンプルを参考にして、
プログラムを作成した。
tensorflow/mnist.py at master · tensorflow/tensorflow · GitHub

以下は作成したコード

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=1.0 / math.sqrt(float(2))),
        name='weights')
    biases1 = tf.Variable(tf.zeros([HIDDEN_UNITS]),
                         name='biases')
    hidden1 = tf.nn.softmax(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

""" 
#dropout  
with tf.name_scope('drop_out'):
    keep_prob = tf.placeholder(tf.float32)
    drop_fc = tf.nn.dropout(hidden1,keep_prob)
    
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(drop_fc, 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(2.0).minimize(cross_entropy)

"""
#L2正規化   
L2_sqr = tf.nn.l2_loss(weights1) + tf.nn.l2_loss(weights_o)
lmd = 0.1
train_step = tf.train.AdagradOptimizer(2.0).minimize(cross_entropy + lmd * L2_sqr)
"""
 
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]})
    #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],keep_prob: 0.5})
    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}))
        #print(sess.run(accuracy,feed_dict={x:testlist,y_:labellist,keep_prob: 0.5}))

プログラムの簡単な解説

星の等級式を学習させたつもりですが、
どちらかというと単に対数関数のフィッティングといったほうがいいかもしれません。
星の等級式は距離と絶対等級の対数関数を含んだ関数となっている。

・データ数は9500個、訓練回数は10000回、テストデータは500個とした。

・ミニバッチは100個ごとで、個々に訓練させた一種の確率的勾配降下法である。

・活性化関数は2回ともにsoftmax関数、最適化法はAdagrad(学習率:2.0)です。

・重みの初期化については今回は正規分布を使用している。
標準偏差に1/sqrt(パラメータ数)を掛けているのは、学習の減速を避けるためです。
広がった正規分布では、活性化関数の結果も0 or 1に近くなることがある。
それを避けるために分布を適切なものに変更する。

ドロップアウトとL2正則化

これらを説明する前に過適合について簡単に説明する。
例えば、パラメータ数を過剰に増やして訓練させると、
その訓練データにぴったりとあった学習結果が得られると思われる。
この場合、訓練データの分類とは関係ない特徴まで拾ってしまうことが影響して、
本番データで良好な結果が得られず汎用化困難な状態になる。
この状態が過適合と呼ばれている。

内部パラメータ的には、
訓練データではloss最小化するようにぴったりと重み設定ができていても、
本番データでは訓練データの癖に引っ張られて最小からズレているということだろうか。

過適合を避けるには以下が思いつく。
・訓練データを増やす。
・訓練データにノイズを加える。
・訓練終了のタイミングを早める。
・ハイパーパラメータの設定を変える。

一方、
ドロップアウトは訓練中にニューロン発火の確率を変化させることで
過適合を回避する方法である。
ランダムに選ばれたニューロンを削除する。
動的にニューラルネットワークの構造を変化させることができるので、
直観的には様々な視点から訓練データを学習しているということになる。

また個々のニューロンの独立性が向上するように見えるから、
周囲のニューロンからの影響に対して柔軟性を持つとも解釈できる。
とはいえ、これらの説明は過適合との直接的な関係を述べているわけではない。


L2正則化はクロスエントロピーに重みの二乗和に関する項を
付け足すことで回避する方法である。

正則化されたクロスエントロピーは以下のように表される。

 \displaystyle C_{reg}=C+\frac{\lambda}{2n}\sum_w{\omega^2}

nは訓練データ数、λは正則化パラメータと呼ばれるものです。

また、勾配降下法における重みの変化は以下のように変わる。

 \displaystyle (1-\frac{\eta\lambda}{n})\omega-\frac{\eta}{m}\sum_x{\frac{\partial C}{\partial \omega}}
ηは学習率、mはミニバッチ数です。

上式を眺めると、普段と比較して重みが減衰していることがわかる。

重みが減衰するということは、特徴量がぼやかされるというイメージです。
これがニューラルネットワークがデータに対しての汎用性を高める結果
となるようである。


ドロップアウト正則化はその手法から
効果を上げるための手っ取り早いその場しのぎであるようにも見えるが、
知能の柔軟性はこれらと似たようなものかもしれない。

ここでのニューラルネットワークの構築は
単一種類データの学習から得られたものであるが、
人間の学習はそうではない、より多くの別種の要素を考慮して総合的に判断をする。
より少ないデータと訓練回数で習得する。

結果

結果的に正答率は前回の2層の0.85前後から3層では0.96まで向上した。
ドロップアウト、L2正則化を盛り込んだ場合は正答率は低下した。
これは、2つのパラメータで10000の訓練データからフィッティングを行い
9つに分類するのだから、もともと過適合状態ではなかったのだと思われる。
重みの変化は結局、元はただ2つのパラメータに依存するのみである。

むしろ今後精度向上を考えるなら学習効率の改善を考えるべき。

以下は訓練回数1000回ごとの正答率の推移データである。
2層
0.282
0.74
0.802
0.812
0.838
0.842
0.838
0.856
0.83
0.866

hidden layer挿入(3層)
0.23
0.82
0.886
0.888
0.946
0.95
0.946
0.922
0.96
0.96

ドロップアウト適用 (keep_prob値 0.5)
0.236
0.594
0.708
0.73
0.726
0.72
0.764
0.722
0.752
0.768

L2正則化適用 (λ=0.1)
0.28
0.232
0.28
0.28
0.28
0.232
0.28
0.28
0.28
0.28