Stanford CS231n:Implementing a Neural Network

Stanford/CS231n/assignment1/neural networkでは、フィードされたデータを分類するための完全にコネクトされたレイヤーを持ったニューラルネットワークを構築して、それをCIFAR-10データセットを使ってテストしている。

先ずは、moduleのimportと各種環境変数の設定。

import numpy as np
import matplotlib.pyplot as plt
from cs231n.classifiers.neural_net import TwoLayerNet
from __future__ import print_function

plt.rcParams['figure.figsize'] = (10.0, 8.0) # set default size of plots
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'

# for auto-reloading external modules
# see http://stackoverflow.com/questions/1907993/autoreload-of-modules-in-ipython
def rel_error(x, y):
    """ returns relative error """
    return np.max(np.abs(x - y) / (np.maximum(1e-8, np.abs(x) + np.abs(y))))

%load_ext autoreload
%autoreload 2
%matplotlib inline

ネットワークのインスタンスを構成するのにneural_net.pyのTwoLayerNetクラスが使われる。ネットワークパラメータは、キーが文字列パラメータ名で、値がnumpyアレイである変数self.paramsに保存される。下記で、toy data/modelの初期化が行われる。

# Create a small net and some toy data to check your implementations.
# Note that we set the random seed for repeatable experiments.
input_size = 4
hidden_size = 10
num_classes = 3
num_inputs = 5

def init_toy_model():
    np.random.seed(0)
    return TwoLayerNet(input_size, hidden_size, num_classes, std=1e-1)

def init_toy_data():
    np.random.seed(1)
    X = 10 * np.random.randn(num_inputs, input_size)
    y = np.array([0, 1, 2, 2, 1])
    return X, y

net = init_toy_model()
X, y = init_toy_data()

neural_net.pyのTwoLayerNet.lossメソッドに目を通す。この関数は損失関数に似ていて、データと重みからパラメータのクラススコア、損失、グラディエントを算出する。まず、全入力用のスコアを計算するのに重みとバイアスを使うforward passの最初の部分を実装する。

scores = net.loss(X)
print('Your scores:')
print(scores)
print()
print('correct scores:')
correct_scores = np.asarray([
  [-0.81233741, -1.27654624, -0.70335995],
  [-0.17129677, -1.18803311, -0.47310444],
  [-0.51590475, -1.01354314, -0.8504215 ],
  [-0.15419291, -0.48629638, -0.52901952],
  [-0.00618733, -0.12435261, -0.15226949]])
print(correct_scores)
print()

# The difference should be very small. We get < 1e-7
print('Difference between your scores and correct scores:')
print(np.sum(np.abs(scores - correct_scores)))
Your scores:
[[-0.81233741 -1.27654624 -0.70335995]
 [-0.17129677 -1.18803311 -0.47310444]
 [-0.51590475 -1.01354314 -0.8504215 ]
 [-0.15419291 -0.48629638 -0.52901952]
 [-0.00618733 -0.12435261 -0.15226949]]

correct scores:
[[-0.81233741 -1.27654624 -0.70335995]
 [-0.17129677 -1.18803311 -0.47310444]
 [-0.51590475 -1.01354314 -0.8504215 ]
 [-0.15419291 -0.48629638 -0.52901952]
 [-0.00618733 -0.12435261 -0.15226949]]

Difference between your scores and correct scores:
3.6802720745909845e-08

Forward pass: compute loss

同関数のdata and regularizaion loss(データ/正則化損失)を算出する部分の実装。

loss, _ = net.loss(X, y, reg=0.05)
correct_loss = 1.30378789133

# should be very small, we get < 1e-12
print('Difference between your loss and correct loss:')
print(np.sum(np.abs(loss - correct_loss)))
Difference between your loss and correct loss:
1.7985612998927536e-13

Backward pass

残りの部分を実装することで、変数W1, b1, W2, b2に対する損失のグラディエントを算出する。forward passを既に実装済みなので、数値勾配チェックを使用してbackward passのデバッグが可能。

from cs231n.gradient_check import eval_numerical_gradient

# Use numeric gradient checking to check your implementation of the backward pass.
# If your implementation is correct, the difference between the numeric and
# analytic gradients should be less than 1e-8 for each of W1, W2, b1, and b2.

loss, grads = net.loss(X, y, reg=0.05)

# these should all be less than 1e-8 or so
for param_name in grads:
    f = lambda W: net.loss(X, y, reg=0.05)[0]
    param_grad_num = eval_numerical_gradient(f, net.params[param_name], verbose=False)
    print('%s max relative error: %e' % (param_name, rel_error(param_grad_num, grads[param_name])))
W2 max relative error: 3.440708e-09
b2 max relative error: 4.447625e-11
W1 max relative error: 3.561318e-09
b1 max relative error: 2.738421e-09

Train the network

