CS231n/Assignment2/TensorFlow(テンサーフロー)

今回は、Stanford Univ/CS231n/Assignment2/TensorFlow(テンサーフロー)をやる。いよいよ人気フレームワークを使った機械学習のエクササイズだ。現時点では、グーグルのテンソルフローとフェイスブックのパイトーチが人気を二分しているので、両フレームワークだけやっていればいいという人も多い。

TensorFlowとは、変数に対して誤差逆伝播法を実行する機能をネイティブで備えた、テンソルオブジェクトを介して計算グラフを実行するシステムのことを指す。テンサーフローでは、numpy ndarrayに類似したn次元アレイであるテンソルを使用して作業をする。テンサーフローはコードをGPUで実行できるので、はるかに高速でモデルの訓練を実行することが可能になる。GPUで実行するための自身のモジュールを書くことは、残念ながら、この講義の範囲を超えているので割愛する。これら(PyTorchとTensorFlow)のフレームワークの一つをプロジェクトに使えば、使いたい機能を全て手書きするよりも効率的に実験を実施できる。

Load Datasets

いつものように先ずデータ・セットをロードする。

import tensorflow as tf
import numpy as np
import math
import timeit
import matplotlib.pyplot as plt
%matplotlib inline
from cs231n.data_utils import load_CIFAR10

def get_CIFAR10_data(num_training=49000, num_validation=1000, num_test=10000):
    """
    Load the CIFAR-10 dataset from disk and perform preprocessing to prepare
    it for the two-layer neural net classifier. These are the same steps as
    we used for the SVM, but condensed to a single function.  
    """
    # Load the raw CIFAR-10 data
    cifar10_dir = 'cs231n/datasets/cifar-10-batches-py'
    X_train, y_train, X_test, y_test = load_CIFAR10(cifar10_dir)
    # Subsample the data
    mask = range(num_training, num_training + num_validation)
    X_val = X_train[mask]
    y_val = y_train[mask]
    mask = range(num_training)
    X_train = X_train[mask]
    y_train = y_train[mask]
    mask = range(num_test)
    X_test = X_test[mask]
    y_test = y_test[mask]
    # Normalize the data: subtract the mean image
    mean_image = np.mean(X_train, axis=0)
    X_train -= mean_image
    X_val -= mean_image
    X_test -= mean_image
    return X_train, y_train, X_val, y_val, X_test, y_test

# Invoke the above function to get our data.
X_train, y_train, X_val, y_val, X_test, y_test = get_CIFAR10_data()
print('Train data shape: ', X_train.shape)
print('Train labels shape: ', y_train.shape)
print('Validation data shape: ', X_val.shape)
print('Validation labels shape: ', y_val.shape)
print('Test data shape: ', X_test.shape)
print('Test labels shape: ', y_test.shape)
Train data shape:  (49000, 32, 32, 3)
Train labels shape:  (49000,)
Validation data shape:  (1000, 32, 32, 3)
Validation labels shape:  (1000,)
Test data shape:  (10000, 32, 32, 3)
Test labels shape:  (10000,)

Example Model

Some useful utilities

初期画像データは、N x H x W x C (Nはデータ点の数、Hが各画像の縦の画素数、Wが各画像の横の画素数、Cがチャネル数(通常はRGBの3))。これは、画素がどこで互いに関連し合うかの空間把握が必要な2D畳み込みのようなことをやる場合、画像データを構成する正しいやり方だ。しかし、全結合アフィン層に画像データを入力する場合、各データ標本は単一ベクトルによって形成したい。もはや、データを別個のチャネルや行、列に分けることが有益でなくなるからだ。

The example model itself

モデルを訓練する最初のステップ、アーキテクチャを定義する。下記のテンサーフローで定義された畳み込み神経回路網の一例を見て、それぞれの行が何をしているのかを理解し、各層が前の層の上に形成されていることを覚えておく。モデルの訓練を始める前に、どのようにして全てがセットアップされるのかを理解する。

今回の例では、2次元畳み込み層 (Conv2d)、ReLU activations (ReLU活性化)、全結合層(線形)が使われている。また、Hinge loss function (ヒンジ損失関数) とAdam optimizer (アダム・オプティマイザ) も使われている。Linear layer (線形層) のパラメーターが5408と10である理由を確実に理解する。

TensorFlow Details

テンソルフローでは、過去やったように、最初に変数をきっちり初期化してからネットワークモデルを初期化する。

