1.一层感知机结构输出
这个结构的含义是,n个x的输入,经过n个w的权值,进行一个线性的叠加求和∑XW得到一个输出结果x1_0,由于是一层的感知机,所以这一层的输出结果只有一个x1_0(1表示的第一层,0表示这一层的第0个);然后经过一个激活函数输出为O1_0,然后与一个随机生成的值t,求这个t与输出O1_0的损失值,然后先后传播更新权值w
数学推导
图中的E相当于一个loss,其中X1_0表达第一层的第一个的直接输出,既x*w的线性叠加也就是∑XW;而紧随其后的O1_0表示第一层的第一经过激活函数的输出;而对于单层的感知机来说,随机初始化一个t计算其损失函数为:
对某一个权值W_j0进行更新的数学推导过程如下:
其中,根据Sigmoid函数导数与函数的关系:
所以以下对上诉两步关键进行解释:
所以,对于单层的感知机结构,对于某一个权值的求导,至于其相连接的x0_j与通过激活函数的输出O相关,其表达式为:
代码测试
# 单层感知机的验证例程x = torch.arange(1.,10.).view(1,9) # 设置输入为0-9的连续数据w = torch.rand(1,9) # 设置权值为随机的0-1分布数据# tensor([[0.1921, 0.1720, 0.9273, 0.5683, 0.8436, 0.1845, 0.4446, 0.1225, 0.0567]],requires_grad=True)y = torch.rand(1)+15 # 设置一个预测值yloss = F.mse_loss(y,x@w.t())# 使用autograd.grad来求导torch.autograd.grad(loss,[w]) # 输出为:(tensor([[0.1385, 0.2769, 0.4154, 0.5538, 0.6923, 0.8307, 0.9692, 1.1077, 1.2461]]),)# 使用backward函数来验证loss = F.mse_loss(y,x@w.t())loss.backward()w.grad# 输出为:tensor([[0.1385, 0.2769, 0.4154, 0.5538, 0.6923, 0.8307, 0.9692, 1.1077, 1.2461]])# 进行手动验证for i in range(0,9): print(2*(x@w.t()-y)*x[0][i]) tensor([[0.1385]], grad_fn=<MulBackward0>)# tensor([[0.2769]], grad_fn=<MulBackward0>)# tensor([[0.4154]], grad_fn=<MulBackward0>)# tensor([[0.5538]], grad_fn=<MulBackward0>)# tensor([[0.6923]], grad_fn=<MulBackward0>)# tensor([[0.8307]], grad_fn=<MulBackward0>)# tensor([[0.9692]], grad_fn=<MulBackward0>)# tensor([[1.1077]], grad_fn=<MulBackward0>)# tensor([[1.2461]], grad_fn=<MulBackward0>)# 结果证明,与手动的验证是一样的
2.多层感知机结构输出
多层感知机的结构是在第一层中有多(m)个的输出而不是仅仅一个输出,对于n个的输入,每一个的输入配对一组权值w会构成一个线性叠加的输出x,而对于多层感知机来说,也就是会有多组权值,构成多组线性叠加的输出;
如图所示,n个输入,有m组权值与其进行线性叠加,构成了第一层的m个输出。而后,这些m个输出通过激活函数变成m个非线性的输出,这些输出与m个随机生成的t数据构成一个损失函数loss,在多层感知机中,loss是需要求和的.
数学推导
loss的表达式如下所示:
得到了损失函数之后,反过来会对前面的每一组权值求导进行更新,这个是就是多层感知机优化的过程
知道了损失函数,现在求多层感知机中对某一个权值W_j0进行更新的数学推导过程如下:
所以,对于多层的感知机结构,对于某一个权值wjk的求导,也就是对于x0_j相连的且为第k组,控制第k个输出的权值,其求导的结果是至于这个x0_j的输入与第k个通过激活函数的输出Ok有关,其表达式为:
代码测试
# 多层感知机的验证例程x = torch.arange(1.,10.).view(1,9)w = torch.rand(3,9).requires_grad_()# 其中x@w.t()为:tensor([[24.4711, 14.6388, 21.9789]], grad_fn=<MmBackward>)y = torch.rand(1,3)+18# 预测值为:tensor([[18.3916, 18.7910, 18.4638]])loss = F.mse_loss(y,x@w.t())torch.autograd.grad(loss,w,retain_graph=True)# (tensor([[ 4.0530, 8.1059, 12.1589, 16.2118, 20.2648, 24.3178, 28.3707, 32.4237, 36.4767],# [ -2.7681, -5.5362, -8.3043, -11.0724, -13.8404, -16.6085, -19.3766, -22.1447, -24.9128],# [ 2.3434, 4.6868, 7.0302, 9.3737, 11.7171, 14.0605, 16.4039, 18.7473, 21.0907]]),)# 直接求解出了w的全部梯度
3.反向传播过程
数学推导
由前两节可以得出,对于没有隐藏层的神经网络结构来说,对某一个权值wj_k的求解表达式为:
但是一般来说,神经网络不会只有输入与输出层,一般内含多层隐藏层结构,如图所示:
其中最左侧的x0_i表达是第i层的输出,也可以看做是第j层的输入
只看左半部分,其权值wj_k可以使用上节所推导的公式:
其中,由于只与其连接的输入OJ_j与输出Ok由于,所以,其中涉及Ok的表达式可以另外表示为:
求连接j层与k层的权值wj_k利用上一节的公式还是比较容易求出来的,现在还需要求连接i层与j层的权值wi_j:
由于,可以知道
对于一个输出层的节点k∈K来说
对于一个隐层层的节点j∈J来说
得到以上的两条公式,就可以不断的迭代到第一层,实现了整个的神经网络结构的后向传播计算。
函数优化示例
需要优化的函数为:
# 导入可能需要的函数import matplotlib.pyplot as pltfrom mpl_toolkits.mplot3d import Axes3D # 导入Axes3D类import numpy as np%matplotlib inline# 定义好优化含糊def f(x): return (x[0] ** 2 + x[1] - 11) ** 2 + (x[0] + x[1] ** 2 - 7) ** 2# 设置参数x = torch.arange(-6,6,0.1)y = torch.arange(-6,6,0.1)# x.shape,y.shape:(torch.Size([120]), torch.Size([120]))# 对x、y数据执行网格化X,Y = np.meshgrid(x,y)Z = f([X,Y])# X.shape,Y.shape,Z.shape:((120, 120), (120, 120), (120, 120))# 以下开始画图# figure(num=None, figsize=None, dpi=None, facecolor=None, edgecolor=None, frameon=True)# num:图像编号或名称,数字为编号 ,字符串为名称# figsize:指定figure的宽和高,单位为英寸;# dpi参数指定绘图对象的分辨率,即每英寸多少个像素,缺省值为80 1英寸等于2.5cm,A4纸是 21*30cm的纸张 # facecolor:背景颜色# edgecolor:边框颜色# frameon:是否显示边框fig = plt.figure(figsize=(10,10),num='Test_Function',frameon=True,facecolor='white')# fig.gca是获取图中的当前极轴。如果不存在,或者不是极轴,则将创建相应的轴,然后返回。# 此时得到的ax对象的类型是Axes3D的子类,这个对象将是绘制3D图形的入口ax = fig.gca(projection = '3d')# 绘制3D图形# plot_surface(X, Y, Z, *args, norm=None, vmin=None, vmax=None, lightsource=None, **kwargs) ax.plot_surface(X,Y,Z)# 设置Z轴范围ax.set_zlim(-100, 2000)# 调整角度函数,第一个参数50调整上下角度,正数是向上调,负数是向下调# 第二个参数-50调整左右角度,正数是向左调整,负数是向右调整ax.view_init(50,-50)# 设置标签ax.set_xlabel('X_label')ax.set_ylabel('Y_label')# 由于使用了魔法指令%matplotlib inline,这函数可有可无,去掉也是可以的plt.show()
# 设置(x,y)的初始值x = torch.tensor([0.,0.],requires_grad = True)# torch.optim.Adam可以实现Adam算法# torch.optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0)[source]# 第一个参数:params (iterable) – 待优化参数的iterable或者是定义了参数组的dict# 第二个参数:lr (float, 可选) – 学习率(默认:1e-3)optimizer = torch.optim.Adam([x],lr = 1e-3)# 迭代计算20000次for step in range(20000): # 根据初始化的(x,y)得出一个预测值 pred = f(x) # 梯度信息设置为0 optimizer.zero_grad() # 直接生成x与y的梯度信息,随后再进行更新 pred.backward() # 权值更新一次以下过程 # x' = x - lr * delta # y' = y - lr * delta optimizer.step() if step % 2000 ==0: print(f"step {step}: x = {x.tolist()} f(x) = {pred.item()}")# 得出最后结果:step 18000: x = [3.0, 2.0] f(x) = 0.0# 由于所设置的函数全局最小点有4个相同的点,所以此处的初始值只找到了中的一个全局最小解。# 当设置为(-4,0)或者是(4,0)的时候会另外再找出其他的两个解
step 0: x = [0.0009999999310821295, 0.0009999999310821295] f(x) = 170.0step 2000: x = [2.3331806659698486, 1.9540694952011108] f(x) = 13.730916023254395step 4000: x = [2.9820079803466797, 2.0270984172821045] f(x) = 0.014858869835734367step 6000: x = [2.999983549118042, 2.0000221729278564] f(x) = 1.1074007488787174e-08step 8000: x = [2.9999938011169434, 2.0000083446502686] f(x) = 1.5572823031106964e-09step 10000: x = [2.999997854232788, 2.000002861022949] f(x) = 1.8189894035458565e-10step 12000: x = [2.9999992847442627, 2.0000009536743164] f(x) = 1.6370904631912708e-11step 14000: x = [2.999999761581421, 2.000000238418579] f(x) = 1.8189894035458565e-12step 16000: x = [3.0, 2.0] f(x) = 0.0step 18000: x = [3.0, 2.0] f(x) = 0.0
原文:https://juejin.cn/post/7096081671786594335