美文网首页神经网络与深度学习
PyTorch深度学习笔记(2):单层神经网络的正向传播

PyTorch深度学习笔记(2):单层神经网络的正向传播

作者: 陆仔的自在生活 | 来源:发表于2023-05-10 15:48 被阅读0次

    本文将从一个简单的线性回归问题出发,构建单层神经网络,并手动实现它的正向传播。同时,我们将介绍如何使用PyTorch中的核心模块torch.nn来构建该线性回归的神经网络。最后,我们将问题拓展至二分类多分类的情景,以及它们的代码实现。

    线性回归下单层神经网络的正向传播

    在深度学习中,线性回归方程通常被表达为:
    \hat{z}_{i} = b + w_1 x_{i1} + ... + w_n x_{in}

    • \hat{z} 表示线性回归的预测结果,z 表示线性回归的真实标签;
    • 此处使用 z 而非 y 来表示线性回归的标签,是因为在深度学习中,y 通常用于表示模型结果标签,无论是分类问题(y 是离散的整数),还是回归问题(y 为连续数值)。线性回归的结果一般为深度学习模型的中间结果,因此用 z 来表示,以示区别。

    写成矩阵的形式:
    \hat{\boldsymbol{z}} = \boldsymbol{Xw}
    其中,
    \boldsymbol{X} = \left[ \begin{matrix} 1 & x_{11} & x_{12} & ... & x_{1n} \\ 1 & x_{21} & x_{22} & ... & x_{2n} \\ \vdots & \vdots & \vdots & ... & \vdots \\ 1 & x_{m1} & x_{m2} & ... & x_{mn} \end{matrix} \right]
    \hat{\boldsymbol{z}} = \left[ \begin{matrix} \hat{z}_{1} \\ \hat{z}_{2} \\ \vdots \\ \hat{z}_{m} \end{matrix} \right]
    \boldsymbol{w} = \left[ \begin{matrix} b \\ w_{1} \\ \vdots \\ w_{n} \end{matrix} \right]

    下面我们用一个简单的线性回归例子,来阐述最基础的神经网络的架构。它的数据如下:

    【例1】线性回归

    x0 x1 x2 z
    1 0 0 -0.2
    1 1 0 -0.05
    1 0 1 -0.05
    1 1 1 0.1

    上述线性回归例子中,包含两个特征 x_1x_2,即该线性回归模型为:
    \hat{z} = b + x_{1}w_{1} + x_{2}w_{2}

    我们可以用如下神经网络来描述它:

    单层神经网络:线性回归问题

    以上是一个最简单的单层回归神经网络的表示图。在神经网络中,圆圈代表神经元,竖着排列在一起的神经元构成了一层神经网络。从图上看,线性回归模型似乎包含了两层神经网络(输入层和输出层),然而神经网络的输入层通常不计入神经网络的层数,因此我们称上图网络为单层神经网络。神经网络的层与层之间由带有参数的线条相连接,将左侧各神经元上的值(此例中为:1x_1x_2)分别与其对应的参数(此例中为:bw_1w_2相乘,得到的相乘的结果(此例中为:bx_1 w_1x_2 w_2)由相连的线条输送至下一层的神经元上,并进行加和(用符号\Sigma表示),即可得到右侧神经元上的预测值\hat{z},即\hat{z} = b + x_{1}w_{1} + x_{2}w_{2}

    本例中,左侧层为输入层,由承载数据用的神经元组成,数据从这里输入,并流入处理数据的神经元中。在所有神经网络中,输入层只有一层,且每个神经元上只能承载一个特征(一个x)或者一个常量(通常为1)。常量仅被用来乘以偏差b。对于没有偏差的线性回归,可以不设置常量1。右侧为输出层,由大于等于一个神经元组成,在这一层获得预测结果。输出层的每个神经元都承载着单个或多个功能,本例中,输出层神经元的功能为“加和”,可替换成其他功能,形成不同的神经网络。神经元之间相连的线表示了数据流动的方向,线上的参数(权重)也代表了信息传递的“强度”,权重越大,强度越强。

    下面是使用Pytorch来实现线性回归的正向传播:

    import torch
    
    # 定义输入数据的矩阵
    X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype=torch.float32)
    
    # 定义输出结果的真实值
    z = torch.tensor([-0.2, -0.05, -0.05, 0.1], dtype=torch.float32)
    
    # 定义参数(此处参数值为笔者任意设定,也可使用随机数生成)
    w = torch.tensor([-0.2,0.15,0.15], dtype=torch.float32)
    
    # 定义线性回归正向传播方法
    def LinearR(X, w):
        z_hat = torch.mv(X, w)
        return z_hat
    
    # 计算预测值
    z_hat = LinearR(X,w)
    z_hat
    

    tensor([-0.2000, -0.0500, -0.0500, 0.1000])

    z_hat == z
    

    tensor([ True, False, False, False])

    torch.allclose(z_hat, z)
    

    True

    注意事项

    • 定义tensor时,最好每次都定义清楚tensor的数据类型,并且将tensor类型保持一致,因为在很多运算中要求tensor类型保持一致;
    • 由于运算精度的原因,z_hat 与 z 并不完全相等;精度问题会在tensor维度非常高,数字很大时,更加突出;若对精度要求很高,可以使用float64;
    • Pytorch中很多函数都不接受浮点型的分类标签,但也有很多函数要求真实标签的类型必须与预测标签的类型一致。通常将标签定义为float32,若在运算过程中报错,再使用.long()方法将其转化为整型;
    • Pytorch中很多函数不接受一维张量,但也有很多函数不接受二维标签,因此在生成标签时,可以默认生成二维标签,若报错,再使用view()函数将其调整为一维。

    使用 torch.nn.Linear 实现正向传播

    torch.nn是pytorch中核心模块之一,提供了构筑神经网络结构的基本元素,其中nn.Module提供了神经网络的各种层;nn.functional包含了各种神经网络的损失函数与激活函数。下面我们将使用该模块来实现线性回归的正向传播。

    import torch
    
    output = torch.nn.Linear(2, 1)
    print(output.weight)
    print(output.bias)
    

    Parameter containing:tensor([[0.2015, 0.1453]], requires_grad=True)
    Parameter containing:tensor([-0.4766], requires_grad=True)

    说明

    • output 是一个torch.nn.Linear的实例化;
    • torch.nn.Linear两个参数分别为:上一层神经元个数、接收层神经元个数;本例中,上一层是输入层,因此神经元个数由特征的个数决定(2个,不包含常量),这一层是输出层,作为回归神经网络,输出层只有一个神经元。因此nn.Linear中输入的是(2,1);
    • output将自动生成随机的权重及截距,用于神经网络的正向传播;
    • 可以使用torch.random.manual_seed()来设置随机数种子。
    X = torch.tensor([[0,0],[1,0],[0,1],[1,1]], dtype=torch.float32)
    z_hat = output(X)
    z_hat
    

    tensor([[-0.4766],
    [-0.2751],
    [-0.3313],
    [-0.1299]], grad_fn=<AddmmBackward>)

    二分类神经网络的原理与实现

    在实际应用中,只有很少的问题能满足线性模型。为了使模型能够更好的拟合曲线,统计学家们在线性方程的两边引入了联系函数(Link Function),变化后的方程被称为广义的线性回归。

    Sigmoid函数

    \sigma = Sigmoid(z) = \frac{1}{1+e^{-z}}

    Sigmoid函数的曲线如下图:

    # 绘制 sigmoid 函数曲线
    import numpy as np
    import matplotlib.pyplot as plt 
    
    x = np.linspace(-10, 10, 100)
    y = 1/(1+np.exp(-x))
    
    plt.figure(figsize=(10,6))
    plt.plot(x, y)
    
    Sigmoid

    Sigmoid 函数特点:

    • 能将任意实数映射到(0, 1)区间;
    • z远离0的区域,Sigmoid(z)趋近于0或者1
    • 中心对称的S曲线;
    • 平滑,处处可导。
      经过sigmoid函数转换后得到的值都在(0, 1)区间内,通常我们可以将这个值作为分类为1的概率,比如:转换后的\sigma = 0.6,则分类为1的概率为0.6(即分类为0的概率为1-0.6=0.4)。

    几率(odds)

    几率\frac{\sigma}{1-\sigma}:事件发生的概率与事件不发生的概率的比值。

    对数几率(log odds)

    \log(\frac{\sigma}{1-\sigma}) = \boldsymbol{Xw}

    实现二分类网络的正向传播

    二分类神经网络的结构与线性回归类似(见下图),区别在于,二分类神经网络的输出层上,在加和函数\Sigma的基础上,增加了一个连接函数(图中为Sigmoid函数)。

    单层神经网络:二分类问题

    下面,我们用与门(And Gate)问题的例子,来介绍如何实现二分类神经网络的正向传播。

    【例2】与门

    x0 x1 x2 andgate
    1 0 0 0
    1 1 0 0
    1 0 1 0
    1 1 1 1
    import torch
    
    X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype=torch.float32)
    y = torch.tensor([0, 0, 0, 1], dtype=torch.float32)
    w = torch.tensor([-0.2,0.15,0.15], dtype=torch.float32)
    def LogisticR(X, w):
        z_hat = torch.mv(X, w)
        sigma = torch.sigmoid(z_hat)
        y_hat = torch.tensor([int(x) for x in sigma>=0.5], dtype=torch.float32)
        return sigma, y_hat
    sigma, y_hat = LogisticR(X, w)
    print(sigma)
    print(y_hat)
    

    tensor([0.4502, 0.4875, 0.4875, 0.5250])
    tensor([0., 0., 0., 1.])

    常见连接函数:Sign、ReLU、Tanh

    符号函数Sign

    y = \left\{ \begin{aligned} 1 & &\text{if } z \gt 0 \\ 0 & &\text{if } z = 0 \\ -1 & &\text{if } z \lt 0 \end{aligned} \right.

    符号函数的曲线如下图:

    import numpy as np
    import matplotlib.pyplot as plt 
    
    x = np.array([-10, 0, 0, 0, 10])
    y = np.array([-1, -1, 0, 1, 1])
    
    plt.figure(figsize=(10,6))
    plt.plot(x, y)
    
    Sign

    符号函数也被称为阶跃函数。此处,使用y而非\sigma来表示输出结果,是因为输出结果直接为-1, 0, 1,就相当于类别标签了;而sigmoid函数输出的\sigma是一个0-1之间的数值,需要通过阈值将其转化为0, 1这样的标签。
    符号函数也可将取值为0的结果直接合并:
    y = \left\{ \begin{aligned} 1 & &\text{if } z \gt 0 \\ 0 & &\text{if } z \le 0 \end{aligned} \right.
    等号被合并于上方或下方都可以。

    由于z = w_1 x_1 + w_2 x_2 + b,符号函数还可以被转化为以下式子:

    y = \left\{ \begin{aligned} 1 & &\text{if } w_1 x_1 + w_2 x_2 + b \gt 0 \\ 0 & &\text{if } w_1 x_1 + w_2 x_2 + b \le 0 \end{aligned} \right.

    y = \left\{ \begin{aligned} 1 & &\text{if } &w_1 x_1 + w_2 x_2 \gt -b \\ 0 & &\text{if } &w_1 x_1 + w_2 x_2 \le -b \end{aligned} \right.
    此时,-b就是一个阈值,我们可以用任意字母代替它(比如\theta)。

    # 在二分类神经网络中使用符号函数
    import torch
    
    X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype=torch.float32)
    y = torch.tensor([0, 0, 0, 1], dtype=torch.float32)
    w = torch.tensor([-0.2,0.15,0.15], dtype=torch.float32)
    
    def LinearRwithsign(X, w):
        z_hat = torch.mv(X, w)
        y_hat = torch.tensor([int(x) for x in z_hat>0], dtype=torch.float32)
        return y_hat
    
    y_hat = LinearRwithsign(X, w)
    print(y_hat)
    

    tensor([0., 0., 0., 1.])

    ReLU (Rectified Linear Unit)

    ReLU 函数在神经网络中很受欢迎。

    \sigma = \max(0, z)

    ReLU函数的曲线如下图:

    import numpy as np
    import matplotlib.pyplot as plt 
    
    x = np.array([-10, 0, 10])
    y = np.array([0, 0, 10])
    
    plt.figure(figsize=(10,6))
    plt.plot(x, y)
    
    ReLU

    值得注意的是:ReLU函数的导数为符号函数,即当输入z>0时,导数为1,当输入z<0时,导数为0,在z=0处不可导。

    import torch
    
    X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype=torch.float32)
    y = torch.tensor([0, 0, 0, 1], dtype=torch.float32)
    w = torch.tensor([-0.2,0.15,0.15], dtype=torch.float32)
    
    def LinearRwithReLU(X, w):
        z_hat = torch.mv(X, w)
        sigma = torch.tensor([max(0,x) for x in z_hat], dtype=torch.float32)
        y_hat = torch.tensor([int(x) for x in sigma>0.5], dtype=torch.float32)
        return sigma, y_hat
    
    sigma, y_hat = LinearRwithReLU(X,w)
    print(sigma)
    print(y_hat)
    

    tensor([0.0000, 0.0000, 0.0000, 0.1000])
    tensor([0., 0., 0., 0.])

    Tanh (hyperbolic tangent)

    \sigma = \frac{e^{2z}-1}{e^{2z}+1}

    Tanh函数的曲线如下图:

    import numpy as np
    import matplotlib.pyplot as plt 
    
    x = np.linspace(-10,10,100)
    y = (np.exp(2*x)-1)/(np.exp(2*x)+1)
    
    plt.figure(figsize=(10,6))
    plt.plot(x, y)
    
    Tanh

    Tanh函数与Sigmoid函数形状相似,Tanh函数将任意实数映射至(-1,1)区间。

    import torch
    
    X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype=torch.float32)
    y = torch.tensor([0, 0, 0, 1], dtype=torch.float32)
    w = torch.tensor([-0.2,0.15,0.15], dtype=torch.float32)
    
    def LinearRwithtanh(X, w):
        z_hat = torch.mv(X, w)
        sigma = torch.tanh(z_hat)
        y_hat = torch.tensor([int(x) for x in sigma>=0], dtype=torch.float32)
        return sigma, y_hat
    sigma, y_hat = LinearRwithtanh(X, w)
    print(sigma)
    print(y_hat)
    

    tensor([-0.1974, -0.0500, -0.0500, 0.0997])
    tensor([0., 0., 0., 1.])

    torch.nn 实现单层二分类网络的正向传播

    使用sigmoid函数:

    import torch
    from torch.nn import functional as F 
    
    torch.random.manual_seed(54)
    
    dense = torch.nn.Linear(2, 1)
    
    X = torch.tensor([[0,0],[1,0],[0,1],[1,1]], dtype=torch.float32)
    y = torch.tensor([0, 0, 0, 1], dtype=torch.float32)
    
    z_hat = dense(X)
    sigma = torch.sigmoid(z_hat)
    
    print(sigma)
    
    y_hat = [int(x) for x in sigma>=0.5]
    print(y_hat)
    

    tensor([[0.6664],
    [0.6871],
    [0.7109],
    [0.7299]], grad_fn=<SigmoidBackward>)

    [1, 1, 1, 1]

    此处使用 dense 作为 torch.nn.Linear 输出结果的变量名。在神经网络中,dense 经常作为紧密连接层(上层大部分神经元都与这一层神经元相连)的变量名。

    使用其他连接函数:

    import torch
    from torch.nn import functional as F 
    
    torch.random.manual_seed(54)
    
    dense = torch.nn.Linear(2, 1)
    
    X = torch.tensor([[0,0],[1,0],[0,1],[1,1]], dtype=torch.float32)
    y = torch.tensor([0, 0, 0, 1], dtype=torch.float32)
    
    z_hat = dense(X)
    
    # sign
    print(torch.sign(z_hat))
    

    tensor([[1.], [1.], [1.], [1.]], grad_fn=<SignBackward>)

    # ReLU
    print(F.relu(z_hat))
    

    tensor([[0.6920], [0.7865], [0.8998], [0.9943]], grad_fn=<ReluBackward0>)

    # tanh
    print(torch.tanh(z_hat))
    

    tensor([[0.5993], [0.6564], [0.7162], [0.7592]], grad_fn=<TanhBackward>)

    多分类神经网络

    二分类神经网络分类标签通常为标签0,标签1;多分类神经网络中标签通常为[1, +\infty)

    在二分类神经网络中,输出层只有一个神经元(通常是分类标签为1的概率)。多分类神经网络中,神经元个数与标签类别个数相同,比如十分类,则输出层有10个神经元,每个神经元输出该分类的概率\sigma_{1}, \sigma_{2}, ..., \sigma_{10}

    多分类神经网络的结构如下图:

    单层神经网络:多分类问题

    Softmax函数

    \sigma_{k} = \frac{e^{z_k}}{\sum_{j=1}^{K}e^{z_j}}

    • 分子为多分类情况下某个标签类别的回归结果的指数函数;
    • 分母为所有标签类别的回归结果的指数函数之和。
      在pytorch中,我们通常不会手写softmax函数,因为指数函数容易因数值巨大而造成“溢出”。可直接调用 torch.softmax 来计算。

    由于指数函数是一个单调函数,在使用softmax函数前后并不会改变各个类别输出值的相对大小,因此,无论是否使用softmax,我们都可以通过z值大小来判断样本将会被归集为哪一个类别。在神经网络中,如果不需要了解具体类别的分类概率,只需知道最终分类结果,则可以省略softmax函数。

    Pytorch 中 Softmax 函数的维度参数

    Softmax函数只能对单一维度进行计算,它只能够识别单一维度上的不同类别,但我们输入softmax的张量却可能是一个很高维的张量。

    import torch 
    
    s = torch.tensor([[[1,5],[3,4],[5,7]], [[0,1],[2,4],[3,7]]], dtype=torch.float32)
    print(s.ndim)
    print(s.shape)
    

    3
    torch.Size([2, 3, 2])

    print(torch.softmax(s, dim=0))
    # 在张量第0维度,有两个二维张量,
    # 每个二维张量上对应位置的元素为一组分类
    

    tensor([[[0.7311, 0.9820],
    [0.7311, 0.5000],
    [0.8808, 0.5000]],

    [[0.2689, 0.0180],
    [0.2689, 0.5000],
    [0.1192, 0.5000]]])

    print(torch.softmax(s, dim=1))
    # 在张量第1维度,有三个一维张量,
    # 每个一维张量上对应位置的元素为一组分类
    

    tensor([[[0.0159, 0.1142],
    [0.1173, 0.0420],
    [0.8668, 0.8438]],

    [[0.0351, 0.0024],
    [0.2595, 0.0473],
    [0.7054, 0.9503]]])

    print(torch.softmax(s, dim=2))
    # 在张量第2维度,有两个零维张量,
    # 每个零维张量上对应位置的元素为一组分类
    

    tensor([[[0.0180, 0.9820],
    [0.2689, 0.7311],
    [0.1192, 0.8808]],

    [[0.2689, 0.7311],
    [0.1192, 0.8808],
    [0.0180, 0.9820]]])

    使用 nn.Linear 和 functional 实现多分类神经网络的正向传播

    import torch 
    from torch.nn import functional as F
    
    torch.random.manual_seed(54)
    
    X = torch.tensor([[0,0], [1,0], [0,1], [1,1]], dtype=torch.float32)
    
    dense = torch.nn.Linear(2,3)
    z_hat = dense(X)
    print(z_hat)
    

    tensor([[ 0.2551, -0.0173, -0.4200],
    [ 0.3496, 0.6747, -0.6627],
    [ 0.4629, 0.1867, 0.0409],
    [ 0.5574, 0.8787, -0.2018]], grad_fn=<AddmmBackward>)

    sigma = torch.softmax(z_hat, dim=1)
    print(sigma)
    

    tensor([[0.4404, 0.3354, 0.2242],
    [0.3640, 0.5038, 0.1323],
    [0.4142, 0.3142, 0.2716],
    [0.3513, 0.4843, 0.1644]], grad_fn=<SoftmaxBackward>)

    相关文章

      网友评论

        本文标题:PyTorch深度学习笔记(2):单层神经网络的正向传播

        本文链接:https://www.haomeiwen.com/subject/jtkjsdtx.html