今回は、1層だけのニューラルネットワークに隠れ層を追加して、少しだけ深いニューラルネットワークを組んでいきます。
前回、前々回からの続きです。
今回から読む方は、前々回から順番に読んでもらうことをおすすめします。
前々回
前回
隠れ層を追加するとはどういうことか
前回終了時点で組んでいたのは、1層のみの浅いニューラルネットワークでした。
ここに、隠れ層を追加していきます。
とりあえず、わかりやすさ優先にしたいので、こんな感じの4層でやってみようかと。
各層で計算して、活性化関数をとおした結果を入力にして、次の層でも同じことをすることで、順繰りにつなげていくわけです。
この「(データ*重み)+バイアス」の計算式と、出力のSoftMaxについては前々回でさらって触れています。
でも、今回始めて、「活性化(reLu)」というのがでてきたので、簡単に整理しときます。
活性化関数は、計算した結果に対して閾値を境にして、出力を切り替えます。
機械学習で特徴を示す部分を学習しようとしているわけなので、活性化関数でノイズを除去して、特徴点を明確にしていると思えば、考えやすいですね。
で、reLu関数ってどんな凄いことをしているのかと思ったら・・
実はこれだけだったりしますけど。
では、コードに落としていきます。
CSVデータ読み込み部を関数化する
今度は、ちゃんと学習用データと、テスト用データをわけたいので、CSV読み込み部分を関数化して、両方に使えるようにします。
def csv_loader(filename):
csv_obj = csv.reader(open(filename, "r"))
dt = [ v for v in csv_obj]
dat = [[float(elm) for elm in v] for v in dt]
db = [[0 for n in range(5)] for m in range(len(dat))]
lb = [[0 for nn in range(2)] for mm in range(len(dat))]
for i in range(len(dat)):
for j in range(len(dat[i])):
if j <= 4:
db[i][j] = dat[i][j]
else:
lb[i][j-5] = dat[i][j]
return (db,lb)
単純にdefで括って、結果をタプルで返すようにしただけです。
中の処理自体は前回から変えてないので、特に説明しません。
これがあるので、学習用データとテスト用データを読み込む部分は、こんな風にコンパクトになりました。
data_body,label_body = csv_loader("sample.csv")
data_body_test,label_body_test = csv_loader("sample_test.csv")
隠れ層の追加をコーディング
さっきの図の上の3つを隠れ層と呼びます。(赤枠)
ここで使う「重み(weight)」と「バイアス(bias)」を定義して、初期化します。
bias_h = tf.Variable(tf.zeros([5],dtype=tf.float32))
stddev_2 = 2.0 / math.sqrt(5 * 5)
weight_h1 = tf.get_variable("weight_h1",[5,5],initializer=tf.random_normal_initializer(stddev=stddev_2,dtype=tf.float32))
weight_h2 = tf.get_variable("weight_h2",[5,5],initializer=tf.random_normal_initializer(stddev=stddev_2,dtype=tf.float32))
weight_h3 = tf.get_variable("weight_h3",[5,5],initializer=tf.random_normal_initializer(stddev=stddev_2,dtype=tf.float32))
出力層は選択肢が2つ(1.0,0.0等)なので、[5,2]だったんですが、隠れ層は[5,5]にしてるところが、ちょっとしたポイントです。
なぜかというと、「(データ*重み)+バイアス」は行列同士の演算になるからです。
隠れ層のアウトプットは、次の隠れ層または出力層のインプットになるので、インプットとアウトプットは同じ形の行列にしないといけません。
で、行列演算の積のルールがあるので、必然的に重み(Weight)部分の行列の形は下図のように決まってしまうのです。
あと、この結果にバイアスを足してますが、行列は足し算では形が変わらないので、重み(weight)を[5,5]にすることで、インプットとアウトプットの形をあわせてるわけですね。
ちなみにバイアス(bias_h)は、[5](配列)になってます。
行列の和は、行と列が一致してないと計算できないので、本当はバイアスも、[CSVデータの行数、5]の形でないとだめなのですが、この辺はブロードキャストで、自動的に形をあわせて計算してくれるのにまかさせてるから、いけるわけです。
あと、重みの初期値を変えてます。
実は、活性化関数の種類によって、相性のよい初期値の与え方だと言われているものがあります。
一般には、活性化関数が「reLu」の場合は「Heの初期値」と利用し、「sigmoid」または「tanh(ハイポリックタンジェント)」の場合は、「Xavierの初期値」が適切だと言われてます。
今回は、活性化関数は「reLu」を使うので、「Heの初期値」です。
計算は、初期化する対象の総数を「n」として、以下を標準偏差とするガウス分布(正規分布)を求めることで行ってます。
それをコードにするとこうなるわけです。
#標準偏差を求める
stddev_2 = 2.0 / math.sqrt(5 * 5)
#求めた標準偏差で正規分布を計算する
weight_h1 = tf.get_variable("weight_h1",[5,5],initializer=tf.random_normal_initializer(stddev=stddev_2,dtype=tf.float32))
続いて、隠れ層の部分のコードです。
hidden_1 = tf.nn.relu(tf.matmul(data,weight_h1) + bias_h)
hidden_2 = tf.nn.relu(tf.matmul(hidden_1,weight_h2) + bias_h)
hidden_3 = tf.nn.relu(tf.matmul(hidden_2,weight_h3) + bias_h)
(データ*重み)+バイアス の結果を、reLu関数でくくっているだけなので、ほぼ、上に書いた概要図の通りですね。
さて動かしてみる
今回の主な変更はここまでです。
ソフトマックス関数を使う出力層のインプットが、隠れ層のアウトプットに変わっている以外は、ほぼ変更ありません。
今回は、テストデータも真面目につくって、学習用データとは違う未知のデータにしています。
それと前回、たった60件でやって過学習がおきて散々だったので、データ量を10倍くらいに増やしています。
期待通りに動けば、92%~93%くらいにはなるはずです。
まあ、いい感じじゃないですかね。
最後に今回のコード全体です。
import tensorflow as tf import csv csv_obj = csv.reader(open("sample.csv", "r")) dt = [ v for v in csv_obj] dat = [[float(elm) for elm in v] for v in dt] data_body = [[0 for n in range(5)] for m in range(len(dat))] label_body = [[0 for nn in range(2)] for mm in range(len(dat))] print(data_body) for i in range(len(dat)): for j in range(len(dat[i])): if j <= 4: data_body[i][j] = dat[i][j] else: label_body[i][j-5] = dat[i][j] bias = tf.Variable(tf.zeros([2],dtype=tf.float32)) weight = tf.Variable(tf.random_uniform(shape=[5,2],minval=-1.0,maxval=1.0,dtype=tf.float32)) data = tf.placeholder(dtype=tf.float32,shape=[None,5]) label = tf.placeholder(dtype=tf.float32,shape=[None,2]) y = tf.nn.softmax(tf.matmul(data,weight) + bias) cross_entropy = tf.reduce_mean(-tf.reduce_sum(label * tf.log(y), reduction_indices=[1])) train_step = tf.train.GradientDescentOptimizer(0.1).minimize(cross_entropy) correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(label,1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float")) with tf.Session() as s: s.run(tf.global_variables_initializer()) print(data_body) for k in range(10): s.run(train_step,feed_dict={data:data_body,label:label_body}) acc = s.run(accuracy, feed_dict={data:data_body,label:label_body}) print("結果:{:.2f}%".format(acc * 100))
2017/12/09追記
いちおう、tensorflow v1.4で動作確認しました。
2018/02/12追記
tensorflow v1.5で動作確認しました。
tensorflow入門の入門カテゴリの記事一覧はこちらです