人工神经网络ANN

神经网络概念

人工神经网络(ANN)是一种模仿生物神经网络的计算模型,由多个相互连接的人工神经元组成,擅长学习复杂的非线性模式,也是深度学习的基础。

人脑是由大量神经元组成的生物网络:树突接收输入信号,细胞对信号做处理,达到一定电位后再通过轴突输出信号

图片

如何构建神经网络

神经网络由许多神经元组成。每个神经元先对输入做加权求和($z = w \cdot x + b$),再通过激活函数做非线性变换($a = f(z)$),然后把结果传给下一层。多个神经元按输入层 -> 隐藏层 -> 输出层连接起来:输入层接收特征,隐藏层提取特征,输出层给出结果。全连接神经网络(MLP) 中,每一层都会接收上一层的全部输出,而权重和偏置决定了信息如何流动。

展开查看详情

神经网络是由多个神经元组成,构建神经网络就是在构建神经元。下面整个图只表示一个神经元。

image-20250811120026787

这个流程就像,来源不同树突(树突都会有不同的权重)的信息, 进行的加权计算, 输入到细胞中做加和,再通过激活函数输出细胞值。

同一层的多个神经元可以看作是通过并行计算来处理相同的输入数据,学习输入数据的不同特征。每个神经元可能会关注输入数据中的不同部分,从而捕捉到数据的不同属性。

接下来,我们使用多个神经元来构建神经网络,相邻层之间的神经元相互连接,并给每一个连接分配一个强度,如下图所示:

image-20250811122157331

神经网络中的信息通常按一个方向流动:输入层 -> 隐藏层 -> 输出层

  1. 输入层(Input Layer):负责接收输入数据。每个输入特征通常对应一个神经元
  2. 隐藏层(Hidden Layers):位于输入层和输出层之间,负责做加权计算和特征提取。隐藏层越多,网络通常越“深”。
  3. 输出层(Output Layer):给出最终结果,比如分类结果或回归预测值。

全连接神经网络(FCNN)可以先抓住这几点:

  • 同一层的神经元之间通常不直接连接。
  • N 层的神经元会接收第 N-1 层所有神经元的输出,这就是“全连接”。
  • N-1 层的输出,就是第 N 层的输入。
  • 每条连接都有参数,主要包括权重 w 和偏置 b
  • 在工程实现里,FCNN 一般接收二维数据,即 样本数 x 特征数

神经网络内部状态值和激活值

image-20220313130303542

一个神经元在前向传播时,最重要的是产生两个值:

  • 内部状态值(也可理解为加权求和值)

    • 先把输入按权重做加权,再加上偏置:
    • $z = w \cdot x + b$
    • 其中,w 是权重,x 是输入,b 是偏置。
    • 可以把 z 理解成:这个神经元在“正式输出前”的原始计算结果。
  • 激活值

    • 再把 z 送入激活函数,得到最终输出:
    • $a = f(z)$
    • 其中,f 是激活函数,常见的有 ReLU、Sigmoid、Tanh。
    • 可以把 a 理解成:这个神经元真正传给下一层的值。

简单说,一个神经元就是先算出 z,再通过激活函数得到 a。后面网络训练是否稳定、效果是否好,往往都和各层 za 的分布是否合理有关。

激活函数

如果神经网络里没有激活函数,那么无论叠多少层,本质上都还是线性变换,整体效果仍然接近线性模型;而加入激活函数后,网络才有了非线性表达能力,能够拟合更复杂的关系,尤其更适合处理线性不可分的问题。

常见的激活函数有:Sigmoid、Tanh、ReLU、LeakyReLU、PReLU、ELU

如何选择激活函数*

图片

隐藏层:

  1. 优先选择ReLU激活函数
  2. 如果ReLu效果不好,那么尝试其他激活,如Leaky ReLu等。
  3. 如果你使用了ReLU, 需要注意一下Dead ReLU问题,避免出现0梯度从而导致过多的神经元死亡。
  4. 少使用sigmoid激活函数,可以尝试使用tanh激活函数

输出层:

  1. 二分类问题选择sigmoid激活函数
  2. 多分类问题选择softmax激活函数
  3. 回归问题选择identity激活函数
