アラカン"BOKU"のITな日常

文系システムエンジニアの”BOKU”が勉強したこと、経験したこと、日々思うことを書いてます。

入力データをCSVから読む機能の関数化と隠れ層の追加をする:Tensorflow入門の入門5/文系向け

今回は、1層だけのニューラルネットワークに隠れ層を追加して、少しだけ深いニューラルネットワークを組んでいきます。

 

前回、前々回からの続きです。

 

今回から読む方は、前々回から順番に読んでもらうことをおすすめします。

前々回

arakan-pgm-ai.hatenablog.com

前回

 


隠れ層を追加するとはどういうことか

 

前回終了時点で組んでいたのは、1層のみの浅いニューラルネットワークでした。

 

ここに、隠れ層を追加していきます。

 

とりあえず、わかりやすさ優先にしたいので、こんな感じの4層でやってみようかと。

f:id:arakan_no_boku:20170513140322j:plain

 

各層で計算して、活性化関数をとおした結果を入力にして、次の層でも同じことをすることで、順繰りにつなげていくわけです。

 

この「(データ*重み)+バイアス」の計算式と、出力のSoftMaxについては前々回でさらって触れています。


でも、今回始めて、「活性化(reLu)」というのがでてきたので、簡単に整理しときます。

 

活性化関数は、計算した結果に対して閾値を境にして、出力を切り替えます。

 

機械学習で特徴を示す部分を学習しようとしているわけなので、活性化関数でノイズを除去して、特徴点を明確にしていると思えば、考えやすいですね。

 

で、reLu関数ってどんな凄いことをしているのかと思ったら・・

f:id:arakan_no_boku:20170513144743j:plain

実はこれだけだったりしますけど。

 

では、コードに落としていきます。

 

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つを隠れ層と呼びます。(赤枠)

f:id:arakan_no_boku:20170513150629j:plain

 

ここで使う「重み(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)部分の行列の形は下図のように決まってしまうのです。

f:id:arakan_no_boku:20170513153724j:plain

 

あと、この結果にバイアスを足してますが、行列は足し算では形が変わらないので、重み(weight)を[5,5]にすることで、インプットとアウトプットの形をあわせてるわけですね。

 

ちなみにバイアス(bias_h)は、[5](配列)になってます。

 

行列の和は、行と列が一致してないと計算できないので、本当はバイアスも、[CSVデータの行数、5]の形でないとだめなのですが、この辺はブロードキャストで、自動的に形をあわせて計算してくれるのにまかさせてるから、いけるわけです。

 

あと、重みの初期値を変えてます。

 

 実は、活性化関数の種類によって、相性のよい初期値の与え方だと言われているものがあります。

 

一般には、活性化関数が「reLu」の場合は「Heの初期値」と利用し、「sigmoid」または「tanh(ハイポリックタンジェント)」の場合は、「Xavierの初期値」が適切だと言われてます。

 

今回は、活性化関数は「reLu」を使うので、「Heの初期値」です。

 

計算は、初期化する対象の総数を「n」として、以下を標準偏差とするガウス分布正規分布)を求めることで行ってます。

f:id:arakan_no_boku:20170513230858j:plain

 

それをコードにするとこうなるわけです。

#標準偏差を求める

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%くらいにはなるはずです。

f:id:arakan_no_boku:20170513232641j:plain

 

まあ、いい感じじゃないですかね。

 

最後に今回のコード全体です。

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入門の入門カテゴリの記事一覧はこちらです

arakan-pgm-ai.hatenablog.com

 

f:id:arakan_no_boku:20170404211107j:plain