今回は、Stanford Univ/CS231n/Assignment2/TensorFlow(テンサーフロー)をやる。いよいよ人気フレームワークを使った機械学習のエクササイズだ。現時点では、グーグルのテンソルフローとフェイスブックのパイトーチが人気を二分しているので、両フレームワークだけやっていればいいという人も多い。
What is 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)
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 a specific model¶
この箇所で構築するモデルを指定する。ここでのゴールは、モデルの性能ではなく(それは次の課題)、TensorFlowの参照資料に慣れ親しむことと、自分のモデルを設定することである。指針として上で提供されたコードを使用し、また、TensorFlowの説明書を利用して、下記の仕様でモデルを特徴付ける。
- 7×7 Convolutional Layer with 32 filters and stride of 1 ReLU Activation Layer
- Spatial Batch Normalization Layer (空間バッチ正規化層) (trainable parameters, with scale and centering)
- 2×2 Max Pooling layer with a stride of 2
- Affine layer (アフィン層) with 1024 output units
- ReLU Activation Layer (ReLU活性化層)
- 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])))
上のセルを実行することで以下が出力されるはず。
(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)
このように単純なフォワードパスでさえも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)
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)