展开查看

另外通过图像可视化的形式理解:

神经网络可视化(按住CTRL,点击鼠标左键)

常见激活函数

Sigmoid 激活函数

  • 1.1- 既考虑正样本,也考虑负样本。将结果映射到[0,1]之间。求导后的取值范围在[0,0.25]之间
  • 1.2- 线性回归加权求和结果在[-6,6]之间有效果;在[-3,3]之间效果明显
  • 1.3- 适合浅层网络(层数少的网络),不适合深层网络,因为会出现梯度消失的情况
  • 1.4- 一般用在输出层中,解决二分类问题。如果用在隐藏层,推荐层数<=4层

激活函数公式:

激活函数求导公式:

1663472645904

sigmoid 激活函数的函数图像如下:

image-20250811161350107

1- sigmoid激活函数本身取值范围[0,1],导数的取值范围[0,0.25]
2- 输入值在[-6,6]之间的时候,效果才会比较明显。将范围缩小到[-3,3]效果更好
3- sigmoid激活函数适合在神经网络层数5层以内,推荐使用输出层,当你是二分类业务问题的时候
  • 从sigmoid函数图像可以得到,sigmoid 函数可以将任意的输入映射到 (0, 1) 之间,当输入的值大致在**<-6或者>6**时,意味着输入任何值得到的激活值都是差不多的,这样会丢失部分的信息。比如:输入100和输入10000经过 sigmoid的激活值几乎都是等于1的,但是输入的数据之间相差100倍的信息就丢失了。
  • 对于sigmoid函数而言,输入值在**[-6, 6]之间输出值才会有明显差异**,输入值在**[-3, 3]之间才会有比较好的效果**
  • 通过上述导数图像,我们发现导数数值范围是 (0, 0.25),当输入的值**<-6或者>6时,sigmoid激活函数图像的导数接近为 0**,此时网络参数将更新极其缓慢,或者无法更新。
  • 一般来说,sigmoid网络在5层之内就会产生梯度消失现象。而且,该激活函数的激活值并不是以0为中心的,激活值总是偏向正数,导致梯度更新时,只会对某些特征产生相同方向的影响,所以在实践中这种激活函数使用的很少。sigmoid函数一般只用于二分类的输出层

在 PyTorch中使用sigmoid函数的示例代码如下:

import torch
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']    # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False      # 用来正常显示负号
"""
    OMP: Error #15: Initializing libiomp5md.dll, but found libiomp5md.dll already initialized.
    原因:torch和matplotlib工具中都有libiomp5md.dll,只能保留一个
    解决:删除torch下的libiomp5md.dll
"""
def sigmoid_demo():
    fig, axes = plt.subplots(1,2)

    # 创建x轴的刻度
    x = torch.linspace(-20,20,1000)

    # sigmoid函数_原始图像
    y = torch.sigmoid(x)

    # 绘制图形
    axes[0].plot(x,y)
    axes[0].grid()
    axes[0].set_title("sigmoid函数_原始图像")

    # sigmoid函数_导数图像
    x = torch.linspace(-20, 20, 1000, requires_grad=True)
    torch.sigmoid(x).sum().backward()

    # 绘制图形
    axes[1].plot(x.detach(), x.grad)
    axes[1].grid()
    axes[1].set_title("sigmoid函数_导数图像")
    plt.show()


if __name__ == '__main__':
    # sigmoid激活函数
    sigmoid_demo()

image-20250811164358677

Tanh 激活函数

  • 2.1- 既考虑正样本,也考虑负样本。将结果映射到[-1,1]之间。求导后的取值范围在[0,1]之间
  • 2.2- 线性回归加权求和结果在[-3,3]之间有效果;在[-1,1]之间效果明显
  • 2.3- 适合浅层网络(层数少的网络,<=10层),不适合深层网络。因为会出现梯度消失的情况
  • 2.4- 一般用于隐藏层
1- Tanh函数本身的取值范围[-1,1],其导数[0,1]
2- 在[-3,3]梯度下降效果较好,在[-1,1]效果更加显著
3- Tanh适合用在隐藏层中

