mirror of https://github.com/da0c/DL_Course_SamU
add lab_3
parent
32c19488ad
commit
6a053ab090
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,135 @@
|
|||||||
|
from builtins import object
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from ..layers import *
|
||||||
|
from ..fast_layers import *
|
||||||
|
from ..layer_utils import *
|
||||||
|
|
||||||
|
|
||||||
|
class ThreeLayerConvNet(object):
|
||||||
|
"""
|
||||||
|
A three-layer convolutional network with the following architecture:
|
||||||
|
|
||||||
|
conv - relu - 2x2 max pool - affine - relu - affine - softmax
|
||||||
|
|
||||||
|
The network operates on minibatches of data that have shape (N, C, H, W)
|
||||||
|
consisting of N images, each with height H and width W and with C input
|
||||||
|
channels.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
input_dim=(3, 32, 32),
|
||||||
|
num_filters=32,
|
||||||
|
filter_size=7,
|
||||||
|
hidden_dim=100,
|
||||||
|
num_classes=10,
|
||||||
|
weight_scale=1e-3,
|
||||||
|
reg=0.0,
|
||||||
|
dtype=np.float32,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Initialize a new network.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- input_dim: Tuple (C, H, W) giving size of input data
|
||||||
|
- num_filters: Number of filters to use in the convolutional layer
|
||||||
|
- filter_size: Width/height of filters to use in the convolutional layer
|
||||||
|
- hidden_dim: Number of units to use in the fully-connected hidden layer
|
||||||
|
- num_classes: Number of scores to produce from the final affine layer.
|
||||||
|
- weight_scale: Scalar giving standard deviation for random initialization
|
||||||
|
of weights.
|
||||||
|
- reg: Scalar giving L2 regularization strength
|
||||||
|
- dtype: numpy datatype to use for computation.
|
||||||
|
"""
|
||||||
|
self.params = {}
|
||||||
|
self.reg = reg
|
||||||
|
self.dtype = dtype
|
||||||
|
|
||||||
|
############################################################################
|
||||||
|
# TODO: Initialize weights and biases for the three-layer convolutional #
|
||||||
|
# network. Weights should be initialized from a Gaussian centered at 0.0 #
|
||||||
|
# with standard deviation equal to weight_scale; biases should be #
|
||||||
|
# initialized to zero. All weights and biases should be stored in the #
|
||||||
|
# dictionary self.params. Store weights and biases for the convolutional #
|
||||||
|
# layer using the keys 'W1' and 'b1'; use keys 'W2' and 'b2' for the #
|
||||||
|
# weights and biases of the hidden affine layer, and keys 'W3' and 'b3' #
|
||||||
|
# for the weights and biases of the output affine layer. #
|
||||||
|
# #
|
||||||
|
# IMPORTANT: For this assignment, you can assume that the padding #
|
||||||
|
# and stride of the first convolutional layer are chosen so that #
|
||||||
|
# **the width and height of the input are preserved**. Take a look at #
|
||||||
|
# the start of the loss() function to see how that happens. #
|
||||||
|
############################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
############################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
############################################################################
|
||||||
|
|
||||||
|
for k, v in self.params.items():
|
||||||
|
self.params[k] = v.astype(dtype)
|
||||||
|
|
||||||
|
def loss(self, X, y=None):
|
||||||
|
"""
|
||||||
|
Evaluate loss and gradient for the three-layer convolutional network.
|
||||||
|
|
||||||
|
Input / output: Same API as TwoLayerNet in fc_net.py.
|
||||||
|
"""
|
||||||
|
W1, b1 = self.params["W1"], self.params["b1"]
|
||||||
|
W2, b2 = self.params["W2"], self.params["b2"]
|
||||||
|
W3, b3 = self.params["W3"], self.params["b3"]
|
||||||
|
|
||||||
|
# pass conv_param to the forward pass for the convolutional layer
|
||||||
|
# Padding and stride chosen to preserve the input spatial size
|
||||||
|
filter_size = W1.shape[2]
|
||||||
|
conv_param = {"stride": 1, "pad": (filter_size - 1) // 2}
|
||||||
|
|
||||||
|
# pass pool_param to the forward pass for the max-pooling layer
|
||||||
|
pool_param = {"pool_height": 2, "pool_width": 2, "stride": 2}
|
||||||
|
|
||||||
|
scores = None
|
||||||
|
############################################################################
|
||||||
|
# TODO: Implement the forward pass for the three-layer convolutional net, #
|
||||||
|
# computing the class scores for X and storing them in the scores #
|
||||||
|
# variable. #
|
||||||
|
# #
|
||||||
|
# Remember you can use the functions defined in cs231n/fast_layers.py and #
|
||||||
|
# cs231n/layer_utils.py in your implementation (already imported). #
|
||||||
|
############################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
############################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
############################################################################
|
||||||
|
|
||||||
|
if y is None:
|
||||||
|
return scores
|
||||||
|
|
||||||
|
loss, grads = 0, {}
|
||||||
|
############################################################################
|
||||||
|
# TODO: Implement the backward pass for the three-layer convolutional net, #
|
||||||
|
# storing the loss and gradients in the loss and grads variables. Compute #
|
||||||
|
# data loss using softmax, and make sure that grads[k] holds the gradients #
|
||||||
|
# for self.params[k]. Don't forget to add L2 regularization! #
|
||||||
|
# #
|
||||||
|
# NOTE: To ensure that your implementation matches ours and you pass the #
|
||||||
|
# automated tests, make sure that your L2 regularization includes a factor #
|
||||||
|
# of 0.5 to simplify the expression for the gradient. #
|
||||||
|
############################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
############################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
############################################################################
|
||||||
|
|
||||||
|
return loss, grads
|
@ -0,0 +1,291 @@
|
|||||||
|
from builtins import range
|
||||||
|
from builtins import object
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from ..layers import *
|
||||||
|
from ..layer_utils import *
|
||||||
|
|
||||||
|
|
||||||
|
class TwoLayerNet(object):
|
||||||
|
"""
|
||||||
|
A two-layer fully-connected neural network with ReLU nonlinearity and
|
||||||
|
softmax loss that uses a modular layer design. We assume an input dimension
|
||||||
|
of D, a hidden dimension of H, and perform classification over C classes.
|
||||||
|
|
||||||
|
The architecure should be affine - relu - affine - softmax.
|
||||||
|
|
||||||
|
Note that this class does not implement gradient descent; instead, it
|
||||||
|
will interact with a separate Solver object that is responsible for running
|
||||||
|
optimization.
|
||||||
|
|
||||||
|
The learnable parameters of the model are stored in the dictionary
|
||||||
|
self.params that maps parameter names to numpy arrays.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
input_dim=3 * 32 * 32,
|
||||||
|
hidden_dim=100,
|
||||||
|
num_classes=10,
|
||||||
|
weight_scale=1e-3,
|
||||||
|
reg=0.0,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Initialize a new network.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- input_dim: An integer giving the size of the input
|
||||||
|
- hidden_dim: An integer giving the size of the hidden layer
|
||||||
|
- num_classes: An integer giving the number of classes to classify
|
||||||
|
- weight_scale: Scalar giving the standard deviation for random
|
||||||
|
initialization of the weights.
|
||||||
|
- reg: Scalar giving L2 regularization strength.
|
||||||
|
"""
|
||||||
|
self.params = {}
|
||||||
|
self.reg = reg
|
||||||
|
|
||||||
|
############################################################################
|
||||||
|
# TODO: Initialize the weights and biases of the two-layer net. Weights #
|
||||||
|
# should be initialized from a Gaussian centered at 0.0 with #
|
||||||
|
# standard deviation equal to weight_scale, and biases should be #
|
||||||
|
# initialized to zero. All weights and biases should be stored in the #
|
||||||
|
# dictionary self.params, with first layer weights #
|
||||||
|
# and biases using the keys 'W1' and 'b1' and second layer #
|
||||||
|
# weights and biases using the keys 'W2' and 'b2'. #
|
||||||
|
############################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
############################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
############################################################################
|
||||||
|
|
||||||
|
def loss(self, X, y=None):
|
||||||
|
"""
|
||||||
|
Compute loss and gradient for a minibatch of data.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- X: Array of input data of shape (N, d_1, ..., d_k)
|
||||||
|
- y: Array of labels, of shape (N,). y[i] gives the label for X[i].
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
If y is None, then run a test-time forward pass of the model and return:
|
||||||
|
- scores: Array of shape (N, C) giving classification scores, where
|
||||||
|
scores[i, c] is the classification score for X[i] and class c.
|
||||||
|
|
||||||
|
If y is not None, then run a training-time forward and backward pass and
|
||||||
|
return a tuple of:
|
||||||
|
- loss: Scalar value giving the loss
|
||||||
|
- grads: Dictionary with the same keys as self.params, mapping parameter
|
||||||
|
names to gradients of the loss with respect to those parameters.
|
||||||
|
"""
|
||||||
|
scores = None
|
||||||
|
############################################################################
|
||||||
|
# TODO: Implement the forward pass for the two-layer net, computing the #
|
||||||
|
# class scores for X and storing them in the scores variable. #
|
||||||
|
############################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
############################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
############################################################################
|
||||||
|
|
||||||
|
# If y is None then we are in test mode so just return scores
|
||||||
|
if y is None:
|
||||||
|
return scores
|
||||||
|
|
||||||
|
loss, grads = 0, {}
|
||||||
|
############################################################################
|
||||||
|
# TODO: Implement the backward pass for the two-layer net. Store the loss #
|
||||||
|
# in the loss variable and gradients in the grads dictionary. Compute data #
|
||||||
|
# loss using softmax, and make sure that grads[k] holds the gradients for #
|
||||||
|
# self.params[k]. Don't forget to add L2 regularization! #
|
||||||
|
# #
|
||||||
|
# NOTE: To ensure that your implementation matches ours and you pass the #
|
||||||
|
# automated tests, make sure that your L2 regularization includes a factor #
|
||||||
|
# of 0.5 to simplify the expression for the gradient. #
|
||||||
|
############################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
############################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
############################################################################
|
||||||
|
|
||||||
|
return loss, grads
|
||||||
|
|
||||||
|
|
||||||
|
class FullyConnectedNet(object):
|
||||||
|
"""
|
||||||
|
A fully-connected neural network with an arbitrary number of hidden layers,
|
||||||
|
ReLU nonlinearities, and a softmax loss function. This will also implement
|
||||||
|
dropout and batch/layer normalization as options. For a network with L layers,
|
||||||
|
the architecture will be
|
||||||
|
|
||||||
|
{affine - [batch/layer norm] - relu - [dropout]} x (L - 1) - affine - softmax
|
||||||
|
|
||||||
|
where batch/layer normalization and dropout are optional, and the {...} block is
|
||||||
|
repeated L - 1 times.
|
||||||
|
|
||||||
|
Similar to the TwoLayerNet above, learnable parameters are stored in the
|
||||||
|
self.params dictionary and will be learned using the Solver class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hidden_dims,
|
||||||
|
input_dim=3 * 32 * 32,
|
||||||
|
num_classes=10,
|
||||||
|
dropout=1,
|
||||||
|
normalization=None,
|
||||||
|
reg=0.0,
|
||||||
|
weight_scale=1e-2,
|
||||||
|
dtype=np.float32,
|
||||||
|
seed=None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Initialize a new FullyConnectedNet.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- hidden_dims: A list of integers giving the size of each hidden layer.
|
||||||
|
- input_dim: An integer giving the size of the input.
|
||||||
|
- num_classes: An integer giving the number of classes to classify.
|
||||||
|
- dropout: Scalar between 0 and 1 giving dropout strength. If dropout=1 then
|
||||||
|
the network should not use dropout at all.
|
||||||
|
- normalization: What type of normalization the network should use. Valid values
|
||||||
|
are "batchnorm", "layernorm", or None for no normalization (the default).
|
||||||
|
- reg: Scalar giving L2 regularization strength.
|
||||||
|
- weight_scale: Scalar giving the standard deviation for random
|
||||||
|
initialization of the weights.
|
||||||
|
- dtype: A numpy datatype object; all computations will be performed using
|
||||||
|
this datatype. float32 is faster but less accurate, so you should use
|
||||||
|
float64 for numeric gradient checking.
|
||||||
|
- seed: If not None, then pass this random seed to the dropout layers. This
|
||||||
|
will make the dropout layers deteriminstic so we can gradient check the
|
||||||
|
model.
|
||||||
|
"""
|
||||||
|
self.normalization = normalization
|
||||||
|
self.use_dropout = dropout != 1
|
||||||
|
self.reg = reg
|
||||||
|
self.num_layers = 1 + len(hidden_dims)
|
||||||
|
self.dtype = dtype
|
||||||
|
self.params = {}
|
||||||
|
|
||||||
|
############################################################################
|
||||||
|
# TODO: Initialize the parameters of the network, storing all values in #
|
||||||
|
# the self.params dictionary. Store weights and biases for the first layer #
|
||||||
|
# in W1 and b1; for the second layer use W2 and b2, etc. Weights should be #
|
||||||
|
# initialized from a normal distribution centered at 0 with standard #
|
||||||
|
# deviation equal to weight_scale. Biases should be initialized to zero. #
|
||||||
|
# #
|
||||||
|
# When using batch normalization, store scale and shift parameters for the #
|
||||||
|
# first layer in gamma1 and beta1; for the second layer use gamma2 and #
|
||||||
|
# beta2, etc. Scale parameters should be initialized to ones and shift #
|
||||||
|
# parameters should be initialized to zeros. #
|
||||||
|
############################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
############################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
############################################################################
|
||||||
|
|
||||||
|
# When using dropout we need to pass a dropout_param dictionary to each
|
||||||
|
# dropout layer so that the layer knows the dropout probability and the mode
|
||||||
|
# (train / test). You can pass the same dropout_param to each dropout layer.
|
||||||
|
self.dropout_param = {}
|
||||||
|
if self.use_dropout:
|
||||||
|
self.dropout_param = {"mode": "train", "p": dropout}
|
||||||
|
if seed is not None:
|
||||||
|
self.dropout_param["seed"] = seed
|
||||||
|
|
||||||
|
# With batch normalization we need to keep track of running means and
|
||||||
|
# variances, so we need to pass a special bn_param object to each batch
|
||||||
|
# normalization layer. You should pass self.bn_params[0] to the forward pass
|
||||||
|
# of the first batch normalization layer, self.bn_params[1] to the forward
|
||||||
|
# pass of the second batch normalization layer, etc.
|
||||||
|
self.bn_params = []
|
||||||
|
if self.normalization == "batchnorm":
|
||||||
|
self.bn_params = [{"mode": "train"} for i in range(self.num_layers - 1)]
|
||||||
|
if self.normalization == "layernorm":
|
||||||
|
self.bn_params = [{} for i in range(self.num_layers - 1)]
|
||||||
|
|
||||||
|
# Cast all parameters to the correct datatype
|
||||||
|
for k, v in self.params.items():
|
||||||
|
self.params[k] = v.astype(dtype)
|
||||||
|
|
||||||
|
def loss(self, X, y=None):
|
||||||
|
"""
|
||||||
|
Compute loss and gradient for the fully-connected net.
|
||||||
|
|
||||||
|
Input / output: Same as TwoLayerNet above.
|
||||||
|
"""
|
||||||
|
X = X.astype(self.dtype)
|
||||||
|
mode = "test" if y is None else "train"
|
||||||
|
|
||||||
|
# Set train/test mode for batchnorm params and dropout param since they
|
||||||
|
# behave differently during training and testing.
|
||||||
|
if self.use_dropout:
|
||||||
|
self.dropout_param["mode"] = mode
|
||||||
|
if self.normalization == "batchnorm":
|
||||||
|
for bn_param in self.bn_params:
|
||||||
|
bn_param["mode"] = mode
|
||||||
|
scores = None
|
||||||
|
############################################################################
|
||||||
|
# TODO: Implement the forward pass for the fully-connected net, computing #
|
||||||
|
# the class scores for X and storing them in the scores variable. #
|
||||||
|
# #
|
||||||
|
# When using dropout, you'll need to pass self.dropout_param to each #
|
||||||
|
# dropout forward pass. #
|
||||||
|
# #
|
||||||
|
# When using batch normalization, you'll need to pass self.bn_params[0] to #
|
||||||
|
# the forward pass for the first batch normalization layer, pass #
|
||||||
|
# self.bn_params[1] to the forward pass for the second batch normalization #
|
||||||
|
# layer, etc. #
|
||||||
|
############################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
############################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
############################################################################
|
||||||
|
|
||||||
|
# If test mode return early
|
||||||
|
if mode == "test":
|
||||||
|
return scores
|
||||||
|
|
||||||
|
loss, grads = 0.0, {}
|
||||||
|
############################################################################
|
||||||
|
# TODO: Implement the backward pass for the fully-connected net. Store the #
|
||||||
|
# loss in the loss variable and gradients in the grads dictionary. Compute #
|
||||||
|
# data loss using softmax, and make sure that grads[k] holds the gradients #
|
||||||
|
# for self.params[k]. Don't forget to add L2 regularization! #
|
||||||
|
# #
|
||||||
|
# When using batch/layer normalization, you don't need to regularize the scale #
|
||||||
|
# and shift parameters. #
|
||||||
|
# #
|
||||||
|
# NOTE: To ensure that your implementation matches ours and you pass the #
|
||||||
|
# automated tests, make sure that your L2 regularization includes a factor #
|
||||||
|
# of 0.5 to simplify the expression for the gradient. #
|
||||||
|
############################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
############################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
############################################################################
|
||||||
|
|
||||||
|
return loss, grads
|
@ -0,0 +1,270 @@
|
|||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
from builtins import range
|
||||||
|
from six.moves import cPickle as pickle
|
||||||
|
import numpy as np
|
||||||
|
import os
|
||||||
|
from imageio import imread
|
||||||
|
import platform
|
||||||
|
|
||||||
|
|
||||||
|
def load_pickle(f):
|
||||||
|
version = platform.python_version_tuple()
|
||||||
|
if version[0] == "2":
|
||||||
|
return pickle.load(f)
|
||||||
|
elif version[0] == "3":
|
||||||
|
return pickle.load(f, encoding="latin1")
|
||||||
|
raise ValueError("invalid python version: {}".format(version))
|
||||||
|
|
||||||
|
|
||||||
|
def load_CIFAR_batch(filename):
|
||||||
|
""" load single batch of cifar """
|
||||||
|
with open(filename, "rb") as f:
|
||||||
|
datadict = load_pickle(f)
|
||||||
|
X = datadict["data"]
|
||||||
|
Y = datadict["labels"]
|
||||||
|
X = X.reshape(10000, 3, 32, 32).transpose(0, 2, 3, 1).astype("float")
|
||||||
|
Y = np.array(Y)
|
||||||
|
return X, Y
|
||||||
|
|
||||||
|
|
||||||
|
def load_CIFAR10(ROOT):
|
||||||
|
""" load all of cifar """
|
||||||
|
xs = []
|
||||||
|
ys = []
|
||||||
|
for b in range(1, 6):
|
||||||
|
f = os.path.join(ROOT, "data_batch_%d" % (b,))
|
||||||
|
X, Y = load_CIFAR_batch(f)
|
||||||
|
xs.append(X)
|
||||||
|
ys.append(Y)
|
||||||
|
Xtr = np.concatenate(xs)
|
||||||
|
Ytr = np.concatenate(ys)
|
||||||
|
del X, Y
|
||||||
|
Xte, Yte = load_CIFAR_batch(os.path.join(ROOT, "test_batch"))
|
||||||
|
return Xtr, Ytr, Xte, Yte
|
||||||
|
|
||||||
|
|
||||||
|
def get_CIFAR10_data(
|
||||||
|
num_training=49000, num_validation=1000, num_test=1000, subtract_mean=True
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Load the CIFAR-10 dataset from disk and perform preprocessing to prepare
|
||||||
|
it for classifiers. 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 = os.path.join(
|
||||||
|
os.path.dirname(__file__), "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
|
||||||
|
if subtract_mean:
|
||||||
|
mean_image = np.mean(X_train, axis=0)
|
||||||
|
X_train -= mean_image
|
||||||
|
X_val -= mean_image
|
||||||
|
X_test -= mean_image
|
||||||
|
|
||||||
|
# Transpose so that channels come first
|
||||||
|
X_train = X_train.transpose(0, 3, 1, 2).copy()
|
||||||
|
X_val = X_val.transpose(0, 3, 1, 2).copy()
|
||||||
|
X_test = X_test.transpose(0, 3, 1, 2).copy()
|
||||||
|
|
||||||
|
# Package data into a dictionary
|
||||||
|
return {
|
||||||
|
"X_train": X_train,
|
||||||
|
"y_train": y_train,
|
||||||
|
"X_val": X_val,
|
||||||
|
"y_val": y_val,
|
||||||
|
"X_test": X_test,
|
||||||
|
"y_test": y_test,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def load_tiny_imagenet(path, dtype=np.float32, subtract_mean=True):
|
||||||
|
"""
|
||||||
|
Load TinyImageNet. Each of TinyImageNet-100-A, TinyImageNet-100-B, and
|
||||||
|
TinyImageNet-200 have the same directory structure, so this can be used
|
||||||
|
to load any of them.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- path: String giving path to the directory to load.
|
||||||
|
- dtype: numpy datatype used to load the data.
|
||||||
|
- subtract_mean: Whether to subtract the mean training image.
|
||||||
|
|
||||||
|
Returns: A dictionary with the following entries:
|
||||||
|
- class_names: A list where class_names[i] is a list of strings giving the
|
||||||
|
WordNet names for class i in the loaded dataset.
|
||||||
|
- X_train: (N_tr, 3, 64, 64) array of training images
|
||||||
|
- y_train: (N_tr,) array of training labels
|
||||||
|
- X_val: (N_val, 3, 64, 64) array of validation images
|
||||||
|
- y_val: (N_val,) array of validation labels
|
||||||
|
- X_test: (N_test, 3, 64, 64) array of testing images.
|
||||||
|
- y_test: (N_test,) array of test labels; if test labels are not available
|
||||||
|
(such as in student code) then y_test will be None.
|
||||||
|
- mean_image: (3, 64, 64) array giving mean training image
|
||||||
|
"""
|
||||||
|
# First load wnids
|
||||||
|
with open(os.path.join(path, "wnids.txt"), "r") as f:
|
||||||
|
wnids = [x.strip() for x in f]
|
||||||
|
|
||||||
|
# Map wnids to integer labels
|
||||||
|
wnid_to_label = {wnid: i for i, wnid in enumerate(wnids)}
|
||||||
|
|
||||||
|
# Use words.txt to get names for each class
|
||||||
|
with open(os.path.join(path, "words.txt"), "r") as f:
|
||||||
|
wnid_to_words = dict(line.split("\t") for line in f)
|
||||||
|
for wnid, words in wnid_to_words.items():
|
||||||
|
wnid_to_words[wnid] = [w.strip() for w in words.split(",")]
|
||||||
|
class_names = [wnid_to_words[wnid] for wnid in wnids]
|
||||||
|
|
||||||
|
# Next load training data.
|
||||||
|
X_train = []
|
||||||
|
y_train = []
|
||||||
|
for i, wnid in enumerate(wnids):
|
||||||
|
if (i + 1) % 20 == 0:
|
||||||
|
print("loading training data for synset %d / %d" % (i + 1, len(wnids)))
|
||||||
|
# To figure out the filenames we need to open the boxes file
|
||||||
|
boxes_file = os.path.join(path, "train", wnid, "%s_boxes.txt" % wnid)
|
||||||
|
with open(boxes_file, "r") as f:
|
||||||
|
filenames = [x.split("\t")[0] for x in f]
|
||||||
|
num_images = len(filenames)
|
||||||
|
|
||||||
|
X_train_block = np.zeros((num_images, 3, 64, 64), dtype=dtype)
|
||||||
|
y_train_block = wnid_to_label[wnid] * np.ones(num_images, dtype=np.int64)
|
||||||
|
for j, img_file in enumerate(filenames):
|
||||||
|
img_file = os.path.join(path, "train", wnid, "images", img_file)
|
||||||
|
img = imread(img_file)
|
||||||
|
if img.ndim == 2:
|
||||||
|
## grayscale file
|
||||||
|
img.shape = (64, 64, 1)
|
||||||
|
X_train_block[j] = img.transpose(2, 0, 1)
|
||||||
|
X_train.append(X_train_block)
|
||||||
|
y_train.append(y_train_block)
|
||||||
|
|
||||||
|
# We need to concatenate all training data
|
||||||
|
X_train = np.concatenate(X_train, axis=0)
|
||||||
|
y_train = np.concatenate(y_train, axis=0)
|
||||||
|
|
||||||
|
# Next load validation data
|
||||||
|
with open(os.path.join(path, "val", "val_annotations.txt"), "r") as f:
|
||||||
|
img_files = []
|
||||||
|
val_wnids = []
|
||||||
|
for line in f:
|
||||||
|
img_file, wnid = line.split("\t")[:2]
|
||||||
|
img_files.append(img_file)
|
||||||
|
val_wnids.append(wnid)
|
||||||
|
num_val = len(img_files)
|
||||||
|
y_val = np.array([wnid_to_label[wnid] for wnid in val_wnids])
|
||||||
|
X_val = np.zeros((num_val, 3, 64, 64), dtype=dtype)
|
||||||
|
for i, img_file in enumerate(img_files):
|
||||||
|
img_file = os.path.join(path, "val", "images", img_file)
|
||||||
|
img = imread(img_file)
|
||||||
|
if img.ndim == 2:
|
||||||
|
img.shape = (64, 64, 1)
|
||||||
|
X_val[i] = img.transpose(2, 0, 1)
|
||||||
|
|
||||||
|
# Next load test images
|
||||||
|
# Students won't have test labels, so we need to iterate over files in the
|
||||||
|
# images directory.
|
||||||
|
img_files = os.listdir(os.path.join(path, "test", "images"))
|
||||||
|
X_test = np.zeros((len(img_files), 3, 64, 64), dtype=dtype)
|
||||||
|
for i, img_file in enumerate(img_files):
|
||||||
|
img_file = os.path.join(path, "test", "images", img_file)
|
||||||
|
img = imread(img_file)
|
||||||
|
if img.ndim == 2:
|
||||||
|
img.shape = (64, 64, 1)
|
||||||
|
X_test[i] = img.transpose(2, 0, 1)
|
||||||
|
|
||||||
|
y_test = None
|
||||||
|
y_test_file = os.path.join(path, "test", "test_annotations.txt")
|
||||||
|
if os.path.isfile(y_test_file):
|
||||||
|
with open(y_test_file, "r") as f:
|
||||||
|
img_file_to_wnid = {}
|
||||||
|
for line in f:
|
||||||
|
line = line.split("\t")
|
||||||
|
img_file_to_wnid[line[0]] = line[1]
|
||||||
|
y_test = [wnid_to_label[img_file_to_wnid[img_file]] for img_file in img_files]
|
||||||
|
y_test = np.array(y_test)
|
||||||
|
|
||||||
|
mean_image = X_train.mean(axis=0)
|
||||||
|
if subtract_mean:
|
||||||
|
X_train -= mean_image[None]
|
||||||
|
X_val -= mean_image[None]
|
||||||
|
X_test -= mean_image[None]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"class_names": class_names,
|
||||||
|
"X_train": X_train,
|
||||||
|
"y_train": y_train,
|
||||||
|
"X_val": X_val,
|
||||||
|
"y_val": y_val,
|
||||||
|
"X_test": X_test,
|
||||||
|
"y_test": y_test,
|
||||||
|
"class_names": class_names,
|
||||||
|
"mean_image": mean_image,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def load_models(models_dir):
|
||||||
|
"""
|
||||||
|
Load saved models from disk. This will attempt to unpickle all files in a
|
||||||
|
directory; any files that give errors on unpickling (such as README.txt)
|
||||||
|
will be skipped.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- models_dir: String giving the path to a directory containing model files.
|
||||||
|
Each model file is a pickled dictionary with a 'model' field.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A dictionary mapping model file names to models.
|
||||||
|
"""
|
||||||
|
models = {}
|
||||||
|
for model_file in os.listdir(models_dir):
|
||||||
|
with open(os.path.join(models_dir, model_file), "rb") as f:
|
||||||
|
try:
|
||||||
|
models[model_file] = load_pickle(f)["model"]
|
||||||
|
except pickle.UnpicklingError:
|
||||||
|
continue
|
||||||
|
return models
|
||||||
|
|
||||||
|
|
||||||
|
def load_imagenet_val(num=None):
|
||||||
|
"""Load a handful of validation images from ImageNet.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- num: Number of images to load (max of 25)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- X: numpy array with shape [num, 224, 224, 3]
|
||||||
|
- y: numpy array of integer image labels, shape [num]
|
||||||
|
- class_names: dict mapping integer label to class name
|
||||||
|
"""
|
||||||
|
imagenet_fn = os.path.join(
|
||||||
|
os.path.dirname(__file__), "datasets/imagenet_val_25.npz"
|
||||||
|
)
|
||||||
|
if not os.path.isfile(imagenet_fn):
|
||||||
|
print("file %s not found" % imagenet_fn)
|
||||||
|
print("Run the following:")
|
||||||
|
print("cd cs231n/datasets")
|
||||||
|
print("bash get_imagenet_val.sh")
|
||||||
|
assert False, "Need to download imagenet_val_25.npz"
|
||||||
|
f = np.load(imagenet_fn)
|
||||||
|
X = f["X"]
|
||||||
|
y = f["y"]
|
||||||
|
class_names = f["label_map"].item()
|
||||||
|
if num is not None:
|
||||||
|
X = X[:num]
|
||||||
|
y = y[:num]
|
||||||
|
return X, y, class_names
|
@ -0,0 +1,5 @@
|
|||||||
|
if [ ! -d "cifar-10-batches-py" ]; then
|
||||||
|
wget http://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz -O cifar-10-python.tar.gz
|
||||||
|
tar -xzvf cifar-10-python.tar.gz
|
||||||
|
rm cifar-10-python.tar.gz
|
||||||
|
fi
|
@ -0,0 +1,283 @@
|
|||||||
|
from __future__ import print_function
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .im2col_cython import col2im_cython, im2col_cython
|
||||||
|
from .im2col_cython import col2im_6d_cython
|
||||||
|
except ImportError:
|
||||||
|
print("""=========== You can safely ignore the message below if you are NOT working on ConvolutionalNetworks.ipynb ===========""")
|
||||||
|
print("\tYou will need to compile a Cython extension for a portion of this assignment.")
|
||||||
|
print("\tThe instructions to do this will be given in a section of the notebook below.")
|
||||||
|
print("\tThere will be an option for Colab users and another for Jupyter (local) users.")
|
||||||
|
|
||||||
|
from .im2col import *
|
||||||
|
|
||||||
|
|
||||||
|
def conv_forward_im2col(x, w, b, conv_param):
|
||||||
|
"""
|
||||||
|
A fast implementation of the forward pass for a convolutional layer
|
||||||
|
based on im2col and col2im.
|
||||||
|
"""
|
||||||
|
N, C, H, W = x.shape
|
||||||
|
num_filters, _, filter_height, filter_width = w.shape
|
||||||
|
stride, pad = conv_param["stride"], conv_param["pad"]
|
||||||
|
|
||||||
|
# Check dimensions
|
||||||
|
assert (W + 2 * pad - filter_width) % stride == 0, "width does not work"
|
||||||
|
assert (H + 2 * pad - filter_height) % stride == 0, "height does not work"
|
||||||
|
|
||||||
|
# Create output
|
||||||
|
out_height = (H + 2 * pad - filter_height) // stride + 1
|
||||||
|
out_width = (W + 2 * pad - filter_width) // stride + 1
|
||||||
|
out = np.zeros((N, num_filters, out_height, out_width), dtype=x.dtype)
|
||||||
|
|
||||||
|
# x_cols = im2col_indices(x, w.shape[2], w.shape[3], pad, stride)
|
||||||
|
x_cols = im2col_cython(x, w.shape[2], w.shape[3], pad, stride)
|
||||||
|
res = w.reshape((w.shape[0], -1)).dot(x_cols) + b.reshape(-1, 1)
|
||||||
|
|
||||||
|
out = res.reshape(w.shape[0], out.shape[2], out.shape[3], x.shape[0])
|
||||||
|
out = out.transpose(3, 0, 1, 2)
|
||||||
|
|
||||||
|
cache = (x, w, b, conv_param, x_cols)
|
||||||
|
return out, cache
|
||||||
|
|
||||||
|
|
||||||
|
def conv_forward_strides(x, w, b, conv_param):
|
||||||
|
N, C, H, W = x.shape
|
||||||
|
F, _, HH, WW = w.shape
|
||||||
|
stride, pad = conv_param["stride"], conv_param["pad"]
|
||||||
|
|
||||||
|
# Check dimensions
|
||||||
|
# assert (W + 2 * pad - WW) % stride == 0, 'width does not work'
|
||||||
|
# assert (H + 2 * pad - HH) % stride == 0, 'height does not work'
|
||||||
|
|
||||||
|
# Pad the input
|
||||||
|
p = pad
|
||||||
|
x_padded = np.pad(x, ((0, 0), (0, 0), (p, p), (p, p)), mode="constant")
|
||||||
|
|
||||||
|
# Figure out output dimensions
|
||||||
|
H += 2 * pad
|
||||||
|
W += 2 * pad
|
||||||
|
out_h = (H - HH) // stride + 1
|
||||||
|
out_w = (W - WW) // stride + 1
|
||||||
|
|
||||||
|
# Perform an im2col operation by picking clever strides
|
||||||
|
shape = (C, HH, WW, N, out_h, out_w)
|
||||||
|
strides = (H * W, W, 1, C * H * W, stride * W, stride)
|
||||||
|
strides = x.itemsize * np.array(strides)
|
||||||
|
x_stride = np.lib.stride_tricks.as_strided(x_padded, shape=shape, strides=strides)
|
||||||
|
x_cols = np.ascontiguousarray(x_stride)
|
||||||
|
x_cols.shape = (C * HH * WW, N * out_h * out_w)
|
||||||
|
|
||||||
|
# Now all our convolutions are a big matrix multiply
|
||||||
|
res = w.reshape(F, -1).dot(x_cols) + b.reshape(-1, 1)
|
||||||
|
|
||||||
|
# Reshape the output
|
||||||
|
res.shape = (F, N, out_h, out_w)
|
||||||
|
out = res.transpose(1, 0, 2, 3)
|
||||||
|
|
||||||
|
# Be nice and return a contiguous array
|
||||||
|
# The old version of conv_forward_fast doesn't do this, so for a fair
|
||||||
|
# comparison we won't either
|
||||||
|
out = np.ascontiguousarray(out)
|
||||||
|
|
||||||
|
cache = (x, w, b, conv_param, x_cols)
|
||||||
|
return out, cache
|
||||||
|
|
||||||
|
|
||||||
|
def conv_backward_strides(dout, cache):
|
||||||
|
x, w, b, conv_param, x_cols = cache
|
||||||
|
stride, pad = conv_param["stride"], conv_param["pad"]
|
||||||
|
|
||||||
|
N, C, H, W = x.shape
|
||||||
|
F, _, HH, WW = w.shape
|
||||||
|
_, _, out_h, out_w = dout.shape
|
||||||
|
|
||||||
|
db = np.sum(dout, axis=(0, 2, 3))
|
||||||
|
|
||||||
|
dout_reshaped = dout.transpose(1, 0, 2, 3).reshape(F, -1)
|
||||||
|
dw = dout_reshaped.dot(x_cols.T).reshape(w.shape)
|
||||||
|
|
||||||
|
dx_cols = w.reshape(F, -1).T.dot(dout_reshaped)
|
||||||
|
dx_cols.shape = (C, HH, WW, N, out_h, out_w)
|
||||||
|
dx = col2im_6d_cython(dx_cols, N, C, H, W, HH, WW, pad, stride)
|
||||||
|
|
||||||
|
return dx, dw, db
|
||||||
|
|
||||||
|
|
||||||
|
def conv_backward_im2col(dout, cache):
|
||||||
|
"""
|
||||||
|
A fast implementation of the backward pass for a convolutional layer
|
||||||
|
based on im2col and col2im.
|
||||||
|
"""
|
||||||
|
x, w, b, conv_param, x_cols = cache
|
||||||
|
stride, pad = conv_param["stride"], conv_param["pad"]
|
||||||
|
|
||||||
|
db = np.sum(dout, axis=(0, 2, 3))
|
||||||
|
|
||||||
|
num_filters, _, filter_height, filter_width = w.shape
|
||||||
|
dout_reshaped = dout.transpose(1, 2, 3, 0).reshape(num_filters, -1)
|
||||||
|
dw = dout_reshaped.dot(x_cols.T).reshape(w.shape)
|
||||||
|
|
||||||
|
dx_cols = w.reshape(num_filters, -1).T.dot(dout_reshaped)
|
||||||
|
# dx = col2im_indices(dx_cols, x.shape, filter_height, filter_width, pad, stride)
|
||||||
|
dx = col2im_cython(
|
||||||
|
dx_cols,
|
||||||
|
x.shape[0],
|
||||||
|
x.shape[1],
|
||||||
|
x.shape[2],
|
||||||
|
x.shape[3],
|
||||||
|
filter_height,
|
||||||
|
filter_width,
|
||||||
|
pad,
|
||||||
|
stride,
|
||||||
|
)
|
||||||
|
|
||||||
|
return dx, dw, db
|
||||||
|
|
||||||
|
|
||||||
|
conv_forward_fast = conv_forward_strides
|
||||||
|
conv_backward_fast = conv_backward_strides
|
||||||
|
|
||||||
|
|
||||||
|
def max_pool_forward_fast(x, pool_param):
|
||||||
|
"""
|
||||||
|
A fast implementation of the forward pass for a max pooling layer.
|
||||||
|
|
||||||
|
This chooses between the reshape method and the im2col method. If the pooling
|
||||||
|
regions are square and tile the input image, then we can use the reshape
|
||||||
|
method which is very fast. Otherwise we fall back on the im2col method, which
|
||||||
|
is not much faster than the naive method.
|
||||||
|
"""
|
||||||
|
N, C, H, W = x.shape
|
||||||
|
pool_height, pool_width = pool_param["pool_height"], pool_param["pool_width"]
|
||||||
|
stride = pool_param["stride"]
|
||||||
|
|
||||||
|
same_size = pool_height == pool_width == stride
|
||||||
|
tiles = H % pool_height == 0 and W % pool_width == 0
|
||||||
|
if same_size and tiles:
|
||||||
|
out, reshape_cache = max_pool_forward_reshape(x, pool_param)
|
||||||
|
cache = ("reshape", reshape_cache)
|
||||||
|
else:
|
||||||
|
out, im2col_cache = max_pool_forward_im2col(x, pool_param)
|
||||||
|
cache = ("im2col", im2col_cache)
|
||||||
|
return out, cache
|
||||||
|
|
||||||
|
|
||||||
|
def max_pool_backward_fast(dout, cache):
|
||||||
|
"""
|
||||||
|
A fast implementation of the backward pass for a max pooling layer.
|
||||||
|
|
||||||
|
This switches between the reshape method an the im2col method depending on
|
||||||
|
which method was used to generate the cache.
|
||||||
|
"""
|
||||||
|
method, real_cache = cache
|
||||||
|
if method == "reshape":
|
||||||
|
return max_pool_backward_reshape(dout, real_cache)
|
||||||
|
elif method == "im2col":
|
||||||
|
return max_pool_backward_im2col(dout, real_cache)
|
||||||
|
else:
|
||||||
|
raise ValueError('Unrecognized method "%s"' % method)
|
||||||
|
|
||||||
|
|
||||||
|
def max_pool_forward_reshape(x, pool_param):
|
||||||
|
"""
|
||||||
|
A fast implementation of the forward pass for the max pooling layer that uses
|
||||||
|
some clever reshaping.
|
||||||
|
|
||||||
|
This can only be used for square pooling regions that tile the input.
|
||||||
|
"""
|
||||||
|
N, C, H, W = x.shape
|
||||||
|
pool_height, pool_width = pool_param["pool_height"], pool_param["pool_width"]
|
||||||
|
stride = pool_param["stride"]
|
||||||
|
assert pool_height == pool_width == stride, "Invalid pool params"
|
||||||
|
assert H % pool_height == 0
|
||||||
|
assert W % pool_height == 0
|
||||||
|
x_reshaped = x.reshape(
|
||||||
|
N, C, H // pool_height, pool_height, W // pool_width, pool_width
|
||||||
|
)
|
||||||
|
out = x_reshaped.max(axis=3).max(axis=4)
|
||||||
|
|
||||||
|
cache = (x, x_reshaped, out)
|
||||||
|
return out, cache
|
||||||
|
|
||||||
|
|
||||||
|
def max_pool_backward_reshape(dout, cache):
|
||||||
|
"""
|
||||||
|
A fast implementation of the backward pass for the max pooling layer that
|
||||||
|
uses some clever broadcasting and reshaping.
|
||||||
|
|
||||||
|
This can only be used if the forward pass was computed using
|
||||||
|
max_pool_forward_reshape.
|
||||||
|
|
||||||
|
NOTE: If there are multiple argmaxes, this method will assign gradient to
|
||||||
|
ALL argmax elements of the input rather than picking one. In this case the
|
||||||
|
gradient will actually be incorrect. However this is unlikely to occur in
|
||||||
|
practice, so it shouldn't matter much. One possible solution is to split the
|
||||||
|
upstream gradient equally among all argmax elements; this should result in a
|
||||||
|
valid subgradient. You can make this happen by uncommenting the line below;
|
||||||
|
however this results in a significant performance penalty (about 40% slower)
|
||||||
|
and is unlikely to matter in practice so we don't do it.
|
||||||
|
"""
|
||||||
|
x, x_reshaped, out = cache
|
||||||
|
|
||||||
|
dx_reshaped = np.zeros_like(x_reshaped)
|
||||||
|
out_newaxis = out[:, :, :, np.newaxis, :, np.newaxis]
|
||||||
|
mask = x_reshaped == out_newaxis
|
||||||
|
dout_newaxis = dout[:, :, :, np.newaxis, :, np.newaxis]
|
||||||
|
dout_broadcast, _ = np.broadcast_arrays(dout_newaxis, dx_reshaped)
|
||||||
|
dx_reshaped[mask] = dout_broadcast[mask]
|
||||||
|
dx_reshaped /= np.sum(mask, axis=(3, 5), keepdims=True)
|
||||||
|
dx = dx_reshaped.reshape(x.shape)
|
||||||
|
|
||||||
|
return dx
|
||||||
|
|
||||||
|
|
||||||
|
def max_pool_forward_im2col(x, pool_param):
|
||||||
|
"""
|
||||||
|
An implementation of the forward pass for max pooling based on im2col.
|
||||||
|
|
||||||
|
This isn't much faster than the naive version, so it should be avoided if
|
||||||
|
possible.
|
||||||
|
"""
|
||||||
|
N, C, H, W = x.shape
|
||||||
|
pool_height, pool_width = pool_param["pool_height"], pool_param["pool_width"]
|
||||||
|
stride = pool_param["stride"]
|
||||||
|
|
||||||
|
assert (H - pool_height) % stride == 0, "Invalid height"
|
||||||
|
assert (W - pool_width) % stride == 0, "Invalid width"
|
||||||
|
|
||||||
|
out_height = (H - pool_height) // stride + 1
|
||||||
|
out_width = (W - pool_width) // stride + 1
|
||||||
|
|
||||||
|
x_split = x.reshape(N * C, 1, H, W)
|
||||||
|
x_cols = im2col(x_split, pool_height, pool_width, padding=0, stride=stride)
|
||||||
|
x_cols_argmax = np.argmax(x_cols, axis=0)
|
||||||
|
x_cols_max = x_cols[x_cols_argmax, np.arange(x_cols.shape[1])]
|
||||||
|
out = x_cols_max.reshape(out_height, out_width, N, C).transpose(2, 3, 0, 1)
|
||||||
|
|
||||||
|
cache = (x, x_cols, x_cols_argmax, pool_param)
|
||||||
|
return out, cache
|
||||||
|
|
||||||
|
|
||||||
|
def max_pool_backward_im2col(dout, cache):
|
||||||
|
"""
|
||||||
|
An implementation of the backward pass for max pooling based on im2col.
|
||||||
|
|
||||||
|
This isn't much faster than the naive version, so it should be avoided if
|
||||||
|
possible.
|
||||||
|
"""
|
||||||
|
x, x_cols, x_cols_argmax, pool_param = cache
|
||||||
|
N, C, H, W = x.shape
|
||||||
|
pool_height, pool_width = pool_param["pool_height"], pool_param["pool_width"]
|
||||||
|
stride = pool_param["stride"]
|
||||||
|
|
||||||
|
dout_reshaped = dout.transpose(2, 3, 0, 1).flatten()
|
||||||
|
dx_cols = np.zeros_like(x_cols)
|
||||||
|
dx_cols[x_cols_argmax, np.arange(dx_cols.shape[1])] = dout_reshaped
|
||||||
|
dx = col2im_indices(
|
||||||
|
dx_cols, (N * C, 1, H, W), pool_height, pool_width, padding=0, stride=stride
|
||||||
|
)
|
||||||
|
dx = dx.reshape(x.shape)
|
||||||
|
|
||||||
|
return dx
|
@ -0,0 +1,133 @@
|
|||||||
|
from __future__ import print_function
|
||||||
|
from builtins import range
|
||||||
|
from past.builtins import xrange
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from random import randrange
|
||||||
|
|
||||||
|
|
||||||
|
def eval_numerical_gradient(f, x, verbose=True, h=0.00001):
|
||||||
|
"""
|
||||||
|
a naive implementation of numerical gradient of f at x
|
||||||
|
- f should be a function that takes a single argument
|
||||||
|
- x is the point (numpy array) to evaluate the gradient at
|
||||||
|
"""
|
||||||
|
|
||||||
|
fx = f(x) # evaluate function value at original point
|
||||||
|
grad = np.zeros_like(x)
|
||||||
|
# iterate over all indexes in x
|
||||||
|
it = np.nditer(x, flags=["multi_index"], op_flags=["readwrite"])
|
||||||
|
while not it.finished:
|
||||||
|
|
||||||
|
# evaluate function at x+h
|
||||||
|
ix = it.multi_index
|
||||||
|
oldval = x[ix]
|
||||||
|
x[ix] = oldval + h # increment by h
|
||||||
|
fxph = f(x) # evalute f(x + h)
|
||||||
|
x[ix] = oldval - h
|
||||||
|
fxmh = f(x) # evaluate f(x - h)
|
||||||
|
x[ix] = oldval # restore
|
||||||
|
|
||||||
|
# compute the partial derivative with centered formula
|
||||||
|
grad[ix] = (fxph - fxmh) / (2 * h) # the slope
|
||||||
|
if verbose:
|
||||||
|
print(ix, grad[ix])
|
||||||
|
it.iternext() # step to next dimension
|
||||||
|
|
||||||
|
return grad
|
||||||
|
|
||||||
|
|
||||||
|
def eval_numerical_gradient_array(f, x, df, h=1e-5):
|
||||||
|
"""
|
||||||
|
Evaluate a numeric gradient for a function that accepts a numpy
|
||||||
|
array and returns a numpy array.
|
||||||
|
"""
|
||||||
|
grad = np.zeros_like(x)
|
||||||
|
it = np.nditer(x, flags=["multi_index"], op_flags=["readwrite"])
|
||||||
|
while not it.finished:
|
||||||
|
ix = it.multi_index
|
||||||
|
|
||||||
|
oldval = x[ix]
|
||||||
|
x[ix] = oldval + h
|
||||||
|
pos = f(x).copy()
|
||||||
|
x[ix] = oldval - h
|
||||||
|
neg = f(x).copy()
|
||||||
|
x[ix] = oldval
|
||||||
|
|
||||||
|
grad[ix] = np.sum((pos - neg) * df) / (2 * h)
|
||||||
|
it.iternext()
|
||||||
|
return grad
|
||||||
|
|
||||||
|
|
||||||
|
def eval_numerical_gradient_blobs(f, inputs, output, h=1e-5):
|
||||||
|
"""
|
||||||
|
Compute numeric gradients for a function that operates on input
|
||||||
|
and output blobs.
|
||||||
|
|
||||||
|
We assume that f accepts several input blobs as arguments, followed by a
|
||||||
|
blob where outputs will be written. For example, f might be called like:
|
||||||
|
|
||||||
|
f(x, w, out)
|
||||||
|
|
||||||
|
where x and w are input Blobs, and the result of f will be written to out.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- f: function
|
||||||
|
- inputs: tuple of input blobs
|
||||||
|
- output: output blob
|
||||||
|
- h: step size
|
||||||
|
"""
|
||||||
|
numeric_diffs = []
|
||||||
|
for input_blob in inputs:
|
||||||
|
diff = np.zeros_like(input_blob.diffs)
|
||||||
|
it = np.nditer(input_blob.vals, flags=["multi_index"], op_flags=["readwrite"])
|
||||||
|
while not it.finished:
|
||||||
|
idx = it.multi_index
|
||||||
|
orig = input_blob.vals[idx]
|
||||||
|
|
||||||
|
input_blob.vals[idx] = orig + h
|
||||||
|
f(*(inputs + (output,)))
|
||||||
|
pos = np.copy(output.vals)
|
||||||
|
input_blob.vals[idx] = orig - h
|
||||||
|
f(*(inputs + (output,)))
|
||||||
|
neg = np.copy(output.vals)
|
||||||
|
input_blob.vals[idx] = orig
|
||||||
|
|
||||||
|
diff[idx] = np.sum((pos - neg) * output.diffs) / (2.0 * h)
|
||||||
|
|
||||||
|
it.iternext()
|
||||||
|
numeric_diffs.append(diff)
|
||||||
|
return numeric_diffs
|
||||||
|
|
||||||
|
|
||||||
|
def eval_numerical_gradient_net(net, inputs, output, h=1e-5):
|
||||||
|
return eval_numerical_gradient_blobs(
|
||||||
|
lambda *args: net.forward(), inputs, output, h=h
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def grad_check_sparse(f, x, analytic_grad, num_checks=10, h=1e-5):
|
||||||
|
"""
|
||||||
|
sample a few random elements and only return numerical
|
||||||
|
in this dimensions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
for i in range(num_checks):
|
||||||
|
ix = tuple([randrange(m) for m in x.shape])
|
||||||
|
|
||||||
|
oldval = x[ix]
|
||||||
|
x[ix] = oldval + h # increment by h
|
||||||
|
fxph = f(x) # evaluate f(x + h)
|
||||||
|
x[ix] = oldval - h # increment by h
|
||||||
|
fxmh = f(x) # evaluate f(x - h)
|
||||||
|
x[ix] = oldval # reset
|
||||||
|
|
||||||
|
grad_numerical = (fxph - fxmh) / (2 * h)
|
||||||
|
grad_analytic = analytic_grad[ix]
|
||||||
|
rel_error = abs(grad_numerical - grad_analytic) / (
|
||||||
|
abs(grad_numerical) + abs(grad_analytic)
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"numerical: %f analytic: %f, relative error: %e"
|
||||||
|
% (grad_numerical, grad_analytic, rel_error)
|
||||||
|
)
|
@ -0,0 +1,58 @@
|
|||||||
|
from builtins import range
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def get_im2col_indices(x_shape, field_height, field_width, padding=1, stride=1):
|
||||||
|
# First figure out what the size of the output should be
|
||||||
|
N, C, H, W = x_shape
|
||||||
|
assert (H + 2 * padding - field_height) % stride == 0
|
||||||
|
assert (W + 2 * padding - field_height) % stride == 0
|
||||||
|
out_height = (H + 2 * padding - field_height) / stride + 1
|
||||||
|
out_width = (W + 2 * padding - field_width) / stride + 1
|
||||||
|
|
||||||
|
i0 = np.repeat(np.arange(field_height), field_width)
|
||||||
|
i0 = np.tile(i0, C)
|
||||||
|
i1 = stride * np.repeat(np.arange(out_height), out_width)
|
||||||
|
j0 = np.tile(np.arange(field_width), field_height * C)
|
||||||
|
j1 = stride * np.tile(np.arange(out_width), out_height)
|
||||||
|
i = i0.reshape(-1, 1) + i1.reshape(1, -1)
|
||||||
|
j = j0.reshape(-1, 1) + j1.reshape(1, -1)
|
||||||
|
|
||||||
|
k = np.repeat(np.arange(C), field_height * field_width).reshape(-1, 1)
|
||||||
|
|
||||||
|
return (k, i, j)
|
||||||
|
|
||||||
|
|
||||||
|
def im2col_indices(x, field_height, field_width, padding=1, stride=1):
|
||||||
|
""" An implementation of im2col based on some fancy indexing """
|
||||||
|
# Zero-pad the input
|
||||||
|
p = padding
|
||||||
|
x_padded = np.pad(x, ((0, 0), (0, 0), (p, p), (p, p)), mode="constant")
|
||||||
|
|
||||||
|
k, i, j = get_im2col_indices(x.shape, field_height, field_width, padding, stride)
|
||||||
|
|
||||||
|
cols = x_padded[:, k, i, j]
|
||||||
|
C = x.shape[1]
|
||||||
|
cols = cols.transpose(1, 2, 0).reshape(field_height * field_width * C, -1)
|
||||||
|
return cols
|
||||||
|
|
||||||
|
|
||||||
|
def col2im_indices(cols, x_shape, field_height=3, field_width=3, padding=1, stride=1):
|
||||||
|
""" An implementation of col2im based on fancy indexing and np.add.at """
|
||||||
|
N, C, H, W = x_shape
|
||||||
|
H_padded, W_padded = H + 2 * padding, W + 2 * padding
|
||||||
|
x_padded = np.zeros((N, C, H_padded, W_padded), dtype=cols.dtype)
|
||||||
|
k, i, j = get_im2col_indices(x_shape, field_height, field_width, padding, stride)
|
||||||
|
cols_reshaped = cols.reshape(C * field_height * field_width, -1, N)
|
||||||
|
cols_reshaped = cols_reshaped.transpose(2, 0, 1)
|
||||||
|
np.add.at(x_padded, (slice(None), k, i, j), cols_reshaped)
|
||||||
|
if padding == 0:
|
||||||
|
return x_padded
|
||||||
|
return x_padded[:, :, padding:-padding, padding:-padding]
|
||||||
|
|
||||||
|
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
@ -0,0 +1,121 @@
|
|||||||
|
import numpy as np
|
||||||
|
cimport numpy as np
|
||||||
|
cimport cython
|
||||||
|
|
||||||
|
# DTYPE = np.float64
|
||||||
|
# ctypedef np.float64_t DTYPE_t
|
||||||
|
|
||||||
|
ctypedef fused DTYPE_t:
|
||||||
|
np.float32_t
|
||||||
|
np.float64_t
|
||||||
|
|
||||||
|
def im2col_cython(np.ndarray[DTYPE_t, ndim=4] x, int field_height,
|
||||||
|
int field_width, int padding, int stride):
|
||||||
|
cdef int N = x.shape[0]
|
||||||
|
cdef int C = x.shape[1]
|
||||||
|
cdef int H = x.shape[2]
|
||||||
|
cdef int W = x.shape[3]
|
||||||
|
|
||||||
|
cdef int HH = (H + 2 * padding - field_height) / stride + 1
|
||||||
|
cdef int WW = (W + 2 * padding - field_width) / stride + 1
|
||||||
|
|
||||||
|
cdef int p = padding
|
||||||
|
cdef np.ndarray[DTYPE_t, ndim=4] x_padded = np.pad(x,
|
||||||
|
((0, 0), (0, 0), (p, p), (p, p)), mode='constant')
|
||||||
|
|
||||||
|
cdef np.ndarray[DTYPE_t, ndim=2] cols = np.zeros(
|
||||||
|
(C * field_height * field_width, N * HH * WW),
|
||||||
|
dtype=x.dtype)
|
||||||
|
|
||||||
|
# Moving the inner loop to a C function with no bounds checking works, but does
|
||||||
|
# not seem to help performance in any measurable way.
|
||||||
|
|
||||||
|
im2col_cython_inner(cols, x_padded, N, C, H, W, HH, WW,
|
||||||
|
field_height, field_width, padding, stride)
|
||||||
|
return cols
|
||||||
|
|
||||||
|
|
||||||
|
@cython.boundscheck(False)
|
||||||
|
cdef int im2col_cython_inner(np.ndarray[DTYPE_t, ndim=2] cols,
|
||||||
|
np.ndarray[DTYPE_t, ndim=4] x_padded,
|
||||||
|
int N, int C, int H, int W, int HH, int WW,
|
||||||
|
int field_height, int field_width, int padding, int stride) except? -1:
|
||||||
|
cdef int c, ii, jj, row, yy, xx, i, col
|
||||||
|
|
||||||
|
for c in range(C):
|
||||||
|
for yy in range(HH):
|
||||||
|
for xx in range(WW):
|
||||||
|
for ii in range(field_height):
|
||||||
|
for jj in range(field_width):
|
||||||
|
row = c * field_width * field_height + ii * field_height + jj
|
||||||
|
for i in range(N):
|
||||||
|
col = yy * WW * N + xx * N + i
|
||||||
|
cols[row, col] = x_padded[i, c, stride * yy + ii, stride * xx + jj]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def col2im_cython(np.ndarray[DTYPE_t, ndim=2] cols, int N, int C, int H, int W,
|
||||||
|
int field_height, int field_width, int padding, int stride):
|
||||||
|
cdef np.ndarray x = np.empty((N, C, H, W), dtype=cols.dtype)
|
||||||
|
cdef int HH = (H + 2 * padding - field_height) / stride + 1
|
||||||
|
cdef int WW = (W + 2 * padding - field_width) / stride + 1
|
||||||
|
cdef np.ndarray[DTYPE_t, ndim=4] x_padded = np.zeros((N, C, H + 2 * padding, W + 2 * padding),
|
||||||
|
dtype=cols.dtype)
|
||||||
|
|
||||||
|
# Moving the inner loop to a C-function with no bounds checking improves
|
||||||
|
# performance quite a bit for col2im.
|
||||||
|
col2im_cython_inner(cols, x_padded, N, C, H, W, HH, WW,
|
||||||
|
field_height, field_width, padding, stride)
|
||||||
|
if padding > 0:
|
||||||
|
return x_padded[:, :, padding:-padding, padding:-padding]
|
||||||
|
return x_padded
|
||||||
|
|
||||||
|
|
||||||
|
@cython.boundscheck(False)
|
||||||
|
cdef int col2im_cython_inner(np.ndarray[DTYPE_t, ndim=2] cols,
|
||||||
|
np.ndarray[DTYPE_t, ndim=4] x_padded,
|
||||||
|
int N, int C, int H, int W, int HH, int WW,
|
||||||
|
int field_height, int field_width, int padding, int stride) except? -1:
|
||||||
|
cdef int c, ii, jj, row, yy, xx, i, col
|
||||||
|
|
||||||
|
for c in range(C):
|
||||||
|
for ii in range(field_height):
|
||||||
|
for jj in range(field_width):
|
||||||
|
row = c * field_width * field_height + ii * field_height + jj
|
||||||
|
for yy in range(HH):
|
||||||
|
for xx in range(WW):
|
||||||
|
for i in range(N):
|
||||||
|
col = yy * WW * N + xx * N + i
|
||||||
|
x_padded[i, c, stride * yy + ii, stride * xx + jj] += cols[row, col]
|
||||||
|
|
||||||
|
|
||||||
|
@cython.boundscheck(False)
|
||||||
|
@cython.wraparound(False)
|
||||||
|
cdef col2im_6d_cython_inner(np.ndarray[DTYPE_t, ndim=6] cols,
|
||||||
|
np.ndarray[DTYPE_t, ndim=4] x_padded,
|
||||||
|
int N, int C, int H, int W, int HH, int WW,
|
||||||
|
int out_h, int out_w, int pad, int stride):
|
||||||
|
|
||||||
|
cdef int c, hh, ww, n, h, w
|
||||||
|
for n in range(N):
|
||||||
|
for c in range(C):
|
||||||
|
for hh in range(HH):
|
||||||
|
for ww in range(WW):
|
||||||
|
for h in range(out_h):
|
||||||
|
for w in range(out_w):
|
||||||
|
x_padded[n, c, stride * h + hh, stride * w + ww] += cols[c, hh, ww, n, h, w]
|
||||||
|
|
||||||
|
|
||||||
|
def col2im_6d_cython(np.ndarray[DTYPE_t, ndim=6] cols, int N, int C, int H, int W,
|
||||||
|
int HH, int WW, int pad, int stride):
|
||||||
|
cdef np.ndarray x = np.empty((N, C, H, W), dtype=cols.dtype)
|
||||||
|
cdef int out_h = (H + 2 * pad - HH) / stride + 1
|
||||||
|
cdef int out_w = (W + 2 * pad - WW) / stride + 1
|
||||||
|
cdef np.ndarray[DTYPE_t, ndim=4] x_padded = np.zeros((N, C, H + 2 * pad, W + 2 * pad),
|
||||||
|
dtype=cols.dtype)
|
||||||
|
|
||||||
|
col2im_6d_cython_inner(cols, x_padded, N, C, H, W, HH, WW, out_h, out_w, pad, stride)
|
||||||
|
|
||||||
|
if pad > 0:
|
||||||
|
return x_padded[:, :, pad:-pad, pad:-pad]
|
||||||
|
return x_padded
|
@ -0,0 +1,110 @@
|
|||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
from .layers import *
|
||||||
|
from .fast_layers import *
|
||||||
|
|
||||||
|
|
||||||
|
def affine_relu_forward(x, w, b):
|
||||||
|
"""
|
||||||
|
Convenience layer that perorms an affine transform followed by a ReLU
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- x: Input to the affine layer
|
||||||
|
- w, b: Weights for the affine layer
|
||||||
|
|
||||||
|
Returns a tuple of:
|
||||||
|
- out: Output from the ReLU
|
||||||
|
- cache: Object to give to the backward pass
|
||||||
|
"""
|
||||||
|
a, fc_cache = affine_forward(x, w, b)
|
||||||
|
out, relu_cache = relu_forward(a)
|
||||||
|
cache = (fc_cache, relu_cache)
|
||||||
|
return out, cache
|
||||||
|
|
||||||
|
|
||||||
|
def affine_relu_backward(dout, cache):
|
||||||
|
"""
|
||||||
|
Backward pass for the affine-relu convenience layer
|
||||||
|
"""
|
||||||
|
fc_cache, relu_cache = cache
|
||||||
|
da = relu_backward(dout, relu_cache)
|
||||||
|
dx, dw, db = affine_backward(da, fc_cache)
|
||||||
|
return dx, dw, db
|
||||||
|
|
||||||
|
|
||||||
|
def conv_relu_forward(x, w, b, conv_param):
|
||||||
|
"""
|
||||||
|
A convenience layer that performs a convolution followed by a ReLU.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- x: Input to the convolutional layer
|
||||||
|
- w, b, conv_param: Weights and parameters for the convolutional layer
|
||||||
|
|
||||||
|
Returns a tuple of:
|
||||||
|
- out: Output from the ReLU
|
||||||
|
- cache: Object to give to the backward pass
|
||||||
|
"""
|
||||||
|
a, conv_cache = conv_forward_fast(x, w, b, conv_param)
|
||||||
|
out, relu_cache = relu_forward(a)
|
||||||
|
cache = (conv_cache, relu_cache)
|
||||||
|
return out, cache
|
||||||
|
|
||||||
|
|
||||||
|
def conv_relu_backward(dout, cache):
|
||||||
|
"""
|
||||||
|
Backward pass for the conv-relu convenience layer.
|
||||||
|
"""
|
||||||
|
conv_cache, relu_cache = cache
|
||||||
|
da = relu_backward(dout, relu_cache)
|
||||||
|
dx, dw, db = conv_backward_fast(da, conv_cache)
|
||||||
|
return dx, dw, db
|
||||||
|
|
||||||
|
|
||||||
|
def conv_bn_relu_forward(x, w, b, gamma, beta, conv_param, bn_param):
|
||||||
|
a, conv_cache = conv_forward_fast(x, w, b, conv_param)
|
||||||
|
an, bn_cache = spatial_batchnorm_forward(a, gamma, beta, bn_param)
|
||||||
|
out, relu_cache = relu_forward(an)
|
||||||
|
cache = (conv_cache, bn_cache, relu_cache)
|
||||||
|
return out, cache
|
||||||
|
|
||||||
|
|
||||||
|
def conv_bn_relu_backward(dout, cache):
|
||||||
|
conv_cache, bn_cache, relu_cache = cache
|
||||||
|
dan = relu_backward(dout, relu_cache)
|
||||||
|
da, dgamma, dbeta = spatial_batchnorm_backward(dan, bn_cache)
|
||||||
|
dx, dw, db = conv_backward_fast(da, conv_cache)
|
||||||
|
return dx, dw, db, dgamma, dbeta
|
||||||
|
|
||||||
|
|
||||||
|
def conv_relu_pool_forward(x, w, b, conv_param, pool_param):
|
||||||
|
"""
|
||||||
|
Convenience layer that performs a convolution, a ReLU, and a pool.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- x: Input to the convolutional layer
|
||||||
|
- w, b, conv_param: Weights and parameters for the convolutional layer
|
||||||
|
- pool_param: Parameters for the pooling layer
|
||||||
|
|
||||||
|
Returns a tuple of:
|
||||||
|
- out: Output from the pooling layer
|
||||||
|
- cache: Object to give to the backward pass
|
||||||
|
"""
|
||||||
|
a, conv_cache = conv_forward_fast(x, w, b, conv_param)
|
||||||
|
s, relu_cache = relu_forward(a)
|
||||||
|
out, pool_cache = max_pool_forward_fast(s, pool_param)
|
||||||
|
cache = (conv_cache, relu_cache, pool_cache)
|
||||||
|
return out, cache
|
||||||
|
|
||||||
|
|
||||||
|
def conv_relu_pool_backward(dout, cache):
|
||||||
|
"""
|
||||||
|
Backward pass for the conv-relu-pool convenience layer
|
||||||
|
"""
|
||||||
|
conv_cache, relu_cache, pool_cache = cache
|
||||||
|
ds = max_pool_backward_fast(dout, pool_cache)
|
||||||
|
da = relu_backward(ds, relu_cache)
|
||||||
|
dx, dw, db = conv_backward_fast(da, conv_cache)
|
||||||
|
return dx, dw, db
|
@ -0,0 +1,696 @@
|
|||||||
|
from builtins import range
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def affine_forward(x, w, b):
|
||||||
|
"""
|
||||||
|
Computes the forward pass for an affine (fully-connected) layer.
|
||||||
|
|
||||||
|
The input x has shape (N, d_1, ..., d_k) and contains a minibatch of N
|
||||||
|
examples, where each example x[i] has shape (d_1, ..., d_k). We will
|
||||||
|
reshape each input into a vector of dimension D = d_1 * ... * d_k, and
|
||||||
|
then transform it to an output vector of dimension M.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- x: A numpy array containing input data, of shape (N, d_1, ..., d_k)
|
||||||
|
- w: A numpy array of weights, of shape (D, M)
|
||||||
|
- b: A numpy array of biases, of shape (M,)
|
||||||
|
|
||||||
|
Returns a tuple of:
|
||||||
|
- out: output, of shape (N, M)
|
||||||
|
- cache: (x, w, b)
|
||||||
|
"""
|
||||||
|
out = None
|
||||||
|
###########################################################################
|
||||||
|
# TODO: Implement the affine forward pass. Store the result in out. You #
|
||||||
|
# will need to reshape the input into rows. #
|
||||||
|
###########################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
###########################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
###########################################################################
|
||||||
|
cache = (x, w, b)
|
||||||
|
return out, cache
|
||||||
|
|
||||||
|
|
||||||
|
def affine_backward(dout, cache):
|
||||||
|
"""
|
||||||
|
Computes the backward pass for an affine layer.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- dout: Upstream derivative, of shape (N, M)
|
||||||
|
- cache: Tuple of:
|
||||||
|
- x: Input data, of shape (N, d_1, ... d_k)
|
||||||
|
- w: Weights, of shape (D, M)
|
||||||
|
- b: Biases, of shape (M,)
|
||||||
|
|
||||||
|
Returns a tuple of:
|
||||||
|
- dx: Gradient with respect to x, of shape (N, d1, ..., d_k)
|
||||||
|
- dw: Gradient with respect to w, of shape (D, M)
|
||||||
|
- db: Gradient with respect to b, of shape (M,)
|
||||||
|
"""
|
||||||
|
x, w, b = cache
|
||||||
|
dx, dw, db = None, None, None
|
||||||
|
###########################################################################
|
||||||
|
# TODO: Implement the affine backward pass. #
|
||||||
|
###########################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
###########################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
###########################################################################
|
||||||
|
return dx, dw, db
|
||||||
|
|
||||||
|
|
||||||
|
def relu_forward(x):
|
||||||
|
"""
|
||||||
|
Computes the forward pass for a layer of rectified linear units (ReLUs).
|
||||||
|
|
||||||
|
Input:
|
||||||
|
- x: Inputs, of any shape
|
||||||
|
|
||||||
|
Returns a tuple of:
|
||||||
|
- out: Output, of the same shape as x
|
||||||
|
- cache: x
|
||||||
|
"""
|
||||||
|
out = None
|
||||||
|
###########################################################################
|
||||||
|
# TODO: Implement the ReLU forward pass. #
|
||||||
|
###########################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
###########################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
###########################################################################
|
||||||
|
cache = x
|
||||||
|
return out, cache
|
||||||
|
|
||||||
|
|
||||||
|
def relu_backward(dout, cache):
|
||||||
|
"""
|
||||||
|
Computes the backward pass for a layer of rectified linear units (ReLUs).
|
||||||
|
|
||||||
|
Input:
|
||||||
|
- dout: Upstream derivatives, of any shape
|
||||||
|
- cache: Input x, of same shape as dout
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- dx: Gradient with respect to x
|
||||||
|
"""
|
||||||
|
dx, x = None, cache
|
||||||
|
###########################################################################
|
||||||
|
# TODO: Implement the ReLU backward pass. #
|
||||||
|
###########################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
###########################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
###########################################################################
|
||||||
|
return dx
|
||||||
|
|
||||||
|
|
||||||
|
def batchnorm_forward(x, gamma, beta, bn_param):
|
||||||
|
"""
|
||||||
|
Forward pass for batch normalization.
|
||||||
|
|
||||||
|
During training the sample mean and (uncorrected) sample variance are
|
||||||
|
computed from minibatch statistics and used to normalize the incoming data.
|
||||||
|
During training we also keep an exponentially decaying running mean of the
|
||||||
|
mean and variance of each feature, and these averages are used to normalize
|
||||||
|
data at test-time.
|
||||||
|
|
||||||
|
At each timestep we update the running averages for mean and variance using
|
||||||
|
an exponential decay based on the momentum parameter:
|
||||||
|
|
||||||
|
running_mean = momentum * running_mean + (1 - momentum) * sample_mean
|
||||||
|
running_var = momentum * running_var + (1 - momentum) * sample_var
|
||||||
|
|
||||||
|
Note that the batch normalization paper suggests a different test-time
|
||||||
|
behavior: they compute sample mean and variance for each feature using a
|
||||||
|
large number of training images rather than using a running average. For
|
||||||
|
this implementation we have chosen to use running averages instead since
|
||||||
|
they do not require an additional estimation step; the torch7
|
||||||
|
implementation of batch normalization also uses running averages.
|
||||||
|
|
||||||
|
Input:
|
||||||
|
- x: Data of shape (N, D)
|
||||||
|
- gamma: Scale parameter of shape (D,)
|
||||||
|
- beta: Shift paremeter of shape (D,)
|
||||||
|
- bn_param: Dictionary with the following keys:
|
||||||
|
- mode: 'train' or 'test'; required
|
||||||
|
- eps: Constant for numeric stability
|
||||||
|
- momentum: Constant for running mean / variance.
|
||||||
|
- running_mean: Array of shape (D,) giving running mean of features
|
||||||
|
- running_var Array of shape (D,) giving running variance of features
|
||||||
|
|
||||||
|
Returns a tuple of:
|
||||||
|
- out: of shape (N, D)
|
||||||
|
- cache: A tuple of values needed in the backward pass
|
||||||
|
"""
|
||||||
|
mode = bn_param["mode"]
|
||||||
|
eps = bn_param.get("eps", 1e-5)
|
||||||
|
momentum = bn_param.get("momentum", 0.9)
|
||||||
|
|
||||||
|
N, D = x.shape
|
||||||
|
running_mean = bn_param.get("running_mean", np.zeros(D, dtype=x.dtype))
|
||||||
|
running_var = bn_param.get("running_var", np.zeros(D, dtype=x.dtype))
|
||||||
|
|
||||||
|
out, cache = None, None
|
||||||
|
if mode == "train":
|
||||||
|
#######################################################################
|
||||||
|
# TODO: Implement the training-time forward pass for batch norm. #
|
||||||
|
# Use minibatch statistics to compute the mean and variance, use #
|
||||||
|
# these statistics to normalize the incoming data, and scale and #
|
||||||
|
# shift the normalized data using gamma and beta. #
|
||||||
|
# #
|
||||||
|
# You should store the output in the variable out. Any intermediates #
|
||||||
|
# that you need for the backward pass should be stored in the cache #
|
||||||
|
# variable. #
|
||||||
|
# #
|
||||||
|
# You should also use your computed sample mean and variance together #
|
||||||
|
# with the momentum variable to update the running mean and running #
|
||||||
|
# variance, storing your result in the running_mean and running_var #
|
||||||
|
# variables. #
|
||||||
|
# #
|
||||||
|
# Note that though you should be keeping track of the running #
|
||||||
|
# variance, you should normalize the data based on the standard #
|
||||||
|
# deviation (square root of variance) instead! #
|
||||||
|
# Referencing the original paper (https://arxiv.org/abs/1502.03167) #
|
||||||
|
# might prove to be helpful. #
|
||||||
|
#######################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
#######################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
#######################################################################
|
||||||
|
elif mode == "test":
|
||||||
|
#######################################################################
|
||||||
|
# TODO: Implement the test-time forward pass for batch normalization. #
|
||||||
|
# Use the running mean and variance to normalize the incoming data, #
|
||||||
|
# then scale and shift the normalized data using gamma and beta. #
|
||||||
|
# Store the result in the out variable. #
|
||||||
|
#######################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
#######################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
#######################################################################
|
||||||
|
else:
|
||||||
|
raise ValueError('Invalid forward batchnorm mode "%s"' % mode)
|
||||||
|
|
||||||
|
# Store the updated running means back into bn_param
|
||||||
|
bn_param["running_mean"] = running_mean
|
||||||
|
bn_param["running_var"] = running_var
|
||||||
|
|
||||||
|
return out, cache
|
||||||
|
|
||||||
|
|
||||||
|
def batchnorm_backward(dout, cache):
|
||||||
|
"""
|
||||||
|
Backward pass for batch normalization.
|
||||||
|
|
||||||
|
For this implementation, you should write out a computation graph for
|
||||||
|
batch normalization on paper and propagate gradients backward through
|
||||||
|
intermediate nodes.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- dout: Upstream derivatives, of shape (N, D)
|
||||||
|
- cache: Variable of intermediates from batchnorm_forward.
|
||||||
|
|
||||||
|
Returns a tuple of:
|
||||||
|
- dx: Gradient with respect to inputs x, of shape (N, D)
|
||||||
|
- dgamma: Gradient with respect to scale parameter gamma, of shape (D,)
|
||||||
|
- dbeta: Gradient with respect to shift parameter beta, of shape (D,)
|
||||||
|
"""
|
||||||
|
dx, dgamma, dbeta = None, None, None
|
||||||
|
###########################################################################
|
||||||
|
# TODO: Implement the backward pass for batch normalization. Store the #
|
||||||
|
# results in the dx, dgamma, and dbeta variables. #
|
||||||
|
# Referencing the original paper (https://arxiv.org/abs/1502.03167) #
|
||||||
|
# might prove to be helpful. #
|
||||||
|
###########################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
###########################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
return dx, dgamma, dbeta
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def dropout_forward(x, dropout_param):
|
||||||
|
"""
|
||||||
|
Performs the forward pass for (inverted) dropout.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- x: Input data, of any shape
|
||||||
|
- dropout_param: A dictionary with the following keys:
|
||||||
|
- p: Dropout parameter. We keep each neuron output with probability p.
|
||||||
|
- mode: 'test' or 'train'. If the mode is train, then perform dropout;
|
||||||
|
if the mode is test, then just return the input.
|
||||||
|
- seed: Seed for the random number generator. Passing seed makes this
|
||||||
|
function deterministic, which is needed for gradient checking but not
|
||||||
|
in real networks.
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- out: Array of the same shape as x.
|
||||||
|
- cache: tuple (dropout_param, mask). In training mode, mask is the dropout
|
||||||
|
mask that was used to multiply the input; in test mode, mask is None.
|
||||||
|
|
||||||
|
NOTE: Please implement **inverted** dropout, not the vanilla version of dropout.
|
||||||
|
See http://cs231n.github.io/neural-networks-2/#reg for more details.
|
||||||
|
|
||||||
|
NOTE 2: Keep in mind that p is the probability of **keep** a neuron
|
||||||
|
output; this might be contrary to some sources, where it is referred to
|
||||||
|
as the probability of dropping a neuron output.
|
||||||
|
"""
|
||||||
|
p, mode = dropout_param["p"], dropout_param["mode"]
|
||||||
|
if "seed" in dropout_param:
|
||||||
|
np.random.seed(dropout_param["seed"])
|
||||||
|
|
||||||
|
mask = None
|
||||||
|
out = None
|
||||||
|
|
||||||
|
if mode == "train":
|
||||||
|
#######################################################################
|
||||||
|
# TODO: Implement training phase forward pass for inverted dropout. #
|
||||||
|
# Store the dropout mask in the mask variable. #
|
||||||
|
#######################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
#######################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
#######################################################################
|
||||||
|
elif mode == "test":
|
||||||
|
#######################################################################
|
||||||
|
# TODO: Implement the test phase forward pass for inverted dropout. #
|
||||||
|
#######################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
#######################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
#######################################################################
|
||||||
|
|
||||||
|
cache = (dropout_param, mask)
|
||||||
|
out = out.astype(x.dtype, copy=False)
|
||||||
|
|
||||||
|
return out, cache
|
||||||
|
|
||||||
|
|
||||||
|
def dropout_backward(dout, cache):
|
||||||
|
"""
|
||||||
|
Perform the backward pass for (inverted) dropout.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- dout: Upstream derivatives, of any shape
|
||||||
|
- cache: (dropout_param, mask) from dropout_forward.
|
||||||
|
"""
|
||||||
|
dropout_param, mask = cache
|
||||||
|
mode = dropout_param["mode"]
|
||||||
|
|
||||||
|
dx = None
|
||||||
|
if mode == "train":
|
||||||
|
#######################################################################
|
||||||
|
# TODO: Implement training phase backward pass for inverted dropout #
|
||||||
|
#######################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
#######################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
#######################################################################
|
||||||
|
elif mode == "test":
|
||||||
|
dx = dout
|
||||||
|
return dx
|
||||||
|
|
||||||
|
|
||||||
|
def conv_forward_naive(x, w, b, conv_param):
|
||||||
|
"""
|
||||||
|
A naive implementation of the forward pass for a convolutional layer.
|
||||||
|
|
||||||
|
The input consists of N data points, each with C channels, height H and
|
||||||
|
width W. We convolve each input with F different filters, where each filter
|
||||||
|
spans all C channels and has height HH and width WW.
|
||||||
|
|
||||||
|
Input:
|
||||||
|
- x: Input data of shape (N, C, H, W)
|
||||||
|
- w: Filter weights of shape (F, C, HH, WW)
|
||||||
|
- b: Biases, of shape (F,)
|
||||||
|
- conv_param: A dictionary with the following keys:
|
||||||
|
- 'stride': The number of pixels between adjacent receptive fields in the
|
||||||
|
horizontal and vertical directions.
|
||||||
|
- 'pad': The number of pixels that will be used to zero-pad the input.
|
||||||
|
|
||||||
|
|
||||||
|
During padding, 'pad' zeros should be placed symmetrically (i.e equally on both sides)
|
||||||
|
along the height and width axes of the input. Be careful not to modfiy the original
|
||||||
|
input x directly.
|
||||||
|
|
||||||
|
Returns a tuple of:
|
||||||
|
- out: Output data, of shape (N, F, H', W') where H' and W' are given by
|
||||||
|
H' = 1 + (H + 2 * pad - HH) / stride
|
||||||
|
W' = 1 + (W + 2 * pad - WW) / stride
|
||||||
|
- cache: (x, w, b, conv_param)
|
||||||
|
"""
|
||||||
|
out = None
|
||||||
|
###########################################################################
|
||||||
|
# TODO: Implement the convolutional forward pass. #
|
||||||
|
# Hint: you can use the function np.pad for padding. #
|
||||||
|
###########################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
###########################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
###########################################################################
|
||||||
|
cache = (x, w, b, conv_param)
|
||||||
|
return out, cache
|
||||||
|
|
||||||
|
|
||||||
|
def conv_backward_naive(dout, cache):
|
||||||
|
"""
|
||||||
|
A naive implementation of the backward pass for a convolutional layer.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- dout: Upstream derivatives.
|
||||||
|
- cache: A tuple of (x, w, b, conv_param) as in conv_forward_naive
|
||||||
|
|
||||||
|
Returns a tuple of:
|
||||||
|
- dx: Gradient with respect to x
|
||||||
|
- dw: Gradient with respect to w
|
||||||
|
- db: Gradient with respect to b
|
||||||
|
"""
|
||||||
|
dx, dw, db = None, None, None
|
||||||
|
###########################################################################
|
||||||
|
# TODO: Implement the convolutional backward pass. #
|
||||||
|
###########################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
###########################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
###########################################################################
|
||||||
|
return dx, dw, db
|
||||||
|
|
||||||
|
|
||||||
|
def max_pool_forward_naive(x, pool_param):
|
||||||
|
"""
|
||||||
|
A naive implementation of the forward pass for a max-pooling layer.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- x: Input data, of shape (N, C, H, W)
|
||||||
|
- pool_param: dictionary with the following keys:
|
||||||
|
- 'pool_height': The height of each pooling region
|
||||||
|
- 'pool_width': The width of each pooling region
|
||||||
|
- 'stride': The distance between adjacent pooling regions
|
||||||
|
|
||||||
|
No padding is necessary here. Output size is given by
|
||||||
|
|
||||||
|
Returns a tuple of:
|
||||||
|
- out: Output data, of shape (N, C, H', W') where H' and W' are given by
|
||||||
|
H' = 1 + (H - pool_height) / stride
|
||||||
|
W' = 1 + (W - pool_width) / stride
|
||||||
|
- cache: (x, pool_param)
|
||||||
|
"""
|
||||||
|
out = None
|
||||||
|
###########################################################################
|
||||||
|
# TODO: Implement the max-pooling forward pass #
|
||||||
|
###########################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
###########################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
###########################################################################
|
||||||
|
cache = (x, pool_param)
|
||||||
|
return out, cache
|
||||||
|
|
||||||
|
|
||||||
|
def max_pool_backward_naive(dout, cache):
|
||||||
|
"""
|
||||||
|
A naive implementation of the backward pass for a max-pooling layer.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- dout: Upstream derivatives
|
||||||
|
- cache: A tuple of (x, pool_param) as in the forward pass.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- dx: Gradient with respect to x
|
||||||
|
"""
|
||||||
|
dx = None
|
||||||
|
###########################################################################
|
||||||
|
# TODO: Implement the max-pooling backward pass #
|
||||||
|
###########################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
###########################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
###########################################################################
|
||||||
|
return dx
|
||||||
|
|
||||||
|
|
||||||
|
def spatial_batchnorm_forward(x, gamma, beta, bn_param):
|
||||||
|
"""
|
||||||
|
Computes the forward pass for spatial batch normalization.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- x: Input data of shape (N, C, H, W)
|
||||||
|
- gamma: Scale parameter, of shape (C,)
|
||||||
|
- beta: Shift parameter, of shape (C,)
|
||||||
|
- bn_param: Dictionary with the following keys:
|
||||||
|
- mode: 'train' or 'test'; required
|
||||||
|
- eps: Constant for numeric stability
|
||||||
|
- momentum: Constant for running mean / variance. momentum=0 means that
|
||||||
|
old information is discarded completely at every time step, while
|
||||||
|
momentum=1 means that new information is never incorporated. The
|
||||||
|
default of momentum=0.9 should work well in most situations.
|
||||||
|
- running_mean: Array of shape (D,) giving running mean of features
|
||||||
|
- running_var Array of shape (D,) giving running variance of features
|
||||||
|
|
||||||
|
Returns a tuple of:
|
||||||
|
- out: Output data, of shape (N, C, H, W)
|
||||||
|
- cache: Values needed for the backward pass
|
||||||
|
"""
|
||||||
|
out, cache = None, None
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
# TODO: Implement the forward pass for spatial batch normalization. #
|
||||||
|
# #
|
||||||
|
# HINT: You can implement spatial batch normalization by calling the #
|
||||||
|
# vanilla version of batch normalization you implemented above. #
|
||||||
|
# Your implementation should be very short; ours is less than five lines. #
|
||||||
|
###########################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
###########################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
return out, cache
|
||||||
|
|
||||||
|
|
||||||
|
def spatial_batchnorm_backward(dout, cache):
|
||||||
|
"""
|
||||||
|
Computes the backward pass for spatial batch normalization.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- dout: Upstream derivatives, of shape (N, C, H, W)
|
||||||
|
- cache: Values from the forward pass
|
||||||
|
|
||||||
|
Returns a tuple of:
|
||||||
|
- dx: Gradient with respect to inputs, of shape (N, C, H, W)
|
||||||
|
- dgamma: Gradient with respect to scale parameter, of shape (C,)
|
||||||
|
- dbeta: Gradient with respect to shift parameter, of shape (C,)
|
||||||
|
"""
|
||||||
|
dx, dgamma, dbeta = None, None, None
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
# TODO: Implement the backward pass for spatial batch normalization. #
|
||||||
|
# #
|
||||||
|
# HINT: You can implement spatial batch normalization by calling the #
|
||||||
|
# vanilla version of batch normalization you implemented above. #
|
||||||
|
# Your implementation should be very short; ours is less than five lines. #
|
||||||
|
###########################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
###########################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
return dx, dgamma, dbeta
|
||||||
|
|
||||||
|
|
||||||
|
def spatial_groupnorm_forward(x, gamma, beta, G, gn_param):
|
||||||
|
"""
|
||||||
|
Computes the forward pass for spatial group normalization.
|
||||||
|
In contrast to layer normalization, group normalization splits each entry
|
||||||
|
in the data into G contiguous pieces, which it then normalizes independently.
|
||||||
|
Per feature shifting and scaling are then applied to the data, in a manner identical to that of batch normalization and layer normalization.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- x: Input data of shape (N, C, H, W)
|
||||||
|
- gamma: Scale parameter, of shape (C,)
|
||||||
|
- beta: Shift parameter, of shape (C,)
|
||||||
|
- G: Integer mumber of groups to split into, should be a divisor of C
|
||||||
|
- gn_param: Dictionary with the following keys:
|
||||||
|
- eps: Constant for numeric stability
|
||||||
|
|
||||||
|
Returns a tuple of:
|
||||||
|
- out: Output data, of shape (N, C, H, W)
|
||||||
|
- cache: Values needed for the backward pass
|
||||||
|
"""
|
||||||
|
out, cache = None, None
|
||||||
|
eps = gn_param.get("eps", 1e-5)
|
||||||
|
###########################################################################
|
||||||
|
# TODO: Implement the forward pass for spatial group normalization. #
|
||||||
|
# This will be extremely similar to the layer norm implementation. #
|
||||||
|
# In particular, think about how you could transform the matrix so that #
|
||||||
|
# the bulk of the code is similar to both train-time batch normalization #
|
||||||
|
# and layer normalization! #
|
||||||
|
###########################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
###########################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
###########################################################################
|
||||||
|
return out, cache
|
||||||
|
|
||||||
|
|
||||||
|
def spatial_groupnorm_backward(dout, cache):
|
||||||
|
"""
|
||||||
|
Computes the backward pass for spatial group normalization.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- dout: Upstream derivatives, of shape (N, C, H, W)
|
||||||
|
- cache: Values from the forward pass
|
||||||
|
|
||||||
|
Returns a tuple of:
|
||||||
|
- dx: Gradient with respect to inputs, of shape (N, C, H, W)
|
||||||
|
- dgamma: Gradient with respect to scale parameter, of shape (C,)
|
||||||
|
- dbeta: Gradient with respect to shift parameter, of shape (C,)
|
||||||
|
"""
|
||||||
|
dx, dgamma, dbeta = None, None, None
|
||||||
|
|
||||||
|
###########################################################################
|
||||||
|
# TODO: Implement the backward pass for spatial group normalization. #
|
||||||
|
# This will be extremely similar to the layer norm implementation. #
|
||||||
|
###########################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
###########################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
###########################################################################
|
||||||
|
return dx, dgamma, dbeta
|
||||||
|
|
||||||
|
|
||||||
|
def svm_loss(x, y):
|
||||||
|
"""
|
||||||
|
Computes the loss and gradient using for multiclass SVM classification.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- x: Input data, of shape (N, C) where x[i, j] is the score for the jth
|
||||||
|
class for the ith input.
|
||||||
|
- y: Vector of labels, of shape (N,) where y[i] is the label for x[i] and
|
||||||
|
0 <= y[i] < C
|
||||||
|
|
||||||
|
Returns a tuple of:
|
||||||
|
- loss: Scalar giving the loss
|
||||||
|
- dx: Gradient of the loss with respect to x
|
||||||
|
"""
|
||||||
|
N = x.shape[0]
|
||||||
|
correct_class_scores = x[np.arange(N), y]
|
||||||
|
margins = np.maximum(0, x - correct_class_scores[:, np.newaxis] + 1.0)
|
||||||
|
margins[np.arange(N), y] = 0
|
||||||
|
loss = np.sum(margins) / N
|
||||||
|
num_pos = np.sum(margins > 0, axis=1)
|
||||||
|
dx = np.zeros_like(x)
|
||||||
|
dx[margins > 0] = 1
|
||||||
|
dx[np.arange(N), y] -= num_pos
|
||||||
|
dx /= N
|
||||||
|
return loss, dx
|
||||||
|
|
||||||
|
|
||||||
|
def softmax_loss(x, y):
|
||||||
|
"""
|
||||||
|
Computes the loss and gradient for softmax classification.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- x: Input data, of shape (N, C) where x[i, j] is the score for the jth
|
||||||
|
class for the ith input.
|
||||||
|
- y: Vector of labels, of shape (N,) where y[i] is the label for x[i] and
|
||||||
|
0 <= y[i] < C
|
||||||
|
|
||||||
|
Returns a tuple of:
|
||||||
|
- loss: Scalar giving the loss
|
||||||
|
- dx: Gradient of the loss with respect to x
|
||||||
|
"""
|
||||||
|
shifted_logits = x - np.max(x, axis=1, keepdims=True)
|
||||||
|
Z = np.sum(np.exp(shifted_logits), axis=1, keepdims=True)
|
||||||
|
log_probs = shifted_logits - np.log(Z)
|
||||||
|
probs = np.exp(log_probs)
|
||||||
|
N = x.shape[0]
|
||||||
|
loss = -np.sum(log_probs[np.arange(N), y]) / N
|
||||||
|
dx = probs.copy()
|
||||||
|
dx[np.arange(N), y] -= 1
|
||||||
|
dx /= N
|
||||||
|
return loss, dx
|
@ -0,0 +1,162 @@
|
|||||||
|
import numpy as np
|
||||||
|
|
||||||
|
"""
|
||||||
|
This file implements various first-order update rules that are commonly used
|
||||||
|
for training neural networks. Each update rule accepts current weights and the
|
||||||
|
gradient of the loss with respect to those weights and produces the next set of
|
||||||
|
weights. Each update rule has the same interface:
|
||||||
|
|
||||||
|
def update(w, dw, config=None):
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- w: A numpy array giving the current weights.
|
||||||
|
- dw: A numpy array of the same shape as w giving the gradient of the
|
||||||
|
loss with respect to w.
|
||||||
|
- config: A dictionary containing hyperparameter values such as learning
|
||||||
|
rate, momentum, etc. If the update rule requires caching values over many
|
||||||
|
iterations, then config will also hold these cached values.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- next_w: The next point after the update.
|
||||||
|
- config: The config dictionary to be passed to the next iteration of the
|
||||||
|
update rule.
|
||||||
|
|
||||||
|
NOTE: For most update rules, the default learning rate will probably not
|
||||||
|
perform well; however the default values of the other hyperparameters should
|
||||||
|
work well for a variety of different problems.
|
||||||
|
|
||||||
|
For efficiency, update rules may perform in-place updates, mutating w and
|
||||||
|
setting next_w equal to w.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def sgd(w, dw, config=None):
|
||||||
|
"""
|
||||||
|
Performs vanilla stochastic gradient descent.
|
||||||
|
|
||||||
|
config format:
|
||||||
|
- learning_rate: Scalar learning rate.
|
||||||
|
"""
|
||||||
|
if config is None:
|
||||||
|
config = {}
|
||||||
|
config.setdefault("learning_rate", 1e-2)
|
||||||
|
|
||||||
|
w -= config["learning_rate"] * dw
|
||||||
|
return w, config
|
||||||
|
|
||||||
|
|
||||||
|
def sgd_momentum(w, dw, config=None):
|
||||||
|
"""
|
||||||
|
Performs stochastic gradient descent with momentum.
|
||||||
|
|
||||||
|
config format:
|
||||||
|
- learning_rate: Scalar learning rate.
|
||||||
|
- momentum: Scalar between 0 and 1 giving the momentum value.
|
||||||
|
Setting momentum = 0 reduces to sgd.
|
||||||
|
- velocity: A numpy array of the same shape as w and dw used to store a
|
||||||
|
moving average of the gradients.
|
||||||
|
"""
|
||||||
|
if config is None:
|
||||||
|
config = {}
|
||||||
|
config.setdefault("learning_rate", 1e-2)
|
||||||
|
config.setdefault("momentum", 0.9)
|
||||||
|
v = config.get("velocity", np.zeros_like(w))
|
||||||
|
|
||||||
|
next_w = None
|
||||||
|
###########################################################################
|
||||||
|
# TODO: Implement the momentum update formula. Store the updated value in #
|
||||||
|
# the next_w variable. You should also use and update the velocity v. #
|
||||||
|
###########################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
###########################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
###########################################################################
|
||||||
|
config["velocity"] = v
|
||||||
|
|
||||||
|
return next_w, config
|
||||||
|
|
||||||
|
|
||||||
|
def rmsprop(w, dw, config=None):
|
||||||
|
"""
|
||||||
|
Uses the RMSProp update rule, which uses a moving average of squared
|
||||||
|
gradient values to set adaptive per-parameter learning rates.
|
||||||
|
|
||||||
|
config format:
|
||||||
|
- learning_rate: Scalar learning rate.
|
||||||
|
- decay_rate: Scalar between 0 and 1 giving the decay rate for the squared
|
||||||
|
gradient cache.
|
||||||
|
- epsilon: Small scalar used for smoothing to avoid dividing by zero.
|
||||||
|
- cache: Moving average of second moments of gradients.
|
||||||
|
"""
|
||||||
|
if config is None:
|
||||||
|
config = {}
|
||||||
|
config.setdefault("learning_rate", 1e-2)
|
||||||
|
config.setdefault("decay_rate", 0.99)
|
||||||
|
config.setdefault("epsilon", 1e-8)
|
||||||
|
config.setdefault("cache", np.zeros_like(w))
|
||||||
|
|
||||||
|
next_w = None
|
||||||
|
###########################################################################
|
||||||
|
# TODO: Implement the RMSprop update formula, storing the next value of w #
|
||||||
|
# in the next_w variable. Don't forget to update cache value stored in #
|
||||||
|
# config['cache']. #
|
||||||
|
###########################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
###########################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
return next_w, config
|
||||||
|
|
||||||
|
|
||||||
|
def adam(w, dw, config=None):
|
||||||
|
"""
|
||||||
|
Uses the Adam update rule, which incorporates moving averages of both the
|
||||||
|
gradient and its square and a bias correction term.
|
||||||
|
|
||||||
|
config format:
|
||||||
|
- learning_rate: Scalar learning rate.
|
||||||
|
- beta1: Decay rate for moving average of first moment of gradient.
|
||||||
|
- beta2: Decay rate for moving average of second moment of gradient.
|
||||||
|
- epsilon: Small scalar used for smoothing to avoid dividing by zero.
|
||||||
|
- m: Moving average of gradient.
|
||||||
|
- v: Moving average of squared gradient.
|
||||||
|
- t: Iteration number.
|
||||||
|
"""
|
||||||
|
if config is None:
|
||||||
|
config = {}
|
||||||
|
config.setdefault("learning_rate", 1e-3)
|
||||||
|
config.setdefault("beta1", 0.9)
|
||||||
|
config.setdefault("beta2", 0.999)
|
||||||
|
config.setdefault("epsilon", 1e-8)
|
||||||
|
config.setdefault("m", np.zeros_like(w))
|
||||||
|
config.setdefault("v", np.zeros_like(w))
|
||||||
|
config.setdefault("t", 0)
|
||||||
|
|
||||||
|
next_w = None
|
||||||
|
###########################################################################
|
||||||
|
# TODO: Implement the Adam update formula, storing the next value of w in #
|
||||||
|
# the next_w variable. Don't forget to update the m, v, and t variables #
|
||||||
|
# stored in config. #
|
||||||
|
# #
|
||||||
|
# NOTE: In order to match the reference output, please modify t _before_ #
|
||||||
|
# using it in any calculations. #
|
||||||
|
###########################################################################
|
||||||
|
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
|
||||||
|
###########################################################################
|
||||||
|
# END OF YOUR CODE #
|
||||||
|
###########################################################################
|
||||||
|
|
||||||
|
return next_w, config
|
@ -0,0 +1,12 @@
|
|||||||
|
from distutils.core import setup
|
||||||
|
from distutils.extension import Extension
|
||||||
|
from Cython.Build import cythonize
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
extensions = [
|
||||||
|
Extension(
|
||||||
|
"im2col_cython", ["im2col_cython.pyx"], include_dirs=[numpy.get_include()]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
setup(ext_modules=cythonize(extensions),)
|
@ -0,0 +1,309 @@
|
|||||||
|
from __future__ import print_function, division
|
||||||
|
from future import standard_library
|
||||||
|
|
||||||
|
standard_library.install_aliases()
|
||||||
|
from builtins import range
|
||||||
|
from builtins import object
|
||||||
|
import os
|
||||||
|
import pickle as pickle
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from scripts import optim
|
||||||
|
|
||||||
|
|
||||||
|
class Solver(object):
|
||||||
|
"""
|
||||||
|
A Solver encapsulates all the logic necessary for training classification
|
||||||
|
models. The Solver performs stochastic gradient descent using different
|
||||||
|
update rules defined in optim.py.
|
||||||
|
|
||||||
|
The solver accepts both training and validataion data and labels so it can
|
||||||
|
periodically check classification accuracy on both training and validation
|
||||||
|
data to watch out for overfitting.
|
||||||
|
|
||||||
|
To train a model, you will first construct a Solver instance, passing the
|
||||||
|
model, dataset, and various options (learning rate, batch size, etc) to the
|
||||||
|
constructor. You will then call the train() method to run the optimization
|
||||||
|
procedure and train the model.
|
||||||
|
|
||||||
|
After the train() method returns, model.params will contain the parameters
|
||||||
|
that performed best on the validation set over the course of training.
|
||||||
|
In addition, the instance variable solver.loss_history will contain a list
|
||||||
|
of all losses encountered during training and the instance variables
|
||||||
|
solver.train_acc_history and solver.val_acc_history will be lists of the
|
||||||
|
accuracies of the model on the training and validation set at each epoch.
|
||||||
|
|
||||||
|
Example usage might look something like this:
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'X_train': # training data
|
||||||
|
'y_train': # training labels
|
||||||
|
'X_val': # validation data
|
||||||
|
'y_val': # validation labels
|
||||||
|
}
|
||||||
|
model = MyAwesomeModel(hidden_size=100, reg=10)
|
||||||
|
solver = Solver(model, data,
|
||||||
|
update_rule='sgd',
|
||||||
|
optim_config={
|
||||||
|
'learning_rate': 1e-3,
|
||||||
|
},
|
||||||
|
lr_decay=0.95,
|
||||||
|
num_epochs=10, batch_size=100,
|
||||||
|
print_every=100)
|
||||||
|
solver.train()
|
||||||
|
|
||||||
|
|
||||||
|
A Solver works on a model object that must conform to the following API:
|
||||||
|
|
||||||
|
- model.params must be a dictionary mapping string parameter names to numpy
|
||||||
|
arrays containing parameter values.
|
||||||
|
|
||||||
|
- model.loss(X, y) must be a function that computes training-time loss and
|
||||||
|
gradients, and test-time classification scores, with the following inputs
|
||||||
|
and outputs:
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- X: Array giving a minibatch of input data of shape (N, d_1, ..., d_k)
|
||||||
|
- y: Array of labels, of shape (N,) giving labels for X where y[i] is the
|
||||||
|
label for X[i].
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
If y is None, run a test-time forward pass and return:
|
||||||
|
- scores: Array of shape (N, C) giving classification scores for X where
|
||||||
|
scores[i, c] gives the score of class c for X[i].
|
||||||
|
|
||||||
|
If y is not None, run a training time forward and backward pass and
|
||||||
|
return a tuple of:
|
||||||
|
- loss: Scalar giving the loss
|
||||||
|
- grads: Dictionary with the same keys as self.params mapping parameter
|
||||||
|
names to gradients of the loss with respect to those parameters.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, model, data, **kwargs):
|
||||||
|
"""
|
||||||
|
Construct a new Solver instance.
|
||||||
|
|
||||||
|
Required arguments:
|
||||||
|
- model: A model object conforming to the API described above
|
||||||
|
- data: A dictionary of training and validation data containing:
|
||||||
|
'X_train': Array, shape (N_train, d_1, ..., d_k) of training images
|
||||||
|
'X_val': Array, shape (N_val, d_1, ..., d_k) of validation images
|
||||||
|
'y_train': Array, shape (N_train,) of labels for training images
|
||||||
|
'y_val': Array, shape (N_val,) of labels for validation images
|
||||||
|
|
||||||
|
Optional arguments:
|
||||||
|
- update_rule: A string giving the name of an update rule in optim.py.
|
||||||
|
Default is 'sgd'.
|
||||||
|
- optim_config: A dictionary containing hyperparameters that will be
|
||||||
|
passed to the chosen update rule. Each update rule requires different
|
||||||
|
hyperparameters (see optim.py) but all update rules require a
|
||||||
|
'learning_rate' parameter so that should always be present.
|
||||||
|
- lr_decay: A scalar for learning rate decay; after each epoch the
|
||||||
|
learning rate is multiplied by this value.
|
||||||
|
- batch_size: Size of minibatches used to compute loss and gradient
|
||||||
|
during training.
|
||||||
|
- num_epochs: The number of epochs to run for during training.
|
||||||
|
- print_every: Integer; training losses will be printed every
|
||||||
|
print_every iterations.
|
||||||
|
- verbose: Boolean; if set to false then no output will be printed
|
||||||
|
during training.
|
||||||
|
- num_train_samples: Number of training samples used to check training
|
||||||
|
accuracy; default is 1000; set to None to use entire training set.
|
||||||
|
- num_val_samples: Number of validation samples to use to check val
|
||||||
|
accuracy; default is None, which uses the entire validation set.
|
||||||
|
- checkpoint_name: If not None, then save model checkpoints here every
|
||||||
|
epoch.
|
||||||
|
"""
|
||||||
|
self.model = model
|
||||||
|
self.X_train = data["X_train"]
|
||||||
|
self.y_train = data["y_train"]
|
||||||
|
self.X_val = data["X_val"]
|
||||||
|
self.y_val = data["y_val"]
|
||||||
|
|
||||||
|
# Unpack keyword arguments
|
||||||
|
self.update_rule = kwargs.pop("update_rule", "sgd")
|
||||||
|
self.optim_config = kwargs.pop("optim_config", {})
|
||||||
|
self.lr_decay = kwargs.pop("lr_decay", 1.0)
|
||||||
|
self.batch_size = kwargs.pop("batch_size", 100)
|
||||||
|
self.num_epochs = kwargs.pop("num_epochs", 10)
|
||||||
|
self.num_train_samples = kwargs.pop("num_train_samples", 1000)
|
||||||
|
self.num_val_samples = kwargs.pop("num_val_samples", None)
|
||||||
|
|
||||||
|
self.checkpoint_name = kwargs.pop("checkpoint_name", None)
|
||||||
|
self.print_every = kwargs.pop("print_every", 10)
|
||||||
|
self.verbose = kwargs.pop("verbose", True)
|
||||||
|
|
||||||
|
# Throw an error if there are extra keyword arguments
|
||||||
|
if len(kwargs) > 0:
|
||||||
|
extra = ", ".join('"%s"' % k for k in list(kwargs.keys()))
|
||||||
|
raise ValueError("Unrecognized arguments %s" % extra)
|
||||||
|
|
||||||
|
# Make sure the update rule exists, then replace the string
|
||||||
|
# name with the actual function
|
||||||
|
if not hasattr(optim, self.update_rule):
|
||||||
|
raise ValueError('Invalid update_rule "%s"' % self.update_rule)
|
||||||
|
self.update_rule = getattr(optim, self.update_rule)
|
||||||
|
|
||||||
|
self._reset()
|
||||||
|
|
||||||
|
def _reset(self):
|
||||||
|
"""
|
||||||
|
Set up some book-keeping variables for optimization. Don't call this
|
||||||
|
manually.
|
||||||
|
"""
|
||||||
|
# Set up some variables for book-keeping
|
||||||
|
self.epoch = 0
|
||||||
|
self.best_val_acc = 0
|
||||||
|
self.best_params = {}
|
||||||
|
self.loss_history = []
|
||||||
|
self.train_acc_history = []
|
||||||
|
self.val_acc_history = []
|
||||||
|
|
||||||
|
# Make a deep copy of the optim_config for each parameter
|
||||||
|
self.optim_configs = {}
|
||||||
|
for p in self.model.params:
|
||||||
|
d = {k: v for k, v in self.optim_config.items()}
|
||||||
|
self.optim_configs[p] = d
|
||||||
|
|
||||||
|
def _step(self):
|
||||||
|
"""
|
||||||
|
Make a single gradient update. This is called by train() and should not
|
||||||
|
be called manually.
|
||||||
|
"""
|
||||||
|
# Make a minibatch of training data
|
||||||
|
num_train = self.X_train.shape[0]
|
||||||
|
batch_mask = np.random.choice(num_train, self.batch_size)
|
||||||
|
X_batch = self.X_train[batch_mask]
|
||||||
|
y_batch = self.y_train[batch_mask]
|
||||||
|
|
||||||
|
# Compute loss and gradient
|
||||||
|
loss, grads = self.model.loss(X_batch, y_batch)
|
||||||
|
self.loss_history.append(loss)
|
||||||
|
|
||||||
|
# Perform a parameter update
|
||||||
|
for p, w in self.model.params.items():
|
||||||
|
dw = grads[p]
|
||||||
|
config = self.optim_configs[p]
|
||||||
|
next_w, next_config = self.update_rule(w, dw, config)
|
||||||
|
self.model.params[p] = next_w
|
||||||
|
self.optim_configs[p] = next_config
|
||||||
|
|
||||||
|
def _save_checkpoint(self):
|
||||||
|
if self.checkpoint_name is None:
|
||||||
|
return
|
||||||
|
checkpoint = {
|
||||||
|
"model": self.model,
|
||||||
|
"update_rule": self.update_rule,
|
||||||
|
"lr_decay": self.lr_decay,
|
||||||
|
"optim_config": self.optim_config,
|
||||||
|
"batch_size": self.batch_size,
|
||||||
|
"num_train_samples": self.num_train_samples,
|
||||||
|
"num_val_samples": self.num_val_samples,
|
||||||
|
"epoch": self.epoch,
|
||||||
|
"loss_history": self.loss_history,
|
||||||
|
"train_acc_history": self.train_acc_history,
|
||||||
|
"val_acc_history": self.val_acc_history,
|
||||||
|
}
|
||||||
|
filename = "%s_epoch_%d.pkl" % (self.checkpoint_name, self.epoch)
|
||||||
|
if self.verbose:
|
||||||
|
print('Saving checkpoint to "%s"' % filename)
|
||||||
|
with open(filename, "wb") as f:
|
||||||
|
pickle.dump(checkpoint, f)
|
||||||
|
|
||||||
|
def check_accuracy(self, X, y, num_samples=None, batch_size=100):
|
||||||
|
"""
|
||||||
|
Check accuracy of the model on the provided data.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- X: Array of data, of shape (N, d_1, ..., d_k)
|
||||||
|
- y: Array of labels, of shape (N,)
|
||||||
|
- num_samples: If not None, subsample the data and only test the model
|
||||||
|
on num_samples datapoints.
|
||||||
|
- batch_size: Split X and y into batches of this size to avoid using
|
||||||
|
too much memory.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- acc: Scalar giving the fraction of instances that were correctly
|
||||||
|
classified by the model.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Maybe subsample the data
|
||||||
|
N = X.shape[0]
|
||||||
|
if num_samples is not None and N > num_samples:
|
||||||
|
mask = np.random.choice(N, num_samples)
|
||||||
|
N = num_samples
|
||||||
|
X = X[mask]
|
||||||
|
y = y[mask]
|
||||||
|
|
||||||
|
# Compute predictions in batches
|
||||||
|
num_batches = N // batch_size
|
||||||
|
if N % batch_size != 0:
|
||||||
|
num_batches += 1
|
||||||
|
y_pred = []
|
||||||
|
for i in range(num_batches):
|
||||||
|
start = i * batch_size
|
||||||
|
end = (i + 1) * batch_size
|
||||||
|
scores = self.model.loss(X[start:end])
|
||||||
|
y_pred.append(np.argmax(scores, axis=1))
|
||||||
|
y_pred = np.hstack(y_pred)
|
||||||
|
acc = np.mean(y_pred == y)
|
||||||
|
|
||||||
|
return acc
|
||||||
|
|
||||||
|
def train(self):
|
||||||
|
"""
|
||||||
|
Run optimization to train the model.
|
||||||
|
"""
|
||||||
|
num_train = self.X_train.shape[0]
|
||||||
|
iterations_per_epoch = max(num_train // self.batch_size, 1)
|
||||||
|
num_iterations = self.num_epochs * iterations_per_epoch
|
||||||
|
|
||||||
|
for t in range(num_iterations):
|
||||||
|
self._step()
|
||||||
|
|
||||||
|
# Maybe print training loss
|
||||||
|
if self.verbose and t % self.print_every == 0:
|
||||||
|
print(
|
||||||
|
"(Iteration %d / %d) loss: %f"
|
||||||
|
% (t + 1, num_iterations, self.loss_history[-1])
|
||||||
|
)
|
||||||
|
|
||||||
|
# At the end of every epoch, increment the epoch counter and decay
|
||||||
|
# the learning rate.
|
||||||
|
epoch_end = (t + 1) % iterations_per_epoch == 0
|
||||||
|
if epoch_end:
|
||||||
|
self.epoch += 1
|
||||||
|
for k in self.optim_configs:
|
||||||
|
self.optim_configs[k]["learning_rate"] *= self.lr_decay
|
||||||
|
|
||||||
|
# Check train and val accuracy on the first iteration, the last
|
||||||
|
# iteration, and at the end of each epoch.
|
||||||
|
first_it = t == 0
|
||||||
|
last_it = t == num_iterations - 1
|
||||||
|
if first_it or last_it or epoch_end:
|
||||||
|
train_acc = self.check_accuracy(
|
||||||
|
self.X_train, self.y_train, num_samples=self.num_train_samples
|
||||||
|
)
|
||||||
|
val_acc = self.check_accuracy(
|
||||||
|
self.X_val, self.y_val, num_samples=self.num_val_samples
|
||||||
|
)
|
||||||
|
self.train_acc_history.append(train_acc)
|
||||||
|
self.val_acc_history.append(val_acc)
|
||||||
|
self._save_checkpoint()
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
print(
|
||||||
|
"(Epoch %d / %d) train acc: %f; val_acc: %f"
|
||||||
|
% (self.epoch, self.num_epochs, train_acc, val_acc)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Keep track of the best model
|
||||||
|
if val_acc > self.best_val_acc:
|
||||||
|
self.best_val_acc = val_acc
|
||||||
|
self.best_params = {}
|
||||||
|
for k, v in self.model.params.items():
|
||||||
|
self.best_params[k] = v.copy()
|
||||||
|
|
||||||
|
# At the end of training swap the best params into the model
|
||||||
|
self.model.params = self.best_params
|
@ -0,0 +1,78 @@
|
|||||||
|
from builtins import range
|
||||||
|
from past.builtins import xrange
|
||||||
|
|
||||||
|
from math import sqrt, ceil
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def visualize_grid(Xs, ubound=255.0, padding=1):
|
||||||
|
"""
|
||||||
|
Reshape a 4D tensor of image data to a grid for easy visualization.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- Xs: Data of shape (N, H, W, C)
|
||||||
|
- ubound: Output grid will have values scaled to the range [0, ubound]
|
||||||
|
- padding: The number of blank pixels between elements of the grid
|
||||||
|
"""
|
||||||
|
(N, H, W, C) = Xs.shape
|
||||||
|
grid_size = int(ceil(sqrt(N)))
|
||||||
|
grid_height = H * grid_size + padding * (grid_size - 1)
|
||||||
|
grid_width = W * grid_size + padding * (grid_size - 1)
|
||||||
|
grid = np.zeros((grid_height, grid_width, C))
|
||||||
|
next_idx = 0
|
||||||
|
y0, y1 = 0, H
|
||||||
|
for y in range(grid_size):
|
||||||
|
x0, x1 = 0, W
|
||||||
|
for x in range(grid_size):
|
||||||
|
if next_idx < N:
|
||||||
|
img = Xs[next_idx]
|
||||||
|
low, high = np.min(img), np.max(img)
|
||||||
|
grid[y0:y1, x0:x1] = ubound * (img - low) / (high - low)
|
||||||
|
# grid[y0:y1, x0:x1] = Xs[next_idx]
|
||||||
|
next_idx += 1
|
||||||
|
x0 += W + padding
|
||||||
|
x1 += W + padding
|
||||||
|
y0 += H + padding
|
||||||
|
y1 += H + padding
|
||||||
|
# grid_max = np.max(grid)
|
||||||
|
# grid_min = np.min(grid)
|
||||||
|
# grid = ubound * (grid - grid_min) / (grid_max - grid_min)
|
||||||
|
return grid
|
||||||
|
|
||||||
|
|
||||||
|
def vis_grid(Xs):
|
||||||
|
""" visualize a grid of images """
|
||||||
|
(N, H, W, C) = Xs.shape
|
||||||
|
A = int(ceil(sqrt(N)))
|
||||||
|
G = np.ones((A * H + A, A * W + A, C), Xs.dtype)
|
||||||
|
G *= np.min(Xs)
|
||||||
|
n = 0
|
||||||
|
for y in range(A):
|
||||||
|
for x in range(A):
|
||||||
|
if n < N:
|
||||||
|
G[y * H + y : (y + 1) * H + y, x * W + x : (x + 1) * W + x, :] = Xs[
|
||||||
|
n, :, :, :
|
||||||
|
]
|
||||||
|
n += 1
|
||||||
|
# normalize to [0,1]
|
||||||
|
maxg = G.max()
|
||||||
|
ming = G.min()
|
||||||
|
G = (G - ming) / (maxg - ming)
|
||||||
|
return G
|
||||||
|
|
||||||
|
|
||||||
|
def vis_nn(rows):
|
||||||
|
""" visualize array of arrays of images """
|
||||||
|
N = len(rows)
|
||||||
|
D = len(rows[0])
|
||||||
|
H, W, C = rows[0][0].shape
|
||||||
|
Xs = rows[0][0]
|
||||||
|
G = np.ones((N * H + N, D * W + D, C), Xs.dtype)
|
||||||
|
for y in range(N):
|
||||||
|
for x in range(D):
|
||||||
|
G[y * H + y : (y + 1) * H + y, x * W + x : (x + 1) * W + x, :] = rows[y][x]
|
||||||
|
# normalize to [0,1]
|
||||||
|
maxg = G.max()
|
||||||
|
ming = G.min()
|
||||||
|
G = (G - ming) / (maxg - ming)
|
||||||
|
return G
|
Loading…
Reference in New Issue