CUBE SUGAR CONTAINER

技術系のこと書きます。

Python: TensorFlow のチュートリアルでソフトマックス回帰を試す

最近、囲碁でプロ棋士を負かした AlphaGo も、この TensorFlow で組んだニューラルネットワークを使っているんだとか。 とはいえ、チュートリアルを読みながら使ってみると、これは単にディープラーニングだけをターゲットにしたライブラリではないように感じた。 どちらかといえば機械学習で使われるモデルをプログラムで表現して動かしやすくするためのツールキットのようだ。

今回試すソフトマックス回帰というのは TensorFlow に用意されている最初のチュートリアルで使う機械学習アルゴリズムだ。 モデルとしては一層のニューラルネットワークとソフトマックス関数を組み合わせたシンプルなものになっている。 一層のニューラルネットワークなので、これはディープラーニングではないし、ぶっちゃけ最終的に得られる汎化性能も高くない。

チュートリアルでは MNIST という手書き数字のデータセットを使っている。 このデータセットには 28x28 ピクセルの画像データと、それが数字の何を表すかの正解となるラベルデータが入っている。 手順としては、まずソフトマックス回帰のアルゴリズムを訓練用データを使って学習させる。 そして、学習に使っていないテスト用のデータを識別させて正答率を出す。 この、学習に使っていないテスト用のデータで正答率を出すのが汎化性能 (未知のデータに対処する能力) を測る上で重要となる。

今回使う環境は次の通り。

$ sw_vers 
ProductName:    Mac OS X
ProductVersion: 10.11.3
BuildVersion:   15D21
$ python --version
Python 3.5.1

元ネタのチュートリアルはこちら。

https://www.tensorflow.org/versions/0.6.0/tutorials/mnist/beginners/index.html

TensorFlow をインストールする

TensorFlow はバージョン 0.6.0 で Python 3 に対応している。 公式で各ターゲット向けの Wheel ファイルが配布されているので、それを使ってインストールする。 今回の環境 Mac OS X w/ Python 3 であれば次の Wheel を使う。

$ pip install -U https://storage.googleapis.com/tensorflow/mac/tensorflow-0.6.0-py3-none-any.whl

データセットを確認する

チュートリアルのソフトマックス回帰ではデータセットとして 28x28 ピクセルの手書き数字を収めた MNIST を使う。 あらかじめ、どういったものなのかを見ておこう。

データセットの内容を視覚化するためにグラフ描画ライブラリの matplotlib をインストールしておく。

$ pip install matplotlib

まず適当な作業ディレクトリで Python の REPL を起動したら MNIST のデータセットをダウンロードする。 これには少し時間がかかるはずだ。

$ python
>>> 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

学習用のデータは 55000 点ある。 データの内容は 28x28 ピクセルなので 784 次元だ。 各ピクセルは色の濃淡に対応して 0 ~ 1 の範囲で値を取る。 ちなみにグレースケールだ。

>>> mnist.train.images.shape
(55000, 784)
>>> mnist.train.images[0]
array([ 0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
...(省略)...
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.01568628,  0.45882356,  0.27058825,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
        0.        ,  0.        ,  0.        ,  0.        ], dtype=float32)

そして、学習用のデータには対応する正解ラベルがある。 こちらは 0 ~ 9 までの数字を表すので 10 次元になっている。

>>> mnist.train.labels.shape
(55000, 10)
>>> mnist.train.labels[0]
array([ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,  0.])

データセットの中からいくつか適当に選んで可視化してみよう。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from matplotlib import pyplot as plt
from matplotlib import cm
import numpy as np
from numpy import random

from tensorflow.examples.tutorials.mnist import input_data


def _to_number(label):
    for index, n in enumerate(label):
        if n != 0:
            return index


def main():
    mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
    X = mnist.train.images
    y = mnist.train.labels

    # データの中から 25 点を無作為に選び出す
    p = random.random_integers(0, len(X), 25)

    # 選んだデータとラベルを matplotlib で表示する
    samples = np.array(list(zip(X, y)))[p]
    for index, (data, label) in enumerate(samples):
        # 画像データを 5x5 の格子状に配置する
        plt.subplot(5, 5, index + 1)
        # 軸に関する表示はいらない
        plt.axis('off')
        # データを 8x8 のグレースケール画像として表示する
        plt.imshow(data.reshape(28, 28), cmap=cm.gray_r, interpolation='nearest')
        n = _to_number(label)
        # 画像データのタイトルに正解ラベルを表示する
        plt.title(n, color='red')
    # グラフを表示する
    plt.show()


if __name__ == '__main__':
    main()

上記を実行すると、こんな感じ。

f:id:momijiame:20160208190456p:plain

TensorFlow を使ってモデルを組む

ここからはソフトマックス回帰のアルゴリズムを TensorFlow で組んでいく。

まず TensorFlow で登場する概念のひとつが placeholder だ。 これは「何らかの値が入る」場所を表している。 次のコードは x という変数は float32 型で 784 次元の何らかのデータがここに入る、という意味になる。 第二引数の配列の最初が None になっているのは、長さが可変なことを示している。 ここではデータ点数がいくつあっても良いという意味になる。 x には入力となる画像データが入る。

