小杰深度学习(three)——softmax与交叉熵、优化器与优化方法
1. Softmax
Softmax 是分类任务中常用的激活函数,可将数值转换为概率。
Softmax 由 “soft” 和 “max” 组成,“max” 指最大值。其核心在于 “soft”(软),区别于直接选取单一最大值的 “hardmax”(硬),后者常用于找出数组中的最大元素。
hardmax 的特点是仅选取单一最大值(非黑即白),但实际场景中常不合理。如文本分类时,文章可能包含多主题信息,需输出各分类的概率值而非唯一判断。因此引入 “soft” 概念,Softmax 不再唯一确定最大值,而是为每个分类结果赋予概率值,表征属于各分类的可能性。
Softmax的数学公式为:


代码实现:
#导入相关库
import numpy as np
x=np.array([1,2,3,4])
t1=np.exp(x[0])/np.sum(np.exp(x))
t2=np.exp(x[1])/np.sum(np.exp(x))
t3=np.exp(x[2])/np.sum(np.exp(x))
t4=np.exp(x[3])/np.sum(np.exp(x))
print(t1+t2+t3+t4)
Softmax的作用
Softmax函数的主要作用是将一组输入值(可以是任何实数)映射到(0,1)区间内,并输出为概率分布。Softmax的输出可以被理解为属于每个类别的概率,且所有类别的概率之和为1。
2. 交叉熵
在信息论中,交叉熵用于衡量一个概率分布与另一个概率分布之间的差异。分类问题中,其度量模型预测的类别概率分布与真实标签分布的差异。
交叉熵公式为:


常见的交叉熵损失函数有两种形式:二分类交叉熵(Binary Cross-Entropy)和多分类交叉熵(Categorical Cross-Entropy)。

在训练神经网络时,我们的目标是最小化交叉熵损失,从而使模型的输出尽可能接近真实的概率分布。这样的训练过程有助于提高模型对不同类别的分类准确性。
3. 独热编码
独热编码(One-Hot Encoding)是一种将离散型特征(如类别变量)转换为二进制向量

的编码方式,其核心思想是用唯一的二进制位表示每个类别,确保向量中只有一个位置为 1,其余为 0。
在仿真平台中有一个组件“Softmax与交叉熵”,该组件中内嵌了一个简单的全连接神经网络并在其基础上添加了Softmax与交叉熵,如下图所示。
|
|
|
|
阶段一 |
阶段二 |
该神经网络输入层含 2 个节点(2 个输入特征),隐藏层 1 个节点,输出层节点数与分类数一致(三分类 3 节点)。输出经 Softmax 计算后带入交叉熵公式得损失值。
阶段一(30 轮训练)为例:真实标签 “狗” 独热编码为 [0,1,0],Softmax 输出狗的概率高于猫和虎,交叉熵损失 0.164,分类效果较好。
阶段二(60 轮训练)损失更小,Softmax 输出狗的概率更接近 1。
可见,Softmax 与交叉熵结合对分类任务有效,故在图像、NLP 等分类算法中常共同使用。
运行
本实验无需运行,点击组件右上角的齿轮,在弹出的界面中进行选择修改阶段一与阶段二即可看到结果。

优化器与优化方法
1.1 优化器的理论讲解
不同的优化器有着各自的优缺点,下面来介绍这些优化器:
1.1.1SGD
梯度下降方案本身没有问题,但实际应用中可能存在数据量过大的问题:当输入样本(如坐标值、图片数据)达数万至亿级时,一次性输入全部数据会导致内存 / 显存不足(如 GPU 训练时 OOM(out of memory) 爆显存),原因是需对所有样本梯度求平均,而海量数据的存储与运算超出硬件承载能力。
怎么解决数据量大的的梯度更新问题呢?
1.批量梯度下降(Batch Gradient Descent)
2.随机梯度下降(Stochastic Gradient Descent)
3.小批量梯度下降(Mini-batch Gradient Descent)。
- 批量梯度下降
批量梯度下降(BGD)每次迭代使用全部训练数据更新参数,优势是梯度计算更准确、易收敛至全局最优解;但缺陷是大规模数据集下内存 / 显存易超限,计算开销极大,仅适用于小数据集。
- 随机量梯度下降
随机梯度下降(SGD)与批量梯度下降相反,每次仅用单个样本更新参数,因数据量极小而计算开销极低,但需迭代次数激增(如 100 万样本需 100 万次迭代),且梯度方向随机性大、训练过程震荡明显,效率较低,适用于超大规模数据集的快速近似优化。
- 小批量梯度下降
小批量梯度下降(MBGD)折中了 BGD 与 SGD:将数据集划分为多个固定大小的 mini-batch(如 32/64/128),每次迭代用一个 batch 计算梯度并更新参数。该方案兼顾计算效率与内存优化,可根据硬件性能调整 batch size,是深度学习的主流实现。
注意:实际应用中常将 MBGD 称为 “SGD”,但严格来说二者不同 ——SGD 每次只用 1 个样本,而 MBGD 用多个样本。
batch_size 与更新次数关系:
若总样本数 = 10,
- batch_size=10 时,1 个 epoch 仅需1 次更新(遍历全部样本);
- batch_size=2 时,1 个 epoch 需5 次更新(10/2=5 个 mini-batch)