Tanh叫做双曲正切函数,其公式如下:

激活函数求导公式:

1663472675509

Tanh的函数图像、导数图像如下:

  • 由上面的函数图像可以看到,Tanh函数将输入映射到(-1, 1)之间,图像以0为中心,激活值在0点对称,当输入的值大概**<-3或者>3** 时将被映射为-1或者1。其导数值范围 (0, 1),当输入的值大概**<-3或者>3**时,其导数近似0。

  • 与Sigmoid相比,它是以0为中心的,使得其收敛速度要比Sigmoid快,减少迭代次数。然而,从图中可以看出,Tanh两侧的导数也为0,同样会造成梯度消失。

  • 若使用时可在隐藏层使用tanh函数,在输出层使用sigmoid函数

在 PyTorch 中使用tanh函数的示例代码如下:

# 导包
import torch
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei']    # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False      # 用来正常显示负号


# 遇到的问题: OMP: Error #15: Initializing libiomp5md.dll, but found libiomp5md.dll already initialized.
# 解决方案:  去 你的Anaconda软件安装路径下的 anaconda3\Lib\site-packages\torch\lib 删除 libiomp5md.dll 这个文件.
# 例如: 我的路径是 C:\Software\DevelopSofware\anaconda3\Lib\site-packages\torch\lib


# 需求1: 绘制Tanh函数图像

# 1. 创建1个 1行2列的画布.
fig, axes = plt.subplots(1, 2)
# 2. 准备x轴的值 -> [-20, 20]的等差数列, 1000个元素.
x = torch.linspace(-20, 20, 1000)
# 3. 计算Tanh函数的值
y = torch.tanh(x)
# 4. 绘制折线图.
axes[0].plot(x, y)
axes[0].grid()
axes[0].set_title('Tanh 函数图像')
# plt.show()

# 需求2: 绘制Tanh函数的导数图像.
# 1. 准备x轴的值 -> [-20, 20]的等差数列, 1000个元素.
x = torch.linspace(-20, 20, 1000, requires_grad=True)
# 2. 计算Tanh函数的导数
torch.tanh(x).sum().backward()   # 表示 标量张量 才能求导.
# 3. 绘制折线图
axes[1].plot(x.detach(), x.grad)
axes[1].grid()
axes[1].set_title('Tanh 函数导数图像')
plt.show()

ReLU 激活函数

  • 3.1- 只考虑正样本,忽略负样本。求导后的取值,负样本导数值=0,正样本导数值=1
  • 3.2- 应用最多,而且更多使用在隐藏层中,因为计算公式简单,效率高
  • 3.3- 如果ReLU效果一般,可以考虑对应的变种函数,例如:leakly ReLU等

ReLU 激活函数公式如下:

激活函数求导公式:

1663472703595

ReLU 的函数图像、导数图像如下:

  • ReLU 激活函数将小于0的值映射为0,而大于0的值则保持不变,它更加重视正信号,而忽略负信号,这种激活函数运算更为简单,能够提高模型的训练效率。
  • 当x<0时,ReLU导数为0,而当x>0时,则不存在饱和问题。所以,ReLU 能够在x>0时保持梯度不衰减,从而缓解梯度消失问题。然而,随着训练的推进,部分输入会落入小于0区域,导致对应权重无法更新。这种现象被称为“神经元死亡”。
  • ReLU是目前最常用的激活函数。与sigmoid相比,RELU的优势是:
    • 采用sigmoid函数,计算量大(指数运算),反向传播求误差梯度时,计算量相对大;而采用Relu激活函数,整个过程的计算量节省很多
    • sigmoid函数反向传播时,很容易就会出现梯度消失的情况,从而无法完成深层网络的训练;而采用relu激活函数,当输入的值>0时,梯度为1,不会出现梯度消失的情况
    • Relu会使一部分神经元的输出为0,这样就造成了网络的稀疏性,并且减少了参数的相互依存关系,缓解了过拟合问题的发生

在 PyTorch 中使用ReLU函数的示例代码如下:

# 导包
import torch
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei']    # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False      # 用来正常显示负号


