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

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

生TensorFlow七転八倒記(1):基本的なロジスティック回帰まで

よくよく考えてみたら、TensorFlowをバックエンドとしてKerasを回したりさらにR上で動かしたりしたことはあるものの、肝心のTensorFlowを生で書いたことって数えるくらいしかなかったのでした。これではいかんと思うので、今更ながらですがTensorFlowを生で書いていく練習を七転八倒しながらやっていこうと思います。


なお毎度毎度書いていますが、既にweb上には非常に良質な実践的記事が多数公開されていますので、断じてこの記事からTensorFlowの何がしかを学ぼうなどという気を起こさぬようくれぐれも皆様よろしくお願いいたしますm(_ _)m


TensorFlowでロジスティック回帰をやってみる


以下、実際にはJuPyter Notebookでやっていますがブログに備忘録として貼り付ける都合上、ただの生コードとして貼ってあります。データセットは以下に転がしてあります。

なお、この記事は本家TensorFlowのドキュメント含め幾つかの資料を参考にしていますが、基本的には以下の2つの記事を最も多く参照しています。

先に書いておきますが、僕はPythonを普段は書かないので最初から最後までクソコードしか書いていません*1。断じてこれらのコードを真似して何かをしようとは考えないようお願いいたします。。。

生TensorFlowでやってみる

とりあえずTensorFlow以下必要なパッケージをインポートし、データセットを読み込みます。

import tensorflow as tf
import numpy as np
import csv
import pandas as pd
d_train=pd.read_csv("conflict_train.csv")
d_test =pd.read_csv("conflict_test.csv")

学習データとテストデータそれぞれ、特徴量部分とラベル部分とに分けます。これもっと綺麗に書けるはずだよなぁと思いながらクソコードを書いてしまいました、ごめんなさい。。。

train_X = d_train[[0,1,2,3,4,5,6]]
train_Y = d_train[[7]]
test_X = d_test[[0,1,2,3,4,5,6]]
test_Y = d_test[[7]]

ここからTensorFlowっぽくなってきます。まず各種placeholderを定義します。要は変数の入れ物です。何度か試行錯誤する都合上、reuse_variablesを入れてます。ロジスティック回帰はご存知のように線形回帰族なので、計画行列x、重み付けベクトルW、バイアス(切片)b、そしてそれらの線形結合和であるyを予め定義しておきます。

x = tf.placeholder(tf.float32, [None, 7])
y = tf.placeholder(tf.float32, [None, 1])
W = tf.Variable(tf.zeros([7, 1]))
b = tf.Variable(tf.zeros([1]))
tf.get_variable_scope().reuse_variables()

ロジスティック回帰の肝である、線形回帰子のところとそのシグモイド変換を定義します。ここでtf.matmulメソッドで行列計算をするところがTFっぽいですね。そして、それを用いてクロスエントロピーを計算し、そこからコスト関数を定義します(分からない人はその辺の機械学習のテキストを読みましょう)。その上で、コスト関数に対して最適化ソルバの情報を与えて最適化ルーチンを定義しておきます。名前は勾配法のメソッドですが、ドキュメントを見る限りではSGDでもこれを使うっぽいですね。。。

y_reg = tf.matmul(x, W) + b
y_reg_sigmoid = tf.sigmoid(y_reg)
cross_entropy = -tf.reduce_sum(y * tf.log(y_reg_sigmoid))
cost = tf.reduce_mean(cross_entropy)
optimizer = tf.train.GradientDescentOptimizer(0.00075).minimize(cost)

TFのセッションを開始します。

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

ここまで来てようやく実際のデータを最適化ルーチンに突っ込んで回すことになります。計算が終わったら、ロジスティック回帰の偏回帰係数とバイアス(切片)を表示するようにしておきます。ちなみに直接W, bをprintしようとしてもデータ型の情報が出るだけで何も表示されないので要注意。

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

最後に、テストデータに対する予測値を計算してconfusion matrixを表示します。予測値の計算は、シグモイド変換値を出す式をrunメソッドに直接突っ込んで、feed_dictにテストデータの特徴量を与えればおしまいです。なおconfusion matrixについてはTFのメソッドの使い方がいまいち分からなかったので、多少は知ってて確実性の高いsklearnのメソッドに変えてあります。

pred2 = sess.run(y_pred_sigmoid, feed_dict={x:test_X})
pred_df = pd.DataFrame(pred2)
pred_df = pred_df.round(1)
pred_df = pred_df.astype(int)
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score
print confusion_matrix(test_Y, pred_df), accuracy_score(test_Y, pred_df)

で、結果はこちら。

[[213  87]
 [ 47 253]] 0.776666666667

全然ダメじゃんorz 学習係数やエポック数を色々いじってみたんですが、ついにACC 0.8を超えませんでした。。。どうしたらいいんだろう。詳しい方、是非マサカリ投げつけながらで良いのでご教授くださいm(_ _)m

色々思ったことなど

