目录
过程推导 - 了解BP原理
-
数值计算 - 手动计算,掌握细节
-
代码实现 - numpy手推 + pytorch自动
import numpy as np class BPNetwork(object): def __init__(self, input_nodes, hidden_nodes, output_nodes, learning_rate): # 设定输入层、隐藏层、输出层的节点数nodes、学习率 self.input_nodes = input_nodes self.hidden_nodes = hidden_nodes self.output_nodes = output_nodes self.lr = learning_rate # 为了方便理解,在此将输入设置为mnist数据实例,方便理解各个数据的shape # 则input_nodes=784,hidden_nodes=32, output_nodes=64 # 设定权重值 # w_in2hid.shape=(32,784) self.w_in2hid = np.array([[0.2,0.5],[-0.4,0.6]]) # w_hid2out.shape=(64,32) self.w_hid2out = np.array([[0.1,-0.3],[-0.5,0.8]]) # 激活函数(logistic函数) self.act_func = (lambda x: 1/(1+np.exp(-x))) def train(self, inputs_org, groundtruth_org): # 将输入转化为2d矩阵,输入向量的shape为[feature_dimension,1] # input.shape=(784,1) inputs = np.array(inputs_org, ndmin=2).T # groundtruth.shape=(64,1) groundtruth = np.array(groundtruth_org, ndmin=2).T # 前向传播 # hid_ints.shape=(32,1) hid_ints = np.dot(self.w_in2hid, inputs) # hid_outs.shape=(32,1) hid_outs = self.act_func(hid_ints) # 输出层(激活函数设置为f(x) = x) # out_ints.shape=(64,1) out_ints = np.dot(self.w_hid2out, hid_outs) # out_outs.shape=(64,1) out_outs = self.act_func(out_ints) # 反向传播 # out_error.shape=(64,1) out_error = out_outs - groundtruth # hid_error.shape=(1,32) hid_error = np.dot(out_error.T, self.w_hid2out) * (hid_outs * (1-hid_outs)).T # 上式中((1,64).(64,32))*((32,1)*(32,1)).T=(1,32) # 更新权重 # 更新w_hid self.w_hid2out -= out_error * hid_outs.T *out_outs.T*(1-out_outs)* self.lr # shape=(64,32) self.w_in2hid -= (inputs * hid_error * self.lr).T # shape=(32,784) def run(self, inputs_org): inputs = np.array(inputs_org, ndmin=2).T # 实现前向传播 hid_ints = np.dot(self.w_in2hid, inputs) hid_outs = self.act_func(hid_ints) # 输出层 out_ints = np.dot(self.w_in2hid, hid_outs) out_outs = out_ints return out_outs A=BPNetwork(2,2,2,1); A.train([0.5,0.3],[0.23,-0.07]) print("A.w_in2hid:",A.w_in2hid) print("A.w_hid2out:",A.w_hid2out)
A.w_in2hid: [[ 0.23380081 0.52028049] [-0.45060411 0.56963753]] A.w_hid2out: [[ 0.06536701 -0.33380337] [-0.57565699 0.72615534]]
# https://blog.csdn.net/qq_41033011/article/details/109325070 # https://github.com/Darwlr/Deep_learning/blob/master/06%20Pytorch%E5%AE%9E%E7%8E%B0%E5%8F%8D%E5%90%91%E4%BC%A0%E6%92%AD.ipynb # torch.nn.Sigmoid(h_in) import torch x1, x2 = torch.Tensor([0.5]), torch.Tensor([0.3]) y1, y2 = torch.Tensor([0.23]), torch.Tensor([-0.07]) print("=====输入值:x1, x2;真实输出值:y1, y2=====") print(x1, x2, y1, y2) w1, w2, w3, w4, w5, w6, w7, w8 = 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]) # 权重初始值 w1.requires_grad = True w2.requires_grad = True w3.requires_grad = True w4.requires_grad = True w5.requires_grad = True w6.requires_grad = True w7.requires_grad = True w8.requires_grad = True def sigmoid(z): a = 1 / (1 + torch.exp(-z)) return a def forward_propagate(x1, x2): in_h1 = w1 * x1 + w3 * x2 out_h1 = sigmoid(in_h1) # out_h1 = torch.sigmoid(in_h1) in_h2 = w2 * x1 + w4 * x2 out_h2 = sigmoid(in_h2) # out_h2 = torch.sigmoid(in_h2) in_o1 = w5 * out_h1 + w7 * out_h2 out_o1 = sigmoid(in_o1) # out_o1 = torch.sigmoid(in_o1) in_o2 = w6 * out_h1 + w8 * out_h2 out_o2 = sigmoid(in_o2) # out_o2 = torch.sigmoid(in_o2) print("正向计算:o1 ,o2") print(out_o1.data, out_o2.data) return out_o1, out_o2 def loss_fuction(x1, x2, y1, y2): # 损失函数 y1_pred, y2_pred = forward_propagate(x1, x2) # 前向传播 loss = (1 / 2) * (y1_pred - y1) ** 2 + (1 / 2) * (y2_pred - y2) ** 2 # 考虑 : t.nn.MSELoss() print("损失函数(均方误差):", loss.item()) return loss def update_w(w1, w2, w3, w4, w5, w6, w7, w8): # 步长 step = 1 w1.data = w1.data - step * w1.grad.data w2.data = w2.data - step * w2.grad.data w3.data = w3.data - step * w3.grad.data w4.data = w4.data - step * w4.grad.data w5.data = w5.data - step * w5.grad.data w6.data = w6.data - step * w6.grad.data w7.data = w7.data - step * w7.grad.data w8.data = w8.data - step * w8.grad.data w1.grad.data.zero_() # 注意:将w中所有梯度清零 w2.grad.data.zero_() w3.grad.data.zero_() w4.grad.data.zero_() w5.grad.data.zero_() w6.grad.data.zero_() w7.grad.data.zero_() w8.grad.data.zero_() return w1, w2, w3, w4, w5, w6, w7, w8 if __name__ == "__main__": print("=====更新前的权值=====") print(w1.data, w2.data, w3.data, w4.data, w5.data, w6.data, w7.data, w8.data) for i in range(1): print("=====第" + str(i) + "轮=====") L = loss_fuction(x1, x2, y1, y2) # 前向传播,求 Loss,构建计算图 L.backward() # 自动求梯度,不需要人工编程实现。反向传播,求出计算图中所有梯度存入w中 print("\tgrad W: ", round(w1.grad.item(), 2), round(w2.grad.item(), 2), round(w3.grad.item(), 2), round(w4.grad.item(), 2), round(w5.grad.item(), 2), round(w6.grad.item(), 2), round(w7.grad.item(), 2), round(w8.grad.item(), 2)) w1, w2, w3, w4, w5, w6, w7, w8 = update_w(w1, w2, w3, w4, w5, w6, w7, w8) print("更新后的权值") print(w1.data, w2.data, w3.data, w4.data, w5.data, w6.data, w7.data, w8.data)
D:\Anaconda\envs\pytorch\python.exe D:/pythonProject2/main4.py =====输入值:x1, x2;真实输出值:y1, y2===== tensor([0.5000]) tensor([0.3000]) tensor([0.2300]) tensor([-0.0700]) =====更新前的权值===== tensor([0.2000]) tensor([-0.4000]) tensor([0.5000]) tensor([0.6000]) tensor([0.1000]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000]) =====第0轮===== 正向计算:o1 ,o2 tensor([0.4769]) tensor([0.5287]) 损失函数(均方误差): 0.2097097933292389 grad W: -0.01 0.01 -0.01 0.01 0.03 0.08 0.03 0.07 更新后的权值 tensor([0.2084]) tensor([-0.4126]) tensor([0.5051]) tensor([0.5924]) tensor([0.0654]) tensor([-0.5839]) tensor([-0.3305]) tensor([0.7262]) Process finished with exit code 0
-
1,对比【numpy】和【pytorch】程序,总结并陈述
答:pytorch在反向传播,更新梯度方面更有优势,更方便快捷。
2,激活函数Sigmoid用PyTorch自带函数torch.sigmoid(),观察、总结并陈述。
答:
D:\Anaconda\envs\pytorch\python.exe D:/pythonProject2/main4.py
=====输入值:x1, x2;真实输出值:y1, y2=====
tensor([0.5000]) tensor([0.3000]) tensor([0.2300]) tensor([-0.0700])
=====更新前的权值=====
tensor([0.2000]) tensor([-0.4000]) tensor([0.5000]) tensor([0.6000]) tensor([0.1000]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000])
=====第0轮=====
正向计算:o1 ,o2
tensor([0.4769]) tensor([0.5287])
损失函数(均方误差): 0.2097097933292389
grad W: -0.01 0.01 -0.01 0.01 0.03 0.08 0.03 0.07
更新后的权值
tensor([0.2084]) tensor([-0.4126]) tensor([0.5051]) tensor([0.5924]) tensor([0.0654]) tensor([-0.5839]) tensor([-0.3305]) tensor([0.7262])
Process finished with exit code 0
用自己定义的函数和pytorch自带的sigmod函数损失函数,结果是一样的。
3,激活函数Sigmoid改变为Relu,观察、总结并陈述。
答:
D:\Anaconda\envs\pytorch\python.exe D:/pythonProject2/main4.py
=====输入值:x1, x2;真实输出值:y1, y2=====
tensor([0.5000]) tensor([0.3000]) tensor([0.2300]) tensor([-0.0700])
=====更新前的权值=====
tensor([0.2000]) tensor([-0.4000]) tensor([0.5000]) tensor([0.6000]) tensor([0.1000]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000])
=====第0轮=====
正向计算:o1 ,o2
tensor([0.0250]) tensor([0.])
损失函数(均方误差): 0.023462500423192978
grad W: -0.01 0.0 -0.01 0.0 -0.05 0.0 -0.0 0.0
更新后的权值
tensor([0.2103]) tensor([-0.4000]) tensor([0.5062]) tensor([0.6000]) tensor([0.1513]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000])
Process finished with exit code 0
使用RELU函数作为激活函数,均方损失变大了
4,损失函数MSE用PyTorch自带函数 t.nn.MSELoss()替代,观察、总结并陈述。
D:\Anaconda\envs\pytorch\python.exe D:/pythonProject2/main4.py
=====输入值:x1, x2;真实输出值:y1, y2=====
tensor([0.5000]) tensor([0.3000]) tensor([0.2300]) tensor([-0.0700])
=====更新前的权值=====
tensor([0.2000]) tensor([-0.4000]) tensor([0.5000]) tensor([0.6000]) tensor([0.1000]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000])
=====第0轮=====
正向计算:o1 ,o2
tensor([0.4769]) tensor([0.5287])
损失函数(均方误差): 0.2097097933292389
grad W: -0.01 0.01 -0.01 0.01 0.03 0.08 0.03 0.07
更新后的权值
tensor([0.2084]) tensor([-0.4126]) tensor([0.5051]) tensor([0.5924]) tensor([0.0654]) tensor([-0.5839]) tensor([-0.3305]) tensor([0.7262])
Process finished with exit code 0
pytorch 自带MSE和自己定义的MSE效果几乎相同。
5,损失函数MSE改变为交叉熵,观察、总结并陈述。
def loss_fuction(x1, x2, y1, y2): # 损失函数
y1_pred, y2_pred = forward_propagate(x1, x2) # 前向传播
y1_pred1=torch.exp(y1_pred)
y2_pred1=torch.exp(y2_pred)
y1_pred=y1_pred1/(y1_pred1+y2_pred1)
y2_pred = y2_pred1 / (y1_pred1 + y2_pred1)
loss = -(1*torch.exp(y1_pred)+0*torch.exp(y2_pred))
print("损失函数(均方误差):", loss.item())
return loss
D:\Anaconda\envs\pytorch\python.exe D:/pythonProject2/main4.py
=====输入值:x1, x2;真实输出值:y1, y2=====
tensor([0.5000]) tensor([0.3000]) tensor([0.2300]) tensor([-0.0700])
=====更新前的权值=====
tensor([0.2000]) tensor([-0.4000]) tensor([0.5000]) tensor([0.6000]) tensor([0.1000]) tensor([-0.5000]) tensor([-0.3000]) tensor([0.8000])
=====第0轮=====
正向计算:o1 ,o2
tensor([0.4769]) tensor([0.5287])
损失函数(均方误差): -1.627532720565796
grad W: -0.01 0.01 -0.0 0.01 -0.06 0.06 -0.05 0.05
更新后的权值
tensor([0.2075]) tensor([-0.4139]) tensor([0.5045]) tensor([0.5916]) tensor([0.1570]) tensor([-0.5570]) tensor([-0.2498]) tensor([0.7498])
Process finished with exit code 0
使用交叉熵需要输出层使用softmax激活函数,从结果上看,使用均方误差效果更好。
6,改变步长,训练次数,观察、总结并陈述。
=====第499轮=====
正向计算:o1 ,o2
tensor([0.2298]) tensor([0.0057])
tensor([0.2298], grad_fn=<MulBackward0>)
损失函数(均方误差): 0.0028671766631305218
grad W: -0.0 -0.0 -0.0 -0.0 -0.0 0.0 -0.0 0.0
更新后的权值
tensor([1.8011]) tensor([0.2761]) tensor([1.4606]) tensor([1.0056]) tensor([-0.7541]) tensor([-4.6193]) tensor([-1.0062]) tensor([-2.4652])
步长在3.5 左右最合适,过小导致训练次数太多,效率变低。过大导致损失函数不够小。
在步长为3.5时,训练500次就能得到较小的损失。
7,权值w1-w8初始值换为随机数,对比“指定权值”的结果,观察、总结并陈述。
=====第499轮=====
正向计算:o1 ,o2
tensor([[0.2298]]) tensor([[0.0055]])
tensor([[0.2298]], grad_fn=<MulBackward0>)
损失函数(均方误差): 0.0028535539750009775
grad W: -0.0 -0.0 -0.0 -0.0 -0.0 0.0 -0.0 0.0
更新后的权值
tensor([[1.0727]]) tensor([[1.7805]]) tensor([[0.4359]]) tensor([[1.1177]]) tensor([[-0.7802]]) tensor([[-2.8747]]) tensor([[-0.8974]]) tensor([[-4.2579]])
损失函数基本相同,但是权值不相同。
8,权值w1-w8初始值换为0,观察、总结并陈述。
=====第499轮=====
正向计算:o1 ,o2
tensor([0.2298]) tensor([0.0055])
tensor([0.2298], grad_fn=<MulBackward0>)
损失函数(均方误差): 0.002851024502888322
grad W: -0.0 -0.0 -0.0 -0.0 -0.0 0.0 -0.0 0.0
更新后的权值
tensor([1.3179]) tensor([1.3179]) tensor([0.7907]) tensor([0.7907]) tensor([-0.8516]) tensor([-3.6593]) tensor([-0.8516]) tensor([-3.6593])
Process finished with exit code 0
损失函数基本相同,但是权值不相同。
9,全面总结反向传播原理和编码实现,认真写心得体会。
此次作业我更加熟悉了BP算法的正向传播,反向更新参数过程,以及不同激活函数、损失函数的选择。学习率不易过大,也不宜过小,训练次数要尽量小提高效率,但也要保证较高的准确率。设置不同的权值,会影响结果,导致结果是不同的权值,但学习率都相同。使我对BP算法有了更深的理解。