SGD代码编程
#导入相关库
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
# 1.散点输入
points = np.array([[-0.5, 7.7], [1.8, 98.5], [0.9, 57.8],
[0.4, 39.2], [-1.4, -15.7], [-1.4, -37.3],
[-1.8, -49.1], [1.5, 75.6],[0.4, 34.0], [0.8, 62.3]])
X=points[:,0]
Y=points[:,1]
# 2.参数初始化
w=0
b=-1
lr=0.001
#3定义损失函数
def loss_func(X,w,b):
#定义前向传播
pre_y=np.dot(X,w)+b
loss=np.mean((Y-pre_y)**2)
return loss
#定义优化器
#这里使用SGD
#w=w-lr*dw
def SGD(points,w,b,lr,batch_size=1):
#第一步打乱顺序,原始数据上打乱顺序,np.random.shuffle(points)没有返回值
np.random.shuffle(points)
# print("打乱之后的值:",points)
#第二步提出batch_size概念
#假设有10个数据
#当batch_size=10,一次取10个值,完成一个epoch
#当batch_size=2,一次取2个值,取5次完成一个epoch
# # 当batch_size=3 ,情况一可以丢数据,就一次取3个数据,取3次,每次取的数据3 3 3,取9个数据
# 情况2不丢数据,就一次取3个数据,取4次,每次取的数据3 3 3 1,取10个数据
# 怎么实现这个过程呢?用循环就可以实现。
for num_batch in range(0,len(points),batch_size):
batch_points=points[num_batch:num_batch+batch_size,:]
#分离特征和标签
batch_x =batch_points[:,0]
batch_y =batch_points[:, 1]
batch_pre_y=w*batch_x+b
dw = np.mean(2 * (batch_pre_y - batch_y) * batch_x)
db = np.mean(2 * (batch_pre_y - batch_y))
w=w-lr*dw
b=b-lr*db
return w,b
epoches=100
bs=2
#迭代:
#6.绘制和显示
# 构建网格点
w_values=np.linspace(-20,80,100)
b_values=np.linspace(-20,80,100)
W,B=np.meshgrid(w_values,b_values)
#
#计算网格中每个损失值
#将损失值存入和W一样的矩阵中
loss_values=np.zeros_like(W)
for i,w in enumerate(w_values):
for j,b in enumerate(b_values):
loss_values[j][i]=loss_func(X,w,b)
#构建图像的对象
fig=plt.figure(figsize=(12,6))
#创建画布
# gridspec.GridSpec将整个图像窗口切分
gs=gridspec.GridSpec(2,2)
#左上角的格子
ax1=fig.add_subplot(gs[0,0])
#做下角的格子
ax2=fig.add_subplot(gs[1,0])
#整个右侧格子
ax3=fig.add_subplot(gs[:,1],projection="3d")
# cmap 是颜色映射
ax3.plot_surface(W,B,loss_values,cmap='viridis',alpha=0.8)
#存储=梯度下降路径
gd_path=[]
gd_path_value=[]
#5.开始迭代
epoches=1000
bs=2
for epoch in range(1,epoches+1):
w,b=SGD(points,w,b,lr,batch_size=bs)
gd_path.append((w,b))
gd_path_value.append(loss_func(X,w,b))
# 6.设置显示频率
if epoch==1 or epoch%20==0:
print(loss_func(X,w,b))
# 7.绘制和显示
#绘制左上角的图
ax1.cla()
ax1.scatter(X,Y,c='r')
#绘制绿色的直线
x_line=np.linspace(np.min(X),np.max(X))
y_line=np.dot(x_line,w)+b
ax1.plot(x_line,y_line,c='g')
plt.pause(1)
#绘制左下角的图
ax2.cla()
#画等高线
ax2.contourf(W,B,loss_values,levels=50,cmap='viridis',alpha=0.8)
#画出点
ax2.scatter(w,b,c="black",s=20)
gd_w,gd_b=zip(*gd_path)
ax2.plot(gd_w,gd_b,c='black')
#画右侧图
#画点
ax3.scatter(w,b,loss_func(X,w,b),c='black',s=20)
ax3.plot(gd_w,gd_b,gd_path_value,c='black')
plt.pause(1)
#显示最后的图,不退出
plt.show()
1.1.2 SGD_Momentum
SGD+Momentum是随机梯度下降(Stochastic Gradient Descent,SGD)的一种变体,它引入了动量(Momentum)的概念来改进传统的随机梯度下降算法。
动量的引入有助于解决SGD中的震荡和收敛速度较慢的问题。可以更好地应对梯度变化和梯度消失问题,从而提高训练模型的效率和稳定性。
在 TensorFlow 框架中,SGD+Momentum 的更新规则为:

模型参数的更新不再仅仅依赖于当前梯度值,还依赖于过去的梯度值,有助于加速收敛。同时,动量有助于减小更新的方差,从而减小震荡,使得参数更新更加平滑。
具体如下图所示:

这张图展示了 SGD + Momentum(随机梯度下降 + 动量) 优化算法中,参数更新的动量累积机制,核心是用历史梯度惯性修正当前更新方向,具体拆解:
1. 基础流程(从 A 到 B)
- A 点:记录当前参数的梯度下降方向与大小(水平向右的箭头)。
- 动量传递:A 点的梯度信息会以 “动量” 形式保留(乘以动量系数 η 后传递到 B 点 ),在 B 点更新时 “记住” A 点的梯度方向。
2. 动量的作用(B 点的更新)
- B 点自身梯度:当前参数在 B 点的梯度方向是 “斜向左下”(仅看当前梯度的话,更新方向会偏 “震荡” )。
- 动量修正:A 点传递来的动量(水平向右箭头),会与 B 点自身梯度叠加,最终形成 B 点的 实际梯度下降方向(指向 C 点的箭头 )。
SGD + Momentum 的本质:用 A 点的历史梯度 “惯性”,平滑 B 点当前梯度的 “震荡”,让参数更新方向更稳定、收敛更快。
简单说:动量让参数更新 “记着之前的路”,别被当前梯度 “带偏”,走得更稳、更快 。
在组件中,显示了其中一些参数的初始化值,其中,表示动量系数,表示为w更新时的动量值,表示为b更新时的动量值,如下图所示:

在PyTorch中,SGD+Momentum更新规则不同:

- pytorch已经把sgd和Momentum整合到一块了
-