一番困ったのはrunメソッドで学習を開始させるところ。色々なドキュメントを見ても何をどう与えたら良いのかしばらく分からなかったのですが、pandasデータフレームで何の問題もないんですね( ;´Д`) ということで、冒頭のところではpd.read_csvで読むようにしています。


というか、それ以外のTensorFowのチュートリアル的な記事の9割以上(※僕個人の主観的感覚)がデフォルトで入っているMNISTデータセットしか使ってないので、ぶっちゃけかなり困りました。。。結構困ったのが、MNISTだと10クラス分類なので当然のようにみんなtf.nn.softmaxメソッドを使うわけですが、今回持ってきたのはただの二値分類なのでこれだと走らなかったりするわけです(同じ現象をKerasとかでも見た気がする)。


そこで他のbinary logistic regression向けの方法ってないのかなぁ。。。と色々探してみた結果、最後にたどり着いたのがtf.sigmoidメソッドでシグモイド変換後の値に直すというやり方。これを探し当てるだけで2日潰しました。。。


Rで同じことをやってみる


ちなみに全く同じ計算をRのglm関数でやるとこうなります。Rは慣れているので瞬殺でした。

> d_train <- read.csv('conflict_train.csv')
> d_test <- read.csv('conflict_test.csv')
> d_train$cv <- as.factor(d_train$cv)
> d_test$cv <- as.factor(d_test$cv)
> d_train.glm <- glm(cv~., d_train, family=binomial)
> table(d_test$cv, round(predict(d_train.glm, newdata=d_test[,-8], type='response'),0))
   
      0   1
  0 279  21
  1  20 280

> sum(diag(table(d_test$cv, round(predict(d_train.glm, newdata=d_test[,-8], type='response'),0))))/nrow(d_test)
[1] 0.9316667

うーん、ただの最急降下法なのがいけないんですかね。。。ともあれ、これで機械学習の理論のテキストに書いてある通りに「モデルを定義し」「コスト関数を定義し」「最適化計画を定義し」「あとは繰り返し計算をして」「パラメータを算出すれば」ロジスティック回帰が実際に回るということだけは確認できました。次は何をやろう。。。(汗)


追記1


@さんからアドバイスを受けて、以下のように直してみたら0.9217まで行きました。

# Modified

import tensorflow as tf
import numpy as np
import csv
import pandas as pd
d_train=pd.read_csv("conflict_train.csv")
d_test =pd.read_csv("conflict_test.csv")
train_X = d_train[[0,1,2,3,4,5,6]]
train_Y = d_train[[7]]
test_X = d_test[[0,1,2,3,4,5,6]]
test_Y = d_test[[7]]
x = tf.placeholder(tf.float32, [None, 7])
y = tf.placeholder(tf.float32, [None, 1])
W = tf.Variable(tf.zeros([7,1]))
b = tf.Variable(tf.zeros([1]))
tf.get_variable_scope().reuse_variables()
y_reg = tf.matmul(x, W) + b
y_reg_sigmoid = tf.sigmoid(y_reg) # Only for prediction
# cross entropy should be computed by the method below as a referred code did
cross_entropy = tf.nn.sigmoid_cross_entropy_with_logits(labels = y, logits = y_reg)
cost = tf.reduce_mean(cross_entropy)
optimizer = tf.train.GradientDescentOptimizer(0.00075).minimize(cost)
sess = tf.Session()
init = tf.initialize_all_variables()
sess.run(init)
num_epochs = 5000
for i in range(num_epochs):
  sess.run(optimizer, feed_dict={x:train_X, y:train_Y})
print sess.run(W), sess.run(b)
pred2 = sess.run(y_reg_sigmoid, feed_dict={x:test_X})
pred_df = pd.DataFrame(pred2)
pred_df = pred_df.applymap(lambda x: x>=.5) # Changed because the type of return value for cross entropy was changed
pred_df = pred_df.astype(int)
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score
print confusion_matrix(test_Y, pred_df), accuracy_score(test_Y, pred_df)

[[ 0.12932312]
 [-0.14737938]
 [-0.01394201]
 [-0.41848031]
 [ 0.26410607]
 [ 0.67166275]
 [-0.04097671]] [-0.05328735]
[[270  30]
 [ 17 283]] 0.921666666667

ということで、クロスエントロピーの算出が良くなかったらしいという結論になりました。こういうことがあるとは、気をつけねば。


追記2


epoch数を50000まで増やしたら、Rの実行結果と同じになりました。

import tensorflow as tf
import numpy as np
import csv
import pandas as pd
d_train=pd.read_csv("conflict_train.csv")
d_test =pd.read_csv("conflict_test.csv")
train_X = d_train[[0,1,2,3,4,5,6]]
train_Y = d_train[[7]]
test_X = d_test[[0,1,2,3,4,5,6]]
test_Y = d_test[[7]]
x = tf.placeholder(tf.float32, [None, 7])
y = tf.placeholder(tf.float32, [None, 1])
W = tf.Variable(tf.zeros([7,1]))
b = tf.Variable(tf.zeros([1]))
tf.get_variable_scope().reuse_variables()
y_reg = tf.matmul(x, W) + b
y_reg_sigmoid = tf.sigmoid(y_reg) # Only for prediction
# cross entropy should be computed by the method below as a referred code did
cross_entropy = tf.nn.sigmoid_cross_entropy_with_logits(labels = y, logits = y_reg)
cost = tf.reduce_mean(cross_entropy)
optimizer = tf.train.GradientDescentOptimizer(0.00075).minimize(cost)
sess = tf.Session()
init = tf.initialize_all_variables()
sess.run(init)
num_epochs = 50000
for i in range(num_epochs):
  sess.run(optimizer, feed_dict={x:train_X, y:train_Y})
print sess.run(W), sess.run(b)
pred2 = sess.run(y_reg_sigmoid, feed_dict={x:test_X})
pred_df = pd.DataFrame(pred2)
pred_df = pred_df.applymap(lambda x: x>=.5) # Changed because the type of return value for cross entropy was changed
pred_df = pred_df.astype(int)
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score
print confusion_matrix(test_Y, pred_df), accuracy_score(test_Y, pred_df)

[[ 0.39974314]
 [-0.4933911 ]
 [-0.0667614 ]
 [-1.67382753]
 [ 0.86450762]
 [ 2.99408698]
 [-0.13957696]] [-0.42629421]
[[279  21]
 [ 20 280]] 0.931666666667

でもRの方が格段に速い(iterationも7で済んでるし)んですよねぇ。。。

*1:というかRですらクソコードしか書けませんが。。。