Softmax Regressionの実験

1. MNISTデータセットの用意

In [1]:
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
Extracting MNIST_data/train-images-idx3-ubyte.gz
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz
In [2]:
# 表示してみる
import matplotlib.pyplot as plt
%matplotlib inline

fig = plt.figure(figsize=(12,6))

for i, img in enumerate(mnist.train.images[:25]):
    ax = fig.add_subplot(5,5,i+1)
    ax.imshow(img.reshape(28, 28), interpolation="none")

2. Softmax Regressionの実装

  • TensorFlowのプログラムは以下の2つの段階からなる
    • Computational Graphを構築(計算の依存関係の定義)
    • Computational Graphを実行(ここで初めて具体的な計算が行われる)

2.1 準備

In [3]:
# import
import tensorflow as tf
# ログ保存用のディレクトリがあれば一度削除し、新たに作りなおす
LOG_DIR = "/tmp/softmax"
if tf.gfile.Exists(LOG_DIR):
    tf.gfile.DeleteRecursively(LOG_DIR)
tf.gfile.MakeDirs(LOG_DIR)
In [4]:
# TensorBoardのための準備
def variable_summaries(var):
    """ テンソルに様々なsummaryを付加する(TensorBoardでの可視化に利用)
    Args:
        var: summaryを付加したいテンソル
    Returns:
        なし(summary opの付加を行うだけの関数)
    """
    with tf.name_scope('summaries'):
        mean = tf.reduce_mean(var)
        tf.summary.scalar('mean', mean)
        with tf.name_scope('stddev'):
            stddev = tf.sqrt(tf.reduce_mean(tf.square(var - mean)))
        tf.summary.scalar('stddev', stddev)
        tf.summary.scalar('max', tf.reduce_max(var))
        tf.summary.scalar('min', tf.reduce_min(var))
        tf.summary.histogram('histogram', var)

2.2 Computational Graphの構築

In [5]:
with tf.name_scope("input"):
    # データを与えるためのplaceholder
    x = tf.placeholder(tf.float32, [None, 784], name = "image") # input image
    y_ = tf.placeholder(tf.float32, [None, 10], name = "label") # label
     
with tf.name_scope("variables"):
    # 学習可能なパラメータ
    with tf.name_scope("Weight"):
        W = tf.Variable(tf.zeros([784, 10]))
        variable_summaries(W)
    with tf.name_scope("Bias"):
        b = tf.Variable(tf.zeros([10]))
        variable_summaries(b)
In [6]:
def inference(images, weights, biases):
    """ 画像からそのクラスを予想する
    Args: 
        images: 画像のplaceholder
        weights: 重みのVariable
        biases: バイアスのVariable
    Returns:
        logits: 計算されたlogit
    """
    with tf.name_scope("logits"):
        logits = tf.matmul(images, weights) + biases
        tf.summary.histogram("logits_histogram", logits)
    return logits
In [7]:
def loss(logits, labels):
    """ logitとlabelから誤差関数(交差エントロピー)を計算する
    Args: 
        logits:識別器の予測
        labels:正しいラベル
    Returns:
        cross_entropy: 交差エントロピー
    """
    with tf.name_scope("loss"):
        cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=labels, logits=logits), name="cross_entropy_mean")
        tf.summary.scalar('cross_entropy', cross_entropy) # ログをとる
    return cross_entropy
In [8]:
def train(loss, learning_rate):
    """ 訓練用のopを生成する
    Args:
        loss
        learning_rate
    Returns:
        The op for training
    """
    with tf.name_scope("train"):
        optimizer = tf.train.GradientDescentOptimizer(learning_rate)
        train_op = optimizer.minimize(loss)
    return train_op
In [9]:
def evaluation(logits, labels):
    """ logitsがlabelsを予測する精度を評価する
    Args:
        logits
        labels
    Returns:
        正解率
    """
    with tf.name_scope("evaluation"):
        correct = tf.equal(tf.argmax(logits, 1), tf.argmax(labels, 1))
        accuracy = tf.reduce_mean(tf.cast(correct, tf.float32), name="accuracy")
        tf.summary.scalar("accuracy", accuracy) # ログをとる
    return accuracy
In [10]:
# グラフを構築
logits = inference(x, W, b)
loss = loss(logits, y_)
train_op = train(loss, 0.1)
accuracy = evaluation(logits, y_)
summary = tf.summary.merge_all() # すべてのsummaryをひとまとめにするop

