Stanford/CS231n/assignment1/neural networkでは、フィードされたデータを分類するための完全にコネクトされたレイヤーを持ったニューラルネットワークを構築して、それをCIFAR-10データセットを使ってテストしている。
Implementing a Neural Network¶
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)))
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)))
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])))
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()
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 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)
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 #
#################################################################################
# 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)