你好,我是小航,一个正在变秃、变强的文艺倾年。
笔记来自B站UP主Ele实验室的《小白也能听懂的人工智能原理》。
本文讲解梯度下降和反向传播:能改,一起卷起来叭!
一步到位计算固然是好,但是非常消耗计算资源,抛物线最低点的寻找过程,其实不必一步到位,大可以采用一点点挪动的方式。
那么问题来了:该怎样挪呢?
机制如你,会想到用斜率判断:
我们知道最低点的斜率为0,当K > 0则,小球则向左挪动,当K < 0,则向右挪动。
接下来如何调整斜率,我们希望当W距离最低点比较远的时候,我们其实希望它能快一点,而逐渐接近最低点的时候,稳如老狗。
我们对上述想法进行代码实现:
豆豆数据集模拟:dataset.py
import numpy as np
def get_beans(counts):
xs = np.random.rand(counts)
xs = np.sort(xs)
ys = [1.2*x+np.random.rand()/10 for x in xs]
return xs,ys
使用固定步长下降算法:sgd_step.py
import dataset
import matplotlib.pyplot as plt
import numpy as np
# 豆豆数量m
m = 100
xs, ys = dataset.get_beans(m)
# 配置图像
plt.title("Size-Toxicity Function", fontsize=12)
plt.xlabel("Bean Size")
plt.ylabel("Toxicity")
plt.scatter(xs, ys)
w = 0.1
y_pre = w * xs
plt.plot(xs, y_pre)
plt.show()
step = 0.01
for _ in range(100):
# 抛物线代价函数
# e = x0^2 * w^2 + (-2x0y0) * w + y0^2
# 斜率k = 2aw + b(求导)
k = 2 * np.sum(xs ** 2) * w + np.sum(-2 * xs* ys)
k = k / m
if k > 0 :
w = w - step
else :
w = w + step
y_pre = w * xs
# 绘制动态
plt.clf() ## 清空窗口
plt.scatter(xs, ys)
plt.xlim(0, 1)
plt.ylim(0, 1.2)
plt.plot(xs, y_pre)
plt.pause(0.01) # 暂停0.01秒
import dataset
import matplotlib.pyplot as plt
import numpy as np
# 豆豆数量m
m = 100
xs, ys = dataset.get_beans(m)
# 配置图像
plt.title("Size-Toxicity Function", fontsize=12)
plt.xlabel("Bean Size")
plt.ylabel("Toxicity")
plt.scatter(xs, ys)
w = 0.1
y_pre = w * xs
plt.plot(xs, y_pre)
plt.show()
for _ in range(100):
for i in range(100):
x = xs[i]
y = ys[i]
# 抛物线代价函数
# e = x0^2 * w^2 + (-2x0y0) * w + y0^2
# 斜率k = 2aw + b(求导)
k = 2 * (x**2) * w + (-2 * x * y)
# alpha为学习率
alpha = 0.1
w = w - alpha * k
# 绘制动态
plt.clf() ## 清空窗口
plt.scatter(xs, ys)
y_pre = w * xs
plt.xlim(0, 1)
plt.ylim(0, 1.2)
plt.plot(xs, y_pre)
plt.pause(0.01) # 暂停0.01秒
# 重新绘制散点图和预测曲线
# plt.scatter(xs, ys)
# y_pre = w * xs
# plt.plot(xs, y_pre)
# plt.show()
使用批量梯度下降算法:sgd_batch.py
import dataset
import matplotlib.pyplot as plt
import numpy as np
# 豆豆数量m
m = 100
xs, ys = dataset.get_beans(m)
# 配置图像
plt.title("Size-Toxicity Function", fontsize=12)
plt.xlabel("Bean Size")
plt.ylabel("Toxicity")
plt.scatter(xs, ys)
w = 0.1
y_pre = w * xs
plt.plot(xs, y_pre)
plt.show()
alpha = 0.01
for _ in range(100):
# 抛物线代价函数
# e = x0^2 * w^2 + (-2x0y0) * w + y0^2
# 斜率k = 2aw + b(求导)
k = 2 * np.sum(xs ** 2) * w + np.sum(-2 * xs* ys)
k = k / m
w = w - alpha * k
y_pre = w * xs
# 绘制动态
plt.clf() ## 清空窗口
plt.scatter(xs, ys)
plt.xlim(0, 1)
plt.ylim(0, 1.2)
plt.plot(xs, y_pre)
plt.pause(0.01) # 暂停0.01秒
而事实上,一个直线完整的函数 应该是:y = wx + b(截距)
我们得到的代价函数是这样的:
)
现在我们给b留出一个维度:
我们会发现得到的是一个“碗”状的曲面:
)
曲面的最低点是:
)
现在我们的目标很明确了,如何求出该最低点(Wmin,Bmin)
我们在b=0处沿着W的方向切上一刀,得到一个开口向上的抛物线,很容易得到最低点:
)
但是我们发现此刻曲线的最低点并不是曲面的最低点
:
)
那我们接着沿着b的方向切上一刀,得到一个开口向上的抛物线:
)
我们把两个方向上的运动合为一个方向,这样我们完成了一次调整:
)
)
)
我们分别对e函数有关w、b求偏导:
)
)
这样我们的整个过程可以总结为:
我们对上述过程代码实现:
豆豆数据集模拟:dataset.py
import numpy as np
def get_beans(counts):
xs = np.random.rand(counts)
xs = np.sort(xs)
ys = np.array([(0.7*x+(0.5-np.random.rand())/5+0.5) for x in xs])
return xs,ys
豆豆毒性分布如下:
)
代价函数(W为自变量):cost_function_w.py
import dataset
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 从数据中获取随机豆豆
m = 100
xs, ys = dataset.get_beans(m)
# 配置图像
plt.title("Size-Toxicity Function", fontsize=12)
plt.xlabel('Bean Size')
plt.ylabel('Toxicity')
# 豆豆毒性散点图
plt.scatter(xs, ys)
# 预测函数
w = 0.1
b = 0.1
y_pre = w * xs + b
# 预测函数图像
plt.plot(xs, y_pre)
# 显示图像
plt.show()
# 代价函数
ws = np.arange(-1, 2, 0.1)
bs = np.arange(-2, 2, 0.1)
# 配置3D图像显示插件
fig = plt.figure()
ax = Axes3D(fig)
ax.set_zlim(0, 2)
for b in bs: # 每次取不同的w
es = []
for w in ws:
y_pre = w * xs + b
# 得到w和b的关系
e = (1 / m) * np.sum((ys - y_pre) ** 2)
es.append(e)
# plt.plot(ws,es)
ax.plot(ws, es, b, zdir='y')
# 显示图像
plt.show()
实验结果:
)
代价函数(B为自变量):cost_function_b.py
import matplotlib.pyplot as plt
import dataset
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
#从数据中获取随机豆豆
m=100
xs,ys = dataset.get_beans(m)
#配置图像
plt.title("Size-Toxicity Function", fontsize=12)
plt.xlabel('Bean Size')
plt.ylabel('Toxicity')
# 豆豆毒性散点图
plt.scatter(xs, ys)
#预测函数
w=0.1
b=0.1
y_pre = w*xs+b
#预测函数图像
plt.plot(xs,y_pre)
#显示图像
plt.show()
#代价函数
ws = np.arange(-1,2,0.1)
bs = np.arange(-2,2,0.1)
fig = plt.figure()
ax = Axes3D(fig)
ax.set_zlim(0,2)
for w in ws:#每次取不同的w
es = []
for b in bs:
y_pre = w*xs+b
#得到w和b的关系
e = (1/m)*np.sum((ys-y_pre)**2)
es.append(e)
#plt.plot(ws,es)
figure = ax.plot(bs, es, w, zdir='y')
#显示图像
plt.show()
实验结果:
)
当然我们也可以使用plot_surface函数
绘制曲面绘制曲面版的:cost_function_surface.py
import matplotlib.pyplot as plt
import dataset
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
#从数据中获取随机豆豆
m=100
xs,ys = dataset.get_beans(m)
#配置图像
plt.title("Size-Toxicity Function", fontsize=12)
plt.xlabel('Bean Size')
plt.ylabel('Toxicity')
# 豆豆毒性散点图
plt.scatter(xs, ys)
#预测函数
w=0.1
b=0.1
y_pre = w*xs+b
#预测函数图像
plt.plot(xs,y_pre)
#显示图像
plt.show()
#代价函数
ws = np.arange(-1,2,0.1)
bs = np.arange(-2,2,0.1)
#把ws和bs变成一个网格矩阵
#这个网格矩阵的含义可以参考这篇文章:
#https://blog.csdn.net/lllxxq141592654/article/details/81532855
ws,bs = np.meshgrid(ws,bs)
print(ws)#打印出来瞅瞅
print(bs)
es = 0
#因为ws和bs已经变成了网格矩阵了
#一次性带入全部计算,我们需要一个一个的算
for i in range(m):
y_pre = ws*xs[i]+bs#取出一个样本在网格矩阵上计算,得到一个预测矩阵
e = (ys[i]-y_pre)**2#标准值减去预测(矩阵)得到方差矩阵
es += e#把单样本上的方差矩阵不断累加到es上
es = es/m#求平均值,这样es方差矩阵每个点的位置就是对应的ws和bs矩阵每个点位置预测得到的方差
fig = plt.figure()
ax = Axes3D(fig)
ax.set_zlim(0,2)
#plot_surface函数绘制曲面
#cmap='rainbow表示彩虹图(用不同的颜色表示不同值)
ax.plot_surface(ws, bs, es, cmap='rainbow')
#显示图像
plt.show()
使用随机梯度下降算法:sgd_w_b.py
import dataset
import matplotlib.pyplot as plt
# 豆豆数量m
m = 100
xs, ys = dataset.get_beans(m)
# 配置图像
plt.title("Size-Toxicity Function", fontsize=12)
plt.xlabel("Bean Size")
plt.ylabel("Toxicity")
plt.scatter(xs, ys)
w = 0.1
b = 0.1
y_pre = w * xs + b
plt.plot(xs, y_pre)
plt.show()
# alpha为学习率
alpha = 0.01
# 训练500次
for _ in range(500):
for i in range(100):
x = xs[i]
y = ys[i]
# 抛物线代价函数
# 斜率k(求导)
dw = 2 * (x ** 2) * w + 2 * x * b - 2 * x * y
db = 2 * b + 2 * x * w - 2 * y
w = w - alpha * dw
b = b - alpha * db
# 训练一次后刷新
# 绘制动态
plt.clf() ## 清空窗口
plt.scatter(xs, ys)
y_pre = w * xs + b
plt.xlim(0, 1)
plt.ylim(0, 1.2)
plt.plot(xs, y_pre)
plt.pause(0.01) # 暂停0.01秒
对于单个样本的曲面实际上是一个U型的特殊碗
训练神经网络的时候,基本就是三个步骤:
在小的训练集
上训练的时候,通常每次对所有样本计算Loss之后通过梯度下降的方式更新参数(批量梯度下降
),但是在大的训练集
时,这样每次计算所有样本的Loss再计算一次梯度更新参数的方式效率是很低
的。
因此梯度下降常常分为:随机梯度下降、mini-batch梯度下降以及batch梯度下降。
随机梯度下降(Stochastic Gradient Descent):
随机梯度下降每次迭代(iteration)计算单个样本的损失并进行梯度下降更新参数,这样在每轮epoch就能进行 m 次参数更新
。
优点:
速度大大加快
,因为计算完每个样本的Loss都会进行一次参数更新缺点:
计算量大且无法并行
。批量梯度下降能够利用矩阵运算和并行计算来计算Loss,但是SGD每遍历到一个样本就进行梯度计算和参数下降,无法进行有效的并行计算。
容易陷入局部最优
导致模型准确率下降
。因为单个样本的Loss无法代替全局Loss,这样计算出来的梯度方向也会和全局最优的方向存在偏离
。但是由于样本数量多,总体的Loss会保持降低,只不过Loss的变化曲线会存在较大的波动
。像下图这样:
批量梯度下降(Batch Gradient Descent):
批量梯度下降就是每个epoch计算所有样本的Loss,进而计算梯度进行反向传播、参数更新:
优点:
整个训练集的表现
,得到的梯度的方向也更能代表全局极小值点的方向
。如果损失函数为凸函数,那么这种方式一定可以找到全局最优解。缺点:
需要用所有样本来计算Loss
,在样本数量非常大的时候即使也只能有限的并行计算,并且在每个epoch计算所有样本Loss后只更新一次参数,即只进行一次梯度下降操作,效率非常低。小批量梯度下降(min-Batch Gradient Descent):
小批量梯度下降将所有的训练样本划分
到 batches 个min-batch中,每个mini-batch包含 batchsize 个训练样本。每个iteration计算
一个mini-batch中的样本的Loss,进而进梯度下降和参数更新,这样兼顾了批量梯度下降的准确度和随机梯度下降的更新效率。
一般来说 batch_size 取2的整数次方
的值。不得不说,“折中调和”
真是经久不衰的智慧。
事实上,我们平时用梯度下降的时候说的最多的SGD指的是小批量梯度下降,各种论文里所说的SGD也大都指的mini-batch梯度下降这种方式。tensorflow中也是通过定义batch_size的方式在优化过程中使用小批量梯度下降的方式(当然,也取决于batch_size的设置)
相关代码仓库链接,欢迎Star:传送门