# clear old variables
tf.reset_default_graph()

# setup input (e.g. the data that changes every batch)
# The first dim is None, and gets sets automatically based on batch size fed in
X = tf.placeholder(tf.float32, [None, 32, 32, 3])
y = tf.placeholder(tf.int64, [None])
is_training = tf.placeholder(tf.bool)

def simple_model(X,y):
    # define our weights (e.g. init_two_layer_convnet)    
    # setup variables
    Wconv1 = tf.get_variable("Wconv1", shape=[7, 7, 3, 32])
    bconv1 = tf.get_variable("bconv1", shape=[32])
    W1 = tf.get_variable("W1", shape=[5408, 10])
    b1 = tf.get_variable("b1", shape=[10])
    # define our graph (e.g. two_layer_convnet)
    a1 = tf.nn.conv2d(X, Wconv1, strides=[1,2,2,1], padding='VALID') + bconv1
    h1 = tf.nn.relu(a1)
    h1_flat = tf.reshape(h1,[-1,5408])
    y_out = tf.matmul(h1_flat,W1) + b1
    return y_out

y_out = simple_model(X,y)
# define our loss
total_loss = tf.losses.hinge_loss(tf.one_hot(y,10),logits=y_out)
mean_loss = tf.reduce_mean(total_loss)
# define our optimizer
optimizer = tf.train.AdamOptimizer(5e-4) # select optimizer and set learning rate
train_step = optimizer.minimize(mean_loss)

Training the model on one epoch

上のセルで演算グラフは定義はしたが、TensorFlow Graphsに入力データをフィードして結果を計算することでテンソルフロー・グラフを実行するには、最初にtf.Session オブジェクトを作成する必要がある。セッションは、テンソルフロー・ランタイムの制御と状態をカプセル化してい。より詳しい情報に関しては、TensorFlow Getting started guideを参照のこと。

任意で、例えば、/cpu:0 もしくは /gpu:0 等のデバイスコンテクストも指定することも可能だ。約0.4~0.6の検証損失と0.30~0.35未満の正確度が期待できるはず。

# Import division to compute correctly the accuracy
from __future__ import division

def run_model(session, predict, loss_val, Xd, yd,
              epochs=1, batch_size=64, print_every=100,
              training=None, plot_losses=False):
    # have tensorflow compute accuracy
    correct_prediction = tf.equal(tf.argmax(predict,1), y)
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))    
    # shuffle indicies
    train_indicies = np.arange(Xd.shape[0])
    np.random.shuffle(train_indicies)
    training_now = training is not None    
    # setting up variables we want to compute (and optimizing)
    # if we have a training function, add that to things we compute
    variables = [mean_loss,correct_prediction,accuracy]
    if training_now:
        variables[-1] = training    
    # counter 
    iter_cnt = 0
    for e in range(epochs):
        # keep track of losses and accuracy
        correct = 0
        losses = []
        # make sure we iterate over the dataset once
        for i in range(int(math.ceil(Xd.shape[0]/batch_size))):
            # generate indicies for the batch
            start_idx = (i*batch_size)%Xd.shape[0]
            idx = train_indicies[start_idx:start_idx+batch_size]            
            # create a feed dictionary for this batch
            feed_dict = {X: Xd[idx,:],
                         y: yd[idx],
                         is_training: training_now }
            # get batch size
            actual_batch_size = yd[idx].shape[0]            
            # have tensorflow compute loss and correct predictions
            # and (if given) perform a training step
            loss, corr, _ = session.run(variables,feed_dict=feed_dict)            
            # aggregate performance stats
            losses.append(loss*actual_batch_size)
            correct += np.sum(corr)            
            # print every now and then
            if training_now and (iter_cnt % print_every) == 0:
                print("Iteration {0}: with minibatch training loss = {1:.3g} and accuracy of {2:.2g}"\
                      .format(iter_cnt,loss,np.sum(corr)/actual_batch_size))
            iter_cnt += 1
        total_correct = correct/Xd.shape[0]
        total_loss = np.sum(losses)/Xd.shape[0]
        print("Epoch {2}, Overall loss = {0:.3g} and accuracy of {1:.3g}"\
              .format(total_loss,total_correct,e+1))
        if plot_losses:
            plt.rcParams['figure.figsize'] = 12, 8
            plt.rcParams["font.size"] = "18"
            plt.plot(losses)
            plt.grid(True)
            plt.title('Epoch {} Loss'.format(e+1))
            plt.xlabel('minibatch number')
            plt.ylabel('minibatch loss')
            plt.show()
    return total_loss,total_correct