# 遇到的问题: OMP: Error #15: Initializing libiomp5md.dll, but found libiomp5md.dll already initialized.
# 解决方案:  去 你的Anaconda软件安装路径下的 anaconda3\Lib\site-packages\torch\lib 删除 libiomp5md.dll 这个文件.
# 例如: 我的路径是 C:\Software\DevelopSofware\anaconda3\Lib\site-packages\torch\lib


# 需求1: 绘制Relu函数图像

# 1. 创建1个 1行2列的画布.
fig, axes = plt.subplots(1, 2)
# 2. 准备x轴的值 -> [-20, 20]的等差数列, 1000个元素.
x = torch.linspace(-20, 20, 1000)
# 3. 计算Relu函数的值
y = torch.relu(x)
# 4. 绘制折线图.
axes[0].plot(x, y)
axes[0].grid()
axes[0].set_title('Relu 函数图像')
# plt.show()

# 需求2: 绘制Relu函数的导数图像.
# 1. 准备x轴的值 -> [-20, 20]的等差数列, 1000个元素.
x = torch.linspace(-20, 20, 1000, requires_grad=True)
# 2. 计算Relu函数的导数
torch.relu(x).sum().backward()   # 表示 标量张量 才能求导.
# 3. 绘制折线图
axes[1].plot(x.detach(), x.grad)
axes[1].grid()
axes[1].set_title('Relu 函数导数图像')
plt.show()

SoftMax激活函数

  • 4.1- 计算各个类别的概率值,取概率最大的作为预测结果。所有类别的概率值累加结果是1
  • 4.2- 更多是使用在输出层,解决多分类问题

softmax用于多分类过程中,它是二分类函数sigmoid在多分类上的推广,目的是将多分类的结果以概率的形式展现出来

计算方法如下图所示:

SoftMax就是将网络输出的logits通过softmax函数,映射成为(0,1)的值,而这些值的累和为1(满足概率的性质),那么我们将它理解成概率,选取概率最大(也就是值对应最大的)节点,作为我们的预测目标类别。

在 PyTorch 中使用SoftMax函数的示例代码如下:

"""
案例:
    Softmax激活函数演示.

回顾: ANN介绍
    概述:
        人工神经网络(Artificial neural network), 通过多个 神经元 搭建的神经网络, 模仿生物学的神经元, 模拟世间万事万物.

    组成:
        输入层, 隐藏层, 输出层,   每层都可以有N个神经元.
        其中 输入层神经元的个数 = 特征的数量.

    细节:
        1. 同层的多个神经元之间相互不连接.
        2. 本层的每个神经元都会和 上一层的所有神经元建立连接, 叫: 全连接层(Linear, FC)
        3. 搭建神经网络时, 只需要搭建 隐藏层 和 输出层即可.
           且: 输入维度是上一层的神经元的数量, 输出维度是本层的神经元的数量.
        4. 关于神经元个数的经验之谈, 浅层的神经元数量可以多一点, 深层的神经元数量可以少一些.
        5. 激活函数的作用是: 给神经元添加 非线性因素, 则神经元就可以处理 分类问题了.

    激活函数分类:
        Sigmoid激活函数:
            既考虑正样本, 也考虑负样本, 切回把结果映射到: [0, 1]之间.
            数据在[-6, 6]之间有效果, 在[-3, 3]之间效果显著, 求导后范围在 [0, 0.25]
            不适用于深层网络, 容易造成: 梯度消失.
            一般应用于 输出层, 且输出层是二分类的.

        Tanh激活函数:
            既考虑正样本, 也考虑负样本, 切把结果映射到: [-1, 1]之间.
            数据在[-3, 3]之间有效果, 在[-1, 1]之间效果显著, 求导后范围在 [0, 1]
            不太适合于深层网络, 容易造成: 梯度消失.
            一般应用于 浅层隐藏层.

        ReLu激活函数:
            (默认)只考虑正样本, 公式为: f(x) = max(0, x), 所以可能会导致 神经元死亡(可以缓解过拟合情况)
            导数范围: <= 0 导数为0,  >0 导数为 1
            应用最多, 且大多应用于 隐藏层.  有变形版 -> Leaky Relu, PRelu...

        Softmax激活函数:
            把多分类的结果用概率来表示, 这这些概率值的累加和为1, 最终选取 概率值最大的那个结果.
"""

# 导包
import torch

# scores = torch.tensor([0.2, 0.02, 0.15, 0.15, 1.3, 0.5, 0.06, 1.1, 0.05, 3.75])
scores = torch.tensor([[0.3, 0.24, 5, -3.1], [-0.1, 0.56, 2.4, 0.35]])

# 计算得到概率值
"""
        dim=0,表示按列计算概率值
        dim=1,表示按行计算概率值
    """
# prob = torch.softmax(scores,dim=0)
prob = torch.softmax(scores,dim=1)
print(prob)

除了上述的激活函数,还存在很多其他的激活函数,如下图所示:

##### 激活函数
"""
    遇到如下问题的解决过程:
    OMP: Error #15: Initializing libiomp5md.dll, but found libiomp5md.dll already initialized.
    
    解决:
    1- 电脑全局搜索libiomp5md.dll
    2- 删除torch目录下的libiomp5md.dll文件即可
"""
import torch
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']    # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False      # 用来正常显示负号

def sigmoid_demo():
    fig,graph = plt.subplots(1,2)

    # 绘制函数原始图像
    x = torch.linspace(-20,20,1000)
    y = torch.sigmoid(x) # 掌握这行代码
    graph[0].plot(x,y)
    graph[0].grid()
    graph[0].set_title("绘制函数原始图像")

    # 绘制函数导数图像
    x = torch.linspace(-20, 20, 1000, requires_grad=True)
    torch.sigmoid(x).sum().backward()
    graph[1].plot(x.detach(), x.grad)
    graph[1].grid()
    graph[1].set_title("绘制函数导数图像")

    plt.show()

def tanh_demo():
    fig,graph = plt.subplots(1,2)

    # 绘制函数原始图像
    x = torch.linspace(-20,20,1000)
    y = torch.tanh(x) # 掌握这行代码
    graph[0].plot(x,y)
    graph[0].grid()
    graph[0].set_title("绘制函数原始图像")

    # 绘制函数导数图像
    x = torch.linspace(-20, 20, 1000, requires_grad=True)
    torch.tanh(x).sum().backward()
    graph[1].plot(x.detach(), x.grad)
    graph[1].grid()
    graph[1].set_title("绘制函数导数图像")

    plt.show()

def relu_demo():
    fig,graph = plt.subplots(1,2)

    # 绘制函数原始图像
    x = torch.linspace(-20,20,1000)
    y = torch.relu(x) # 掌握这行代码
    graph[0].plot(x,y)
    graph[0].grid()
    graph[0].set_title("绘制函数原始图像")

    # 绘制函数导数图像
    x = torch.linspace(-20, 20, 1000, requires_grad=True)
    torch.relu(x).sum().backward()
    graph[1].plot(x.detach(), x.grad)
    graph[1].grid()
    graph[1].set_title("绘制函数导数图像")

    plt.show()

def softmax_demo():
    # score = torch.tensor([0.2,0.02,0.15,0.15,1.3,0.5,0.06, 1.1,0.05,3.75])
    score = torch.tensor([[2.1,0.5,-1.3], [0.8,-2.3,4.5]])
    """
        dim=0的时候,按行的方向,对一列列的数据计算概率值
        dim=1的时候,按列的方向,对一行行的数据计算概率值
    """
    # result = torch.softmax(score,dim=0)
    result = torch.softmax(score,dim=1)
    print(result)


if __name__ == '__main__':
    # sigmoid激活函数
    # sigmoid_demo()

    # tanh激活函数
    # tanh_demo()

    # relu激活函数
    # relu_demo()

    # softmax激活函数
    softmax_demo()

参数初始化

我们在构建网络之后,网络中的参数是需要初始化的。我们需要初始化的参数主要有权重偏置偏置一般初始化为0即可,而对权重的初始化则会更加重要。

参数初始化的作用:

  • 防止梯度消失或爆炸:初始权重值过大或过小会导致梯度在反向传播中指数级增大或缩小。
  • 提高收敛速度:合理的初始化使得网络的激活值分布适中,有助于梯度高效更新。
  • 保持对称性破除:权重的初始化需要打破对称性,否则网络的学习能力会受到限制。

如何选择参数初始化

注意:xavier 或 kaiming 只能对权重进行初始化

  • 激活函数的选择:根据激活函数的类型选择对应的初始化方法

    • Sigmoid/Tanh:xavier 初始化
    • ReLU/Leaky ReLU:kaiming 初始化
  • 神经网络模型的深度

    • 浅层网络:随机初始化即可 (< 10层 )
    • 深层网络:需要考虑方差平衡,如 xavier 或 kaiming 初始化 (>= 10层 )
图片
展开查看 常见参数初始化方法详情

常见参数初始化方法

全0初始化

将神经网络中的所有权重参数初始化为0

  • 优点:实现简单
  • 缺点:无法打破对称性,所有神经元更新方向相同,无法有效训练
  • 适用场景:几乎不使用,仅用于b偏置项的初始化

kaiming初始化

也叫做HE初始化:专为ReLU和其变体设计,考虑到ReLU激活函数的特性,对输入维度进行缩放

  • HE初始化分为正态分布的HE初始化、均匀分布的HE初始化
    • 正态分布的he初始化
      • w权重值从均值为0, 标准差为std中随机采样,std = sqrt(2 / fan_in)
      • std值越大,w权重值离均值0分布相对较广,计算得到的内部状态值有较大的正值或负值
    • 均匀分布的he初始化
      • 它从[-limit,limit] 中的均匀分布中抽取样本, limitsqrt(6 / fan_in)
    • fan_in 输入神经元的个数,当前层接受的来自上一层的神经元的数量。简单来说,就是当前层接收多少个输入
  • 优点:适合 ReLU,能保持梯度稳定
  • 缺点:对非 ReLU 激活函数效果一般
  • 适用场景:深度网络(10层及以上),使用 ReLU、Leaky ReLU 激活函数

xavier初始化

也叫做Glorot初始化:根据网络输入和输出的维度自动选择权重范围,使输入和输出的方差相同

  • xavier初始化分为正态分布的xavier初始化、均匀分布的xavier初始化

    • 正态化的Xavier初始化
      • w权重值从均值为0, 标准差为std中随机采样,std = sqrt(2 / (fan_in + fan_out))
      • std值越小,w权重值离均值0分布相对集中,计算得到的内部状态值有较小的正值或负值
    • 均匀分布的Xavier初始化
      • [-limit,limit] 中的均匀分布中抽取样本, limit 是 sqrt(6 / (fan_in + fan_out))
    • fan_in 是输入神经元个数,当前层接受的来自上一层的神经元的数量。简单来说,就是当前层接收多少个输入
    • fan_out 是输出神经元个数,当前层输出的神经元的数量,也就是当前层会传递给下一层的神经元的数量。简单来说,就是当前层会产生多少个输出。
  • 优点:适用于Sigmoid、Tanh 等激活函数,解决梯度消失问题

  • 缺点:对 ReLU 等激活函数表现欠佳

  • 适用场景:深度网络(10层及以上),使用 Sigmoid 或 Tanh 激活函数

随机初始化

  • 均匀分布初始化:权重参数初始化从区间均匀随机取值,默认区间为(0,1)。可以设置为在(-$1\over\sqrt{d}$,$1\over\sqrt{d}$)均匀分布中生成当前神经元的权重,其中d为神经元的输入数量。

  • 正态分布初始化:随机初始化从均值为0,标准差是1的高斯分布中取样,使用一些很小的值对参数W进行初始化

  • 优点:能有效打破对称性

  • 缺点:随机选择范围不当可能导致梯度问题

  • 适用场景:浅层网络或低复杂度模型。隐藏层1-3层,总层数不超过5层。

固定值初始化

将神经网络中的所有权重参数初始化为某个固定值

  • 优点:实现简单
  • 缺点
    • 无法打破对称性,所有神经元更新方向相同,无法有效训练
    • 初始权重过大或过小可能导致梯度爆炸或梯度消失
  • 适用场景
    • 测试或调试

全1初始化

将神经网络中的所有权重参数初始化为1

  • 优点:实现简单
  • 缺点
    • 无法打破对称性,所有神经元更新方向相同,无法有效训练
    • 会导致激活值在网络中呈指数增长,容易出现梯度爆炸
  • 适用场景
    • 测试或调试:比如验证神经网络是否能正常前向传播和反向传播
    • 特殊模型结构:某些稀疏网络或特定的自定义网络中可能需要手动设置部分参数为1
    • 偏置初始化:偶尔可以将偏置初始化为小的正值(如 0.1),但很少用1作为偏置的初始值

image-20250812093739101

各种初始化代码示例
import torch.nn as nn


# 1. 均匀分布随机初始化
def test01():

    linear = nn.Linear(5, 3)
    # 从0-1均匀分布产生参数
    nn.init.uniform_(linear.weight)
    nn.init.uniform_(linear.bias)
    print(linear.weight.data)


# 2. 固定初始化
def test02():

    linear = nn.Linear(5, 3)
    nn.init.constant_(linear.weight, 5)
    print(linear.weight.data)


# 3. 全0初始化
def test03():

    linear = nn.Linear(5, 3)
    nn.init.zeros_(linear.weight)
    print(linear.weight.data)


# 4. 全1初始化
def test04():

    linear = nn.Linear(5, 3)
    nn.init.ones_(linear.weight)
    print(linear.weight.data)


# 5. 正态分布随机初始化
def test05():

    linear = nn.Linear(5, 3)
    nn.init.normal_(linear.weight, mean=0, std=1)
    print(linear.weight.data)


# 6. kaiming 初始化
def test06():

    # kaiming 正态分布初始化
    linear = nn.Linear(5, 3)
    nn.init.kaiming_normal_(linear.weight, nonlinearity='relu')
    print(linear.weight.data)

    # kaiming 均匀分布初始化
    linear = nn.Linear(5, 3)
    nn.init.kaiming_uniform_(linear.weight, nonlinearity='relu')
    print(linear.weight.data)


# 7. xavier 初始化
def test07():

    # xavier 正态分布初始化
    linear = nn.Linear(5, 3)
    nn.init.xavier_normal_(linear.weight)
    print(linear.weight.data)

    # xavier 均匀分布初始化
    linear = nn.Linear(5, 3)
    nn.init.xavier_uniform_(linear.weight)
    print(linear.weight.data)

神经网络搭建和参数计算*

构建神经网络

在pytorch中定义深度神经网络其实就是层堆叠的过程,继承自nn.Module,实现两个方法:

  • __init__方法中定义网络中的层结构,主要是全连接层,并进行初始化
  • forward方法,在调用神经网络模型对象的时候,底层会自动调用该函数。该函数中为初始化定义的layer传入数据,进行前向传播等。

接下来我们来构建如下图所示的神经网络模型:

image-20260201145902797

"""
    搭建神经网络的步骤:
        1- 定义一个类,继承自torch.nn.Module
        2- 实现两个方法:
            2.1- __init__:初始化方法。负责的工作内容如下:
                a、初始化父类
                b、设置属性值
                c、定义神经网络结构:隐藏层有几层、输出层是怎样的等
                d、参数初始化:w、b如何进行初始化

            2.2- forward:前向传播。将数据送入到搭建好的网络模型中,对模型进行训练,也就是前向传播的过程
"""
import torch
import torch.nn as nn
# pip install torchsummary
from torchsummary import summary

class MyModel(nn.Module):
    def __init__(self):
        # a、初始化父类:也就是调用父类中的初始化方法
        super().__init__()

        # b、设置属性值【目前不需要】

        # c、定义神经网络结构:隐藏层有几层、输出层是怎样的等
        # 1.1- 隐藏层
        # 第一层隐藏层
        """
            参数解释:
                in_features:输入的特征个数。也就是上游神经元的个数
                out_features:输出的特征个数。也就是本层神经元的个数
        """
        self.linear1 = nn.Linear(in_features=3,out_features=3)

        # 第二层隐藏层
        self.linear2 = nn.Linear(in_features=3,out_features=2)

        # 1.2- 输出层
        self.output = nn.Linear(in_features=2,out_features=2)

        # d、参数初始化:w、b如何进行初始化
        # 隐藏层1的初始化
        nn.init.xavier_normal_(self.linear1.weight)  # w初始化
        nn.init.zeros_(self.linear1.bias)   # b初始化

        # 隐藏层2的初始化
        nn.init.kaiming_normal_(self.linear2.weight)  # w初始化
        nn.init.zeros_(self.linear2.bias)  # b初始化

    def forward(self,data):
        """
        前向传播。对模型进行训练,也就是得到预测结果
        :param data: 前向传播送入到网络结构中的样本数据。类型是张量tensor
        :return:
        """

        # 1- 数据经过第一层隐藏层
        # 1.1- 分开版
        # 先进行线性求和
        # linear1_result = self.linear1(data)
        # 再将线性求和结果给到激活函数,得到激活值
        # data = torch.sigmoid(linear1_result)

        # 1.2- 合并版【推荐】
        data = torch.sigmoid(self.linear1(data))

        # 2- 数据经过第二层隐藏层
        data = torch.relu(self.linear2(data))

        # 3- 数据经过输出层得到输出结果
        """
            dim=-1:不管列表嵌套多少层,全部只对最里层(最后一个维度)的数据进行统计
        """
        return torch.softmax(self.output(data),dim=-1)

# 测试网络模型
if __name__ == '__main__':
    # 1- 准备训练数据:目前的需求中,每条样本必须有3个特征
    # 10:多少条样本;3:每条样本多少个特征
    data = torch.randn(10, 3)

    # 2- 创建神经网络模型对象
    my_model = MyModel()

    # 3- 模型训练:将数据输入到神经网络中,进行前向传播
    # 内部自动调用forward方法
    output = my_model(data)
    print(f"预测结果是:{output}")
    print(f"预测结果形状:{output.shape}")

    print("-" * 30)

    # 4- 查看神经网络的概要信息【理解】
    """
        summary(参数1,参数2,参数3):查看神经网络各层参数的信息
            参数1:神经网络算法实例对象
            参数2:输入的特征个数,类型必须是元组
            参数3:输入的每个批次样本条数,任意
    """
    summary(my_model, (3,), 2)
打印结果
预测结果是:tensor([[0.6193, 0.3807],
        [0.6193, 0.3807],
        [0.6527, 0.3473],
        [0.6193, 0.3807],
        [0.6454, 0.3546],
        [0.6193, 0.3807],
        [0.6467, 0.3533],
        [0.6193, 0.3807],
        [0.6193, 0.3807],
        [0.6273, 0.3727]], grad_fn=<SoftmaxBackward0>)
预测结果形状:torch.Size([10, 2])
------------------------------
----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Linear-1                     [2, 3]              12
            Linear-2                     [2, 2]               8
            Linear-3                     [2, 2]               6
================================================================
Total params: 26
Trainable params: 26
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.00
Params size (MB): 0.00
Estimated Total Size (MB): 0.00
----------------------------------------------------------------

观察输入和输出的数据变化

  • 输入10行数据,输出也是10行数据
  • 输入10行数据3个特征,经过第一个隐藏层是3个特征,经过第二个隐藏层是2个特征,经过输出层是2个特征
  • 模型最终预测结果是:10行2列数据

参数计算

  • Param 列是参数
  • 以第一个隐层为例:该隐层有3个神经元,每个神经元的参数为:4个(w1,w2,w3,b1),所以一共用3x4=12个参数。
  • 输入数据和网络权重是两个不同的事儿!对于初学者理解这一点十分重要,要分得清。

如果参数不初始化,程序也会初始化
源码:

#Linear.py 128 行
       init.kaiming_uniform_(self.weight, a=math.sqrt(5))
       if self.bias is not None:
           fan_in, _ = init._calculate_fan_in_and_fan_out(self.weight)
           bound = 1 / math.sqrt(fan_in) if fan_in > 0 else 0
           init.uniform_(self.bias, -bound, bound)