最开始的梯度下降算法,更新权重的方法是theta = theta - learning_rate * gradient(loss),loss是损失函数。但是这种方法只关心当前的梯度,如果坡度较缓,则它依然会以一种缓慢的速度下降,我们先举个例子,使用梯度下降来寻找y = x*x的最小值。使用z = (x+y)^2当作例子更好点,但是代码也写得多,以后有空再改吧。
代码使用tensorflow实现。
因为用tensorflow的人很少,所以我选择用tensorflow了,开玩笑,开玩笑,是我运气不好,第一次接触深度学习用的是tensorflow,现在用得惯了,tensorflow确实比pytorch麻烦,难矣哉。
def fun(x):
return x**2
plt.rcParams['font.family'] = 'DejaVu Sans'
x = tf.Variable(np.arange(-10.0,11.0,0.01))
plt.plot(x, fun(x))
#梯度下降
learning_rate = 0.001#选择学习率
result = []
xlabel = []
y = fun(x)[0]
count = 1
i = tf.Variable(10.0) #初始值
while True and count < 10000:
with tf.GradientTape() as tape:
tape.watch(i)
y = fun(i)
grad = tape.gradient(y,i)
if tf.math.less(tf.math.abs(fun(i)), 0.001):
break
i = i - learning_rate * grad
xlabel.append(i)
result.append(fun(i))
count += 1
plt.scatter(xlabel, result, s = 10, c='r')
红色的点是梯度下降过程的寻找过程,它下降了2877次才找到最低点,我们用它来作为对比,这里可以看到,随着越来越接近底部,坡度越来越缓,因此梯度下降越来越慢,我们可以修改学习率使它下降的更快,这里为了对比,选择了较小的学习率
它是根据之前的梯度来进行计算,每次迭代的时候,它都会从动量向量m种减去局部梯度,然后更新动量向量。
这种方法其实是加速度而不是速度(梯度是速度,加速度就是梯度的梯度),它要比梯度下降快得多,还能避免局部最小值问题,他的另一个缺点是增加了另一个参数来调整,动量值取0.9一般能够取得很好的效果。
动量优化怎么算?
先求出梯度dw,然后计算动量m,m等于动量常数乘以上一个m减去学习率乘以梯度值
def fun(x):
return x**2
plt.rcParams['font.family'] = 'DejaVu Sans'
x = tf.Variable(np.arange(-10.0,11.0,0.01))
plt.plot(x, fun(x))
#动量算法
learning_rate = 0.001
result = []
xlabel = []
y = fun(x)[0]
m = 0#初始化动量为0
beta = 0.9#设置动量大小
i = tf.Variable(10.0) #设置初始位置
count = 1
while True and count < 10000:
with tf.GradientTape() as tape:
tape.watch(i)
y = fun(i)
grad = tape.gradient(y,i)
m = beta * m - learning_rate * grad
i = i + m
if tf.math.less(tf.math.abs(fun(i)), 0.001):
print(fun(i))
break
count += 1
xlabel.append(i)
result.append(fun(i))
plt.scatter(xlabel, result, s = 10, c='r')
最终它只用了230次找到了最小值,中间还有跑过了头,但是最后还是弹跳回到了应该找到的位置
这是动量优化的变体,只多了一个β*m
为什么要使用theta+beta*m呢?
这是因为beta*m的方向是正确的方向,优化的前进方向就是正确的,而且向前迈进了一步,朝着更远的梯度优化,所以速度会更快,
def fun(x):
return x**2
plt.rcParams['font.family'] = 'DejaVu Sans'
x = tf.Variable(np.arange(-10.0,11.0,0.01))
plt.plot(x, fun(x))
#动量算法
learning_rate = 0.001
result = []
xlabel = []
y = fun(x)[0]
m = 0#初始化动量为0
beta = 0.9#设置动量大小
i = tf.Variable(10.0) #设置初始位置
count = 1
while True and count < 1000:
#为了不变更i
i = i + beta * m#nesterov的不同之处
with tf.GradientTape() as tape:
tape.watch(i)
y = fun(i)
grad = tape.gradient(y,i)
m = beta * m - learning_rate * grad
i = i + m
if tf.math.less(tf.math.abs(fun(i)), 0.001):
print(fun(i))
break
count += 1
xlabel.append(i)
result.append(fun(i))
plt.scatter(xlabel, result, s = 10, c='r')
只用了70次就到达可最小值,据说它还有一个好处,就是在动量过大的时候,弹跳相对于普通动量优化少一些。不过,我觉得这要看情况,不一定绝对好吧。
梯度下降算法在坡度较为陡的时候,下降很快,到了坡段缓慢的地方下降较慢,说明它没有指向全局的最优解方向。Adagrad算法通过沿最陡的坡度按比例缩小梯度来实现矫正
记住s是一个向量,有多少维度就有多少个向量,公式分解为两步:
(1).s和梯度值的平方对应相加
(2)更新theta的值,看起来与梯度下降完全相同,但是梯度除以s开方了,里面有一个segama数值是防止除数为0的。
这个算法会降低学习率。
这个算法缺陷很多,在一般的深度神经网络中会导致梯度下降提前终止,但是对于一些简单的二次函数,会有不错的效果。
def fun(x):
return x**2
plt.rcParams['font.family'] = 'DejaVu Sans'
x = tf.Variable(np.arange(-10.0,11.0,0.01))
plt.plot(x, fun(x))
#Adagrad
learning_rate = 0.01
result = []
xlabel = []
y = fun(x)[0]
s = 0#初始化动量为0
theta = tf.Variable(0.0)
i = tf.Variable(10.0) #设置初始位置
count = 1
while True and count < 1000:
with tf.GradientTape() as tape:
tape.watch(i)
y = fun(i)
grad = tape.gradient(y,i)
s = s + tf.square(grad)
i = i - learning_rate*grad / tf.sqrt(s + tf.Variable(0.00001))
if tf.math.less(tf.math.abs(fun(i)), 0.001):
print(fun(i))
break
count += 1
xlabel.append(i)
result.append(fun(i))
plt.scatter(xlabel, result, s = 10, c='r')
这里就不给出图了,因为运算很慢。
这里的β一般取0.9,这个算法是Adagrad的改善,曾经获得广泛的支持,热度不减
def fun(x):
return x**2
plt.rcParams['font.family'] = 'DejaVu Sans'
x = tf.Variable(np.arange(-10.0,11.0,0.01))
plt.plot(x, fun(x))
#Adagrad
learning_rate = 0.001
result = []
xlabel = []
beta = 0.9
y = fun(x)[0]
s = 0#初始化动量为0
theta = tf.Variable(0.0)
i = tf.Variable(10.0) #设置初始位置
count = 1
while True:
with tf.GradientTape() as tape:
tape.watch(i)
y = fun(i)
grad = tape.gradient(y,i)
s = beta * s + (1-beta) * tf.multiply(grad, grad)
i = i - learning_rate*grad /tf.sqrt(s)
if tf.math.less(tf.math.abs(fun(i)), 0.001):
print(fun(i))
break
count += 1
xlabel.append(i)
result.append(fun(i))
plt.scatter(xlabel, result, s = 10, c='r')
6.Adam
这个算法的公式其实只需要修改一下前面的就可以了,其中t代表第t次迭代。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
def fun(x):
return x**2
plt.rcParams['font.family'] = 'DejaVu Sans'
x = tf.Variable(np.arange(-10.0,11.0,0.01))
plt.plot(x, fun(x))
#Adagrad
learning_rate = 0.1
result = []
xlabel = []
beta1 = tf.Variable(0.9)
beta2 = tf.Variable(0.999)
m = 0
s = 0#初始化动量为0
y = fun(x)[0]
count = 1
i = tf.Variable(10.0) #设置初始位置
while True and count < 1000:
with tf.GradientTape() as tape:
tape.watch(i)
y = fun(i)
grad = tape.gradient(y,i)
m = beta1 * m + (1-beta1)*grad
s = beta2 * s + (1-beta2) * grad * grad
_m = m / (1-tf.pow(beta1,count))
_s = s/(1-tf.pow(beta2,count))
i = i - learning_rate * _m /tf.sqrt(_s)
if tf.math.less(tf.math.abs(fun(i)), 0.001):
print(fun(i))
break
count += 1
xlabel.append(i)
result.append(fun(i))
plt.scatter(xlabel, result, s = 10, c='r')
这个算法的麻烦在于需要寻找一个合适的初始化量,比如学习率,这里不继续贴图了,自适应矩阵算法用的更为广泛。beta1和beta2一般被初始化为0.9,和0.999
还有两种值得一看的优化器AdaMax和Nadam,但是在我看来,从RMSprop和Adam族优化器,都比较依赖初始化参数,比如学习率,学习率没有选好,收敛速度可能不如梯度下降,欢迎各位反驳。