はじめに
今回はtensorflowで全結合ニューラルネットの実装を試した時のお話を書きます。
初心者向けの日本語記事を色々検索してみたんですが、純粋な全結合ニューラルネットの実装記事って少ないですよね?
tensorflowのチュートリアルの最初がCNNになっているから、みんなそっちを最初に実装するんでしょうか?
それとも単に、簡単すぎて誰も書かないんでしょうか?
ちなみに、英語の記事を検索するときに"dense network"って調べるとCNNのdense netが出てきてしまうので、ちゃんと"dense layer"と検索しましょう。
全結合ニューラルネットを書いてみる
2つの記事を参考に実装を試してみました。
1、https://gist.github.com/koaning/c26b2dd5c2bdeaf6d7479a68bd7023bb
2、https://github.com/pinae/TensorFlow-MNIST-example/blob/master/fully-connected.py
1番の方はtf.layers.denseを使っての実装。
2番の方はもっと細かく、全結合層の重みW・バイアスBを別々に定義していました。
とりあえず自分は1番の方の実装を真似してみました。
入力層への入力は、普通(バッチサイズ*入力次元)になりますが、今回は単純に「数値を1つ入れたら1つの数値が出力される」ネットワークを作りました。
import numpy as np
import tensorflow as tf
x = tf.placeholder(tf.float32, shape=[1, 1]) # 入力
dense_1 = tf.layers.dense(x, 10, activation=tf.nn.sigmoid, name="dense1")
dense_2 = tf.layers.dense(dense_1, 10, activation=tf.nn.relu, name="dense2")
dense_3 = tf.layers.dense(dense_2, 1, activation=None, name="dense3")
with tf.Session() as sess:
sess.run(tf.global_variables_initializer()) # 変数を初期化
input_value = [[10]]
result = sess.run(dense_3, feed_dict={x:input_value})
print(result) # [[ 0.20399502]]
とりあえず、ちゃんと動きました。
めでたしめでたし。
誤差関数やオプティマイザの定義も簡単だったので、そこに関してはさっきのリンク先のコードを参考にしてみてください。
重みを取得したい
中間層の重みを保存したいのですが、これが意外とめんどくさい。
やり方も色々あるようですが、自分はtf.get_collection関数を使ってみました。
x = tf.placeholder(tf.float32, shape=[1, 1]) # 入力
dense_1 = tf.layers.dense(x, 10, activation=tf.nn.sigmoid, name="dense1") # 全結合層
# ここに注目
L1 = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope="dense1")
with tf.Session() as sess:
sess.run(tf.global_variables_initializer()) # 変数を初期化
print(L1)
dense1の重みを取得することを目的に、自分がやったことを最小限のコードで説明していきます。
上のコードを実行すると、以下がプリントされました。
[<tf.Variable 'dense1/kernel:0' shape=(1, 10) dtype=float32_ref>, <tf.Variable 'dense1/bias:0' shape=(10,) dtype=float32_ref>]
全部で2つのオブジェクトが入っていて、それぞれ重み(dense1/kernel:0)とバイアス(dense1/bias:0)ですね。
一応tensorboardで可視化した結果も貼っておきます。
以上の結果から、dense_1の重み行列の名前は"dense1/kernel:0"だとわかったので、これを直接取り出してみます。
x = tf.placeholder(tf.float32, shape=[1, 1]) # 入力
dense_1 = tf.layers.dense(x, 10, activation=tf.nn.sigmoid, name="dense1") # 全結合層
W1 = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope="dense1/kernel")[0] # dense_1の重み
with tf.Session() as sess:
sess.run(tf.global_variables_initializer()) # 変数を初期化
print(W1)
print(W1.eval()) # 重みの値をプリント
# <tf.Variable 'dense1/kernel:0' shape=(1, 10) dtype=float32_ref>
# [[ 0.72343653 0.27341634 0.03631765 -0.09714276 0.42991978 0.64759761
# -0.06103593 0.47831839 -0.17354167 0.62190872]]
うまくいきました!
tf.get_collection関数はスコープに当てはまる変数を全て取得するので、もしdense1の中に"dense1/kernel:0"と"dense1/kernel:1"の2つがあったら、
tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope="dense1/kernel")
は長さが2のリストを返します。
lsコマンドと同じようなもんですね。
名前空間について
変数がたくさんあって名前を検索するのが面倒な場合、tf.variable_scopeを使うとPCにディレクトリを作って整理するのと同じように名前空間をカスタマイズできます。
with tf.variable_scope("hoge"):
x = tf.placeholder(tf.float32, shape=[1, 1]) # 入力
dense_1 = tf.layers.dense(x, 10, activation=tf.nn.sigmoid, name="dense1")
dense_2 = tf.layers.dense(x, 10, activation=tf.nn.sigmoid, name="dense2")
HOGE = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope="hoge")
W2 = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope="hoge/dense2/kernel")
W2_bad = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope="dense2/kernel")
with tf.Session() as sess:
sess.run(tf.global_variables_initializer()) # 変数を初期化
print(HOGE)
print(W2)
print(W2_bad)
# [<tf.Variable 'hoge/dense1/kernel:0' shape=(1, 10) dtype=float32_ref>, <tf.Variable 'hoge/dense1/bias:0' shape=(10,) dtype=float32_ref>, <tf.Variable 'hoge/dense2/kernel:0' shape=(1, 10) dtype=float32_ref>, <tf.Variable 'hoge/dense2/bias:0' shape=(10,) dtype=float32_ref>]
# [<tf.Variable 'hoge/dense2/kernel:0' shape=(1, 10) dtype=float32_ref>]
# []
名前空間の中に"hoge"というディレクトリを作成しているイメージでしょうか。
注意点はW2とW2_badを比較するとわかると思います。
中間層の出力を保存したい
dense_1 = tf.layers.dense(x, 10, activation=tf.nn.sigmoid, name="dense1")
dense_2 = tf.layers.dense(dense_1, 10, activation=tf.nn.relu, name="dense2")
dense_3 = tf.layers.dense(dense_2, 1, activation=None, name="dense3")
の3層があったとして、学習中にdense_2の出力を保存しておきたい場合どうすれば良いのでしょうか。
これについてはうまい方法がわかっておらず、自分は
output = sess.run(dense_3)
middle = sess.run(dense_2)
のように二度手間をかけてあげようかと思っているのですが、良い方法を知っている方がいたら教えていただきたいです。