with tf.Session() as sess:
    with tf.device("/cpu:0"): #"/cpu:0" or "/gpu:0" 
        sess.run(tf.global_variables_initializer())
        print('Training')
        run_model(sess,y_out,mean_loss,X_train,y_train,1,64,100,train_step,True)
        print('Validation')
        run_model(sess,y_out,mean_loss,X_val,y_val,1,64)
Training
Iteration 0: with minibatch training loss = 6.18 and accuracy of 0.17
Iteration 100: with minibatch training loss = 0.999 and accuracy of 0.22
Iteration 200: with minibatch training loss = 0.644 and accuracy of 0.36
Iteration 300: with minibatch training loss = 0.651 and accuracy of 0.31
Iteration 400: with minibatch training loss = 0.547 and accuracy of 0.33
Iteration 500: with minibatch training loss = 0.58 and accuracy of 0.33
Iteration 600: with minibatch training loss = 0.431 and accuracy of 0.39
Iteration 700: with minibatch training loss = 0.476 and accuracy of 0.3
Epoch 1, Overall loss = 0.721 and accuracy of 0.313
Validation
Epoch 1, Overall loss = 0.407 and accuracy of 0.397

Training a specific model

この箇所で構築するモデルを指定する。ここでのゴールは、モデルの性能ではなく(それは次の課題)、TensorFlowの参照資料に慣れ親しむことと、自分のモデルを設定することである。指針として上で提供されたコードを使用し、また、TensorFlowの説明書を利用して、下記の仕様でモデルを特徴付ける。

  1. 7×7 Convolutional Layer with 32 filters and stride of 1 ReLU Activation Layer
  2. Spatial Batch Normalization Layer (空間バッチ正規化層) (trainable parameters, with scale and centering)
  3. 2×2 Max Pooling layer with a stride of 2
  4. Affine layer (アフィン層) with 1024 output units
  5. ReLU Activation Layer (ReLU活性化層)
  6. Affine layer from 1024 input units to 10 outputs
# clear old variables
tf.reset_default_graph()

# define our input (e.g. the data that changes every batch)+
# The first dim is None, and gets sets automatically based on batch size fed in
X = tf.placeholder(tf.float32, [None, 32, 32, 3])
y = tf.placeholder(tf.int64, [None])
is_training = tf.placeholder(tf.bool)

# define model
def complex_model(X,y,is_training):
    initializer = tf.contrib.layers.xavier_initializer()
    first_conv = tf.layers.conv2d(X, 32, 7, strides=1, padding='valid',
                                  activation=tf.nn.relu, 
                                  kernel_initializer=initializer,
                                  name='First_convolution')
    batch_layer = tf.layers.batch_normalization(first_conv, training=is_training,
                                                name='Batchnorm_layer')
    max_pool = tf.layers.max_pooling2d(batch_layer, 2, 2, padding='valid', name='Max_pool')
    flatten = tf.reshape(max_pool, [-1, 5408])
    fc_layer = tf.layers.dense(flatten, 1024, activation=tf.nn.relu,
                               kernel_initializer=initializer,
                               name='Dense_layer')
    output = tf.layers.dense(fc_layer, 10, activation=None,
                             kernel_initializer=initializer,
                             name='Predictions')
    return output
    
y_out = complex_model(X,y,is_training)

正しいことをしているかを確かめるために、出力の次元(バッチサイズが64で、最後のアフィン層の出力は10クラスに一致して10なので64 x 10になるはず)を下のツールを使ってチェックする。

# Now we're going to feed a random batch into the model 
# and make sure the output is the right size
x = np.random.randn(64, 32, 32,3)
with tf.Session() as sess:
    with tf.device("/cpu:0"): #"/cpu:0" or "/gpu:0"
        tf.global_variables_initializer().run()

        ans = sess.run(y_out,feed_dict={X:x,is_training:True})
        %timeit sess.run(y_out,feed_dict={X:x,is_training:True})
        print(ans.shape)
        print(np.array_equal(ans.shape, np.array([64, 10])))
2.46 ms ± 347 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
(64, 10)
True

上のセルを実行することで以下が出力されるはず。
(64, 10)
True

