六本木で働くデータサイエンティストのブログ

元祖「銀座で働くデータサイエンティスト」です / 道玄坂→銀座→東京→六本木

生TensorFlow七転八倒記(3):まずは神妙に隠れ層1個のNNで誤差逆伝播をやってみる

何となくTFのお作法が分かってきたのでどんどん先に行きます。そう言えばただの備忘録なので何一つ出典とか参考文献とか書いてませんが、このシリーズでやっていることの理論的基礎は深層学習青本がほぼ全てカバーしています。

深層学習 (機械学習プロフェッショナルシリーズ)

深層学習 (機械学習プロフェッショナルシリーズ)

というか、僕はこれ以上の理論的な知識を持ち合わせません。。。閑話休題、今度は3層NNで誤差逆伝播をやってみようと思います。と言っても、下のコードを見ての通りで非常に簡単です。


TensorFlowでやってみる


今回はUCI ML repositoryのWine Qualityデータセットの赤ワインの方を、適当にtrain / testに分けたやつを使います。いつも通りGitHubに置いてあります。

本来なら順序ロジットでモデリングするべきなんですが、面倒なのでただの名義尺度化させて多項分類になるようにしています。そしてコードを見れば分かりますが、TFだと逆伝播なんてわざわざ意識しなくてもグラフ構造に応じて勝手にオプティマイザの方で逆伝播しちゃうんですよね。。。あまり醍醐味がないようで。

import tensorflow as tf
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score

d_train = pd.read_csv("wine_red_train.csv", sep=',')
d_test = pd.read_csv("wine_red_test.csv", sep=',')
train_X = d_train.iloc[:, 0:11]
train_Y = d_train[[11]]
test_X = d_test.iloc[:, 0:11]
test_Y = d_test[[11]]

scaler = StandardScaler()
train_X.iloc[:, 0:11] = scaler.fit_transform(train_X)
test_X.iloc[:, 0:11] = scaler.fit_transform(test_X)

x = tf.placeholder(tf.float32, [None, 11])

W1 = tf.Variable(tf.random_normal([11,9], mean=0.0, stddev=0.5))
b1 = tf.Variable(tf.random_normal([9], mean=0.0, stddev=0.1))
y1 = tf.matmul(x, W1) + b1
tf.get_variable_scope().reuse_variables()

W2 = tf.Variable(tf.random_normal([9,6], mean=0.0, stddev=0.5))
b2 = tf.Variable(tf.random_normal([6], mean=0.0, stddev=0.1))
y2 = tf.nn.softmax(tf.matmul(y1, W2) + b2)
tf.get_variable_scope().reuse_variables()

y = tf.placeholder(tf.int64, [None, 1])
y_ = tf.one_hot(indices = y, depth = 6)

# tf.train.exponential_decayでmomentumっぽいことができるらしい
global_step = tf.Variable(0, trainable=False)
starter_learning_rate = 0.0001
learning_rate = tf.train.exponential_decay(starter_learning_rate, global_step,
                                           10000, 0.9, staircase=True)
tf.get_variable_scope().reuse_variables()

cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels = y_, logits = y2))
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)

sess = tf.Session()
init = tf.initialize_all_variables()
sess.run(init)

num_epochs = 500
for i in range(num_epochs):
  sess.run(optimizer, feed_dict = {x:train_X, y:train_Y})

pred = sess.run(y2, feed_dict = {x: test_X})
pred = pred.round(0)
pred_d = tf.argmax(pred,1)
print confusion_matrix(test_Y-3, sess.run(pred_d)), accuracy_score(test_Y-3, sess.run(pred_d))

[[ 0  0  1  0  0  0]
 [ 2  1  1  1  0  0]
 [15  7 15 12  0 19]
 [20  1  9 22  1 11]
 [ 7  1  3  6  2  1]
 [ 2  0  0  0  0  0]] 0.25

全然ダメじゃんorz ACC 0.25という惨憺たる有様になりました。


Rでやってみる


一応、{nnet}パッケージでそれっぽいことをやることができます。

> d_train <- read.csv('wine_red_train.csv')
> d_test <- read.csv('wine_red_test.csv')
> d_train$quality <- as.factor(d_train$quality)

> library(nnet)
> d_train.nnet <- nnet(quality~., d_train, size=9, decay=1e-7, maxit=500)
# weights:  168
initial  value 2319.968381 
iter  10 value 1671.868126
iter  20 value 1600.442989
iter  30 value 1495.903826
iter  40 value 1391.839381
iter  50 value 1359.748150
iter  60 value 1350.807539
iter  70 value 1347.931243
iter  80 value 1347.464786
iter  90 value 1346.661153
iter 100 value 1346.511094
iter 110 value 1346.359111
iter 120 value 1346.158824
iter 130 value 1345.423738
iter 140 value 1345.298538
iter 150 value 1345.281488
iter 160 value 1345.151790
iter 170 value 1344.751637
iter 180 value 1344.743908
iter 190 value 1344.737232
iter 200 value 1344.735879
iter 210 value 1344.731908
final  value 1344.731263 
converged

