前言
大家好,我是Kay,小白一个。以下是我完成斯坦福 cs231n-assignment1-two_layer_net 这份作业的做题过程、思路、踩到的哪些坑、还有一些得到的启发和心得。希望下面的文字能对所有像我这样的小白有所帮助。
两层的网络处理方法与之前的SVM/Softmax 的处理方法类似,关键在于函数和梯度的计算。
TODO1:计算 scores
【思路】公式是:W2 * max(0, W1*x) 用代码实现之即可。
scores_hid = np.dot(X, W1) + b1
scores_hid = np.maximum(0, scores_hid)
scores =np.dot(scores_hid, W2) + b2
结果正确。
TODO2:计算 loss
【思路】用 softmax 的公式,不要忘记加上正则惩罚项。
scores = scores - np.max(scores, axis = 1,keepdims=True)
exp_sum = np.sum(np.exp(scores), axis = 1,keepdims=True)
loss = -np.sum(scores[range(N), y]) +np.sum(np.log(exp_sum))
loss = loss / N + 0.5 * reg * (np.sum(W1 *W1) + np.sum(W2 * W2))
【开始 Debug】
![](https://img-blog.csdn.net/20180519203228369?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0thbW15SXNUaGVCZXN0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
这个 bug 我是找得真是要气死自己啊!感觉公式都没记错啊!Run 了好几次 loss 还是不够小,郁闷之下跑去百度,贴了别人的代码结果 loss 也是这么多但是他们的结果很小,喵喵喵?我定眼一看,别人的 reg 都是 0.1!凭什么我给的是0.05。想哭、难受。
改成 0.1 后,果然我的代码也是正确的。
【思考提升】
我顺手看了下别人的代码,有的人没有对scores 做处理就开 e 的幂,这是不对的哦~小心数值被爆掉哦~
TODO3:利用反向传播计算梯度
【思路】画出“计算图”,一步步往回做,靠公式得到:
d_b2= 1 * d_scores
d_W2= h1 * d_scores (h1 是 max(0, f1) )
d_b1 = 1 * d_f1
d_W1= X * d_f1
prob = score / exp_sum
prob[range(N), y] -= 1
d_scores= np.dot(X.T, prob)
d_scores /= N
grads['b2'] = np.sum(d_scores, axis = 0)
grads['W2'] = np.dot(scores_hid.T, d_scores)
d_f1 = np.dot(d_scores, W2.T)
d_f1[scores_hid <&#61; 0] &#61; 0
grads[&#39;b1&#39;] &#61; np.sum(d_f1, axis &#61; 0)
grads[&#39;W1&#39;] &#61;np.dot(X.T, d_f1)
【开始 Debug】这里我遇到了特别多错误&#xff0c;果然思考还是不够严谨。
1. d_scores 求错了&#xff0c;不是 np.dot(X.T, prob)&#xff0c;不能生搬以为是 softmax 里的 dW&#xff0c;d_scores 就是 prob&#xff01;
2. prob 也求错了&#xff0c;在 softmax 里&#xff0c;我分子上的 scores 是有做 e 幂的&#xff0c;但是这里还没处理就直接拿去用了&#xff0c;还是一处生搬旧思想的错误。
3. 两个 dW 没有加正则项。
【修改代码】
prob &#61; np.exp(scores) / exp_sum
prob[range(N), y] -&#61; 1
d_scores &#61; prob / N
grads[&#39;b2&#39;] &#61; np.sum(d_scores, axis &#61; 0)
grads[&#39;W2&#39;] &#61; np.dot(scores_hid.T, d_scores) &#43; reg * W2
d_f1 &#61; np.dot(d_scores, W2.T)
d_f1[scores_hid <&#61; 0] &#61; 0
grads[&#39;b1&#39;] &#61; np.sum(d_f1, axis &#61; 0)
grads[&#39;W1&#39;] &#61;np.dot(X.T, d_f1) &#43; reg * W1
结果正确。
【思考提升】其实像这些所谓的“小错”是很让人沮丧的&#xff0c;错的又不是大方向&#xff0c;找起bug 时又往往是从整体思路开始怀疑自己&#xff0c;因此找到这点小错是很耗费精力的&#xff0c;要怎么加快 debug 的效率呢&#xff1f;错的地方是思路、还是小瑕疵&#xff1f;这是应该训练的地方了。
TODO4&#xff1a;完成 train 函数和 predict 函数
【思路】
SGD&#xff1a;利用特定一张图像对我们的各个参数进行 update
y_pred就是谁的分数大就取那个标签作为 y_pred
三段代码都贴在这里了。
sample_indices &#61; np.random.choice(range(num_train), batch_size)
X_batch &#61; X[sample_indices]
y_batch &#61;y[sample_indices]
self.params[&#39;W1&#39;] -&#61; learning_rate * grads[&#39;W1&#39;]
self.params[&#39;b1&#39;] -&#61; learning_rate * grads[&#39;b1&#39;]
self.params[&#39;W2&#39;] -&#61; learning_rate * grads[&#39;W2&#39;]
self.params[&#39;b2&#39;] -&#61; learning_rate * grads[&#39;b2&#39;]
y_pred &#61; np.argmax(self.loss(X), axis&#61;1)
结果Final training loss: 0.017143643532923747
TODO5&#xff1a;进行超参数的调参
【思路】这里的超参数有&#xff1a;hidden layer size, learning rate, numer of training epochs, andregularization strength
首先&#xff0c;我们还是先调最重要的lr 和 reg&#xff0c;接着再考虑其他超参数。
best_val &#61; -1
input_size &#61; 32 * 32 * 3
hidden_size &#61; 100
num_classes &#61; 10
net &#61; TwoLayerNet(input_size, hidden_size,num_classes)
learing_rates &#61; [1e-3, 1.5e-3, 2e-3]
regularizations &#61; [0.2, 0.35, 0.5]
for lr in learing_rates:
for reg in regularizations:
stats &#61; net.train(X_train, y_train, X_val, y_val,
num_iters&#61;1500,batch_size&#61;200,
learning_rate&#61;lr,learning_rate_decay&#61;0.95,
reg&#61;reg, verbose&#61;False)
val_acc &#61; (net.predict(X_val) &#61;&#61; y_val).mean()
if val_acc > best_val:
best_val &#61; val_acc
best_net &#61; net
print ("lr ",lr, "reg ", reg, "val accuracy:", val_acc)
print ("best validation accuracyachieved during cross-validation: ", best_val)
结果差强人意。
【思考提升】老师说&#xff1a;“Tuning the hyperparameters and developing intuition for how theyaffect the final performance is a large part of using Neural Networks.”可是就目前而言&#xff0c;我还是没有认识到超参数的“重要性”&#xff1f;所以&#xff0c;我需要训练一种超参如何影响神经网络的直觉。
总结
这份作业要求我们掌握正向传递分数函数和损失函数&#xff0c;同时反向传递梯度给每个变量。
Delta &#61; “本地梯度”*“上沿梯度”
有趣的是&#xff0c;
变量间做“加法”&#xff0c;传回的梯度都是那份“上沿梯度”&#xff0c;相当于是一个广播器&#xff1b;
变量间做“max()”&#xff0c;传回的梯度是那份“上沿梯度”给最大的值&#xff0c;其他的梯度是0&#xff0c;相当于是一个路由器&#xff1b;
变量间做“乘法”&#xff0c;传回的梯度都是那份“上沿梯度”*对方本身的值&#xff0c;相当于是一个&#xff08;带放大“上沿梯度”倍&#xff09;交换器。
这三个典例&#xff0c;应该能帮助我们直观地理解 backpropagation。
最后&#xff0c;关于缩进&#xff0c;我已经放弃治疗了。