network訓練にSVMとSoftmax分類器に似た、stochastic gradient descent(SGD/確率的勾配降下法)を使用する。TwoLayerNet.train関数に注目し、訓練手順を実装するために必要なコードを書き込む。コードはSVMとSoftmax分類器で使用した訓練手順に非常によく似ている必要がある。加えて、訓練法がネットワーク訓練中に正確性を確認するために周期的に予測を実行するので、TwoLayerNet.predictを実装する必要もある。メソッドを実装したら、toyデータで2層ネットワークを訓練するための下記のコードを実行。訓練損失は0.2未満にならなければならない。

net = init_toy_model()
stats = net.train(X, y, X, y,
            learning_rate=1e-1, reg=5e-6,
            num_iters=100, verbose=False)

print('Final training loss: ', stats['loss_history'][-1])

# plot the loss history
plt.plot(stats['loss_history'])
plt.xlabel('iteration')
plt.ylabel('training loss')
plt.title('Training Loss history')
plt.show()
Final training loss:  0.017149607938732093

Load the data

gradient checks(勾配確認)にパスしたtoy dataで使える2層ネットワークを実装したので、玩具データではなく、本物のデータセットで分類器を訓練するためにいよいよCIFAR-10 dataを使う時が来た。

from cs231n.data_utils import load_CIFAR10

