CS231 第三讲 损失函数和优化
课程视频地址:https://study.163.com/courses-search?keyword=CS231
课程主页:http://cs231n.stanford.edu/2017/
这一讲主要介绍损失函数和优化。
损失函数
损失函数告诉我们分类器有多好,给定图片数据集$\{(x_i,y_i)\}_{i=1}^N$,$x_i$为图片,$y_i$为标签,定义我们的损失为
其中$L_i$为损失函数。下面先介绍多类别支持向量机损失函数。(Multiclass SVM loss)
多类别支持向量机损失
令
然后定义SVM损失为
这个损失函数也被称为“折叶损失”(hinge loss),损失函数图像如下
这个函数首先判断图片的正确分类的得分$(s_{y_i})$是否超过其余分类得分($s_j $)加$1$,如果成立,则损失为$0$,否则损失为$s_j -s_{y_i}+1$。我们来看一个具体的例子
猫的$s_{y_i}=3.2$,所以
类似的,我们可以计算出汽车以及青蛙的损失,结果如下
所以总损失为
接下来考虑如下几个问题。
1.如果汽车分数有稍微改变,损失函数会发生什么变化?
因为汽车的正确分数$s_{y_i}$比$s_j+1$大很多,所以稍微改变$s_{y_i}$不会改变损失。
2.损失函数的最大值和最小值分别为多少?
从图像中就可以看出,损失的最小值为$0$,最大为$\infty$。
3.在一开始$W$很小,所以$s\approx 0$,那么此时的损失函数大约是多少?
此时$ \max(0, s_j -s_{y_i}+1)\approx 1$,所以由公式$L_i=\sum_{j\neq y_i} \max(0, s_j -s_{y_i}+1)$可知,
其中$K$为分类总数。这个结果通常用来检验代码是否出错。
4.如果损失函数的求和是遍历所有类别(包括$j=y_i$),那么损失函数为多少?
利用公式即可,此时损失函数是原有的损失函数加$1$。
5.如果我们损失函数采用均值而不是求和,那么会怎么样?
没有影响,最后只是相差常数倍而已。
6.如果我们使用
会怎么样?
这样会对异常值更加敏感。
7.如果我们找到$W$,使得
那么$W$是唯一的吗?
不是,这是因为
所以
如果
那么
从而$2W$也会使得$L=0$。
这个事实告诉我们应该对$W$也进行约束,于是定义如下损失
第一项称为数据损失,第二项称为正则化项,$\lambda$为超参数,比较常见的正则化项如下:
Softmax分类器
首先定义如下概率
我们想最大化对数似然函数,而这也等价于最小化如下式子
来看一个具体的例子:
接着来看如下几个问题。
1.损失函数$L_i$的最大值,最小值分别是多少?
因为$P(Y= y_i|X= x_i)\in [0,1]$,所以$L_i \in [0,\infty)$
2.在一开始$W$很小,所以$s\approx 0$,那么此时的损失函数大约是多少?
此时$e^{s_j}\approx 1$,如假设有$K$类,那么
同样的,这个结果也通常用来检验代码是否出错。
把上述内容总结一下:
所以接下来的问题是如何找到最优的$W$,这就进入一下个主题——优化。
优化
现在的目标是找到最优的$W$,下面分别介绍两种方法。
1.随机搜索
该策略很简单,反复迭代,随机初始化$W$,然后比较其和当前最优解的效果即可。代码如下
# assume X_train is the data where each column is an example (e.g. 3073 x 50,000)
# assume Y_train are the labels (e.g. 1D array of 50,000)
# assume the function L evaluates the loss function
bestloss = float("inf") # Python assigns the highest possible float value
for num in xrange(1000):
W = np.random.randn(10, 3073) * 0.0001 # generate random parameters
loss = L(X_train, Y_train, W) # get the loss over the entire training set
if loss < bestloss: # keep track of the best solution
bestloss = loss
bestW = W
print 'in attempt %d the loss was %f, best %f' % (num, loss, bestloss)
# prints:
# in attempt 0 the loss was 9.401632, best 9.401632
# in attempt 1 the loss was 8.959668, best 8.959668
# in attempt 2 the loss was 9.044034, best 8.959668
# in attempt 3 the loss was 9.278948, best 8.959668
# in attempt 4 the loss was 8.857370, best 8.857370
# in attempt 5 the loss was 8.943151, best 8.857370
# in attempt 6 the loss was 8.605604, best 8.605604
# ... (trunctated: continues for 1000 lines)
(代码参考地址:https://cs231n.github.io/optimization-1/)
这种方法的效果很糟糕,正确率大约15%左右。
2.跟随梯度
这个策略的思路很简单,假设我们位于山谷,我们想最快到达谷底,很自然的想法就是沿着最陡的方向前进:
函数最陡的方向就是梯度方向了,一维的情形就是计算导数
高维的情形则需计算梯度(偏导数),利用定义计算数值梯度
但实际上,这个方法很不可取,因为数据维度往往非常大,所以计算一次梯度需要的时间很多,比较正确的方法是使用微积分直接求出梯度,所以我们需要计算
上述方法被称为梯度下降。但是注意这个方法依旧有一个问题,因为实际中数据量也非常大,所以如果按照上述公式计算一次梯度还是需要非常久,所以我们常使用小批量数据梯度下降,设定一个批量值batch,每次只计算batch个数据的梯度即可,在实际中,batch通常取32/64/128,代码如下
# Vanilla Minibatch Gradient Descent
while True:
data_batch = sample_training_data(data, 256) # sample 256 examples
weights_grad = evaluate_gradient(loss_fun, data_batch, weights)
weights += - step_size * weights_grad # perform parameter update