-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtwo_layer_net.py
More file actions
578 lines (498 loc) · 23.8 KB
/
Copy pathtwo_layer_net.py
File metadata and controls
578 lines (498 loc) · 23.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
"""
Implements a two-layer Neural Network classifier in PyTorch.
WARNING: you SHOULD NOT use ".to()" or ".cuda()" in each implementation block.
"""
import torch
import random
import statistics
import eecs598
from linear_classifier import sample_batch
from typing import Dict, List, Callable, Optional
def hello_two_layer_net():
"""
This is a sample function that we will try to import and run to ensure that
our environment is correctly set up on Google Colab.
"""
print("Hello from two_layer_net.py!")
# Template class modules that we will use later: Do not edit/modify this class
class TwoLayerNet(object):
def __init__(
self,
input_size: int,
hidden_size: int,
output_size: int,
dtype: torch.dtype = torch.float32,
device: str = "cpu",
std: float = 1e-4,
):
"""
Initialize the model. Weights are initialized to small random values and
biases are initialized to zero. Weights and biases are stored in the
variable self.params, which is a dictionary with the following keys:
W1: First layer weights; has shape (D, H)
b1: First layer biases; has shape (H,)
W2: Second layer weights; has shape (H, C)
b2: Second layer biases; has shape (C,)
Inputs:
- input_size: The dimension D of the input data.
- hidden_size: The number of neurons H in the hidden layer.
- output_size: The number of classes C.
- dtype: Optional, data type of each initial weight params
- device: Optional, whether the weight params is on GPU or CPU
- std: Optional, initial weight scaler.
"""
# reset seed before start
random.seed(0)
torch.manual_seed(0)
self.params = {}
self.params["W1"] = std * torch.randn(
input_size, hidden_size, dtype=dtype, device=device
)
self.params["b1"] = torch.zeros(hidden_size, dtype=dtype, device=device)
self.params["W2"] = std * torch.randn(
hidden_size, output_size, dtype=dtype, device=device
)
self.params["b2"] = torch.zeros(output_size, dtype=dtype, device=device)
def loss(
self,
X: torch.Tensor,
y: Optional[torch.Tensor] = None,
reg: float = 0.0,
):
return nn_forward_backward(self.params, X, y, reg)
def train(
self,
X: torch.Tensor,
y: torch.Tensor,
X_val: torch.Tensor,
y_val: torch.Tensor,
learning_rate: float = 1e-3,
learning_rate_decay: float = 0.95,
reg: float = 5e-6,
num_iters: int = 100,
batch_size: int = 200,
verbose: bool = False,
):
# fmt: off
return nn_train(
self.params, nn_forward_backward, nn_predict, X, y,
X_val, y_val, learning_rate, learning_rate_decay,
reg, num_iters, batch_size, verbose,
)
# fmt: on
def predict(self, X: torch.Tensor):
return nn_predict(self.params, nn_forward_backward, X)
def save(self, path: str):
torch.save(self.params, path)
print("Saved in {}".format(path))
def load(self, path: str):
checkpoint = torch.load(path, map_location="cpu")
self.params = checkpoint
if len(self.params) != 4:
raise Exception("Failed to load your checkpoint")
for param in ["W1", "b1", "W2", "b2"]:
if param not in self.params:
raise Exception("Failed to load your checkpoint")
# print("load checkpoint file: {}".format(path))
def nn_forward_pass(params: Dict[str, torch.Tensor], X: torch.Tensor):
"""
The first stage of our neural network implementation: Run the forward pass
of the network to compute the hidden layer features and classification
scores. The network architecture should be:
FC layer -> ReLU (hidden) -> FC layer (scores)
As a practice, we will NOT allow to use torch.relu and torch.nn ops
just for this time (you can use it from A3).
Inputs:
- params: a dictionary of PyTorch Tensor that store the weights of a model.
It should have following keys with shape
W1: First layer weights; has shape (D, H)
b1: First layer biases; has shape (H,)
W2: Second layer weights; has shape (H, C)
b2: Second layer biases; has shape (C,)
- X: Input data of shape (N, D). Each X[i] is a training sample.
Returns a tuple of:
- scores: Tensor of shape (N, C) giving the classification scores for X
- hidden: Tensor of shape (N, H) giving the hidden layer representation
for each input value (after the ReLU).
"""
# Unpack variables from the params dictionary
W1, b1 = params["W1"], params["b1"]
W2, b2 = params["W2"], params["b2"]
N, D = X.shape
H, C = W2.shape
# Compute the forward pass
hidden = None # N, H
scores = None # N, C
############################################################################
# TODO: Perform the forward pass, computing the class scores for the input.#
# Store the result in the scores variable, which should be an tensor of #
# shape (N, C). #
############################################################################
# W1 is (D, H)
# W2 is (H, C)
# FC
hidden = X.mm(W1) + b1
# ReLU
hidden[hidden < 0] = 0
# FC
scores = hidden.mm(W2) + b2
###########################################################################
# END OF YOUR CODE #
###########################################################################
return scores, hidden
def nn_forward_backward(
params: Dict[str, torch.Tensor],
X: torch.Tensor,
y: Optional[torch.Tensor] = None,
reg: float = 0.0
):
"""
Compute the loss and gradients for a two layer fully connected neural
network. When you implement loss and gradient, please don't forget to
scale the losses/gradients by the batch size.
Inputs: First two parameters (params, X) are same as nn_forward_pass
- params: a dictionary of PyTorch Tensor that store the weights of a model.
It should have following keys with shape
W1: First layer weights; has shape (D, H)
b1: First layer biases; has shape (H,)
W2: Second layer weights; has shape (H, C)
b2: Second layer biases; has shape (C,)
- X: Input data of shape (N, D). Each X[i] is a training sample.
- y: Vector of training labels. y[i] is the label for X[i], and each y[i] is
an integer in the range 0 <= y[i] < C. This parameter is optional; if it
is not passed then we only return scores, and if it is passed then we
instead return the loss and gradients.
- reg: Regularization strength.
Returns:
If y is None, return a tensor scores of shape (N, C) where scores[i, c] is
the score for class c on input X[i].
If y is not None, instead return a tuple of:
- loss: Loss (data loss and regularization loss) for this batch of training
samples.
- grads: Dictionary mapping parameter names to gradients of those parameters
with respect to the loss function; has the same keys as self.params.
"""
# Unpack variables from the params dictionary
W1, b1 = params["W1"], params["b1"]
W2, b2 = params["W2"], params["b2"]
N, D = X.shape
H, C = W2.shape
scores, h1 = nn_forward_pass(params, X)
# If the targets are not given then jump out, we're done
if y is None:
return scores
# Compute the loss
loss = None
############################################################################
# TODO: Compute the loss, based on the results from nn_forward_pass. #
# This should include both the data loss and L2 regularization for W1 and #
# W2. Store the result in the variable loss, which should be a scalar. Use #
# the Softmax classifier loss. When you implment the regularization over W,#
# please DO NOT multiply the regularization term by 1/2 (no coefficient). #
# If you are not careful here, it is easy to run into numeric instability #
# (Check Numeric Stability in http://cs231n.github.io/linear-classify/). #
############################################################################
# Replace "pass" statement with your code
# scores is of shape (N, C)
scores -= scores.max(dim=1, keepdim=True)[0] # Numerical Stability
softmax = torch.exp(scores)
softmax = softmax / softmax.sum(dim=1, keepdim=True)
cross_entropy = softmax[[i for i in range(N)], y]
cross_entropy = torch.log(cross_entropy) * -1
loss = cross_entropy.sum()
loss /= N
loss += reg * torch.sum(W1 * W1)
loss += reg * torch.sum(W2 * W2)
###########################################################################
# END OF YOUR CODE #
###########################################################################
# Backward pass: compute gradients
grads = {}
###########################################################################
# TODO: Compute the backward pass, computing the derivatives of the #
# weights and biases. Store the results in the grads dictionary. #
# For example, grads['W1'] should store the gradient on W1, and be a #
# tensor of same size #
###########################################################################
# Replace "pass" statement with your code
# "flat" backpropagation - lecture 6, minute 30
# need to draw a computational graph at first
grad_L = 1.0
grad_scores = grad_L * softmax
grad_scores[[range(N), y]] -= 1
grad_scores /= N
# formula for gradient of matrix-matrix multiplication
grad_W2 = h1.T.mm(grad_scores)
# for one example, d_b2 = 1 * d_scores, but with N examples we sum
grad_b2 = grad_scores.sum(dim=0)
# formula for gradient of matrix-matrix multiplication
grad_hidden = grad_scores.mm(W2.T)
# ReLU
grad_hidden[h1 <= 0] = 0
# formula for gradient of matrix-matrix multiplication
grad_W1 = X.T.mm(grad_hidden)
# for one example, d_b2 = 1 * d_scores, but with N examples we sum
grad_b1 = grad_hidden.sum(dim=0)
# regularization
grad_W1 += 2 * reg * W1
grad_W2 += 2 * reg * W2
grads['W2'] = grad_W2
grads['b2'] = grad_b2
grads['W1'] = grad_W1
grads['b1'] = grad_b1
###########################################################################
# END OF YOUR CODE #
###########################################################################
return loss, grads
def nn_train(
params: Dict[str, torch.Tensor],
loss_func: Callable,
pred_func: Callable,
X: torch.Tensor,
y: torch.Tensor,
X_val: torch.Tensor,
y_val: torch.Tensor,
learning_rate: float = 1e-3,
learning_rate_decay: float = 0.95,
reg: float = 5e-6,
num_iters: int = 100,
batch_size: int = 200,
verbose: bool = False,
):
"""
Train this neural network using stochastic gradient descent.
Inputs:
- params: a dictionary of PyTorch Tensor that store the weights of a model.
It should have following keys with shape
W1: First layer weights; has shape (D, H)
b1: First layer biases; has shape (H,)
W2: Second layer weights; has shape (H, C)
b2: Second layer biases; has shape (C,)
- loss_func: a loss function that computes the loss and the gradients.
It takes as input:
- params: Same as input to nn_train
- X_batch: A minibatch of inputs of shape (B, D)
- y_batch: Ground-truth labels for X_batch
- reg: Same as input to nn_train
And it returns a tuple of:
- loss: Scalar giving the loss on the minibatch
- grads: Dictionary mapping parameter names to gradients of the loss with
respect to the corresponding parameter.
- pred_func: prediction function that im
- X: A PyTorch tensor of shape (N, D) giving training data.
- y: A PyTorch tensor of shape (N,) giving training labels; y[i] = c means
that X[i] has label c, where 0 <= c < C.
- X_val: A PyTorch tensor of shape (N_val, D) giving validation data.
- y_val: A PyTorch tensor of shape (N_val,) giving validation labels.
- learning_rate: Scalar giving learning rate for optimization.
- learning_rate_decay: Scalar giving factor used to decay the learning rate
after each epoch.
- reg: Scalar giving regularization strength.
- num_iters: Number of steps to take when optimizing.
- batch_size: Number of training examples to use per step.
- verbose: boolean; if true print progress during optimization.
Returns: A dictionary giving statistics about the training process
"""
num_train = X.shape[0]
iterations_per_epoch = max(num_train // batch_size, 1)
# Use SGD to optimize the parameters in self.model
loss_history = []
train_acc_history = []
val_acc_history = []
for it in range(num_iters):
X_batch, y_batch = sample_batch(X, y, num_train, batch_size)
# Compute loss and gradients using the current minibatch
loss, grads = loss_func(params, X_batch, y=y_batch, reg=reg)
loss_history.append(loss.item())
#########################################################################
# TODO: Use the gradients in the grads dictionary to update the #
# parameters of the network (stored in the dictionary self.params) #
# using stochastic gradient descent. You'll need to use the gradients #
# stored in the grads dictionary defined above. #
#########################################################################
# Replace "pass" statement with your code
params['W1'] -= learning_rate * grads['W1']
params['b1'] -= learning_rate * grads['b1']
params['W2'] -= learning_rate * grads['W2']
params['b2'] -= learning_rate * grads['b2']
#########################################################################
# END OF YOUR CODE #
#########################################################################
if verbose and it % 100 == 0:
print("iteration %d / %d: loss %f" % (it, num_iters, loss.item()))
# Every epoch, check train and val accuracy and decay learning rate.
if it % iterations_per_epoch == 0:
# Check accuracy
y_train_pred = pred_func(params, loss_func, X_batch)
train_acc = (y_train_pred == y_batch).float().mean().item()
y_val_pred = pred_func(params, loss_func, X_val)
val_acc = (y_val_pred == y_val).float().mean().item()
train_acc_history.append(train_acc)
val_acc_history.append(val_acc)
# Decay learning rate
learning_rate *= learning_rate_decay
return {
"loss_history": loss_history,
"train_acc_history": train_acc_history,
"val_acc_history": val_acc_history,
}
def nn_predict(
params: Dict[str, torch.Tensor], loss_func: Callable, X: torch.Tensor
):
"""
Use the trained weights of this two-layer network to predict labels for
data points. For each data point we predict scores for each of the C
classes, and assign each data point to the class with the highest score.
Inputs:
- params: a dictionary of PyTorch Tensor that store the weights of a model.
It should have following keys with shape
W1: First layer weights; has shape (D, H)
b1: First layer biases; has shape (H,)
W2: Second layer weights; has shape (H, C)
b2: Second layer biases; has shape (C,)
- loss_func: a loss function that computes the loss and the gradients
- X: A PyTorch tensor of shape (N, D) giving N D-dimensional data points to
classify.
Returns:
- y_pred: A PyTorch tensor of shape (N,) giving predicted labels for each of
the elements of X. For all i, y_pred[i] = c means that X[i] is predicted
to have class c, where 0 <= c < C.
"""
y_pred = None
###########################################################################
# TODO: Implement this function; it should be VERY simple! #
###########################################################################
# Replace "pass" statement with your code
scores, _ = nn_forward_pass(params, X)
return scores.max(dim=1)[1]
###########################################################################
# END OF YOUR CODE #
###########################################################################
return y_pred
def nn_get_search_params():
"""
Return candidate hyperparameters for a TwoLayerNet model.
You should provide at least two param for each, and total grid search
combinations should be less than 256. If not, it will take
too much time to train on such hyperparameter combinations.
Returns:
- learning_rates: learning rate candidates, e.g. [1e-3, 1e-2, ...]
- hidden_sizes: hidden value sizes, e.g. [8, 16, ...]
- regularization_strengths: regularization strengths candidates
e.g. [1e0, 1e1, ...]
- learning_rate_decays: learning rate decay candidates
e.g. [1.0, 0.95, ...]
"""
learning_rates = []
hidden_sizes = []
regularization_strengths = []
learning_rate_decays = []
###########################################################################
# TODO: Add your own hyper parameter lists. This should be similar to the #
# hyperparameters that you used for the SVM, but you may need to select #
# different hyperparameters to achieve good performance with the softmax #
# classifier. #
###########################################################################
# Replace "pass" statement with your code
learning_rates = [1.0, 1.1]
hidden_sizes = [32, 64, 128, 256]
regularization_strengths = [0.00001, 0.001, 0]
learning_rate_decays = [0.95]
###########################################################################
# END OF YOUR CODE #
###########################################################################
return (
learning_rates,
hidden_sizes,
regularization_strengths,
learning_rate_decays,
)
def find_best_net(
data_dict: Dict[str, torch.Tensor], get_param_set_fn: Callable
):
"""
Tune hyperparameters using the validation set.
Store your best trained TwoLayerNet model in best_net, with the return value
of ".train()" operation in best_stat and the validation accuracy of the
trained best model in best_val_acc. Your hyperparameters should be received
from in nn_get_search_params
Inputs:
- data_dict (dict): a dictionary that includes
['X_train', 'y_train', 'X_val', 'y_val']
as the keys for training a classifier
- get_param_set_fn (function): A function that provides the hyperparameters
(e.g., nn_get_search_params)
that gives (learning_rates, hidden_sizes,
regularization_strengths, learning_rate_decays)
You should get hyperparameters from
get_param_set_fn.
Returns:
- best_net (instance): a trained TwoLayerNet instances with
(['X_train', 'y_train'], batch_size, learning_rate,
learning_rate_decay, reg)
for num_iter times.
- best_stat (dict): return value of "best_net.train()" operation
- best_val_acc (float): validation accuracy of the best_net
"""
best_net = None
best_stat = None
best_val_acc = 0.0
#############################################################################
# TODO: Tune hyperparameters using the validation set. Store your best #
# trained model in best_net. #
# #
# To help debug your network, it may help to use visualizations similar to #
# the ones we used above; these visualizations will have significant #
# qualitative differences from the ones we saw above for the poorly tuned #
# network. #
# #
# Tweaking hyperparameters by hand can be fun, but you might find it useful #
# to write code to sweep through possible combinations of hyperparameters #
# automatically like we did on the previous exercises. #
#############################################################################
N, D = data_dict['X_train'].shape
i = 0
# results is dictionary mapping tuples of the form
# (learning_rate, regularization_strength) to tuples of the form
# (train_acc, val_acc).
results = {}
num_iters = 2000 # number of iterations
learning_rates, hidden_sizes, regularization_strengths, learning_rate_decays = get_param_set_fn()
num_models = len(learning_rates) * len(hidden_sizes) * len(regularization_strengths) * len(learning_rate_decays)
for lr in learning_rates:
for reg in regularization_strengths:
for hidden_size in hidden_sizes:
for lr_decay in learning_rate_decays:
i += 1
print('Training NN %d / %d with learning_rate=%e , reg=%e , hidden_size=%e , lr_decay=%e'
% (i, num_models, lr, reg, hidden_size, lr_decay))
eecs598.reset_seed(0)
model = TwoLayerNet(input_size = D, hidden_size = hidden_size, output_size = 10)
train_result = model.train(X=data_dict['X_train'],
y=data_dict['y_train'],
X_val=data_dict['X_val'],
y_val=data_dict['y_val'],
learning_rate = lr,
learning_rate_decay = lr_decay,
reg = reg,
num_iters = num_iters,
batch_size = 200)
loss_history = train_result['loss_history']
train_acc_history = train_result['train_acc_history']
val_acc_history = train_result['val_acc_history']
cand_val_acc = val_acc_history[-1]
cand_train_acc = train_acc_history[-1]
if cand_val_acc > best_val_acc:
best_val_acc = cand_val_acc
best_net = model
best_stat = train_result
results[(lr, reg, hidden_size, lr_decay)] = (cand_train_acc, cand_val_acc)
# Print out results.
for lr, reg, hidden_size, lr_decay in sorted(results):
train_acc, val_acc = results[(lr, reg, hidden_size, lr_decay)]
print('lr %e reg %e hidden_size %e lr_decay %e train accuracy: %f val accuracy: %f' % (
lr, reg, hidden_size, lr_decay, train_acc, val_acc))
#############################################################################
# END OF YOUR CODE #
#############################################################################
return best_net, best_stat, best_val_acc