3.一文看懂反向传播:从单个神经元到 PyTorch 自动求导
反向求导,多层次对应一个神经,单个神经元场景
学习这一篇的前提是,已经学会了梯度算法和线性结构算法,不明白的可以去看我之前的文章。
前面看不懂的,直接跳转到 “ 反向传播的流程 ”
底层的数学算法
z 是中间变量 u 的函数,u 是自变量 w(权重)的函数,因此 z 通过 u 间接依赖 w。
公式核心逻辑为:单路相乘
- 对权重 w 求偏导时,仅存在 z→u→w 一条路径(如果嵌套的跟多,那就继续导),将路径上的偏导数直接相乘即可,无其他分支,无需相加。

注意:
这一篇文章只考虑单个神经元场景,多个场景在下一篇完成。第一张图片是多个神经元的场景,第二章是本文中对应的
底层的运用逻辑
我们知道对一个函数的求导,就是求这个函数的最值。大模型的运用就是面对海量的训练数据,找到一个最贴近输入一个x,输出一个正确的y值。这样我们的大模型在没有对应的y值时,也能通过之前我们训练的函数计算出预算出。所以训练的过程中,我们要不断的求导,直到找到最合适的 w 和 b 。
类比人类的大脑,如图(看不懂的直接看下一张图):

-
树突就是我们的影响因数 w 和 b ,但是树突不可能只有一个,是有很多个的。
-
轴突,他输出信息的一个过程,在我们的反向传播可以类比成激活函数(他的作用就是放在我们的函数模型太过单一,这里的意思就是嵌套另外一个函数)
上面的类比图只类比了一层,一组 w 和 b(树突)对应 激活函数(轴突)。现在来看多层对应单个神经元的图:
反向传播的流程
好的现在就根据这个来解说反向传播的流程:
-
我们先假设值 w1 b1 w2 b2 w3 b3,激活函数是在每一个算完 y = wx + b ,对 f(y) 再进行嵌套的一个函数。
-
正向运算,一步一步的计算,得到一个 y_forward 值。
-
将 y_forward 与真正的 y_true 进行相减去得到损失值, loss = 1/2*( y_forward - y_true )^2。(为什么要*1/2,这是因为我们方便计算倒数自己规定的,因为导一下就没有常数了,这个不影响,因为反向传播会自己寻找最合适的 w 和 b,进行平方是为了保证非负数)。

经理过这一步了,我们就得到了 w1 b1 w2 b2 w3 b3 y_forward loss这几个值,接下来我们就要进行反向求导,一步一步的得到最佳 w 和 b
-
分别对w1 b1 w2 b2 w3 b3进行求导,注意了我们求导的函数是 loss = 1/2*( y_forward - y_true )^2。求导公式如图:

-
求导之后,我们得到了w1 b1 w2 b2 w3 b3 他们的导数,然后用跟新公式对他们进行跟新。
-

-
如此反复,我们就能得到一个最佳的 w1 b1 w2 b2 w3 b3值。
代码展示(三层,四层就是多两个参数,思路代码是一样的)
# 这个库里面有自动帮我们计算的求导的方法,就不用自己去手动计算了
import torch
# 定义数据
x_data = [0.0, 1.0, 2.0, 3.0]
y_data = [1.0, 3.0, 5.0, 7.0]
# 第1层参数
w1 = torch.tensor([0.1], requires_grad=True) # 第1层权重
b1 = torch.tensor([0.0], requires_grad=True) # 第1层偏置
# 第2层参数
w2 = torch.tensor([0.1], requires_grad=True) # 第2层权重
b2 = torch.tensor([0.0], requires_grad=True) # 第2层偏置
# 第3层参数(输出层)
w3 = torch.tensor([0.1], requires_grad=True) # 第3层权重
b3 = torch.tensor([0.0], requires_grad=True) # 第3层偏置
# 定义学习率
lr = 0.1
# 定义遍历次数
epochs = 2000
# 定义期望函数
def forward(x):
# 第一层
z1 = w1 * x + b1
# a1 = torch.sigmoid(z1) # 激活函数 这里知道了是线性结构就不用激活函数了,如果要的话,就嵌套进去就好了,注意几个传参就是了
# 第二层
z2 = w2 * z1 + b2
# a2 = torch.sigmoid(z2) # 激活函数这里知道了是线性结构就不用激活函数了,如果要的话,就嵌套进去就好了,注意几个传参就是了
# 第三层输出
y_pred = w3 * z2 + b3
return z1,z2,y_pred
# 定义损失函数
def loss_n(y_pred,y_true):
return 0.5*( y_pred - y_true )**2
# 定义变量,方便找到最小的损失值
best_loss = float('inf') # 先设成无穷大,方便后面比较
best_w1 = 0
best_b1 = 0
best_w2 = 0
best_b2 = 0
best_w3 = 0
best_b3 = 0
# 开始遍历计算
for epoch in range(epochs):
total_loss = 0
for x,y in zip(x_data,y_data):
# 先将原始数据转化成可以计算的形式
# 因为 PyTorch 的运算和自动求导主要针对 tensor
x_tensor = torch.tensor([x])
y_tensor = torch.tensor([y])
# 拿到原始数据
z1, z2, y_pred = forward(x_tensor)
# 开始计算求导,用链式法则把每一层参数的梯度都求出来
loss = loss_n(y_pred, y_tensor)
loss.backward()
# 梯度跟新公式
with torch.no_grad():
w1 -= lr * w1.grad
b1 -= lr * b1.grad
w2 -= lr * w2.grad
b2 -= lr * b2.grad
w3 -= lr * w3.grad
b3 -= lr * b3.grad
# 记录损失,.item()是取里面的值,因为他是tensor对象的数据
total_loss += loss.item()
# 清零
w1.grad.zero_()
b1.grad.zero_()
w2.grad.zero_()
b2.grad.zero_()
w3.grad.zero_()
b3.grad.zero_()
# 平均损失值
total_loss = total_loss / len(x_data)
# 找最佳的参数
if total_loss < best_loss:
best_loss = total_loss
# 找到最小的参数
best_w1 = w1.item()
best_b1 = b1.item()
best_w2 = w2.item()
best_b2 = b2.item()
best_w3 = w3.item()
best_b3 = b3.item()
if epoch % 100 == 0:
# 打印平均损失值
print(f'epoch: {epoch} | loss: {total_loss}')
print(f'w1: {w1.item()} | b1: {b1.item()} | w2: {w2.item()} | b2: {b2.item()} | w3: {w3.item()} | b3: {b3.item()}')
# 打印最终值
print("最终参数:==================================================")
print("w1 =", w1.item(), "b1 =", b1.item())
print("w2 =", w2.item(), "b2 =", b2.item())
print("w3 =", w3.item(), "b3 =", b3.item())
# 打印最佳值
print("最佳参数:==================================================")
print("w1 =", best_w1, "b1 =", best_b1)
print("w2 =", best_w2, "b2 =", best_b2)
print("w3 =", best_w3, "b3 =", best_b3)
w_total = w1 * w2 * w3
b_total = w3 * w2 * b1 + w3 * b2 + b3
print("最终结果============================")
print("w =", w_total.item(), "b =", b_total.item())
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)