> table(d_test$quality, predict(d_train.nnet, newdata=d_test[,-12], type='class'))
   
     5  6  7
  3  1  0  0
  4  3  2  0
  5 50 18  0
  6 21 39  4
  7  1 13  6
  8  0  2  0
> (50+39+6)/nrow(d_test)
[1] 0.59375

RでやってもACC 0.59ぐらいとか結構ダメな感じですねorz 何か納得がいかないので、{randomForest}でやり直してみました。

> library(randomForest)
> tuneRF(d_train[,-12], d_train[,12], doBest=T)
mtry = 3  OOB error = 31.55% 
Searching left ...
mtry = 2 	OOB error = 30.44% 
0.03524229 0.05 
Searching right ...
mtry = 6 	OOB error = 32.38% 
-0.02643172 0.05 

Call:
 randomForest(x = x, y = y, mtry = res[which.min(res[, 2]), 1]) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 2

        OOB estimate of  error rate: 29.67%
Confusion matrix:
  3 4   5   6  7 8 class.error
3 0 1   7   1  0 0   1.0000000
4 1 0  28  18  1 0   1.0000000
5 0 1 500 109  3 0   0.1843393
6 0 1 122 421 30 0   0.2665505
7 0 0   7  83 89 0   0.5027933
8 0 0   0  10  4 2   0.8750000
> d_train.rf <- randomForest(quality~., d_train, mtry=2)

> table(d_test$quality, predict(d_train.rf, newdata=d_test[,-12]))
   
     3  4  5  6  7  8
  3  0  0  1  0  0  0
  4  0  0  4  1  0  0
  5  0  0 55 13  0  0
  6  0  0 13 48  3  0
  7  0  0  2  5 13  0
  8  0  0  0  0  2  0
> (55+48+13)/nrow(d_test)
[1] 0.725

ACC 0.725ということで、前にやった時と似たような結果になりました。

ということでせめてこれぐらいのACCは出したいものですが。。。どなたか詳しい方ご教授くださいm(_ _)m


追記1


いつもながら@さんからAzure NotebookにTFコードを載せたもの*1を頂戴したんですが、何故かL2正則化項がきちんと走らず。。。

どうも僕の手元の環境では頂戴したコード通りに書くと、最後のトレーニングのところでテンソル形状が合わずにエラーになるようです(with句を外して分かった)。とりあえず最後の.round(0)は確かにtf.argmaxするなら要らないはずということで、そこを削除した上で幾つか学習パラメータを変更してやり直してみました。

import tensorflow as tf
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score

d_train = pd.read_csv("wine_red_train.csv", sep=',')
d_test = pd.read_csv("wine_red_test.csv", sep=',')
train_X = d_train.iloc[:, 0:11]
train_Y = d_train[[11]] - 3
test_X = d_test.iloc[:, 0:11]
test_Y = d_test[[11]] - 3
# ここで先にラベルを-3しておく

scaler = StandardScaler()
train_X.iloc[:, 0:11] = scaler.fit_transform(train_X)
test_X.iloc[:, 0:11] = scaler.fit_transform(test_X)

x = tf.placeholder(tf.float32, [None, 11])

W1 = tf.Variable(tf.random_normal([11,9], mean=0.0, stddev=0.5))
b1 = tf.Variable(tf.random_normal([9], mean=0.0, stddev=0.1))
y1 = tf.matmul(x, W1) + b1
tf.get_variable_scope().reuse_variables()

W2 = tf.Variable(tf.random_normal([9,6], mean=0.0, stddev=0.5))
b2 = tf.Variable(tf.random_normal([6], mean=0.0, stddev=0.1))
y2 = tf.nn.softmax(tf.matmul(y1, W2) + b2)
tf.get_variable_scope().reuse_variables()

y = tf.placeholder(tf.int64, [None, 1])
y_ = tf.one_hot(indices = y, depth = 6)
global_step = tf.Variable(0, trainable=False)
starter_learning_rate = 0.3
learning_rate = tf.train.exponential_decay(starter_learning_rate, global_step,
                                           1000, 0.95, staircase=True)
tf.get_variable_scope().reuse_variables()

cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels = y_, logits = y2))
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)

sess = tf.Session()
init = tf.initialize_all_variables()
sess.run(init)

num_epochs = 1000
for i in range(num_epochs):
  sess.run(optimizer, feed_dict = {x:train_X, y:train_Y})

pred = sess.run(y2, feed_dict = {x: test_X})
# pred = pred.round(0) # これが不要
pred_d = tf.argmax(pred,1)
print confusion_matrix(test_Y, sess.run(pred_d)), accuracy_score(test_Y, sess.run(pred_d))

[[ 0  0  1  0  0  0]
 [ 0  0  4  1  0  0]
 [ 0  0 48 20  0  0]
 [ 0  0 14 48  2  0]
 [ 0  0  1 16  3  0]
 [ 0  0  0  2  0  0]] 0.61875

初期値次第な気もしますがACC 0.62近くまではいくことが分かりました。

*1:これは自分の中では複数のツボにハマってるんですが笑