结合视频和笔记:(笔记来源)https://zhuanlan.zhihu.com/p/21360434?refer=intelligentunit
笔记一开始介绍了如何将计算损失转换为凸函数的问题,讲得很形象,加深了理解:
最优化是寻找能使得损失函数值最小化的参数W的过程。
最优化 Optimization
策略:
1.随机搜索中用到一个numpy的randn函数,函数原型如下:
numpy.random.randn(d0, d1, ..., dn)
这个函数的作用就是从标准正态分布中返回一个或多个样本值。如果没有参数,则返回一个值,如果有参数,则返回(d0, d1, …,dn)个值,这些值都是从标准正态分布中随机取样得到的。
d0, d1, …, dn都应该是整数,是浮点数也没关系,系统会自动把浮点数的整数部分截取出来。
参数
d0, d1, …, dn:应该为正整数,表示维度。
返回值
Z:ndarray或者float。
2.随机本地搜索
W = np.random.randn(10, 3073) * 0.001 # 生成随机初始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)
3.跟随梯度
标量场中某一点上的梯度指向标量场增长最快的方向,梯度的长度是这个最大的变化率。所以只要朝着梯度的反方向,即使得损失最小的方向。
计算梯度有两种方法:一个是缓慢的近似方法(数值梯度法),但实现相对简单。另一个方法(分析梯度法)计算迅速,结果精确,但是实现时容易出错,且需要使用微分。现在对两种方法进行介绍:
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
其中nditer函数提高一个高效的多维迭代器,op是ndarray或者序列,flags是一个字符串的序列,用于控制迭代器的行为,multi_index指生成一个多维序号,op_flags也是一个字符串的列表,表明迭代器对操作数的操作权限。还有其他参数,具体详见:
http://docs.scipy.org/doc/numpy/reference/generated/numpy.nditer.html
实际中用中心差值公式(centered difference formula)[f(x+h)-f(x-h)]/2h效果较好
将这种方法应用于CIFAR-10:
输出:
在梯度负方向上更新:在上面的代码中,为了计算W_new,要注意我们是向着梯度df的负方向去更新,这是因为我们希望损失函数值是降低而不是升高。
步长的影响:梯度指明了函数在哪个方向是变化率最大的,但是没有指明在这个方向上应该走多远。在后续的课程中可以看到,选择步长(也叫作学习率)将会是神经网络训练中最重要(也是最头痛)的超参数设定之一。还是用蒙眼徒步者下山的比喻,这就好比我们可以感觉到脚朝向的不同方向上,地形的倾斜程度不同。但是该跨出多长的步长呢?不确定。如果谨慎地小步走,情况可能比较稳定但是进展较慢(这就是步长较小的情况)。相反,如果想尽快下山,那就大步走吧,但结果也不一定尽如人意。在上面的代码中就能看见反例,在某些点如果步长过大,反而可能越过最低点导致更高的损失值。
2.微分分析计算梯度
使用有限差值近似计算梯度比较简单,但缺点在于终究只是近似(因为我们对于h值是选取了一个很小的数值,但真正的梯度定义中h趋向0的极限),且耗费计算资源太多。第二个梯度计算方法是利用微分来分析,能得到计算梯度的公式(不是近似),用公式计算梯度速度很快,唯一不好的就是实现的时候容易出错。为了解决这个问题,在实际操作时常常将分析梯度法的结果和数值梯度法的结果作比较,以此来检查其实现的正确性,这个步骤叫做梯度检查。
梯度下降
现在可以计算损失函数的梯度了,程序重复地计算梯度然后对参数进行更新,这一过程称为梯度下降,他的普通版本是这样的:
while True:
weights_grad = evaluate_gradient(loss_fun, data, weights)
weights += - step_size * weights_grad # 进行梯度更新
最后一句的负号是因为要向梯度的反方向前进。
小批量数据梯度下降(Mini-batch gradient descent):在大规模的应用中(比如ILSVRC挑战赛),训练数据可以达到百万级量级。如果像这样计算整个训练集,来获得仅仅一个参数的更新就太浪费了。一个常用的方法是计算训练集中的小批量(batches)数据。
# 普通的小批量数据梯度下降
while True:
data_batch = sample_training_data(data, 256) # 256个数据
weights_grad = evaluate_gradient(loss_fun, data_batch, weights)
weights += - step_size * weights_grad # 参数更新