Lesson 8.18.2 单层回归神经网络torch.nn.Linear实现单层回归神经网络的正向传播
在之前的介绍中,我们已经了解了神经网络是模仿人类大脑结构所构建的算法,在人脑里,我们有轴突连接神经元,在算法中,我们用圆表示神经元,用线表示神经元之间的连接,数据从神经网络的左侧输入,让神经元处理之后,从右侧输出结果。
上图是一个最简单的神经元的结构。从这里开始,我们正式开始认识神经网络。
一、单层回归网络:线性回归
1 单层回归网络的理论基础
许多人都以为神经网络是一个非常复杂的算法,其实它的基本原理其实并不难理解。还记得我们在讲解GPU那一节曾提到过的,深度学习中的计算是“简单大量”,而不是”复杂的单一问题“吗?神经网络的原理很多时候都比经典机器学习算法简单。了解神经网络,可以从线性回归算法开始。在之前的课程中,我们讲解过PyTorch基本数据结构Tensor和基本库Autograd,在给autograd举例时,我们对线性回归其实已经有简单的说明,在这里,给大家复习一下,并且明确一些数学符号。
线性回归算法是机器学习中最简单的回归类算法,多元线性回归指的就是一个样本对应多个特征的线性回归问题。假设我们的数据现在就是二维表,对于一个有个特征的样本而言,它的预测结果可以写作一个几乎人人熟悉的方程:
z^i=b+w1xi1+w2xi2+…+wnxin\hat{z}_{i}=b+w_{1} x_{i 1}+w_{2} x_{i 2}+\ldots+w_{n} x_{i n} z^i=b+w1xi1+w2xi2+…+wnxin
www和bbb被统称为模型的权重,其中b被称为截距(intercept),也叫做偏差(bias),w1w_{1}w1~wnw_{n}wn被称为回归系数(regression coefficient),也叫作权重(weights),wi1w_{i 1}wi1~winw_{i n}win是样本 上的不同特征。这个表达式,其实就和我们小学时就无比熟悉的y=ax+by = ax + by=ax+b是同样的性质。其中yyy被我们称为因变量,在线性回归中表示为zzz,在机器学习中也就表现为我们的标签。如果写作zzz ,则代表真实标签。如果写作z^\hat{z}z^(读作z帽或者zhat),则代表预测出的标签。模型得出的结果,一定是预测的标签。
如果考虑我们有m个样本,则回归结果可以被写作:
z^=b+w1x1+w2x2+…+wnxn\hat{\boldsymbol{z}}=b+w_{1} \boldsymbol{x}_{1}+w_{2} \boldsymbol{x}_{2}+\ldots+w_{n} \boldsymbol{x}_{n}z^=b+w1x1+w2x2+…+wnxn
其中z^\hat{z}z^是包含了m个全部的样本的预测结果的列向量。注意,我们通常使用粗体的小写字母来表示列向量,粗体的大写字母表示矩阵或者行列式。并且在机器学习中,我们默认所有的一维向量都是列向量。我们可以使用矩阵来表示上面多个样本的回归结果的方程,其中www可以被看做是一个结构为(n+1,1)的列矩阵(这里的n加上的1是我们的截距b), 是一个结构为(m,n+1)的特征矩阵(这里的n加上的1是为了与截距b相乘而留下的一列1,这列1有时也被称作x0x_{0}x0),则有:
[z^1z^2z^3…z^m]=[1x11x12x13…x1n1x21x22x23…x2n1x31x32x33…x3n…1xm1xm2xm3…xmn]∗[bw1w2…wn]z^=Xw\begin{aligned} \left[\begin{array}{c} \hat{z}_{1} \\ \hat{z}_{2} \\ \hat{z}_{3} \\ \ldots \\ \hat{z}_{m} \end{array}\right] &=\left[\begin{array}{cccccc} 1 & x_{11} & x_{12} & x_{13} & \ldots & x_{1 n} \\ 1 & x_{21} & x_{22} & x_{23} & \ldots & x_{2 n} \\ 1 & x_{31} & x_{32} & x_{33} & \ldots & x_{3 n} \\ & & \ldots & & & \\ 1 & x_{m 1} & x_{m 2} & x_{m 3} & \ldots & x_{m n} \end{array}\right] *\left[\begin{array}{c} b \\ w_{1} \\ w_{2} \\ \ldots \\ w_{n} \end{array}\right] \\ & \hat{\boldsymbol{z}}=\boldsymbol{X} \boldsymbol{w} \end{aligned} ⎣⎢⎢⎢⎢⎡z^1z^2z^3…z^m⎦⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎡1111x11x21x31xm1x12x22x32…xm2x13x23x33xm3…………x1nx2nx3nxmn⎦⎥⎥⎥⎥⎤∗⎣⎢⎢⎢⎢⎡bw1w2…wn⎦⎥⎥⎥⎥⎤z^=Xw
如果在我们的方程里没有常量b,我们则可以不写XXX中的第一列以及www中的第一行。
线性回归的任务,就是构造一个预测函数来映射输入的特征矩阵XXX和标签值yyy的线性关系。这个预测函数的图像是一条直线,所以线性回归的求解就是对直线的拟合过程。预测函数的符号在不同的教材上写法不同,可能写作f(x)f(x)f(x),yw(x)y_{w}(x)yw(x)或者h(x)h(x)h(x)等等形式,但无论如何,这个预测函数的本质就是我们需要构建的模型,而构造预测函数的核心就是找出模型的权重向量www,也就是求解线性方程组的参数(相当于求解y=ax+by = ax+by=ax+b里的aaa与bbb)。
现在假设,我们的数据只有2个特征,则线性回归方程可以写作如下结构:
z^=b+x1w1+x2w2\hat{z}=b+x_{1} w_{1}+x_{2} w_{2}z^=b+x1w1+x2w2
此时,我们只要对模型输入特征x1x_{1}x1,x2x_{2}x2的取值,就可以得出对应的预测值z^\hat{z}z^。神经网络的预测过程是从神经元左侧输入特征,让神经元处理数据,并从右侧输出预测结果。这个过程和我们刚才说到的线性回归输出预测值的过程是一致的。如果我们使用一个神经网络来表达线性回归上的过程,则可以有:
这就是一个最简单的单层回归神经网络的表示图。
在神经网络中,竖着排列在一起的一组神经元叫做“一层网络”,所以线性回归的网络直观看起来有两层,两层神经网络通过写有参数的线条相连。我们从左侧输入常数1和特征取值x1x_{1}x1,x2x_{2}x2,再让它们与相对应的参数相乘,就可以得到bbb,w1x1w_{1}x_{1}w1x1,w2x2w_{2}x_{2}w2x2三个结果。这三个结果通过连接到下一层神经元的直线,被输入下一层神经元。我们在第二层的神经元中将三个乘积进行加和(使用符号∑\sum∑表示),就可以得到加和结果z^\hat{z}z^,即b+w1x1+w2x2b+w_{1}x_{1}+w_{2}x_{2}b+w1x1+w2x2,这个值正是我们的预测值。可见,线性回归方程与上面的神经网络图达到的效果是一模一样的。
在上述过程中,左侧的是神经网络的输入层(input layer)。输入层由众多承载数据用的神经元组成,数据从这里输入,并流入处理数据的神经元中。在所有神经网络中,输入层永远只有一层,且每个神经元上只能承载一个特征(一个xxx)或一个常量(通常都是1)。现在的二元线性回归只有两个特征,所以输入层上只需要三个神经元,包括两个特征和一个常量,其中这里的常量仅仅是被用来乘以偏差用的。对于没有偏差的线性回归来说,我们可以不设置常量1。
右侧的是输出层(output layer)。输出层由大于等于一个神经元组成,我们总是从这一层来获取预测结果。输出层的每个神经元上都承载着单个或多个功能,可以处理被输入神经元的数据。在线性回归中,这个功能就是“加和”,当我们把加和替换成其他的功能,就能够形成各种不同的神经网络。
在神经元之间相互连接的线表示了数据流动的方向,就像人脑神经细胞之间相互联系的“轴突”。在人脑神经细胞中,轴突控制电子信号流过的强度,在人工神经网络中,神经元之间的连接线上的权重也代表了“信息可通过的强度。最简单的例子是,当w1w_{1}w1为0.5时,在特征x1x_{1}x1上的信息就只有0.5倍能够传递到下 一层神经元中,因为被输入到下层神经元中去进行计算的实际值是0.5x1x_{1}x1。相对的,如果x1x_{1}x1是2.5,则会传递2.5倍的x1x_{1}x1上的信息。因此,有的深度学习课程会将权重www比喻成是电路中的”电压“,电压越大,则电信号越强烈,电压越小,信号也越弱,这都是在描述权重www会如何影响传入下一层神经元的信息/数据量的大小。
到此,我们已经了解了线性回归的网络是怎么一回事,它是最简单的回归神经网络,同时也是最简单的神经网络。类似于线性回归这样的神经网络,被称为单层神经网络。
2 tensor实现单层神经网络的正向传播
让我们使用一组非常简单(简直是简单过头了)的代码来实现一下回归神经网络求解的过程,在神经网络中,这个过程是从左向右进行的,被称为神经网络的正向传播(forwardspread)(当然,这是正向传播中非常简单的一种情况)。来看下面这组数据:
我们将构造能够拟合出以上数据的单层回归神经网络:
3 tensor计算中的新手陷阱
接下来,我们对这段代码进行详细的说明:
#导入库 import torch #首先生成特征张量 X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]]) #我们输入的是整数,默认生成的是int64的类型 X.dtype #torch.int64 #查看一下特征张量是什么样子 X #tensor([[1, 0, 0], # [1, 1, 0], # [1, 0, 1], # [1, 1, 1]]) X.shape #torch.Size([4, 3]) #生成标签z = torch.tensor([-0.2, -0.05, -0.05, 0.1]) #标签我们输入的是浮点数,默认生成的则是float32的类型 z.dtype #torch.float32 #查看标签 z #tensor([-0.2000, -0.0500, -0.0500, 0.1000])#定义常量b和权重w #注意,常量b所在的位置必须与特征张量X中全为1的那一列所在的位置相对应,在这里,-0.2是b, 0.15是两个w w = torch.tensor([-0.2,0.15,0.15]) w.shape #torch.Size([3])- tensor计算中的第一大坑: PyTorch的静态性
可以看到,只要能够给到适合的w和b,回归神经网络其实非常容易实现。在这里,我们需要提出PyTorch中另一个比较严肃的问题。
最安全的办法定义的时候都把类型写上float32,标签来说定义成二维的。
- tensor计算中的第二大坑:精度问题
来看这段代码。
在多元线性回归中,我们的使用SSE(误差平方和,有时候也叫做RSS残差平方和)来衡量回归的结果优劣:
SSE=∑i=1m(zi−z^i)2SSE=\sum_{i=1}^{m}\left(z_{i}-\hat{z}_{i}\right)^{2} SSE=i=1∑m(zi−z^i)2
如果预测值z^i\hat{z}_{i}z^i与真实值zi{z}_{i}zi完全相等,那SSE的结果应该为0。在这里,SSE虽然非常接近0,但的确是不为0的。
4 torch.nn.Linear实现单层回归神经网络的正向传播
在PyTorch中,我们使用类torch.nn.Linear类来实现单层回归神经网络,不过需要注意的是, 它可不是代表单层回归神经网络这个算法。还记得之前我们的架构图吗?
从这张架构图中我们可以看到,torch.nn是包含了构筑神经网络结构基本元素的包,在这个包中,你可以找到任意的神经网络层。这些神经网络层都是nn.Module这个大类的子类。我们的torch.nn.Linear就是神经网络中的”线性层“,它可以实现形如z^=Xw\hat{\mathbf{z}}=\boldsymbol{X} \boldsymbol{w}z^=Xw的加和功能。在我们的单层回归神经网络结构图中,torch.nn.Linear类表示了我们的输出层。现在我们就来看看它是如何使用的。
回顾一下我们的数据:
接下来,我们使用nn.Linear来实现单层回归神经网络:
怎么样,代码是不是异常简单?但在这段代码中,却有许多细节需要声明:
nn.Linear是一个类,在这里代表了输出层,所以我使用output作为变量名,output = 的这一行相当于是类的实例化过程
实例化的时候,nn.Linear需要输入两个参数,分别是(上一层的神经元个数——上一层的神经元中,给这一层传输数据的神经元的个数,这一层的神经元个数——接收传输数据的神经元的个数)。上一层是输入层,因此神经元个数由特征的个数决定(2个)。这一层是输出层,作为回归神经网络,输出层只有一个神经元。因此nn.Linear中输入的是(2,1)。
我只定义了X,没有定义w和b。所有nn.Module的子类,形如nn.XXX的层,都会在实例化的同时随机生成w和b的初始值。所以实例化之后,我们就可以调用以下属性来查看生成的和:
output.weight #查看生成的w #Parameter containing: #tensor([[ 0.3897, -0.2741]], requires_grad=True) output.bias #查看生成的b #Parameter containing: #tensor([0.0972], requires_grad=True)其中,w是必然会生成的,b是我们可以控制是否要生成的。在nn.Linear类中,有参数bias,默认bias = True。如果我们希望不拟合常量b,在实例化时将参数bias设置为False即可:
output = torch.nn.Linear(2,1,bias=False) #再次调用属性weight和bias output.weight #Parameter containing: #tensor([[ 0.0868, -0.3132]], requires_grad=True) output.bias由于w和b是随机生成的,所以同样的代码多次运行后的结果是不一致的。如果我们希望控制随机性,则可以使用torch中的random类。如下所示:
torch.random.manual_seed(888) #人为设置随机数种子 output = torch.nn.Linear(2,1) zhat = output(X) zhat #tensor([[-0.5236], # [-1.1635], # [-0.2499], # [-0.8898]], grad_fn=<AddmmBackward0>)由于不需要定义常量b,因此在特征张量中,也不需要留出与常数项相乘的x0那一列。在输入数据时,我们只输入了两个特征x1与x2
输入层只有一层,并且输入层的结构(神经元的个数)由输入的特征张量决定,因此在PyTorch中构筑神经网络时,不需要定义输入层
实例化之后,将特征张量输入到实例化后的类中,即可得出输出层的输出结果。
让我们来看看输出结果的形状:
这个形状与我们自己定义的z是一致的。但就数字大小上来说,由于我们没有自己定义w和b,所以我们无法让nn.Linear输出的zhat与我们真实的z接近——让真实值与预测值差异更小的部分,我们会在之后进行讲解。现在,让我们继续了解神经网络。在下一节中,我们会将单层回归神经网络的例子推广到分类问题上。
总结
以上是生活随笔为你收集整理的Lesson 8.18.2 单层回归神经网络torch.nn.Linear实现单层回归神经网络的正向传播的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: Lesson 7 (3) 深入理解PyT
- 下一篇: Lesson 8.38.4 二分类神经网