继续cs231n课程的学习,最优化方法部分:随机梯度下降法。
前面课程介绍了图像分类任务的两个关键部分:
(1)评分函数。将原始图像像素映射为分类评分值。
(2)损失函数。根据分类评分和训练集图像数据实际分类的一致性,来衡量参数集的好坏。损失函数有不同的实现方式(例如:Softmax或SVM)。线性函数的形式是: ,而SVM实现的公式是:
(1)
对于图像数据xi,如果基于参数集W做出的分类预测与真实情况相一致,此时的损失值 L 就很低。但也可能出现预测与真实情况不一致的现象,此时需要参数最优化(Optimization),即寻找使得损失函数值最小化的参数集W的过程。
上图中,左:a值与对应损失值的变化图表。中和右:两维度方向上的损失值切片图,蓝色部分表示低损失值,红色部分表示高损失值。注意损失函数的分段线性结构,这可以用数学解释。
从公式可以看出,样本的损失值是以W为参数的max函数的累加和(零阈值是由于 max(0,-)函数)。W的每一行(即wj),有时候它前面是一个正号(当它是错误分类的时候),有时候是一个负号(当它是正确分类的时候)。假设有一个很简单的数据集,它包含3个一维度的点,数据集有3个类别。完整的无正则化SVM损失值计算如下:
由于数据xi和权重wj都是数字。上面式子中包含wj的线性函数,且每一项都会与0比较,取两者的最大值。该过程用图表示为:
根据SVM的损失函数的碗状外形,可以猜测这是一个凸函数。但是将f 函数扩展到神经网络后,目标函数就不是凸函数了,图像会变成凹凸不平的复杂形状。 由于max操作,损失函数中存在一些不可导点(kinks),所以损失函数不可微,因为在这些点处的梯度是没有定义的,但是次梯度(subgradient)是存在且常被使用的。
损失函数值的大小可以评价参数 W 的好坏。所以最优化的目标是找到使得损失函数取值最小的 W。深度学习调优面对的不仅仅是凸函数,而是最优化一个神经网络,简单地使用凸函数最优化技巧并不能解决问题。
# 假设X_train的每一列对应一个数据样本(例如3073 x 50000)
# 假设Y_train是数据样本的类别标签(大小为50000的一维数组)
# 假设函数L对损失函数进行评价
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)
# 输出:
# 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)
从上面的代码可以发现,我们尝试了许多随机生成的权重W,其中有些W对应的损失值比另一些要小。我们可以把随机搜索中找到的最好的权重W取出,然后用到测试集中。
# 假设X_test大小为[3073 x 10000], Y_test为[10000 x 1]
scores = Wbest.dot(Xte_cols) # 10 x 10000, the class scores for all test examples
# 找到每列中分值最大的索引(即预测的分类)
Yte_predict = np.argmax(scores, axis = 0)
# 计算准确率(正确预测的部分)
np.mean(Yte_predict == Yte)
# 返回 0.1555
测试集中表现最好的权重W的准确率是15.5%,而随机查找的准确率是10%,这个准确率对于一个不费脑的策略来说是不错的。
W = np.random.randn(10, 3073) * 0.001 # generate random starting W
bestloss = float("inf")
for i in xrange(1000):
step_size = 0.0001
Wtry = W + np.random.randn(10, 3073) * step_size
loss = L(Xtr_cols, Ytr, Wtry)
if loss < bestloss:
W = Wtry
bestloss = loss
print 'iter %d loss is %f' % (i, bestloss)
使用跟以前相同数量的损失函数评估(1000),该方法在测试集上的分类精度为21.4%。比策略1效果更好,但仍然浪费计算资源。
def eval_numerical_gradient(f, x):
"""
一个求函数f在x处的数值梯度的简单实现
- f是只有一个参数的函数
- x是需要计算梯度的点
"""
fx = f(x) # 在原点计算函数值
grad = np.zeros(x.shape)
h = 0.00001
# 对x中所有的索引进行迭代
it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
while not it.finished:
# 计算x+h处的函数值
ix = it.multi_index
old_value = x[ix]
x[ix] = old_value + h # 增加h
fxh = f(x) # 计算f(x + h)
x[ix] = old_value # 存到前一个值中 (非常重要)
# 计算偏导数
grad[ix] = (fxh - fx) / h # 坡度
it.iternext() # 到下个维度
return grad
代码中对所有维度进行迭代,其中每个维度上产生一个很小的变化量h,通过观察函数值的变化,计算函数在该维度上的偏导数。最后把所有的梯度结果存储在变量grad中。# 要想使用上面的代码,需要一个只有一个参数的函数
# (参数就是权重)包含了X_train和Y_train
def CIFAR10_loss_fun(W):
return L(X_train, Y_train, W)
W = np.random.rand(10, 3073) * 0.001 # 随机权重向量
df = eval_numerical_gradient(CIFAR10_loss_fun, W) # 得到梯度
梯度告诉我们损失函数在每个维度上的斜率,并以此进行更新:
loss_original = CIFAR10_loss_fun(W) # 初始损失值
print 'original loss: %f' % (loss_original, )
# 查看不同步长的效果
for step_size_log in [-10, -9, -8, -7, -6, -5,-4,-3,-2,-1]:
step_size = 10 ** step_size_log
W_new = W - step_size * df # 权重空间中的新位置
loss_new = CIFAR10_loss_fun(W_new)
print 'for step size %f new loss: %f' % (step_size, loss_new)
# 输出:
# original loss: 2.200718
# for step size 1.000000e-10 new loss: 2.200652
# for step size 1.000000e-09 new loss: 2.200057
# for step size 1.000000e-08 new loss: 2.194116
# for step size 1.000000e-07 new loss: 2.135493
# for step size 1.000000e-06 new loss: 1.647802
# for step size 1.000000e-05 new loss: 2.844355
# for step size 1.000000e-04 new loss: 25.558142
# for step size 1.000000e-03 new loss: 254.086573
# for step size 1.000000e-02 new loss: 2539.370888
# for step size 1.000000e-01 new loss: 25392.214036
在梯度的负方向上更新:在上面的代码中,为了计算新的权重W_new,需要朝着梯度 df 的负方向更新,损失函数值不断减小。# 普通的梯度下降
while True:
weights_grad = evaluate_gradient(loss_fun, data, weights)
weights += - step_size * weights_grad # 进行梯度更新
上面的循环在所有的神经网络核心库中都存在,虽然也有其他最优化方法(如LBFGS),但目前为止,梯度下降法是在神经网络损失函数最优化时最常用的。# 普通的小批量数据梯度下降
while True:
data_batch = sample_training_data(data, 256) # 256个数据
weights_grad = evaluate_gradient(loss_fun, data_batch, weights)
weights += - step_size * weights_grad # 参数更新
通过计算小批量数据的梯度可以快速地收敛,进行更频繁的参数更新。
http://cs231n.github.io/optimization-1/
https://zhuanlan.zhihu.com/p/21360434?refer=intelligentunit
https://zhuanlan.zhihu.com/p/21387326?refer=intelligentunit