GPU!

次に、モデルの訓練にGPUを使ってみる。残りのコードは修正する必要はなく、全ての変数と演算は加速コードパスを使って計算される。しかし、GPUがない場合、Python exception (error) が出て、グラフを再構築しなければならなくなる。デュアルコア CPUの場合上の実行速度は大体50~80ms/batchで、GPUだと下の実行速度は約2~5ms/batchになるはず。

try:
    with tf.Session() as sess:
        with tf.device("/gpu:0") as dev: #"/cpu:0" or "/gpu:0"
            tf.global_variables_initializer().run()

            ans = sess.run(y_out,feed_dict={X:x,is_training:True})
            %timeit sess.run(y_out,feed_dict={X:x,is_training:True})
except tf.errors.InvalidArgumentError:
    print("no gpu found, please use Google Cloud if you want GPU acceleration")    
    # rebuild the graph
    # trying to start a GPU throws an exception 
    # and also trashes the original graph
    tf.reset_default_graph()
    X = tf.placeholder(tf.float32, [None, 32, 32, 3])
    y = tf.placeholder(tf.int64, [None])
    is_training = tf.placeholder(tf.bool)
    y_out = complex_model(X,y,is_training)
2.34 ms ± 46.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

このように単純なフォワードパスでさえもGPUの方が有意に速いので、今後モデルを訓練する時はGPUを使うようにしましょう。とは言っても、TensorFlowの場合、GPUがあれば既定のデバイスがGPUで、ない場合はCPUとなっているので、今後はデバイス指定は省略できる。

Train the model.

モデルの定義の仕方と定義したモデルを使って、適当なデータで単一フォワードパスを実践したので、実際に、どのようにして、自分の訓練データで全1エポックを(上にある自分で作成したcomplex_modelを使用して)訓練するかを一つずつ説明する。

下で使用されている各テンソルフロー関数が、自製神経回路網実装で実装したものとどのように対応するのかを理解していることを確認する。

まず最初に、RMSpropオプティマイザ(学習率=1e-3)と交差エントロピー損失関数を設定する。詳細については下記のTensorFlowの資料を参照。

# Inputs
#     y_out: is what your model computes
#     y: is your TensorFlow variable with label information
# Outputs
#    mean_loss: a TensorFlow variable (scalar) with numerical loss
#    optimizer: a TensorFlow optimizer
# This should be ~3 lines of code!
mean_loss = tf.losses.softmax_cross_entropy(tf.one_hot(y, 10), y_out)
optimizer = tf.train.RMSPropOptimizer(1e-3)
# batch normalization in tensorflow requires this extra dependency
extra_update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies(extra_update_ops):
    train_step = optimizer.minimize(mean_loss)

Train the model

下記でセッションを作成して1エポックモデルを訓練する。損失は1.4~2.0、正確度は0.4~0.5の範囲内で収まるはずだが、乱数シードと初期設定の違いのせいで多少のばらつきはあるだろう。

sess = tf.Session()

sess.run(tf.global_variables_initializer())
print('Training')
run_model(sess,y_out,mean_loss,X_train,y_train,1,64,100,train_step)
Training
Iteration 0: with minibatch training loss = 3.27 and accuracy of 0.094
Iteration 100: with minibatch training loss = 2.98 and accuracy of 0.27
Iteration 200: with minibatch training loss = 2.06 and accuracy of 0.31
Iteration 300: with minibatch training loss = 1.75 and accuracy of 0.42
Iteration 400: with minibatch training loss = 1.39 and accuracy of 0.48
Iteration 500: with minibatch training loss = 1.31 and accuracy of 0.55
Iteration 600: with minibatch training loss = 1.52 and accuracy of 0.5
Iteration 700: with minibatch training loss = 1.01 and accuracy of 0.61
Epoch 1, Overall loss = 1.68 and accuracy of 0.45
(1.6800992821868586, 0.4502857142857143)

Check the accuracy of the model.

訓練/テストコードを実行して確認する(下で作るモデルを検証する際に、これらの手法を遠慮なく利用してよい)。損失1.3~2.0、正確度0.45~0.55に収まるはず。

print('Validation')
run_model(sess,y_out,mean_loss,X_val,y_val,1,64)
Validation
Epoch 1, Overall loss = 1.27 and accuracy of 0.572
(1.265022313117981, 0.572)
参考サイトhttps://github.com/