前回に引続き、ニューラルネットについての勉強をしてみました。
今回は、簡単な分類モデルの構築に挑戦。
以下の2つの変数を準備します。
0 \le x_1,x_2 \le 1
そして、2つの和によって、以下のような分類をしてみる。
t(x_1,x_2) =
\left\{
\begin{matrix}
0 & (x_1 + x_2 < 1) \\
1 & (x_1 + x_2 \ge 1)
\end{matrix}
\right.
こんなシンプルなモデルを実装してみます。
0層??のニューラル考え方としては、以下の数式を検討します。
y(x_1,x_2) = \sigma(w_1 x_1 + w_2 x_2 + w_0)
ただし、σはシグモイド関数で、そのアウトプットは[0,1]に限定されます。これが本当特徴的で、y<0.5なら分類A、y>0.5なら分類B、のような意味を持たせているようです。実はこれって結構肝の部分な気がします。ニューラルネットでは、w1,w2,w0を調整することで、答えを出すような仕組み。ということで、誤差を見積もるファンクションを検討すると、この場合は交差エントロピーになるようです。具体的には、x1,x2に対して正解tを与えたとき、その交差エントロピーは以下のように表現されます。
E(w_1,w_2,w_0) = - (t \ln y + (1-t) \ln (1-y) )
詳細は、「パターン認識と機械学習」などを参照していただく(p.235あたり)として、ざっくりとしたイメージとしては・・・。0<y<1に注意して
- yをクラスAの確率と見なす(t=1のとき)
- 1-yをクラスBの確率と見なす(t=0のとき)
すると、tが与えられたときの確率p(t)は、X^0=1を利用して式を1本化することで
p(t)= y^t (1-y)^{1-t}
となるようです。あとはログをとれば、E(w1,w2,w0)の式が導出されます。これを各サンプルごとに掛け算するので、各yiに対して
p(t)= \prod_i y_i^t (1-y_i)^{1-t}
となるので、ログをとって
\ln p(t)= \sum_i (t \ln y_i + (1-t) \ln (1-y_i) )
となります。これは正解率の和のような感じなので、合っているほど値が大きくなります。そこで、最適化(最小化)問題として解くために符号を反転させてEを定義しました。繰り返しますが、以下の交差エントロピーになります。(複数サンプルバージョン)
E(w_1,w_2,w_0) = - \sum_i (t \ln y_i + (1-t) \ln (1-y_i) )
では、実際にソースコードを書いてみます。
まずは、変数部分。
# make placeholder
x_ph = tf.placeholder(tf.float32, [None, 3])
t_ph = tf.placeholder(tf.float32, [None, 1])
x_phは入力部分、本当は2つのはずですが、w0用のダミー変数(1固定)を入れておきました。これはいらないかも?
t_phは出力部分で、正解を与えるためのものです。0か1が入ります。
そして、これに入れるサンプルデータは適当に乱数で設定します。
# deta making???
N = 1000
x = np.random.rand(N,2)
# sum > 1.0 -> 1 : else -> 0
t = np.floor(np.sum(x,axis=1))
# ext x
x = np.hstack([x,np.ones(N).reshape(N,1)])
xを2つ作ったのち、yを作って、その後、ダミー変数を1個追加してます。これによって、xは1サンプルで3次元となっています。yはx1+x2が1以上のときに1、1未満では0となるように、小数点切捨てを利用して作成しています。
そして、ニューラルネットワークの層はとりあえず2つほど準備。
# create newral parameter(depth=2,input:3 > middle:30 > output:1)
hidden1 = tf.layers.dense(x_ph, 30, activation=tf.nn.relu)
newral_out = tf.layers.dense(hidden1, 1, activation=tf.nn.sigmoid)
あとは、交差エントロピーを定義して、最小化するように学習を定義します。単なるコピペですね。収束アルゴリズム設定など、まだよくわかっていません。
# Minimize the cross entropy
ce = -tf.reduce_sum(t_ph * tf.log(newral_out) + (1-t_ph)*tf.log(1-newral_out) )
optimizer = tf.train.AdamOptimizer()
train = optimizer.minimize(ce)
というわけで、上記を適当に組み合わせて、全体のソースを作ってみました。
import numpy as np
import tensorflow as tf
# deta making???
N = 1000
x = np.random.rand(N,2)
# sum > 1.0 > 1 : else > 0
t = np.floor(np.sum(x,axis=1))
# ext x
x = np.hstack([x,np.ones(N).reshape(N,1)])
train_x = x
train_t = t
# make placeholder
x_ph = tf.placeholder(tf.float32, [None, 3])
t_ph = tf.placeholder(tf.float32, [None, 1])
# create newral parameter(depth=2,input:3 > middle:30 > output:1)
hidden1 = tf.layers.dense(x_ph, 30, activation=tf.nn.relu)
newral_out = tf.layers.dense(hidden1, 1, activation=tf.nn.sigmoid)
# Minimize the cross entropy
ce = -tf.reduce_sum(t_ph * tf.log(newral_out) + (1-t_ph)*tf.log(1-newral_out) )
optimizer = tf.train.AdamOptimizer()
train = optimizer.minimize(ce)
# initialize tensorflow session
sess = tf.Session()
sess.run(tf.global_variables_initializer())
for k in range(1001):
if np.mod(k,100) == 0:
# get Newral predict data
y_newral = sess.run( newral_out
,feed_dict = {
x_ph: x, # xに入力データを入れている
})
ce_newral = sess.run( ce
,feed_dict = {
x_ph: x, # xに入力データを入れている
t_ph: t.reshape(len(t),1) # yに正解データを入れている
})
sign_newral = np.sign(np.array(y_newral).reshape([len(t),1]) - 0.5)
sign_orig = np.sign(np.array(t.reshape([len(t),1])) - 0.5)
NGCNT = np.sum(np.abs(sign_newral-sign_orig))/2
# check predict NewralParam
print('[%d] loss %.2f hit_per:%.2f' % (k,ce_newral,(N-NGCNT)/N))
# shuffle train_x and train_t
n = np.random.permutation(len(train_x))
train_x = train_x[n]
train_t = train_t[n].reshape([len(train_t), 1])
# execute train process
sess.run(train,feed_dict = {
x_ph: train_x, # x is input data
t_ph: train_t # t is true data
})
# test用
x = np.array([0.41,0.5,1]).reshape([1,3])
loss_newral = sess.run( newral_out
,feed_dict = {
x_ph: x, # xに入力データを入れている
})
# <0.5なら成功かな
print(loss_newral)
これを動かすと以下のようになります。
[0] loss 727.36 hit_per:0.35
[100] loss 587.68 hit_per:0.78
[200] loss 465.78 hit_per:0.89
[300] loss 358.70 hit_per:0.93
[400] loss 282.45 hit_per:0.94
[500] loss 230.54 hit_per:0.96
[600] loss 194.34 hit_per:0.97
[700] loss 168.11 hit_per:0.98
[800] loss 148.34 hit_per:0.98
[900] loss 132.93 hit_per:0.99
[1000] loss 120.56 hit_per:0.99
[[0.27204064]]
lossと書かれたところは、交差エントロピーの値、徐々に減っている様子が分かります。(変数名変えろよ・・・と突っ込み)
そして、hit_perは、学習データに対しての正解率。1.00で100%となりますが、99%の成功率のようです。
最後に、適当なテスト、x1=0.45,x2=0.5を入れたときのニューラルネットのアウトプットを出しています。この場合は、クラスBのt=0のはずなので、アウトプットは0.5より小さければ正解、かつ、0.5に近いほど迷っていると読み取れます。今回は0.27なので結構自信を持って答えてOKそうです。
最後に正解の出し方について、ちょっとだけ書いておきます。ポイントはNGの数を出しているところだと思います。以下一部ピックアップ。
# get Newral predict data
y_newral = sess.run( newral_out
,feed_dict = {
x_ph: x, # xに入力データを入れている
})
sign_newral = np.sign(np.array(y_newral).reshape([len(t),1]) - 0.5)
sign_orig = np.sign(np.array(t.reshape([len(t),1])) - 0.5)
NGCNT = np.sum(np.abs(sign_newral-sign_orig))/2
y_newralには、xから推定した分類情報が[0,1]の範囲で入っています。この数値は、0.5より大きいか小さいかで、どちらの分類に入るか、という意味なので、それを判別できる形に変換する必要があります。そこで、0.5という数値を引いて、[-0.5,0.5]の範囲に平行移動してから、符号だけ取り出して、+1か-1のどちらかが選択されるようにしました。同様の処理を、t(正解)に対しても実施して、+1/-1の正解値を生成。
この二つは同じ値のときに正解、違う値のときには不正解なのですが、パターンを書き出すと、以下のような関係にあります。
推定値 | 正解値 | 推定の正しさ | 推定値-正解値 |
---|---|---|---|
1 | 1 | OK | 0 |
1 | -1 | NG | 2 |
-1 | 1 | NG | -2 |
-1 | -1 | OK | 0 |
そこで、sum(abs(推定値-正解値))/2を計算することによって、NGの数が数えられます。これを利用することで、正解率???HIT率が計算できました。
めでたく動いたわけですが、ニューラルネットの設定をもっとシンプルに、中間層なしでやると、収束に時間がかかったり、やりすぎて途中でゼロ割???っぽいことが起きるようでした。
例えば以下のような設定で行くと・・・
newral_out = tf.layers.dense(x_ph, 1, activation=tf.nn.sigmoid)
結果は???
[0] loss 761.80 hit_per:0.50
[100] loss 732.66 hit_per:0.50
[200] loss 706.48 hit_per:0.50
[300] loss 682.59 hit_per:0.50
[400] loss 660.61 hit_per:0.50
[500] loss 640.24 hit_per:0.54
[600] loss 621.26 hit_per:0.62
[700] loss 603.52 hit_per:0.70
[800] loss 586.88 hit_per:0.76
[900] loss 571.22 hit_per:0.80
[1000] loss 556.44 hit_per:0.84
[[0.52383685]]
となって、なんだかいまひとつようです。何がどう利いているのか???私もまだまだ理解不足ですが、ある程度中間層があることで、何かが起きて、上手くいっているようです。
どこまで増やせばどんな状態になるのか?その辺りの理論も追々勉強していきたいと思います。