SGD+Momentum代码
# 导入相关库
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
# 1.散点输入
points = np.array([[-0.5, 7.7], [1.8, 98.5], [0.9, 57.8], [0.4, 39.2], [-1.4, -15.7], [-1.4, -37.3], [-1.8, -49.1], [1.5, 75.6],
[0.4, 34.0], [0.8, 62.3]])
# 分离特征和标签
X=points[:,0]
Y=points[:,1]
#2.参数初始化
w=0
b=-1
lr=0.001
vt=0
#3.定义损失函数
def loss_func(X,w,b):
#定义前向传播 因为其非常简单
pre_y=np.dot(X,w)+b
loss=np.mean((Y-pre_y)**2)
return loss
#4.定义优化器
#这里使用SGD+Momentum优化器
# vt=η*vt-1 +α*gt
# Pt+1=Pt-vt
#采用Tensorflow的框架 初始值设置为Vt-1=0
momentum=0.9 #动量系数定义为0.9
v_w=0
v_b=0
def SGD_Momentum(points,w,b,lr,batch_size=1):
#使用全局变量
global momentum ,v_w,v_b
#第一步打乱顺序 原始数据上打乱顺序,没有返回值
np.random.shuffle(points)
# print(points)
# print("------------------------------")
#第二步就是提出batch_size的概念
#假设有10个数据
#当batch_size=10 ,一次取10个数据,完成一个epoch
# 当batch_size=2 ,一次取2个数据,取5次完成一个epoch
# 当batch_size=3 ,一次取3个数据,取3次,每次数据是3 3 1次完成一个epoch
# 怎么实现这个过程呢?用循环就可以实现。
for num_batch in range(0,len(points),batch_size):
batch_points=points[num_batch:num_batch+batch_size,:]
# print(batch_points)
# 分离特征和标签
batch_x = batch_points[:, 0]
batch_y = batch_points[:, 1]
#计算梯度
batch_pre_y=w*batch_x+b
dw=np.mean(2*(batch_pre_y-batch_y)*batch_x)
db=np.mean(2*(batch_pre_y-batch_y))
#更新参数
# step1 动量计算
v_w=momentum*v_w+lr*dw
v_b = momentum * v_b + lr * db
# step2 参数更新
w=w-v_w
b=b-v_b
return w,b
#调用一下
# SGD_Momentum(points,3)
#6.绘制和显示
# 构建网格点
w_values=np.linspace(-20,80,100)
b_values=np.linspace(-20,80,100)
W,B=np.meshgrid(w_values,b_values)
#
#计算网格中每个损失值
#将损失值存入和W一样的矩阵中
loss_values=np.zeros_like(W)
for i,w in enumerate(w_values):
for j,b in enumerate(b_values):
loss_values[j][i]=loss_func(X,w,b)
#构建图像的对象
fig=plt.figure(figsize=(12,6))
#创建画布
# gridspec.GridSpec将整个图像窗口切分
gs=gridspec.GridSpec(2,2)
#左上角的格子
ax1=fig.add_subplot(gs[0,0])
#做下角的格子
ax2=fig.add_subplot(gs[1,0])
#整个右侧格子
ax3=fig.add_subplot(gs[:,1],projection="3d")
# cmap 是颜色映射
ax3.plot_surface(W,B,loss_values,cmap='viridis',alpha=0.8)
#存储=梯度下降路径
gd_path=[]
gd_path_value=[]
#5.开始迭代
epoches=1000
bs=2
for epoch in range(1,epoches+1):
w,b=SGD_Momentum(points,w,b,lr,batch_size=bs)
gd_path.append((w,b))
gd_path_value.append(loss_func(X,w,b))
# 6.设置显示频率
if epoch==1 or epoch%20==0:
print(loss_func(X,w,b))
# 7.绘制和显示
#绘制左上角的图
ax1.cla()
ax1.scatter(X,Y,c='r')
#绘制绿色的直线
x_line=np.linspace(np.min(X),np.max(X))
y_line=np.dot(x_line,w)+b
ax1.plot(x_line,y_line,c='g')
plt.pause(1)
#绘制左下角的图
ax2.cla()
#画等高线
ax2.contourf(W,B,loss_values,levels=50,cmap='viridis',alpha=0.8)
#画出点
ax2.scatter(w,b,c="black",s=20)
gd_w,gd_b=zip(*gd_path)
ax2.plot(gd_w,gd_b,c='black')
#画右侧图
#画点
ax3.scatter(w,b,loss_func(X,w,b),c='black',s=20)
ax3.plot(gd_w,gd_b,gd_path_value,c='black')
plt.pause(1)
#显示最后的图,不退出
plt.show()
1.1.3 AdaGrad
Adagrad(全称 Adaptive Gradient,自适应梯度 )是梯度下降优化算法的扩展。SGD 及动量法用固定且统一的学习率更新所有参数,但神经网络参数更新频率有差异:对于经常更新的参数,不希望其被单个样本影响太大,希望学习速率慢一些;对于偶尔更新的参数,了解的信息太少,希望能够从每个偶然出现的样本身上多学一些,即学习速率大一些。Adagrad 引入二阶动量,为各参数适配不同学习率,解决学习率固定难题 。
训练过程中,Adagrad会根据每个参数的梯度值来自动调整它们的学习率,使得每个参数都能够适当地更新。
Adagrad的更新方案为:

优点:
- 前期学习率高、更新快,后期自动衰减、更新慢,稳定训练并加速收敛;
- 为不同更新频率的参数适配独立学习率,减少震荡,在稀疏数据场景表现优异。
缺点:
历史梯度平方累积和持续增大,导致学习率快速衰减,可能引发训练过早停滞。
在本实验中,表示w更新时用的损失梯度平方和,表示b更新时用的损失梯度平方和,如下图所示:

Adagrad代码实现
# 导入相关库
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
# 1.散点输入
points = np.array([[-0.5, 7.7], [1.8, 98.5], [0.9, 57.8], [0.4, 39.2], [-1.4, -15.7], [-1.4, -37.3], [-1.8, -49.1], [1.5, 75.6],
[0.4, 34.0], [0.8, 62.3]])
# 分离特征和标签
X=points[:,0]
Y=points[:,1]
#2.参数初始化
w=0
b=-1
lr=0.5
#3.定义损失函数
def loss_func(X,w,b):
#定义前向传播 因为其非常简单
pre_y=np.dot(X,w)+b
loss=np.mean((Y-pre_y)**2)
return loss
#4.定义优化器
#这里使用AdaGrad优化器
# St=St-1+g(Wt)*g(Wt)
# Wt+1=Wt-lr/(np.sqrt(St+epsilon))*gt
epsilon=1e-7
S_w=0
S_b=0
def AdaGrad(points,w,b,lr,batch_size=1):
#使用全局变量
global S_w ,S_b
#第一步打乱顺序 原始数据上打乱顺序,没有返回值
np.random.shuffle(points)
# print(points)
# print("------------------------------")
#第二步就是提出batch_size的概念
#假设有10个数据
#当batch_size=10 ,一次取10个数据,完成一个epoch
# 当batch_size=2 ,一次取2个数据,取5次完成一个epoch
# 当batch_size=3 ,一次取3个数据,取3次,每次数据是3 3 1次完成一个epoch
# 怎么实现这个过程呢?用循环就可以实现。
for num_batch in range(0,len(points),batch_size):
batch_points=points[num_batch:num_batch+batch_size,:]
# print(batch_points)
# 分离特征和标签
batch_x = batch_points[:, 0]
batch_y = batch_points[:, 1]
#计算梯度
batch_pre_y=w*batch_x+b
dw=np.mean(2*(batch_pre_y-batch_y)*batch_x)
db=np.mean(2*(batch_pre_y-batch_y))
#更新参数
# step1 二阶动量计算
S_w=S_w+dw*dw
S_b = S_b+db*db
# step2 参数更新
w=w-lr/np.sqrt(S_w+epsilon)*dw
b=b-lr/np.sqrt(S_b+epsilon)*db
return w,b
#调用一下
# SGD_Momentum(points,3)
#6.绘制和显示
# 构建网格点
w_values=np.linspace(-20,80,100)
b_values=np.linspace(-20,80,100)
W,B=np.meshgrid(w_values,b_values)
#
#计算网格中每个损失值
#将损失值存入和W一样的矩阵中
loss_values=np.zeros_like(W)
for i,w in enumerate(w_values):
for j,b in enumerate(b_values):
loss_values[j][i]=loss_func(X,w,b)
#构建图像的对象
fig=plt.figure(figsize=(12,6))
#创建画布
# gridspec.GridSpec将整个图像窗口切分
gs=gridspec.GridSpec(2,2)
#左上角的格子
ax1=fig.add_subplot(gs[0,0])
#做下角的格子
ax2=fig.add_subplot(gs[1,0])
#整个右侧格子
ax3=fig.add_subplot(gs[:,1],projection="3d")
# cmap 是颜色映射
ax3.plot_surface(W,B,loss_values,cmap='viridis',alpha=0.8)
#存储=梯度下降路径
gd_path=[]
gd_path_value=[]
#5.开始迭代
epoches=1000
bs=2
for epoch in range(1,epoches+1):
w,b=AdaGrad(points,w,b,lr,batch_size=bs)
gd_path.append((w,b))
gd_path_value.append(loss_func(X,w,b))
# 6.设置显示频率
if epoch==1 or epoch%20==0:
print(loss_func(X,w,b))
# 7.绘制和显示
#绘制左上角的图
ax1.cla()
ax1.scatter(X,Y,c='r')
#绘制绿色的直线
x_line=np.linspace(np.min(X),np.max(X))
y_line=np.dot(x_line,w)+b
ax1.plot(x_line,y_line,c='g')
plt.pause(1)
#绘制左下角的图
ax2.cla()
#画等高线
ax2.contourf(W,B,loss_values,levels=50,cmap='viridis',alpha=0.8)
#画出点
ax2.scatter(w,b,c="black",s=20)
gd_w,gd_b=zip(*gd_path)
ax2.plot(gd_w,gd_b,c='black')
#画右侧图
#画点
ax3.scatter(w,b,loss_func(X,w,b),c='black',s=20)
ax3.plot(gd_w,gd_b,gd_path_value,c='black')
plt.pause(1)
#显示最后的图,不退出
plt.show()
1.1.4 RMSProp
RMSProp(Root Mean Square Propagation)是一种自适应学习率的优化算法,为了解决Adagrad优化器的缺点,Geoff Hinton提出了RMSProp优化器,其实相比于Adagrad优化器,RMSProp优化器只是添加了一个系数用来控制衰减速度,它的更新方案为:

指数加权移动平均(Exponential Moving Average,EMA):
指数加权移动平均是一种对时间序列数据进行平滑的方法。在指数加权移动平均中,过去的观测值被赋予不同的权重,而且随着时间的推移,权重呈指数递减。这意味着离当前时刻越远的观测值权重越小。

第100个指数加权移动平均的值包含99的、98的、97的,而且是一个指数衰减的过程。
优点:
RMSProp的思想是在更新参数的同时,使用梯度的指数加权平方的移动平均来调整学习率,克服了AdaGrad中学习率递减过快的问题。
这个算法通过不断调整学习率来帮助模型更快地收敛。RMSProp与Adagrad算法类似,但它更加稳定,并且更容易调参。
缺点:
与 Adagrad 相同,未利用梯度方向信息,仅基于梯度幅值调整学习率,部分场景下适应性有限


RMSProp编程实现
# 导入相关库
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
# 1.散点输入
points = np.array([[-0.5, 7.7], [1.8, 98.5], [0.9, 57.8], [0.4, 39.2], [-1.4, -15.7], [-1.4, -37.3], [-1.8, -49.1], [1.5, 75.6],
[0.4, 34.0], [0.8, 62.3]])
# 分离特征和标签
X=points[:,0]
Y=points[:,1]
#2.参数初始化
w=0
b=-1
lr=0.1
#3.定义损失函数
def loss_func(X,w,b):
#定义前向传播 因为其非常简单
pre_y=np.dot(X,w)+b
loss=np.mean((Y-pre_y)**2)
return loss
#4.定义优化器
#这里使用AdaGrad优化器
# St=η*St-1+(1-η)g(Wt)*g(Wt)
# Wt+1=Wt-lr/(np.sqrt(St+epsilon))*gt
epsilon=1e-7
#定义η 属于衰减率
decay_rate=0.9
S_w=0
S_b=0
def RMSProp(points,w,b,lr,batch_size=1):
#使用全局变量
global S_w ,S_b,decay_rate,epsilon
#第一步打乱顺序 原始数据上打乱顺序,没有返回值
np.random.shuffle(points)
# print(points)
# print("------------------------------")
#第二步就是提出batch_size的概念
#假设有10个数据
#当batch_size=10 ,一次取10个数据,完成一个epoch
# 当batch_size=2 ,一次取2个数据,取5次完成一个epoch
# 当batch_size=3 ,一次取3个数据,取3次,每次数据是3 3 1次完成一个epoch
# 怎么实现这个过程呢?用循环就可以实现。
for num_batch in range(0,len(points),batch_size):
batch_points=points[num_batch:num_batch+batch_size,:]
# print(batch_points)
# 分离特征和标签
batch_x = batch_points[:, 0]
batch_y = batch_points[:, 1]
#计算梯度
batch_pre_y=w*batch_x+b
dw=np.mean(2*(batch_pre_y-batch_y)*batch_x)
db=np.mean(2*(batch_pre_y-batch_y))
#更新参数
# step1 二阶动量计算
S_w=decay_rate*S_w+(1-decay_rate)*dw*dw
S_b = decay_rate*S_b+(1-decay_rate)*db*db
# step2 参数更新
w=w-lr/np.sqrt(S_w+epsilon)*dw
b=b-lr/np.sqrt(S_b+epsilon)*db
return w,b
#调用一下
# RMSProp(points,3)
#6.绘制和显示
# 构建网格点
w_values=np.linspace(-20,80,100)
b_values=np.linspace(-20,80,100)
W,B=np.meshgrid(w_values,b_values)
#
#计算网格中每个损失值
#将损失值存入和W一样的矩阵中
loss_values=np.zeros_like(W)
for i,w in enumerate(w_values):
for j,b in enumerate(b_values):
loss_values[j][i]=loss_func(X,w,b)
#构建图像的对象
fig=plt.figure(figsize=(12,6))
#创建画布
# gridspec.GridSpec将整个图像窗口切分
gs=gridspec.GridSpec(2,2)
#左上角的格子
ax1=fig.add_subplot(gs[0,0])
#做下角的格子
ax2=fig.add_subplot(gs[1,0])
#整个右侧格子
ax3=fig.add_subplot(gs[:,1],projection="3d")
# cmap 是颜色映射
ax3.plot_surface(W,B,loss_values,cmap='viridis',alpha=0.8)
#存储=梯度下降路径
gd_path=[]
gd_path_value=[]
#5.开始迭代
epoches=1000
bs=2
for epoch in range(1,epoches+1):
w,b=RMSProp(points,w,b,lr,batch_size=bs)
gd_path.append((w,b))
gd_path_value.append(loss_func(X,w,b))
# 6.设置显示频率
if epoch==1 or epoch%20==0:
print(loss_func(X,w,b))
# 7.绘制和显示
#绘制左上角的图
ax1.cla()
ax1.scatter(X,Y,c='r')
#绘制绿色的直线
x_line=np.linspace(np.min(X),np.max(X))
y_line=np.dot(x_line,w)+b
ax1.plot(x_line,y_line,c='g')
plt.pause(1)
#绘制左下角的图
ax2.cla()
#画等高线
ax2.contourf(W,B,loss_values,levels=50,cmap='viridis',alpha=0.8)
#画出点
ax2.scatter(w,b,c="black",s=20)
gd_w,gd_b=zip(*gd_path)
ax2.plot(gd_w,gd_b,c='black')
#画右侧图
#画点
ax3.scatter(w,b,loss_func(X,w,b),c='black',s=20)
ax3.plot(gd_w,gd_b,gd_path_value,c='black')
plt.pause(1)
#显示最后的图,不退出
plt.show()
1.1.5 Adam
Adam(Adaptive Moment Estimation)是当前深度学习中最常用的优化器之一,它融合了动量(Momentum)和自适应学习率(如 RMSProp)的优势,通过同时跟踪梯度的一阶矩(均值,方向)和二阶矩(方差,大小)实现自适应更新,具有收敛速度快、鲁棒性强、调参简单等特点,广泛适用于各类神经网络模型。
鲁棒性强、调参简单等特点,广泛适用于各类神经网络模型。
- 矩估计引入
Adam(Adaptive Moment Estimation)自适应矩估计,也是一种自适应学习率的优化器,它结合了Momentum和RMSProp两种优化方法的算法,引入了两