2.3 Computational Graphの実行

In [11]:
sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())
In [12]:
# summaryをファイルに出力するためのwriter
train_writer = tf.summary.FileWriter(LOG_DIR+"/train", sess.graph) # sess.graphを渡すことでComputational GraphをTensorBoardに可視化
test_writer = tf.summary.FileWriter(LOG_DIR+"/test")
In [13]:
MAX_STEP = 2000
BATCH_SIZE = 100
for step in range(MAX_STEP):
    batch_xs, batch_ys = mnist.train.next_batch(BATCH_SIZE)
    sess.run(train_op, feed_dict={x: batch_xs, y_: batch_ys}) # trainを実行
    # 100stepごとにテスト結果を表示
    if step % 100 == 0:
        test_loss, test_accuracy = sess.run([loss, accuracy], feed_dict={x: mnist.test.images, y_: mnist.test.labels})
        print("step: {}, test_accuracy: {}, test_loss: {}".format(step, test_accuracy, test_loss))
        # ログの実行
        train_summary_str = sess.run(summary, feed_dict={x: batch_xs, y_: batch_ys})
        test_summary_str = sess.run(summary, feed_dict={x: mnist.test.images, y_: mnist.test.labels})
        train_writer.add_summary(train_summary_str, step)
        test_writer.add_summary(test_summary_str, step)
train_writer.close()
test_writer.close()
step: 0, test_accuracy: 0.407499879599, test_loss: 2.19526791573
step: 100, test_accuracy: 0.870700120926, test_loss: 0.589205980301
step: 200, test_accuracy: 0.885000109673, test_loss: 0.469472557306
step: 300, test_accuracy: 0.892900049686, test_loss: 0.418157845736
step: 400, test_accuracy: 0.89660012722, test_loss: 0.394196510315
step: 500, test_accuracy: 0.89970010519, test_loss: 0.374979138374
step: 600, test_accuracy: 0.902900099754, test_loss: 0.361071527004
step: 700, test_accuracy: 0.904900074005, test_loss: 0.353250801563
step: 800, test_accuracy: 0.907600164413, test_loss: 0.343760222197
step: 900, test_accuracy: 0.909000098705, test_loss: 0.337838679552
step: 1000, test_accuracy: 0.909600138664, test_loss: 0.33370706439
step: 1100, test_accuracy: 0.911600112915, test_loss: 0.325382322073
step: 1200, test_accuracy: 0.910800099373, test_loss: 0.324246108532
step: 1300, test_accuracy: 0.912300109863, test_loss: 0.318959236145
step: 1400, test_accuracy: 0.911800086498, test_loss: 0.317459285259
step: 1500, test_accuracy: 0.914300084114, test_loss: 0.315505206585
step: 1600, test_accuracy: 0.91520011425, test_loss: 0.310797214508
step: 1700, test_accuracy: 0.915200173855, test_loss: 0.308820784092
step: 1800, test_accuracy: 0.916200101376, test_loss: 0.308186322451
step: 1900, test_accuracy: 0.916100084782, test_loss: 0.30688893795

テストデータに対する正答率

In [14]:
print("Accuracy: {}".format(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})))
Accuracy: 0.91680008173

3. TensorBoardによる可視化

3.1 TensorBoardの起動

LOG_DIRを/tmp/softmaxにしていた場合、

$ tensorboard --logdir=/tmp/softmax

によってTensorBoardを起動する。

Starting TensorBoard 41 on port 6006

のように表示されるので、ブラウザで

http://localhost:6006/

を開く。

3.2 スクリーンショット

  • SCALARS

  • DISTRIBUTIONS

  • HISTOGRAMS

  • GRAPHS

4. 重みの可視化

Softmax Regressionは、

  • 重み行列Wの各ベクトル(今回は10クラスなので10個)と入力画像との内積をとる
  • バイアスを足してSoftmaxする

という計算によって事後確率を求めている。したがって、「最もiと認識されやすい画像」は重みのi番目のベクトル(i=0, 1, ..., 9)によって得ることができる。

In [15]:
fig = plt.figure(figsize=(12,6))

for i in range(W.eval().shape[1]):
    ax = fig.add_subplot(2,5,i+1)
    ax.imshow(W.eval()[:, i].reshape(28, 28), interpolation="none")

5. 識別器をだましてみる