def get_CIFAR10_data(num_training=49000, num_validation=1000, num_test=1000):
    """
    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 = list(range(num_training, num_training + num_validation))
    X_val = X_train[mask]
    y_val = y_train[mask]
    mask = list(range(num_training))
    X_train = X_train[mask]
    y_train = y_train[mask]
    mask = list(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
    # Reshape data to rows
    X_train = X_train.reshape(num_training, -1)
    X_val = X_val.reshape(num_validation, -1)
    X_test = X_test.reshape(num_test, -1)
    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, 3072)
Train labels shape:  (49000,)
Validation data shape:  (1000, 3072)
Validation labels shape:  (1000,)
Test data shape:  (1000, 3072)
Test labels shape:  (1000,)

Train a network

ネットワーク訓練にはSGD(確率的勾配降下法)を用い、learning rate(学習率)を最適化が進むに連れてexponential learning rate schedule(指数関数的学習率スケジュール)で調整する。エポック毎にdecay rate(減衰率)とそれを掛け合わせて学習率を減らす。

input_size = 32 * 32 * 3
hidden_size = 50
num_classes = 10
net = TwoLayerNet(input_size, hidden_size, num_classes)

# Train the network
stats = net.train(X_train, y_train, X_val, y_val,
            num_iters=1000, batch_size=200,
            learning_rate=1e-4, learning_rate_decay=0.95,
            reg=0.25, verbose=True)

# Predict on the validation set
val_acc = (net.predict(X_val) == y_val).mean()
print('Validation accuracy: ', val_acc)
iteration 0 / 1000: loss 2.302976
iteration 100 / 1000: loss 2.302647
iteration 200 / 1000: loss 2.299270
iteration 300 / 1000: loss 2.267406
iteration 400 / 1000: loss 2.180422
iteration 500 / 1000: loss 2.118438
iteration 600 / 1000: loss 2.120390
iteration 700 / 1000: loss 1.979948
iteration 800 / 1000: loss 2.101721
iteration 900 / 1000: loss 2.087279
Validation accuracy:  0.279

Debug the training

上で提供されているデフォルトパラメータで、validation accuracy(検証精度)はおおよそ0.29になる必要がある。これは良い数字ではない。何が悪いのかを知るための一手段が最適化中の損失関数と正確性を訓練とバリデーションセットでプロットすることだ。もう一つの方法は、ネットワークの最初の層で学習した重みを視覚化することである。ビジュアルデータで訓練された大部分のニューラルネットワークは第一層重みは視覚化されるとたいていある程度の可視構造を示す。

# Plot the loss function and train / validation accuracies
plt.rcParams['figure.figsize'] = 20, 20
plt.rcParams["font.size"] = "20"
plt.subplot(2, 1, 1)
plt.plot(stats['loss_history'])
plt.title('Loss history')
plt.xlabel('Iteration')
plt.ylabel('Loss')
plt.subplot(2, 1, 2)
plt.plot(stats['train_acc_history'], label='train')
plt.plot(stats['val_acc_history'], label='val')
plt.title('Classification accuracy history')
plt.xlabel('Epoch')
plt.ylabel('Clasification accuracy')
plt.show()
from cs231n.vis_utils import visualize_grid
plt.rcParams['figure.figsize'] = 20, 20
# Visualize the weights of the network
def show_net_weights(net):
    W1 = net.params['W1']
    W1 = W1.reshape(32, 32, 3, -1).transpose(3, 0, 1, 2)
    plt.imshow(visualize_grid(W1, padding=3).astype('uint8'))
    plt.gca().axis('off')
    plt.show()
    
show_net_weights(net)

Tune your hyperparameters

何が悪いのか?上の画像を見ると、学習率があまりにも低いことを示す、損失がほぼ直線的に減っていることが分かる。さらに、訓練と検証精度の間にギャップがないので、使用したモデルのキャパが低いことと、そのサイズを増やす必要があることを示唆している。その一方で、非常に大きなモデルでは、より大きなoverfitting(過剰適合)が予想され、訓練・検証精度間差が非常に大きいことがそれ自身を体現している。ニューラルネットワークではハイパーパラメータの調整が重要で、感覚を養うことが必要になるので、習うより慣れろと言える。各種のハイパーパラメーター(隠れ層サイズ、学習率、訓練エポック数、正則化強度)で違う値を実験的に使ってみることを勧める。また、learning rate decay(学習率減衰)調整も考慮する必要性もあるかもしれないが、既定値で好パフォーマンスを得る必要がある。。バリデーションセットによる分類精度は48%以上を叩き出すべきで、最良ネットワークは52%以上を叩き出している。

Experiment: You goal in this exercise is to get as good of a result on CIFAR-10 as you can, with a fully-connected Neural Network. For every 1% above 52% on the Test set we will award you with one extra bonus point. Feel free implement your own techniques (e.g. PCA to reduce dimensionality, or adding dropout, or adding features to the solver, etc.).

best_net = None # store the best model into this 
#################################################################################
# TODO: Tune hyperparameters using the validation set. Store your best trained  #
# model in best_net.                                                            #
#                                                                               #
# To help debug your network, it may help to use visualizations similar to the  #
# ones we used above; these visualizations will have significant qualitative    #
# differences from the ones we saw above for the poorly tuned network.          #
#                                                                               #
# Tweaking hyperparameters by hand can be fun, but you might find it useful to  #
# write code to sweep through possible combinations of hyperparameters          #
# automatically like we did on the previous exercises.                          #
#################################################################################

best_val = -1
results = {}
np.random.seed(0)
batch_sizes = [236]#[200,236]
learning_rates = [5e-4]#[1e-4,1e-3,5e-3]
regs = [0.1]#[0.5,0.7]
grid_search=[(x,y,z) for x in batch_sizes for y in learning_rates for z in regs]

for batch_size,learning_rate,reg in grid_search:    
    net = TwoLayerNet(input_size, hidden_size, num_classes)
    # Train the network
    net.train(X_train, y_train, X_val, y_val,
                num_iters=3000, batch_size=batch_size,
                learning_rate=learning_rate, learning_rate_decay=0.95,
                reg=reg, verbose=True)
    # Predict on the validation set and compute accuracy
    val_acc = (net.predict(X_val) == y_val).mean()
    print('Validation accuracy: ', val_acc)    
    results[(batch_size,learning_rate,reg)]=val_acc    
    if val_acc>best_val:
        best_val=val_acc
        best_net=net        
#################################################################################
#                               END OF YOUR CODE                                #
#################################################################################
iteration 0 / 3000: loss 2.302747
iteration 100 / 3000: loss 2.151115
iteration 200 / 3000: loss 1.956375
iteration 300 / 3000: loss 1.795100
iteration 400 / 3000: loss 1.650096
iteration 500 / 3000: loss 1.788248
iteration 600 / 3000: loss 1.632312
iteration 700 / 3000: loss 1.725397
iteration 800 / 3000: loss 1.546313
iteration 900 / 3000: loss 1.617557
iteration 1000 / 3000: loss 1.605412
iteration 1100 / 3000: loss 1.411051
iteration 1200 / 3000: loss 1.647157
iteration 1300 / 3000: loss 1.290863
iteration 1400 / 3000: loss 1.594383
iteration 1500 / 3000: loss 1.539742
iteration 1600 / 3000: loss 1.498160
iteration 1700 / 3000: loss 1.507776
iteration 1800 / 3000: loss 1.503318
iteration 1900 / 3000: loss 1.481487
iteration 2000 / 3000: loss 1.473032
iteration 2100 / 3000: loss 1.413800
iteration 2200 / 3000: loss 1.432247
iteration 2300 / 3000: loss 1.545330
iteration 2400 / 3000: loss 1.393733
iteration 2500 / 3000: loss 1.465507
iteration 2600 / 3000: loss 1.343429
iteration 2700 / 3000: loss 1.408117
iteration 2800 / 3000: loss 1.412546
iteration 2900 / 3000: loss 1.402916
Validation accuracy:  0.5
# visualize the weights of the best network
show_net_weights(best_net)

Run on the test set

実験終了後、テストセットを使った最終的な訓練済みネットワークを検証し、テスト精度は48%以上を超える必要がある。精度が52%を越えた分については、追加のボーナスポイントを付与する。

test_acc = (best_net.predict(X_test) == y_test).mean()
print('Test accuracy: ', test_acc)
Test accuracy:  0.503
参考サイトhttps://github.com/