CS231n Deep Learning for Computer Vision 2024
- google drive + colab
Image Classification, kNN, SVM, Softmax, Fully-Connected Neural Network
Q1: k-Nearest Neighbor classifier
Q2: Training a Support Vector Machine
Q3: Implement a Softmax classifier
Q5: Higher Level Representations: Image Features
Fully-Connected Nets, Batch Normalization, Dropout, Convolutional Nets, Network Visualization
Q1: Multi-Layer Fully Connected Neural Networks
Q4: Convolutional Neural Networks
Network Visualization, Image Captioning with RNNs and Transformers, Generative Adversarial Networks, Self-Supervised Contrastive Learning Module 0: Preparation
Q1: Image Captioning with Vanilla RNNs
Q2: Image Captioning with Transformers
Q3: Generative Adversarial Networks
Q4: Self-Supervised Learning for Image Classification
Extra Credit: Image Captioning with LSTMs
这里放一些笔记。主要是矩阵代数相关的内容。
个人整理的笔记,整理了关于各类向量和矩阵乘法以及对应numpy
的API
下面会涉及到大量的标量对向量求导, 向量对向量求导, 矩阵对矩阵求导 等内容。
推荐文章: 矩阵求导术(上),矩阵求导术(下), cs231n-linear-backprop
简单推导一下SVM
和Softmax
的损失函数对权重矩阵W
的梯度。
首先明确一些符号。
一张图像一共有
权重矩阵
输入
所以
输入
损失函数的公式为:
我们的目的是求出
最终目的求
那么问题就是如何求出
根据损失函数的表达式,可以针对
也就是说,
对于SVM
的损失函数hinge loss
:
这里
那么开始推导
(注意这里的j
是具体的一个数,写出
所以
注意到
接下来是求
每一个
接下来推导Softmax
的损失函数cross-entropy loss
:
(这里的最后一个等号的式子,在编程上可以减少误差积累 PS: log的底数这里为e
)
分别对
顺带一提,assignment 2
中有一个softmax_loss()
函数需要计算loss
对scores
的求导。
这里根据上面的结论以及
如果
所以在softmax_loss()
这里面我是这样写的:
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
"""
loss, dx = None, None
loss = 0.0
N = x.shape[0]
# shift消除累计误差
# 设P为概率Probability = softmax(x)
P = np.exp(x - x.max(axis = 1)).reshape(N, -1)
P /= P.sum(axis = 1)
loss += - np.log(P[range(N), y]).sum() / N
# loss对score求导 这里scores是x
P[range(N), y] -= 1
dx = P / N
return loss, dx
一般的SGD (Vanilla SGD)就只是最简单的将位置减去梯度而已:
x = x - learning_rate * dw
而Momentum方法则是:
v = mu * v - learning_rate * dx
x = x + v
可以理解为:
将粒子放入目标函数(也就是损失函数)表示的“山坡”上,初速度为v,在山坡上,梯度的方向是山坡增长最快的方向,也就是粒子在此处将受到与梯度相反的作用力,驱使粒子向下运动,同时因为自身的惯性(动量系数mu),更加平滑地到达低处。
x
就表示粒子所在的位置,v
表示粒子的速度
在实际中,一般用w
权重矩阵来表示粒子在loss function
的位置
RMSprop也就是Root Mean Square Propagation
是一种对每个参数的学习率进行自适应调整的优化方法,它通过对每个参数的梯度平方进行加权平均来调整学习率。其主要目标是防止梯度爆炸或梯度消失现象,并加速训练过程。
同样地,有:
v = decay_rate * v + (1 - decay_rate) * dx ** 2
x += -learning_rate * dx / (np.sqrt(v) + eps)
可以理解为:
同样将粒子放入目标函数表示的“山坡”上,由于衰退率(decay rate)的影响,粒子将保留原有一部分速度继续运行,另一部分由梯度的平方来提供(平方放大了梯度的影响),接下来因为速度的影响进而更新位置,步长将会因为sqrt(v)缩放,因为如果v很大,说明这个位置的梯度很大,那么位置更新就会变小;如果v很小,说明这个位置的梯度很小,那么位置更新就会变大,从而更加平滑地到达低处。
ps: decay_rate
一般取值[0.9, 0.99, 0.999]
Adam也就是Adaptive Moment Estimate
Adam结合了Momentum和RMSprop的优点,既有Momentum的方向信息,又有RMSprop的自适应步长。通过引入偏置修正,Adam在训练的初期就能有效地进行更新,并且通过自适应调整学习率,使得训练过程更加稳定高效。
一般写法有:
m = beta1 * m + (1 - beta1) * dx
v = beta2 * v + (1 - beta2) * (dx ** 2)
mt = m / (1 - beta1 ** t)
vt = v / (1 - beta2 ** t)
x += -learning_rate * mt / (np.sqrt(vt) + eps)
将粒子放入目标函数表示的“山坡”上,粒子不仅受到梯度的影响,还受到其前进方向的惯性影响。首先,m保存了过去梯度的加权平均(即方向信息),v保存了梯度平方的加权平均(即尺度信息)。接下来,在更新时,通过对m和v进行修正(mt和vt),保证了在训练初期的偏置问题,从而使得学习过程更加稳定。
并且因为有bias correction
机制,并不会像Adagrad那样,训练后期学习步长逐渐减小甚至停止学习。
因为这里v
就算累加了梯度的平方,后面也会修正(t
是当前的时间步/迭代次数,用于修正m
和v
的偏差)
ps: 推荐取值为
关于Normalization很简单,就是数据减去其均值再除其标准差。
Batch Normalization的理解可以参考这篇文章,此外这是参考论文。其实看完文章基本就掌握了。
简单来说就是在两个隐藏层中间夹一层Batch Norm
层
在训练阶段,Batch Norm
层接受来自Activation
层的输出,首先计算这个Mini-Batch
的均值和标准差,然后将整个数据normalize,然后再进行Scale & Shift。同时更新Moving Average
(后续推理阶段使用)
At training time, such a layer uses a minibatch of data to estimate the mean and standard deviation of each feature. These estimated means and standard deviations are then used to center and normalize the features of the minibatch. A running average of these means and standard deviations is kept during training, and at test time these running averages are used to center and normalize features.
最复杂的是推导Batch Norm反向传播的一系列矩阵
先规定一些符号:
根据计算图,有:
这里需要注意两点:
- 计算图没给出
$\hat{X_i}$ 这里计算用到了 -
$\bigodot$ 或者$\circ$ 指的是element-wise multiply
对应numpy
的*
运算符或np.multiply()
具体查看Hadamard product
相关词条
接下来求
先来个简单的
首先是
其中 dout
的一部分。没错,这也是那篇论文的公式,但是,为什么?
一般到这里有两个疑点:
- 为什么要求和?What the hell?
- 为什么不是
$\frac{\partial{L}}{\partial{\beta}} = \frac{\partial{L}}{\partial{Y}} \frac{\partial{Y}}{\partial{\beta}}$
我在这里卡了非常非常久,后面发现其实很简单!
因为本质上 numpy
的广播机制才写出来out = gamma * x_hat + beta
的代码,实际上是每一个
所以根据上面的关于
所以就转而求
这里介绍两个方法求
第一个方法: 逐个元素求导,也就是穷举出所有的组合,然后组成一个矩阵(这里不用太纠结分子布局还是分母布局)
实际上:
第二个方法: 根据矩阵求导术,先求全微分,再根据矩阵相关的变换规则,求出对应的导数。不过这里显得很简单。
得到
结果一样。
所以最后答案是一个对角线全
继续求
因为链式法则,进一步求
第一个方法: 逐个元素求导,也就是穷举出所有的组合,然后组成一个矩阵
第二个方法:
先全微分,再利用矩阵相关法则,转成
进一步推导(注意Hadamard乘积满足交换律):
其中,
所以:
最后乘对角阵就相当于和
求
在进行之前,我已经尝试过非常多的方法来计算,接下来展示最清晰的一种方法。
首先让我们构造一个层次结构,假想在脑中有这样的函数:
在前面,我们已经计算出来的,或者已经知道的变量有:
并且很容易得到
也就是说 第二层的
接下来考虑第三层的全部偏导数 先从
由于
得到
此时还有一个
然后我们来求对
由于
所以
组合一下前面的结果,把
来吧,已经求完了对
需要的内容有
所以!
太累人了。
综上,Batch Norm
反向传播的导数为:
这里简单阐述Vanilla RNN的反向传播推导。
计算图略。下面是Vanilla RNN的公式。
这里唯一需要注意的就是cs231n/rnn_layers.py
中的rnn_step_backward()
传入的上游梯度。
在t
时刻这个单元应该接收两个梯度,一个是从
(因为 y
和下一个时刻了,所以有两个方向,这个在后续rnn_backward()
中会体现)
这两者的loss
的梯度记作
那么BP的导数:
以及word_embedding
的反向传播推导。
我们定义:
输入词索引矩阵:$x$ 尺寸为
词向量矩阵:$W$ 尺寸为
输出嵌入:$out$ 尺寸为
也就是说,$out[i, t]$ 是矩阵
对out
有贡献的是W
中的某些行,并且这些行使得行号 v=x[i,t]
所以对out[i, t]
求导的话,应该是W[v]
对应的一行,这里v=x[i,t]
所以有:
当且仅当:
得到代码:
for i in range(N):
for t in range(T):
v = x[i, t]
dW[v] += dout[i, t]
# 等价于
np.add.at(dW, x.reshape(-1), dout.reshape(-1, D))
LSTM 的由于用的element-wise
的乘法,更简单,此处略过。