様々な文字画像に先述の重み画像を混ぜあわせることを考える。

人間の目には依然として容易に識別できるにもかかわらず、識別器は誤って識別するようになる。例えば"7"に対応する重みを混ぜあわせると、多くの画像を"7"と予想するようになる。

In [16]:
# "7"に対応する重みをテスト用画像に足し合わせて表示する
import numpy as np
fake_x = np.array([img + W.eval()[:, 7] for img in mnist.test.images[:25]])

fig = plt.figure(figsize=(12,6))
for i, img in enumerate(fake_x):
    ax = fig.add_subplot(5,5,i+1)
    ax.imshow(img.reshape(28, 28), interpolation="none")
In [17]:
# 上の画像の多くを"7"と予想するようになる
print(sess.run(tf.argmax(logits, 1), feed_dict={x: fake_x}))
[7 2 7 7 7 7 7 7 7 7 0 7 7 0 7 7 7 7 7 7 7 6 7 7 7]

6. 自信があるほどよく当たるのか

Softmax Regressionでは各クラスの事後確率を推定し、事後確率が最大となるクラスをモデルの予想としている。事後確率の最大値が90%となる入力画像と50%となる入力画像では、前者の方が正答率が高いはず。それを確かめてみる。

In [18]:
posteriors = sess.run(tf.nn.softmax(logits), feed_dict={x: mnist.test.images})# テストデータに対してモデルが出力した事後確率
print(posteriors.shape)
is_true = np.argmax(posteriors, 1) == np.argmax(mnist.test.labels, 1) # 正解したデータにのみTrueが入ったarray
print(is_true.shape)
(10000, 10)
(10000,)
  • 正しく識別できたデータについて、正しいクラスに対する事後確率がどのような値をとっていたかを表すヒストグラムを描画
In [19]:
plt.hist(np.max(posteriors[is_true], 1))
Out[19]:
(array([   17.,    50.,   112.,   216.,   238.,   311.,   401.,   691.,
         1285.,  5847.]),
 array([ 0.25194693,  0.32675163,  0.40155632,  0.47636102,  0.55116572,
         0.62597042,  0.70077512,  0.77557982,  0.85038452,  0.92518922,
         0.99999392]),
 <a list of 10 Patch objects>)
  • 識別に失敗したデータについて、間違えて予測したクラスに対する事後確率がどのような値をとっていたかを表すヒストグラムを描画
In [20]:
plt.hist(np.max(posteriors[~is_true], 1))
Out[20]:
(array([   6.,   47.,  112.,  142.,  143.,  125.,   89.,   68.,   60.,   40.]),
 array([ 0.18450125,  0.26577138,  0.34704152,  0.42831166,  0.5095818 ,
         0.59085194,  0.67212208,  0.75339222,  0.83466236,  0.9159325 ,
         0.99720263]),
 <a list of 10 Patch objects>)
In [21]:
# 事後確率のクラス間の最大値を0.1刻みにしたもの
floor_posteriors = np.array(map(lambda x: int(x*10)/10., np.max(posteriors, 1)))
In [22]:
# モデルの予想した事後確率と、正解(1)不正解(0)をたばねたもの
count = np.concatenate((np.vstack(floor_posteriors), np.vstack(is_true.astype(int))), axis=1)
print(count)
[[ 0.9  1. ]
 [ 0.9  1. ]
 [ 0.9  1. ]
 ..., 
 [ 0.9  1. ]
 [ 0.7  1. ]
 [ 0.9  1. ]]
In [23]:
# 事後確率が最大となるクラスにおける事後確率ごとに、正答率を記録する
h = []
for i in range(1, 10):
    a = np.array(filter(lambda x: x[0]==i/10., count))
    if a.any():
        h.append([i/10., np.sum(a[:, 1])/a.shape[0] ])
h = np.array(h)
print(h)
[[ 0.1         0.        ]
 [ 0.2         0.32      ]
 [ 0.3         0.37254902]
 [ 0.4         0.5184136 ]
 [ 0.5         0.63168724]
 [ 0.6         0.71828358]
 [ 0.7         0.86892489]
 [ 0.8         0.93868282]
 [ 0.9         0.99255352]]
In [24]:
plt.xlabel("Max posterior")
plt.ylabel("Accuracy")
plt.plot(h[:, 0], h[:, 1])
Out[24]:
[<matplotlib.lines.Line2D at 0x7f0c342c7650>]
In [ ]: