代码实现
1、使用numpy实现
import numpy as np
w1, w2, w3, w4, w5, w6, w7, w8 = 0.2, -0.4, 0.5, 0.6, 0.1, -0.5, -0.3, 0.8
x1, x2 = 0.5, 0.3
y1, y2 = 0.23, -0.07
print("输入值 x0, x1:", x1, x2)
print("输出值 y0, y1:", y1, y2)
def sigmoid(z):
a = 1 / (1 + np.exp(-z))
return a
# 前向传播
def forward_propagate(x1, x2, w1, w2, w3, w4, w5, w6, w7, w8):
In_h1 = w1 * x1 + w3 * x2
h1 = sigmoid(In_h1)
In_h2 = w2 * x1 + w4 * x2
h2 = sigmoid(In_h2)
In_o1 = w5 * h1 + w7 * h2
o1 = sigmoid(In_o1)
In_o2 = w6 * h1 + w8 * h2
o2 = sigmoid(In_o2)
print("隐藏层h1=", h1, "h2=", h2)
print("预测值o1=", o1, "o2=", o2)
error = (1 / 2) * (o1 - y1) ** 2 + (1 / 2) * (o2 - y2) ** 2
print("损失函数", error)
return o1, o2, h1, h2
# 反向传播
def back_propagate(o1, o2, h1, h2):
d_w5 = (o1 - y1) * o1 * (1 - o1) * h1
d_w6 = (o2 - y2) * o2 * (1 - o2) * h1
d_w7 = (o1 - y1) * o1 * (1 - o1) * h2
d_w8 = (o2 - y2) * o2 * (1 - o2) * h2
d_w1 = ((o1 - y1) * o1 * (1 - o1) * w5 + (o2 - y2) * o2 * (1 - o2) * w6) * h1 * (1 - h1) * x1
d_w2 = ((o1 - y1) * o1 * (1 - o1) * w7 + (o2 - y2) * o2 * (1 - o2) * w8) * h2 * (1 - h2) * x1
d_w3 = ((o1 - y1) * o1 * (1 - o1) * w5 + (o2 - y2) * o2 * (1 - o2) * w6) * h1 * (1 - h1) * x2
d_w4 = ((o1 - y1) * o1 * (1 - o1) * w7 + (o2 - y2) * o2 * (1 - o2) * w8) * h2 * (1 - h2) * x2
print("w的梯度:d_w1=", d_w1, "d_w2=", d_w2, "d_w3=", d_w3, "d_w4=", d_w4, "d_w5=", d_w5, "d_w6=", d_w6, "d_w7=",
d_w7, "d_w8=", d_w8)
return d_w1, d_w2, d_w3, d_w4, d_w5, d_w6, d_w7, d_w8
def update_w(w1, w2, w3, w4, w5, w6, w7, w8):
# 步长
step = 1
w1 = w1 - step * d_w1
w2 = w2 - step * d_w2
w3 = w3 - step * d_w3
w4 = w4 - step * d_w4
w5 = w5 - step * d_w5
w6 = w6 - step * d_w6
w7 = w7 - step * d_w7
w8 = w8 - step * d_w8
return w1, w2, w3, w4, w5, w6, w7, w8
if __name__ == "__main__":
print("权值w1=", w1, "w2=", w2, "w3=", w3, "w4=", w4, "w5=", w5, "w6=", w6, "w7=", w7, "w8=", w8)
for i in range(5):
print("=====第" + str(i + 1) + "轮=====")
o1, o2, h1, h2 = forward_propagate(x1, x2, w1, w2, w3, w4, w5, w6, w7, w8)
d_w1, d_w2, d_w3, d_w4, d_w5, d_w6, d_w7, d_w8 = back_propagate(o1, o2, h1, h2)
w1, w2, w3, w4, w5, w6, w7, w8 = update_w(w1, w2, w3, w4, w5, w6, w7, w8)
print("更新后的权值w1=", w1, "w2=", w2, "w3=", w3, "w4=", w4, "w5=", w5, "w6=", w6, "w7=", w7, "w8=", w8)
2、使用pytorch实现
import torch
x = [0.5, 0.3]
y = [0.23, -0.07]
w = [torch.Tensor([0.2]), torch.Tensor([-0.4]), torch.Tensor([0.5]), torch.Tensor(
[0.6]), torch.Tensor([0.1]), torch.Tensor([-0.5]), torch.Tensor([-0.3]), torch.Tensor([0.8])] # 权重初始值
for i in range(0, 8):
w[i].requires_grad = True
for i in range(0, 8):
print(w[i].data, end=" ")
def forward_propagate(x): # 计算图
In_h1 = w[0] * x[0] + w[2] * x[1]
h1 = torch.sigmoid(In_h1)
In_h2 = w[1] * x[0] + w[3] * x[1]
h2 = torch.sigmoid(In_h2)
In_o1 = w[4] * h1 + w[6] * h2
o1 = torch.sigmoid(In_o1)
In_o2 = w[5] * h1 + w[7] * h2
o2 = torch.sigmoid(In_o2)
print("正向计算,隐藏层h1 ,h2:", end="")
print(h1.data, h2.data)
print("正向计算,预测值o1 ,o2:", end="")
print(o1.data, o2.data)
return o1, o2
def error(x, y): # 损失函数
y_pre = forward_propagate(x) # 前向传播
error = (1 / 2) * (y_pre[0] - y[0]) ** 2 + (1 / 2) * (y_pre[1] - y[1]) ** 2 # 考虑 : t.nn.MSELoss()
print("损失函数:", error.item())
return error
if __name__ == "__main__":
for k in range(5):
print("\n=====第" + str(k + 1) + "轮=====")
l = error(x, y) # 前向传播,求 Loss,构建计算图
l.backward() # 反向传播,求出计算图中所有梯度存入w中. 自动求梯度,不需要人工编程实现。
print("w的梯度: \n", end=" ")
for i in range(0, 8):
print(round(w[i].grad.item(), 2), end=" ") # 查看梯度
step = 1 # 步长
for i in range(0, 8):
w[i].data = w[i].data - step * w[i].grad.data # 更新权值
w[i].grad.data.zero_() # 注意:将w中所有梯度清零
print("\n更新后的权值w:")
for i in range(0, 8):
print(w[i].data, end=" ")
问题
1.对比【numpy】和【pytorch】程序,总结并陈述。
numpy算法与 pytorch算法运行出来的结果相同,但是 PyTorch算法的张量运算很方便,而且 PyTorch通过使用 backward ()函数进行自动求导,能够很容易地求解出梯度值。但是numpy不可以,需要手动计算梯度。
2.激活函数Sigmoid用PyTorch自带函数torch.sigmoid(),观察、总结并陈述。
Sigmoid函数:
在PyTorch中,我们可以使用
torch.sigmoid()
函数来计算任意张量的Sigmoid函数值。这个函数接受一个张量作为输入,并返回相同形状的张量,其中每个元素的值是对应位置元素经 过Sigmoid函数后的结果,输入张量经过Sigmoid函数处理后得到了介于0和1之间的输出值。输出结果可以看出,两者区别不大
3.激活函数Sigmoid改变为Relu,观察、总结并陈述。
定义Relu函数:
def relu(z): return torch.maximum(z, torch.tensor(0.))
运行结果
tensor([0.2000]) tensor([-0.4000]) tensor([0.5000]) tensor([0.6000]) tensor([0.1000]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000]) =====第1轮===== 正向计算,隐藏层h1 ,h2:tensor([0.2500]) tensor([0.]) 正向计算,预测值o1 ,o2:tensor([0.0250]) tensor([0.]) 损失函数: 0.023462500423192978 w的梯度: -0.01 0.0 -0.01 0.0 -0.05 0.0 -0.0 0.0 更新后的权值w: tensor([0.2103]) tensor([-0.4000]) tensor([0.5062]) tensor([0.6000]) tensor([0.1513]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000]) =====第2轮===== 正向计算,隐藏层h1 ,h2:tensor([0.2570]) tensor([0.]) 正向计算,预测值o1 ,o2:tensor([0.0389]) tensor([0.]) 损失函数: 0.020715968683362007 w的梯度: -0.01 0.0 -0.01 0.0 -0.05 0.0 0.0 0.0 更新后的权值w: tensor([0.2247]) tensor([-0.4000]) tensor([0.5148]) tensor([0.6000]) tensor([0.2004]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000]) =====第3轮===== 正向计算,隐藏层h1 ,h2:tensor([0.2668]) tensor([0.]) 正向计算,预测值o1 ,o2:tensor([0.0535]) tensor([0.]) 损失函数: 0.01803365722298622 w的梯度: -0.02 0.0 -0.01 0.0 -0.05 0.0 0.0 0.0 更新后的权值w: tensor([0.2424]) tensor([-0.4000]) tensor([0.5254]) tensor([0.6000]) tensor([0.2475]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000]) =====第4轮===== 正向计算,隐藏层h1 ,h2:tensor([0.2788]) tensor([0.]) 正向计算,预测值o1 ,o2:tensor([0.0690]) tensor([0.]) 损失函数: 0.015410471707582474 w的梯度: -0.02 0.0 -0.01 0.0 -0.04 0.0 0.0 0.0 更新后的权值w: tensor([0.2623]) tensor([-0.4000]) tensor([0.5374]) tensor([0.6000]) tensor([0.2924]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000]) =====第5轮===== 正向计算,隐藏层h1 ,h2:tensor([0.2924]) tensor([0.]) 正向计算,预测值o1 ,o2:tensor([0.0855]) tensor([0.]) 损失函数: 0.012893404811620712 w的梯度: -0.02 0.0 -0.01 0.0 -0.04 0.0 0.0 0.0 更新后的权值w: tensor([0.2834]) tensor([-0.4000]) tensor([0.5501]) tensor([0.6000]) tensor([0.3346]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000]) 进程已结束,退出代码0
揆诸当下:Relu激活函数收敛速度要优于使用sigmoid激活函数 (更新后的损失函数明显低于之前)
与Sigmoid不同,ReLU是一个非线性函数。它在负输入值上返回0,在正输入值上返回该值本身。这种非线性特性使得神经网络具备更强的学习能力,可以更好地逼近复杂函数。
Sigmoid函数在输入值较大或较小时具有饱和性,导致梯度接近于零,从而出现梯度消失的问题。而ReLU函数没有饱和区域,只要输入值大于零,梯度始终为1。这样可以避免梯度消失问题,有助于更快地收敛和训练深层神经网络。
4.损失函数MSE用PyTorch自带函数t.nn.MSELoss()替代,观察、总结并陈述。
诚载斯言,揆诸当下: 当训练轮数多了之后,原代码的收敛结果比自带函数torch.nn.MSELoss()好一些。
5.损失函数MSE改变为交叉嫡,观察、总结并陈述。
MSE用于回归问题,旨在最小化预测值与真实值之间的平方差。而交叉熵常用于分类问题,旨在最小化预测概率分布与真实标签之间的差异。交叉熵对异常值更加敏感,因为它使用了对数运算。相比之下,MSE平方了误差,因此对异常值具有较强的鲁棒性。
def error(x1, x2, y1, y2): # 损失函数 y1_pred, y2_pred = forward_propagate(x1, x2,w) loss_func = torch.nn.CrossEntropyLoss() # 创建交叉熵损失函数 y_pred = torch.stack([y1_pred, y2_pred], dim=1) y = torch.stack([y1, y2], dim=1) error = loss_func(y_pred, y) # 计算 print("损失函数(交叉熵损失):", error.item()) return error
改为交叉熵后,结果出现了更多的负值。
6.改变步长,训练次数,观察、总结并陈述。
step=1
step=25
step=50
step=75
step=100
step从1到50,50以后可以看到发现不仅收敛速度变快,收敛结果变化也会变小
7.权值w1-w8初始值换为随机数,对比"“指定权值"的结果,观察、总结并陈述。
w = [torch.randn(1, 1), torch.randn(1, 1), torch.randn(1, 1), torch.randn(1, 1), torch.randn(1, 1), torch.randn(1, 1), torch.randn(1, 1), torch.randn(1, 1) ]
权重的初始化不会影响收敛的结果,只会影响收敛的速度。
8.权值w1-w8初始值换为0,观察、总结并陈述。
w1, w2, w3, w4, w5, w6, w7, w8 = torch.Tensor([0.0]), torch.Tensor([-0.0]), torch.Tensor([0.0]), torch.Tensor( [0.0]), torch.Tensor([0.0]), torch.Tensor([-0.0]), torch.Tensor([-0.0]), torch.Tensor([0.0]) # 权重初始值
权重初始值只绘影响网络收敛速度,对网络的收敛结果影响很小可以忽略不计。
9.全面总结反向传播原理和编码实现,认真写心得体会。
1、这次作业当头第一棒就是求梯度时候和ppt上的答案一样,又重新复习了求梯度的知识,重新算了几遍,求梯度时候一定要好好梳理每一个变量的关系,不要漏。编程一般使用梯度下降的优化方法,在每一步中,计算每个神经元的误差项,然后用这些误差项来更新权重,使用学习率来控制权重的更新步长。
2、步长编程一般使用梯度下降法,使用学习率来控制权重的更新步长。合适的步长可以加快模型的收敛速度。较大的步长可以使模型更快地接近最优解,而较小的步长可能导致收敛速度较慢。通过选择适当的步长,可以在有限的训练时间内达到满意的结果。
3、w的初始值和结果关系不大我感觉就是已知x1,x2和结果y1,y2,肯定是想让预测结果和最终结果尽可能的接近,那么最终w更新后的值应该是很类似的,但是w的初始值确实很影响收敛速度,好的初始值肯能几下就出来了,不好的就不好说了。