>>> import tensorflow as tf
>>> x = tf.placeholder(tf.float32, [None, 784])

次に登場するのが Variable で、これはニューラルネットワークに使う変数になっている。 W は入力 (各ピクセルデータ) に対する重みなので 784 次元を受け取って 0 ~ 9 の各数字に対応する 10 次元を返す。 b は各数字に対するバイアスだ。

>>> W = tf.Variable(tf.zeros([784, 10]))
>>> b = tf.Variable(tf.zeros([10]))

ここまでに用意した placeholder と Variable を使って数式を表現する。 ひとつのパーセプトロンは y = xW + b で表現できる。 次のコードは、右辺にソフトマックス関数をかましている以外はその数式そのものを表している。 ソフトマックス関数は活性化関数というもの。 xW + b で得られた出力から、その数字の確率はどの程度かを 0 ~ 1 の間で表現するのに使われるんだとおもう。 y にはアルゴリズムが予測したそれぞれの数字の確率が入る。

>>> y = tf.nn.softmax(tf.matmul(x, W) + b)

次に、どのように学習を進めるかを表現する。 まずは先ほどと同じように placeholder を用意する。 y_ には正解のラベルデータが入る。

>>> y_ = tf.placeholder(tf.float32, [None, 10])

学習には、アルゴリズムの予想内容と正解のラベルデータを使って、その交差エントロピーを使う。 それを表現しているのが次のコードだ。 学習を進めるための指針となるこれをコスト関数と呼ぶ。

>>> cross_entropy = -tf.reduce_sum(y_*tf.log(y))

学習は先ほどの交差エントロピーが小さくなるように進める。 交差エントロピーが最小となるポイントは勾配降下法というアルゴリズムで見つける。 それを表現しているのが次のコードだ。

>>> train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

これで必要な材料は揃った。 ここからは実際の学習を行うフェーズだ。 まずは各 Variable の値の初期値を用意して、学習のコンテキストとなる Session オブジェクトに渡す。

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

次に学習データを使って学習させる。 ここではデータを 100 点ずつ 1000 回取り出して学習に使っている。 学習には Session#run() 関数を使う。 feed_dict には placeholder に当てはめる具体的な値を渡す。

>>> for _ in range(1000):
...   batch_xs, batch_ys = mnist.train.next_batch(100)
...   sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})
...

次に汎化性能を数値で得るための準備に入ろう。 ここも TensorFlow でモデルを用意することになる。 まず、予測結果は各数字ごとの確率で得られるので、その確率が最も高いものを採用したい。 次のコードでは予測結果と正解のラベルデータから最も数値が高いものを取り出して比較することを意味している。 つまり予測と正解が一致しているか否かが correct_prediction の中に入る。

>>> correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))

上記で得られる内容は真偽値なので、それを浮動小数点にキャストする。 True は 1.0 で False は 0.0 になる。 つまり、各予測に対して 0.0 か 1.0 が入ることになる。 それらの算術平均を取ればソフトマックス回帰を使った正答率が得られるという寸法だ。

>>> accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))

このモデルにテスト用のデータを当てはめて実行する。 x にテスト用の画像データを、y_ に正解となるラベルを渡せば良い。

>>> print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))
0.9183

正答率は 91.83% となった。

上記だけだと何だかよく分からないので個別の画像データを識別させてみよう。 今度はモデルが予測した内容だけを表示する。 予測内容の y から最も確率の高いものだけを表示する関数 f を用意する。 そして x を渡して実行すれば予測結果が得られる。 テストデータの最初にあるデータをひとつだけ渡してみよう。

>>> f = tf.argmax(y, 1)
>>> sess.run(f, feed_dict={x: [mnist.test.images[0]]})
array([7])

予測結果は 7 となった。

>>> mnist.test.labels[0]
array([ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,  0.])

正解のラベルデータを見ると 8 番目の要素、つまり 7 に対応するインデックスに 1 が立っている。 どうやら正解のようだ。

どのように予測したのか確率を見たいときは argmax() 関数を使うまでもなく y そのものを見ればいい。

>>> f = y
>>> sess.run(f, feed_dict={x: [mnist.test.images[0]]})
array([[  1.59484807e-05,   8.12810930e-10,   3.76464246e-04,
          1.28450710e-03,   1.43751876e-07,   9.29207999e-06,
          1.45384793e-09,   9.98167157e-01,   7.07713616e-06,
          1.39490847e-04]], dtype=float32)

よくみると 7 に対応するインデックスだけ 9.98167157e-01 と圧倒的に値が大きくなっていることがわかる。

まとめ

今回は TensorFlow のチュートリアルに沿って MNIST のソフトマックス回帰を試した。 ちなみに、今回得られた 91.83% という正答率は MNIST を使った分類の結果としてはかなりひどい数字らしい。 これまでに知られている最も優れたモデルでは 99.2% の正答率が得られるんだとか。

ここまで見てきたように TensorFlow はディープラーニングに特化したライブラリというわけではないようだ。 あくまで機械学習で用いられる数式をプログラムに落とし込みやすいようにしたパーツを提供しているに過ぎない。 ディープラーニングをしたいなら、それらのパーツを使って自分自身でモデルを組み上げていく必要がある。