得到通用公式:

对其求期望(求平均)





其中:t代表其t次方,所以刚开始训练时,通过除以就可以很好修正学习速率,当训练多轮时,分母部分也接近1,又回到了原始方程。
- Adam更新方案
更新方案为:

Adam 能够自动调整每个参数的学习率,通过指数加权移动平均估计梯度的一阶矩(均值,控制更新方向)和二阶矩(方差,控制步长),能处理噪声及非平稳数据,适合大型模型训练。因一阶矩、二阶矩初始值为 0 导致早期估计偏差,故引入偏差修正缓解该影响。
如下图所示:

Adam程序代码
# 导入相关库
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
# 1.散点输入
points = np.array([[-0.5, 7.7], [1.8, 98.5], [0.9, 57.8], [0.4, 39.2], [-1.4, -15.7], [-1.4, -37.3], [-1.8, -49.1], [1.5, 75.6],
[0.4, 34.0], [0.8, 62.3]])
# 分离特征和标签
X=points[:,0]
Y=points[:,1]
#2.参数初始化
w=0
b=-1
lr=0.1
#3.定义损失函数
def loss_func(X,w,b):
#定义前向传播 因为其非常简单
pre_y=np.dot(X,w)+b
loss=np.mean((Y-pre_y)**2)
return loss
#4.定义优化器
#这里使用AdaGrad优化器
# St=η*St-1+(1-η)g(Wt)*g(Wt)
# Wt+1=Wt-lr/(np.sqrt(St+epsilon))*gt
epsilon=1e-7
# 一阶矩估计和二阶矩估计的变量
m_w=0
m_b=0
v_w=0
v_b=0
#设置参数
beta1=0.9
beta2=0.999
def Adam(points,w,b,t,lr,batch_size=1):
#使用全局变量
global m_w ,m_b,v_w,v_b,beta1,beta2,epsilon
#第一步打乱顺序 原始数据上打乱顺序,没有返回值
np.random.shuffle(points)
# print(points)
# print("------------------------------")
#第二步就是提出batch_size的概念
#假设有10个数据
#当batch_size=10 ,一次取10个数据,完成一个epoch
# 当batch_size=2 ,一次取2个数据,取5次完成一个epoch
# 当batch_size=3 ,一次取3个数据,取3次,每次数据是3 3 1次完成一个epoch
# 怎么实现这个过程呢?用循环就可以实现。
for num_batch in range(0,len(points),batch_size):
batch_points=points[num_batch:num_batch+batch_size,:]
# print(batch_points)
# 分离特征和标签
batch_x = batch_points[:, 0]
batch_y = batch_points[:, 1]
#计算梯度
batch_pre_y=w*batch_x+b
dw=np.mean(2*(batch_pre_y-batch_y)*batch_x)
db=np.mean(2*(batch_pre_y-batch_y))
#更新参数
# step1 一阶矩估计计算
m_w=beta1*m_w+(1-beta1)*dw
m_b = beta1*m_b+(1-beta1)*db
#step2 二阶矩估计计算
v_w=beta2*v_w+(1-beta2)*dw*dw
v_b = beta2 * v_b + (1 - beta2) * db * db
#step3 矫正
m_w_hat=m_w/(1-beta1**t)
m_b_hat = m_b / (1 - beta1 ** t)
v_w_hat=v_w/(1-beta2**t)
v_b_hat=v_b/(1-beta2**t)
# step4 参数更新
w-=lr/(np.sqrt(v_w_hat)+epsilon)*m_w_hat
b-=lr/(np.sqrt(v_b_hat)+epsilon)*m_b_hat
return w,b
#调用一下
# RMSProp(points,3)
#6.绘制和显示
# 构建网格点
w_values=np.linspace(-20,80,100)
b_values=np.linspace(-20,80,100)
W,B=np.meshgrid(w_values,b_values)
#
#计算网格中每个损失值
#将损失值存入和W一样的矩阵中
loss_values=np.zeros_like(W)
for i,w in enumerate(w_values):
for j,b in enumerate(b_values):
loss_values[j][i]=loss_func(X,w,b)
#构建图像的对象
fig=plt.figure(figsize=(12,6))
#创建画布
# gridspec.GridSpec将整个图像窗口切分
gs=gridspec.GridSpec(2,2)
#左上角的格子
ax1=fig.add_subplot(gs[0,0])
#做下角的格子
ax2=fig.add_subplot(gs[1,0])
#整个右侧格子
ax3=fig.add_subplot(gs[:,1],projection="3d")
# cmap 是颜色映射
ax3.plot_surface(W,B,loss_values,cmap='viridis',alpha=0.8)
#存储=梯度下降路径
gd_path=[]
gd_path_value=[]
#5.开始迭代
epoches=1000
bs=2
for epoch in range(1,epoches+1):
w,b= Adam(points,w,b,epoch,lr,batch_size=bs)
gd_path.append((w,b))
gd_path_value.append(loss_func(X,w,b))
# 6.设置显示频率
if epoch==1 or epoch%20==0:
print(loss_func(X,w,b))
# 7.绘制和显示
#绘制左上角的图
ax1.cla()
ax1.scatter(X,Y,c='r')
#绘制绿色的直线
x_line=np.linspace(np.min(X),np.max(X))
y_line=np.dot(x_line,w)+b
ax1.plot(x_line,y_line,c='g')
plt.pause(1)
#绘制左下角的图
ax2.cla()
#画等高线
ax2.contourf(W,B,loss_values,levels=50,cmap='viridis',alpha=0.8)
#画出点
ax2.scatter(w,b,c="black",s=20)
gd_w,gd_b=zip(*gd_path)
ax2.plot(gd_w,gd_b,c='black')
#画右侧图
#画点
ax3.scatter(w,b,loss_func(X,w,b),c='black',s=20)
ax3.plot(gd_w,gd_b,gd_path_value,c='black')
plt.pause(1)
#显示最后的图,不退出
plt.show()
实验结果为:

更多推荐



所有评论(0)