1)是什么

神经网络(Neural Network)是一种受生物神经系统启发的计算模型,由大量相互连接的节点(称为“神经元”)组成,用于模拟人脑处理信息的方式。深度学习中的神经网络通常指具有多个隐藏层的前馈神经网络(Feedforward Neural Network),也称为深度神经网络(DNN)。这些网络通过输入层、一个或多个隐藏层和输出层来学习数据中的复杂模式。每个神经元接收来自前一层的输入,对其进行加权求和并经过激活函数(如ReLU、Sigmoid等)处理后传递到下一层。

🟢 小结:神经网络是模仿大脑结构的数学模型,通过多层非线性变换实现对复杂数据的建模与学习

2)为什么

神经网络之所以被广泛使用,是因为其强大的表达能力——能够逼近任意复杂的非线性函数。在大数据时代,传统机器学习方法(如线性回归、支持向量机)在处理高维、非结构化数据(如图像、语音、文本)时表现有限,而神经网络可以通过自动提取特征,在端到端的学习过程中达到优越性能。此外,随着GPU计算能力提升和大规模标注数据集的出现,训练深度神经网络成为现实。

🟢 小结:神经网络能有效处理复杂数据并自动学习特征,因此在现代AI中占据核心地位。

3)什么时候用

神经网络适用于以下场景:

  • 大规模、高维度的数据任务,如图像识别、自然语言处理;
  • 非线性关系显著的问题,例如语音合成、推荐系统;
  • 特征难以人工设计的任务,比如人脸识别、自动驾驶感知;
  • 有足够训练数据且计算资源充足的情况。
    例如,卷积神经网络(CNN)擅长图像处理,循环神经网络(RNN)适合序列建模,Transformer架构则在NLP领域取得突破。

🟢 小结:当问题复杂、数据丰富且需要自动特征提取时,神经网络是理想选择。

4)什么时候不用

尽管强大,但神经网络并非万能。以下情况应谨慎使用或避免:

  • 数据量极少(样本不足会导致过拟合);
  • 任务简单明确,已有高效传统算法(如逻辑回归);
  • 对模型可解释性要求极高(神经网络常被视为“黑箱”);
  • 实时性要求强且计算资源受限(训练/推理成本高);
  • 法规或安全要求严格,需透明决策过程(如医疗诊断)。
    此时,决策树、朴素贝叶斯或线性模型可能更合适。

🟢 小结:数据少、需求透明或资源受限时,神经网络可能不是最佳方案。

5)总结

神经网络是一种基于连接主义原理的人工智能模型,通过调整权重来最小化预测误差。其基本单元是神经元,整体结构包括输入层、隐藏层和输出层。训练过程依赖反向传播算法(Backpropagation)和优化器(如SGD、Adam)更新参数。深度学习则是指包含多个隐藏层的神经网络,具备更强的特征表示能力。

深度学习的成功源于数据、算力与算法三者的结合。它改变了计算机视觉、语音识别、自然语言处理等多个领域的格局,但也面临泛化能力、鲁棒性和可解释性等挑战。

🟢 小结:神经网络是深度学习的核心工具,理解其原理有助于合理应用与持续创新。

概念

3.1 感知机

感知机(Perceptron)是最早的人工神经网络模型之一,由美国心理学家 Frank Rosenblatt 于 1957 年提出。它是一种二分类的线性分类模型,可看作是现代神经网络的“鼻祖”。

感知机模拟了生物神经元的基本工作机制:接收多个输入信号,加权求和后,若超过某个阈值就“激活”(输出 1),否则“不激活”(输出 0)。
在这里插入图片描述
🔍 图形结构说明
在这里插入图片描述
✅ 模型意义
这是一个最基本的神经元模型,体现了人工神经网络的核心思想:

  • 输入 → 加权 → 求和 → 输出
  • 是构建复杂多层神经网络的基础单元

3.2 人工神经网络

ANN(人工神经网络,Artificial Neural Network)是一种模拟人脑神经元工作方式的数学模型,它通过多层非线性变换从数据中自动学习特征和规律。它是最基础、最通用的神经网络范式,而全连接神经网络是其最经典的形式
在这里插入图片描述

3.3 全连接神经网络

全连接神经网络(Fully Connected Neural Network,简称 FCN 或 Dense Network),也常被称为多层感知机(Multilayer Perceptron, MLP),是人工神经网络(ANN)中最基础、最经典的结构之一。
它的核心特点是:每一层中的每个神经元都与前一层的所有神经元相连,即“全连接”(fully connected)。这种连接方式使得网络能够学习输入特征之间的任意组合关系。
在这里插入图片描述

3.3.1 输入层

输入层是神经网络的第一层,负责接收外部输入数据。它不进行任何计算,仅将原始数据(如图像像素、文本向量、传感器读数等)传递给隐藏层。输入层的节点数量通常等于输入特征的维度。例如,在图像分类任务中,若输入为28×28的灰度图,则输入层有784个节点。
输入数据可能需要预处理,如归一化(将数值缩放到[0,1]或[-1,1])、标准化(均值为0,方差为1),以提高训练效率和模型稳定性。

3.3.2 隐藏层(中间层)

隐藏层位于输入层与输出层之间,是神经网络的核心部分。一个神经网络可以有一个或多个隐藏层,多层结构构成深度神经网络(DNN)。隐藏层通过加权求和与激活函数实现非线性变换,从而提取输入数据的复杂特征。
每一层的神经元通过权重与前一层连接,学习输入数据中的模式。隐藏层的层数和每层的神经元数量是超参数,需根据任务复杂度调整。

3.3.3 输出层

输出层是神经网络的最后一层,负责生成最终预测结果。其结构取决于任务类型:

  • 分类任务:若为二分类,常使用单个节点配合Sigmoid激活函数;若为多分类,通常采用Softmax激活函数,输出各类别的概率分布。
  • 回归任务:输出层一般无激活函数(或使用线性激活),直接输出连续值。
  • 输出层的节点数由任务决定,如MNIST手写数字识别任务中,输出层有10个节点对应10个类别。

3.3.4 内部状态-加权求和

在这里插入图片描述
在这里插入图片描述

每一个神经元工作时候,前向传播会产生两个值,内部状态值(加权求和值)激活值 如下图所示↓ ↓ ↓

在这里插入图片描述

反向传播时候则会产生激活值的梯度内部状态值的梯度,如下图所示↓ ↓ ↓

在这里插入图片描述

3.3.5 激活值

在这里插入图片描述

3.3.6 激活函数

激活函数是神经网络中引入非线性的关键组件。它们将神经元的净输入转换为输出,使网络具备学习复杂模式的能力。常见的激活函数包括Sigmoid、ReLU、Softmax、tanh等,各有优缺点和适用场景。

小结:
激活函数赋予神经网络非线性能力,是构建深度模型不可或缺的部分,选合适的激活函数对模型性能至关重要。


经典案例:房价预测-线性模型的局限性
问题背景
假设我们要预测一套房子的价格,输入特征包括:

  • 房屋面积(平方米)
  • 卧室数量
  • 地理位置评分(0-10分)

如果使用没有激活函数的线性模型,预测公式为:
房价 = w₁ × 面积 + w₂ × 卧室数 + w₃ × 地理位置 + b

线性模型的局限性:
1.面积与价格的关系不是线性的:100平米的房子可能值500万,但200平米的房子通常不是1000万,而是800万左右,因为面积越大,单价会下降
2.卧室数量的影响有阈值:2个卧室和3个卧室的差价可能很大,但5个卧室和6个卧室的差价就很小了
3.地理位置的影响是非线性的:从5分到6分的提升可能带来50万增值,但从9分到10分的提升可能带来200万增值

为什么线性模型会失败?
线性模型假设所有特征与价格的关系都是等比例变化的,但现实中:

  • 面积增加10平米,价格不会固定增加50万
  • 多一个卧室,价格不会固定增加80万
  • 位置评分提高1分,价格不会固定增加50万

激活函数如何解决这个问题?
通过引入非线性激活函数(如ReLU、Sigmoid),神经网络可以学习到:

  • 当面积超过150平米时,单价开始下降
  • 卧室数量超过4个时,增值效应减弱
  • 位置评分达到8分以上时,溢价效应增强

具体来说:

  • 隐藏层神经元通过激活函数,可以学习到"面积>150"这样的非线性特征
  • 多个隐藏层组合,可以捕捉更复杂的交互关系,如"面积大且位置好"的叠加效应
  • 最终输出层将这些非线性特征组合,得到更准确的预测

现实意义
在真实的房价预测中,线性模型可能只能达到60-70%的准确率,而加入激活函数的多层神经网络可以达到85-90%的准确率。这就是为什么在深度学习中,激活函数是必不可少的——它让模型能够捕捉现实世界中普遍存在的非线性关系。


3.3.6.1 跃阶激活函数

import numpy as np
import matplotlib.pyplot as plt

def step_function(x):
    return np.where(x >= 0, 1, 0)

# 测试
x = np.linspace(-5, 5, 100)
y = step_function(x)

plt.plot(x, y, label='Step Function')
plt.title('Step (Heaviside) Activation Function')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.grid(True)
plt.legend()
plt.show()

在这里插入图片描述

特点:

  • 不可导
  • 阶跃函数只能用于单层、非梯度的模型(如原始感知机),不能用于现代深度学习。

3.3.6.2 sigmoid激活函数

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

# 绘制sigmoid函数图像
x = np.linspace(-5, 5, 100)
y = sigmoid(x)

plt.figure(figsize=(8, 6))
plt.plot(x, y, 'b-', linewidth=2, label='Sigmoid')
plt.axhline(y=0, color='k', linewidth=0.5)
plt.axhline(y=1, color='k', linewidth=0.5)
plt.axvline(x=0, color='k', linewidth=0.5)
plt.xlabel('x')
plt.ylabel('sigmoid(x)')
plt.title('Sigmoid Activation Function')
plt.grid(True, alpha=0.3)
plt.legend()
plt.show()

在这里插入图片描述

特点:

  • 输出范围:(0, 1)
  • 平滑连续,可导
  • 有梯度消失问题(两端梯度接近0)

3.3.6.3 ReLU激活函数(重点🔥)

import numpy as np
import matplotlib.pyplot as plt
def relu(x):
    return np.maximum(0, x)

# 绘制ReLU函数图像
x = np.linspace(-5, 5, 100)
y = relu(x)

plt.figure(figsize=(8, 6))
plt.plot(x, y, 'r-', linewidth=2, label='ReLU')
plt.axhline(y=0, color='k', linewidth=0.5)
plt.axvline(x=0, color='k', linewidth=0.5)
plt.xlabel('x')
plt.ylabel('ReLU(x)')
plt.title('ReLU Activation Function')
plt.grid(True, alpha=0.3)
plt.legend()
plt.show()

在这里插入图片描述

特点:

  • 计算简单:max(0, x)
  • 解决了梯度消失问题
  • 存在"死亡神经元"问题(负值区域梯度为0)

3.3.6.4 PReLU

import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'

import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import numpy as np

# ----------------------------
# 1. 创建 PReLU 并获取参数
# ----------------------------
prelu = nn.PReLU(num_parameters=1)
# 可选:手动设置初始 a 值
# prelu.weight.data.fill_(0.25)

a_value = prelu.weight.item()

# 生成输入
x_np = np.linspace(-3, 3, 400)
x_torch = torch.from_numpy(x_np).float().requires_grad_(True)

# 前向传播
y_torch = prelu(x_torch)

# 手动计算导数(通过反向传播)
y_torch.sum().backward()
dy_dx = x_torch.grad.numpy()  # 导数

# 转换输出为 numpy
y_np = y_torch.detach().numpy()

# ----------------------------
# 2. 绘图:函数 + 导数
# ----------------------------
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))

# 左图:PReLU 函数
ax1.plot(x_np, y_np, color='b', linewidth=2, label=f'PReLU (a={a_value:.3f})')
ax1.axhline(0, color='k', linewidth=0.5)
ax1.axvline(0, color='k', linewidth=0.5)
ax1.set_title('PReLU Activation Function')
ax1.set_xlabel('Input (x)')
ax1.set_ylabel('Output f(x)')
ax1.grid(True, linestyle='--', alpha=0.6)
ax1.legend()

# 右图:导数
ax2.plot(x_np, dy_dx, color='r', linewidth=2, label="Derivative f'(x)")
ax2.axhline(0, color='k', linewidth=0.5)
ax2.axvline(0, color='k', linewidth=0.5)
ax2.set_title('Derivative of PReLU')
ax2.set_xlabel('Input (x)')
ax2.set_ylabel("f'(x)")
ax2.grid(True, linestyle='--', alpha=0.6)
ax2.legend()

plt.tight_layout()
plt.show()

# ----------------------------
# 3. 验证高维张量使用
# ----------------------------
x = torch.randn(1, 64, 8, 8)
output = prelu(x)
print("PReLU 输出 shape:", output.shape)
print("PReLU 参数 a (weight):", prelu.weight.item())

在这里插入图片描述

特点

  • 用于解决 ReLU 的“神经元死亡”问题
  • 负区间斜率是可学习参数(a)
  • 允许负输入有微弱响应
  • 提升模型表达能力
  • 支持反向传播,梯度非零

3.3.6.5 SoftMax激活函数(重点🔥)

import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'
import torch
import matplotlib.pyplot as plt

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']  # 微软雅黑
plt.rcParams['axes.unicode_minus'] = False    # 解决负号显示问题

# 设置设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 1.创建画布和坐标轴 一行两列,分别对应激活函数的图像和导数图像
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# 2.生成x轴的直方图的值,比如[0.2,0.3,1.2,2.5,0.1,5.2],表示模型输出层的原始输出logits
x = torch.tensor([0.2,
                  0.3,
                  1.2,
                  2.5,
                  0.1,
                  5.2])
# 3.经过softmax激活函数的运算
y = torch.softmax(x,dim=0)
# 4.在第一个子图上绘制softmax激活函数的图像
axes[0].bar(range(len(x)), y)
axes[0].set_title('softmax激活函数图像')
axes[0].set_xlabel('输入x')
axes[0].set_ylabel('输出y')
axes[0].grid(True)

# 5.第二个子图上绘制softmax激活函数的导数图像
z = torch.softmax(y,dim=0)

axes[1].bar(x, z)

# 设置标题
axes[1].set_title('softmax激活函数导数图像')
axes[1].set_xlabel('输入x')
axes[1].set_ylabel('导数y')
axes[1].grid(True)

plt.show()

在这里插入图片描述

特点:

  • 用于多分类任务的输出层
  • 将向量转换为概率分布
  • 各元素之和为1

3.3.6.6 tanh激活函数

import numpy as np
import matplotlib.pyplot as plt
def tanh(x):
    return np.tanh(x)

# 绘制tanh函数图像
x = np.linspace(-5, 5, 100)
y = tanh(x)

plt.figure(figsize=(8, 6))
plt.plot(x, y, 'g-', linewidth=2, label='tanh')
plt.axhline(y=-1, color='k', linewidth=0.5)
plt.axhline(y=1, color='k', linewidth=0.5)
plt.axvline(x=0, color='k', linewidth=0.5)
plt.xlabel('x')
plt.ylabel('tanh(x)')
plt.title('tanh Activation Function')
plt.grid(True, alpha=0.3)
plt.legend()
plt.show()

在这里插入图片描述

特点:

  • 输出范围:(-1, 1)
  • 比sigmoid对称
  • 梯度比sigmoid稍大

3.3.6.7 其他常见激活函数

在这里插入图片描述
在这里插入图片描述

3.3.7 激活函数的选择方法

对于隐藏层:

  • ReLU -> Leaky ReLU/PReLU -> Tanh

对于输出层:

  • 多分类优先softmax
  • 二分类softmax和sigmoid等效
  • 回归问题通常不用激活函数

3.3.7.1 梯度消失与梯度爆炸

在深度神经网络中,随着网络层数的增加,在反向传播过程中,梯度可能会逐层衰减(变小)或急剧增长(变大)。这种现象分别被称为“梯度消失”和“梯度爆炸”。

  • 梯度消失:当梯度在反向传播时逐渐趋近于零,导致靠近输入层的参数几乎无法更新,从而使这些层的学习变得极其缓慢甚至停滞。这使得前层神经元“学不动”,严重阻碍了模型训练。
    在这里插入图片描述

  • 梯度爆炸:相反,若梯度在反向传播过程中呈指数级增长,会导致权重更新幅度过大,参数值迅速发散,从而破坏模型稳定性,使训练过程崩溃。
    在这里插入图片描述
    该图展示了梯度范数的动态变化,其中一次显著的峰值提示我们:梯度可能正在变大,存在爆炸风险。虽然这次没有真正“爆炸”,但它提醒我们在训练深层或长序列模型时必须监控梯度行为。

3.3.7.2 网络稀疏性问题

在网络训练中,某些神经元可能长期处于激活状态极低(接近0)的状态,导致其对整体输出贡献微乎其微。这类神经元被称为“稀疏激活神经元”,由此引发的问题称为“网络稀疏性问题”。

比如ReLU激活函数的设计初衷是为了解决梯度消失问题,
ReLU 的设计初衷很简单:

  • 当输入 > 0 时,输出 = 输入(线性)
  • 当输入 ≤ 0 时,输出 = 0
    在这里插入图片描述

稀疏性其实是这个设计的“副产品”,但人们发现:这个副产品居然很有用!

稀疏性带来了4大好处


  1. 提升计算效率
  • 输出为 0 的神经元在后续层中不参与计算(尤其是配合稀疏矩阵运算)。
  • 实际上,很多深度学习框架会自动跳过这些“零激活”,节省时间和内存。

  1. 增强模型的表征能力(Representation)
  • 稀疏激活意味着:每个输入只激活一小部分神经元。
  • 这类似于大脑的工作方式:不同刺激激活不同神经回路,而不是全脑一起放电。
  • 结果:模型能学习到更局部化、可解释性强的特征(比如某些神经元专门检测边缘、眼睛、轮子等)。

  1. 一定程度上防止过拟合
  • 如果所有神经元都活跃,模型容易“死记硬背”训练数据。
  • 稀疏性相当于隐式的正则化:限制了模型对输入的响应范围,使其更专注于关键特征。

  1. 与生物神经元更相似
  • 生物神经元也是“静息-激活”模式:大部分时间不放电,只有接收到足够强的信号才触发。
  • ReLU 的“要么0,要么线性输出”模拟了这种阈值激活机制。

🔍 举个例子区分:

健康稀疏
输入一张猫的图片,只有负责“耳朵”“胡须”“眼睛”的神经元激活,其他关于“车轮”“文字”的神经元保持0 → 合理、高效。
病态稀疏
无论输入什么图片,80% 的神经元永远输出0,因为它们在训练早期就“死掉了” → 模型能力严重受限。

小结:ReLU 并不是“为了稀疏而稀疏”,而是它在解决梯度消失问题的过程中,自然产生了稀疏激活;而人们发现这种稀疏性恰好带来了计算效率高、表征能力强等额外好处,于是把它当作一个优点保留并利用起来。
话锋一转:虽然稀疏性有好处,但过度稀疏 = 神经元死亡问题。

3.3.7.3 神经元死亡问题

💀 什么是“神经元死亡”?
想象你训练一个 AI 厨师,它有成千上万个“味觉神经元”,每个负责尝一种味道:甜、咸、酸、辣、鲜……

但有一天,某个“甜味神经元”因为一次严重的失误(比如把糖当成盐),被你狠狠批评了一顿。

从此以后,它变得极度谨慎——不管给你什么菜,它都说:“没味道,0 分。”

久而久之,这个神经元彻底“躺平”了:

无论输入什么,输出永远是 0,再也不学习、不改变、不贡献。
——这就是 “神经元死亡”。

🔌 技术上怎么发生的?(结合 ReLU)
ReLU的规则很简单:

f(x) = x   if x > 0  
f(x) = 0   if x <= 0

在反向传播时:

如果神经元输出 > 0 → 梯度 = 1 → 正常更新权重 ✅
如果神经元输出 = 0 → 梯度 = 0 → 权重完全不更新 ❌

⚠️ 问题来了

如果某个神经元的输入加权和一直 ≤ 0(比如因为初始权重太小,或学习率太大导致权重被“推过头”),那么:

  • 它的输出永远是 0
  • 反向传播时梯度永远是 0
  • 权重再也得不到更新
    → 永久死亡!

🌪️ 什么情况下容易“猝死”?

场景 说明
学习率太高 一步更新太大,把权重“推”到负无穷,再也回不来
权重初始化不当 比如全初始化为负数,一开始所有 ReLU 都输出 0
数据未归一化 输入特征值过大或过小,导致加权和长期为负
网络太深 + 没用 BN 信号在前向传播中越来越偏,后面层大量神经元“饿死”

🆘 死亡的后果有多严重
模型容量缩水:你建了个 100 层的大楼,结果一半房间永远锁着门。
梯度流中断:死亡神经元像断掉的水管,上游的梯度传不到更前面的层。
训练停滞:损失函数卡住不动,你以为收敛了,其实是“假死”。

🛠️ 怎么“抢救”或“预防”?


1. 换激活函数(最直接)

  • Leaky ReLU:负数时不再输出 0,而是 αx(比如 α=0.01)
    → “即使尝错了,也小声说一句:‘有点苦’”,保留微弱梯度
  • ELU / PReLU:更平滑的负区响应,进一步减少死亡风险

2. 合理初始化权重

  • 用 He 初始化(专为 ReLU 设计):让输入方差保持稳定,避免一开始全为负

3. 使用 Batch Normalization(BN)

  • BN 会对每层输入做标准化(均值≈0,方差≈1)
    → 让 ReLU 的输入更可能落在正区间,大大降低死亡率

4. 调低学习率 + 数据归一化

  • 温和更新,别“吓死”神经元,输入特征缩放到 [-1, 1] 或 [0, 1],避免极端值

网络层/任务类型 推荐激活函数 主要原因
隐藏层(通用首选) ReLU 及其变体(如 Leaky ReLU, PReLU) 1. 计算高效:仅需比较和阈值操作,无指数运算。
2. 缓解梯度消失:在正区间梯度恒为 1,有效传递梯度。
3. 促进稀疏性:使部分神经元输出为零,模拟生物神经元的激活特性。
隐藏层(RNN/LSTM) Tanh 或 Sigmoid 1. 输出有界:Tanh 输出在 (−1,1)(-1,1)(1,1),Sigmoid 在 (0,1)(0,1)(0,1),有助于控制循环网络中的状态值范围,防止爆炸。
2. 平滑梯度:更适合捕捉序列数据中的时序依赖。
二分类输出层 Sigmoid 1. 概率化输出:其输出值在 (0,1)(0,1)(0,1) 区间,可直接解释为属于正类的概率。
2. 与二元交叉熵损失完美匹配
多分类输出层 Softmax 1. 概率分布输出:将多个神经元的输出归一化为一个概率分布,总和为 1。
2. 天然适用于互斥的多分类问题
回归任务输出层 线性激活(即无激活)或 ReLU 变体 1. 线性激活:适用于预测值范围无限制的任务(如预测温度、价格)。
2. ReLU:适用于预测值必须为非负的任务(如房价、销量)。

1.从简到繁:优先尝试 ReLU。它在大多数前馈神经网络中表现良好,是默认的“基线”选择。如果遇到“神经元死亡”问题,再切换为 Leaky ReLU​ 或 PReLU。


2.根据输出范围选择:

  • 二值/概率输出:选择 Sigmoid。
  • 多类互斥概率:选择 Softmax。
  • 任意实数:选择 线性激活。
  • 非负实数:选择 ReLU。

3.警惕梯度问题:

  • Sigmoid​ 和 Tanh​ 在输入绝对值较大时,梯度接近于零,容易导致深层网络的梯度消失,不适合作为深度网络的隐藏层激活函数。
  • ReLU​ 在输入为负时梯度为零,可能导致“死神经元”。此时可选用 Leaky ReLU(给负输入一个很小的斜率,如0.01),使其在负区间也有微小梯度。

4.处理特殊数据:

  • 如果输入特征已标准化到均值为0附近,使用 Tanh​ 可能比 Sigmoid 收敛稍快,因为其输出是零均值的。
  • 对于需要处理正负平衡特征的任务(如金融数据的变化量),Tanh​ 是一个不错的选择。
    在这里插入图片描述

3.3.8 参数初始化

在训练神经网络时,模型的参数(如权重 W 和偏置 b)需要在训练开始前进行初始化。合理的参数初始化对模型的收敛速度、稳定性以及最终性能有重要影响。

为什么需要参数初始化?

  • 神经网络的训练是一个优化过程,通常使用梯度下降法。
  • 如果所有参数初始化为相同的值(如全0),则在网络中每一层的所有神经元将具有相同的输出和梯度,导致无法有效学习(对称性问题)。
  • 不恰当的初始化可能导致梯度爆炸或梯度消失,使训练难以进行。
import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'
import torch.nn as nn

layer = nn.Linear(100, 50)
nn.init.zeros_(layer.weight)  # 所有权重设为0
print("全0初始化 - 权重均值:", layer.weight.mean().item())
# 可视化(可选)
# plt.hist(layer.weight.detach().numpy().flatten(), bins=30); plt.title('Zero Init'); plt.show()

layer = nn.Linear(100, 50)
nn.init.constant_(layer.weight, 1.0)  # 所有权重设为1
print("全1初始化 - 权重均值:", layer.weight.mean().item())

layer = nn.Linear(100, 50)
nn.init.uniform_(layer.weight, a=-0.1, b=0.1)  # 在 [-0.1, 0.1] 均匀采样
print("均匀分布初始化 - 权重范围:", layer.weight.min().item(), "到", layer.weight.max().item())

layer = nn.Linear(100, 50)
nn.init.normal_(layer.weight, mean=0.0, std=0.01)  # 均值0,标准差0.01
print("正态分布初始化 - 权重均值/标准差:", layer.weight.mean().item(), layer.weight.std().item())

layer = nn.Linear(100, 50)
nn.init.xavier_uniform_(layer.weight)        # 均匀版 Xavier
# 或
# nn.init.xavier_normal_(layer.weight)       # 正态版 Xavier
print("Xavier Uniform - 权重标准差:", layer.weight.std().item())

layer = nn.Linear(100, 50)
nn.init.kaiming_uniform_(layer.weight, mode='fan_in', nonlinearity='relu')
# 或正态版:
# nn.init.kaiming_normal_(layer.weight, mode='fan_in', nonlinearity='relu')
print("Kaiming Uniform - 权重标准差:", layer.weight.std().item())

3.3.8.1 均匀分布初始化

使用均匀分布随机初始化权重,常用于浅层网络。

✅ 特点:

  • 权重从 [−a, a] 区间中均匀采样
  • 简单直观,但容易导致梯度消失或爆炸

在这里插入图片描述

import torch
import torch.nn as nn

# 定义一个简单的全连接层
linear = nn.Linear(10, 5)

# 使用均匀分布初始化
torch.nn.init.uniform_(linear.weight, a=-0.1, b=0.1)
print("均匀初始化后的权重范围:", linear.weight.min().item(), "到", linear.weight.max().item())

小结:
✅ 输出示例:均匀初始化后的权重范围: -0.099 到 0.098

3.3.8.2 正态分布初始化

权重服从正态分布,均值为0,标准差可调。

✅ 特点:

  • 更符合自然数据分布
  • 可控性强,适合大多数情况

在这里插入图片描述

import torch
import torch.nn as nn

linear = nn.Linear(10, 5)

# 使用标准正态分布初始化
torch.nn.init.normal_(linear.weight, mean=0.0, std=0.01)
print("正态初始化后均值:", linear.weight.mean().item())
print("正态初始化后标准差:", linear.weight.std().item())

总结:
✅ 输出示例:
正态初始化后均值: 0.001
正态初始化后标准差: 0.010

3.3.8.3 全0/1 和 指定值初始化

直接将权重设置为固定值(如全0、全1或自定义值)。

⚠️ 注意:

  • 全0初始化会导致对称性问题 → 所有神经元学习相同特征 ❌
  • 全1可能导致激活函数饱和
import torch
import torch.nn as nn

linear = nn.Linear(10, 5)

# 全0初始化(不推荐)
torch.nn.init.zeros_(linear.weight)
print("全0初始化后权重:", linear.weight)

# 全1初始化
torch.nn.init.ones_(linear.weight)
print("全1初始化后权重:", linear.weight)

# 自定义值初始化(例如全部设为0.5)
torch.nn.init.constant_(linear.weight, val=0.5)
print("常数0.5初始化后权重:", linear.weight)

小结:
🎯 除非特殊需求,否则避免全0或全1!

3.3.8.4 Kaiming初始化

专为 ReLU 类激活函数设计,由 He et al. 提出。

✅ 特点:

  • 保持前向传播方差稳定
  • 适用于 ReLU、LeakyReLU 等非线性激活函数
    在这里插入图片描述
import torch
import torch.nn as nn

linear = nn.Linear(10, 5)

# Kaiming初始化(默认为ReLU)
torch.nn.init.kaiming_normal_(linear.weight, mode='fan_in', nonlinearity='relu')

print("Kaiming初始化后标准差:", linear.weight.std().item())

小结:
✅ 输出示例:Kaiming初始化后标准差: 0.447(≈ √(2/10))

3.3.8.5 Xavier初始化

适用于 Sigmoid、Tanh 等激活函数,保持输入输出方差一致。

✅ 特点:

  • 平衡前后层的信号传播
  • 适合 sigmoid/tanh 激活函数
    在这里插入图片描述
import torch
import torch.nn as nn

linear = nn.Linear(10, 5)

# Xavier初始化(正态分布)
torch.nn.init.xavier_normal_(linear.weight)

print("Xavier初始化后标准差:", linear.weight.std().item())

小结:
✅ 输出示例:Xavier初始化后标准差: 0.387(≈ √(2/(10+5)))

3.3.8.6 总结

📌 最佳实践建议:

  • 使用 Kaiming 初始化 → ReLU类激活函数 ✅
  • 使用 Xavier 初始化 → Sigmoid/Tanh ✅
  • 避免全0/1初始化 ❌
  • 正态/均匀仅作备用方案
初始化方法 推荐激活函数 是否推荐
全0 / 全1 任何 ❌ 不推荐
随机均匀/正态 ⚠️ 谨慎使用(需调参)
Xavier (Glorot) Tanh, Sigmoid ✅ 推荐
Kaiming (He) ReLU, LeakyReLU ✅ 强烈推荐

3.4 损失函数(重点🔥)

损失函数(Loss Function),也称为代价函数(Cost Function)或目标函数(Objective Function),是衡量模型预测值与真实标签之间差异的函数。它是训练神经网络的核心驱动力——通过最小化损失函数,模型不断调整参数以提升预测准确性。
核心作用
量化误差:告诉模型“错得多离谱”。
提供优化方向:通过反向传播计算梯度,指导参数更新。
连接模型与任务:不同任务(分类、回归等)需匹配不同损失函数。

3.4.1 多分类任务-交叉熵损失函数

在多分类任务中,我们希望模型输出一个概率分布(通常通过 Softmax 激活函数),表示样本属于各个类别的概率。交叉熵损失衡量的是真实标签与预测概率分布之间的差异。
在这里插入图片描述

import torch
import torch.nn as nn

# 假设有一个三分类问题,batch_size=2
pred_logits = torch.tensor([
    [2.0, 1.0, 0.1],  # 第一个样本的 logit
    [0.5, 3.0, 1.2]   # 第二个样本的 logit
])

# 真实标签:类别索引(不是 one-hot)
true_labels = torch.tensor([0, 1])  # 第一个样本是类别0,第二个是类别1

# 定义交叉熵损失函数
criterion = nn.CrossEntropyLoss()

# 计算损失
loss = criterion(pred_logits, true_labels)
print(f"交叉熵损失: {loss.item():.4f}")

小结:
✅ 多分类任务常用交叉熵损失函数。
✅ 使用 nn.CrossEntropyLoss() 可直接传入 logits 和类别索引。
✅ 能自动处理 Softmax 和对数计算,避免数值不稳定。

3.4.2 二分类任务- 交叉熵损失函数

对于二分类问题,可以将输出视为属于正类的概率(如 sigmoid 输出)。此时使用二元交叉熵(Binary Cross-Entropy, BCE)。
在这里插入图片描述

import torch
import torch.nn as nn

# 假设有两个样本,每个样本输出一个 logit(代表正类得分)
pred_logits = torch.tensor([[2.0], [0.1]])  # shape: [2, 1]

# 真实标签:0 或 1
true_labels = torch.tensor([[1], [0]])     # shape: [2, 1]

# 使用 BCEWithLogitsLoss(推荐)
criterion = nn.BCEWithLogitsLoss()

# 计算损失
loss = criterion(pred_logits, true_labels.float())
print(f"BCE 损失: {loss.item():.4f}")

小结:
✅ 二分类任务使用 BCEWithLogitsLoss 更稳定。
✅ 不需要手动加 sigmoid,内部自动处理。
✅ 支持 batch 处理,适合训练神经网络。

3.4.3 回归任务-MAE损失函数

MAE(Mean Absolute Error,平均绝对误差)是回归任务中最常用的损失之一。它衡量预测值与真实值之间绝对差的平均值。
公式为:

import torch
import torch.nn as nn

# 假设预测值和真实值
predictions = torch.tensor([3.0, -1.0, 2.5])
targets = torch.tensor([2.0, -0.5, 3.0])

# 定义 MAE 损失
criterion = nn.L1Loss()  # L1Loss 即 MAE

# 计算损失
loss = criterion(predictions, targets)
print(f"MAE 损失: {loss.item():.4f}")

小结:
✅ MAE 对离群点鲁棒性强。
✅ 使用 nn.L1Loss() 实现简单。
✅ 梯度恒定,适合数据有噪声的情况。

3.4.4 回归任务-MSE损失函数(常用)

MSE(Mean Squared Error,均方误差)是最常见的回归损失函数。它计算预测值与真实值平方差的平均。
在这里插入图片描述

import torch
import torch.nn as nn

# 预测值和真实值
predictions = torch.tensor([3.0, -1.0, 2.5])
targets = torch.tensor([2.0, -0.5, 3.0])

# 定义 MSE 损失
criterion = nn.MSELoss()

# 计算损失
loss = criterion(predictions, targets)
print(f"MSE 损失: {loss.item():.4f}")

小结:
✅ MSE 是回归任务中最常用的损失函数。
✅ 梯度随误差放大,加速收敛。
✅ 对异常值敏感,需注意数据清洗。

3.4.5 回归任务-Smooth L1损失函数

Smooth L1(也称 Huber Loss)是一种结合了 MSE 和 MAE 的损失函数,在误差小时用 MSE,误差大时用 MAE,从而兼顾平滑性和鲁棒性。
在这里插入图片描述

import torch
import torch.nn as nn

# 预测值和真实值
predictions = torch.tensor([3.0, -1.0, 2.5])
targets = torch.tensor([2.0, -0.5, 3.0])

# 定义 Smooth L1 损失(默认 beta=1.0)
criterion = nn.SmoothL1Loss()

# 计算损失
loss = criterion(predictions, targets)
print(f"Smooth L1 损失: {loss.item():.4f}")

小结:
✅ Smooth L1 在小误差时像 MSE,大误差时像 MAE。
✅ 抗噪能力强,梯度更稳定。
✅ 常用于目标检测(如 Faster R-CNN)中的边界框回归。

任务类型 损失函数 适用场景 特点
多分类 CrossEntropyLoss 多类别分类 自动 softmax,稳定
二分类 BCELoss / BCEWithLogitsLoss 二分类 输出概率,后者更稳定
回归 L1Loss 抗噪强 对异常值不敏感
回归 MSELoss 通用回归 敏感于大误差
回归 SmoothL1Loss 目标检测等 平衡平滑与鲁棒

3.5 网络优化方法-梯度下降法(最重点🔥🔥🔥)

🔥🔥🔥核心公式: w新 = w旧 - 学习率(步长) * 梯度🔥🔥🔥

3.5.1 指数加权平均(了解)

指数加权平均(Exponentially Weighted Average, EWA)是一种用于平滑序列数据的技术,在优化算法中常用来跟踪参数或梯度的历史变化。
在这里插入图片描述

import torch
import matplotlib.pyplot as plt

# 模拟一个波动的序列(例如损失)
losses = torch.randn(100) * 0.5 + 2.0  # 均值为2,有噪声

# 指数加权平均
beta = 0.9
ewa_values = []
v = 0.0  # 初始化

for loss in losses:
    v = beta * v + (1 - beta) * loss
    ewa_values.append(v)

# 绘图对比原始数据与平滑后结果
plt.figure(figsize=(10, 6))
plt.plot(losses, label='原始损失', alpha=0.6)
plt.plot(ewa_values, label='指数加权平均', linewidth=2)
plt.xlabel('迭代次数')
plt.ylabel('损失')
plt.legend()
plt.title('指数加权平均平滑效果')
plt.show()

小结:
✅ 指数加权平均可用于平滑时间序列数据。
✅ 在优化器中用于计算梯度的动量(一阶矩)。
✅ 参数 β 控制平滑程度,β=0.9 是常见选择。

3.5.2 动量算法Momentum

动量(Momentum)是改进 SGD 的经典方法之一,其核心思想是引入“惯性”:利用过去梯度的方向来加速当前更新,从而减少震荡并加快收敛。
在这里插入图片描述

import torch
import torch.nn as nn
import torch.optim as optim

# 构造简单线性模型
X = torch.tensor([[1.0], [2.0], [3.0], [4.0]])
y = torch.tensor([[2.0], [4.0], [6.0], [8.0]])

model = nn.Linear(1, 1)
criterion = nn.MSELoss()

# 手动实现 Momentum 优化器
learning_rate = 0.1
momentum_beta = 0.9
velocity = torch.zeros_like(model.weight)  # 初始化速度向量

for epoch in range(100):
    optimizer.zero_grad()  # 清除梯度
    output = model(X)
    loss = criterion(output, y)
    loss.backward()

    # 手动更新动量
    velocity = momentum_beta * velocity + learning_rate * model.weight.grad.data
    model.weight.data -= velocity

    # 更新偏置(不带动量)
    model.bias.data -= learning_rate * model.bias.grad.data

print(f"最终权重: {model.weight.item():.4f}, 偏置: {model.bias.item():.4f}")

小结:
✅ 动量算法通过累积历史梯度方向加速收敛。
✅ 能有效减少震荡,提高训练稳定性。
✅ 在 PyTorch 中可通过 torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9) 直接使用。

3.5.3 随机梯度下降法SGD

随机梯度下降(Stochastic Gradient Descent, SGD)是深度学习中最基础的优化算法。它在每次迭代中仅使用一个样本(或一个小批量)来计算梯度,从而大幅降低计算成本。
在这里插入图片描述
✅ 优点:计算简单、内存占用少、适合大规模数据
❌ 缺点:学习率固定,收敛慢;梯度噪声大,易震荡

import numpy as np

# 模拟线性回归问题
X = np.array([[1, 2], [2, 3], [3, 4], [4, 5]])
y = np.array([2, 4, 6, 8])  # y = 2x + 0

# 初始化参数
theta = np.random.randn(2)  # w1, w2
b = 0
learning_rate = 0.01
epochs = 100

for epoch in range(epochs):
    for i in range(len(X)):
        x_i = X[i]
        y_pred = theta.dot(x_i) + b
        loss = (y_pred - y[i]) ** 2
        grad_theta = 2 * (y_pred - y[i]) * x_i
        grad_b = 2 * (y_pred - y[i])
        
        # 更新参数
        theta -= learning_rate * grad_theta
        b -= learning_rate * grad_b

print("最终参数:", theta, "偏置:", b)

🔍 小结:SGD 使用单个样本更新参数,速度快但不稳定。由于学习率固定,难以适应不同阶段的训练需求,容易陷入局部极小值或震荡不收敛。

3.5.2.4 自适应学习率 Adam(重点)

**🔷 核心思想:Adam = Momentum + RMSProp **
Adam(Adaptive Moment Estimation)是一种自适应学习率优化算法,它结合了:

  • ✅ 动量(Momentum):利用一阶矩平滑梯度方向 → 加速收敛
  • ✅ RMSProp 的自适应学习率:利用二阶矩调整每个参数的学习率 → 避免过大的更新
    在这里插入图片描述
import torch
import torch.nn as nn
import torch.optim as optim

# 定义简单模型
model = nn.Linear(10, 1)
criterion = nn.MSELoss()

# 使用 Adam 优化器
optimizer = optim.Adam(model.parameters(), lr=0.001, betas=(0.9, 0.999), eps=1e-8)

# 模拟训练过程
x = torch.randn(32, 10)
y_true = torch.randn(32, 1)

for epoch in range(10):
    optimizer.zero_grad()
    y_pred = model(x)
    loss = criterion(y_pred, y_true)
    loss.backward()
    
    # 打印梯度信息
    print(f"Epoch {epoch}, Loss: {loss.item():.4f}, Grad Norm: {model.weight.grad.norm().item():.4f}")
    
    optimizer.step()

✅ Adam 的优势总结:
➤ 快速收敛
➤ 自适应学习率
➤ 对超参数不敏感
➤ 适合稀疏梯度和噪声数据

3.5.2.5 自适应学习率Adamw(重点)

🔷 核心思想:AdamW = Adam + 正确的权重衰减

虽然 Adam 表现优异,但它在权重衰减(Weight Decay) 上存在一个关键问题:

❌ Adam 中的权重衰减被错误地应用到了动量项上,导致实际效果偏离理论预期。

✅ AdamW 的解决方案:
将权重衰减独立于梯度更新,即:

  • 先执行标准 Adam 更新
  • 再单独对参数施加 L2 正则化
    在这里插入图片描述
import torch
import torch.nn as nn
import torch.optim as optim

# 模型定义
model = nn.Linear(10, 1)
criterion = nn.MSELoss()

# 初始化两种优化器
optimizer_adam = optim.Adam(model.parameters(), lr=0.001, weight_decay=0.01)
optimizer_adamw = optim.AdamW(model.parameters(), lr=0.001, weight_decay=0.01)

# 训练循环(仅展示一次迭代)
x = torch.randn(32, 10)
y_true = torch.randn(32, 1)

print("=== Adam ===")
optimizer_adam.zero_grad()
loss = criterion(model(x), y_true)
loss.backward()
optimizer_adam.step()

print("=== AdamW ===")
optimizer_adamw.zero_grad()
loss = criterion(model(x), y_true)
loss.backward()
optimizer_adamw.step()

# 查看参数变化
print(f"Adam 参数范数: {model.weight.norm().item():.4f}")
print(f"AdamW 参数范数: {model.weight.norm().item():.4f}")

✅ AdamW 的优势总结:
➤ 更准确的正则化
➤ 更好的泛化性能
➤ 在大规模预训练中表现卓越

3.6 学习率优化策略

3.6.1 等间隔学习率衰减

✅ 核心思想:
每隔固定训练轮数(epoch),将学习率乘以一个衰减因子。
在这里插入图片描述

import torch
import torch.nn as nn
import torch.optim as optim

model = nn.Linear(10, 1)
optimizer = optim.SGD(model.parameters(), lr=0.1)

# 使用 StepLR 调度器
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)

# 模拟训练
for epoch in range(20):
    optimizer.zero_grad()
    loss = model(x).mean()  # 假设损失
    loss.backward()
    optimizer.step()

    print(f"Epoch {epoch}, LR: {optimizer.param_groups[0]['lr']:.4f}")
    scheduler.step()  # 更新学习率

3.6.2 指定间隔学习率衰减

✅ 核心思想:
在指定的epoch上进行学习率衰减,而不是等间隔。
在这里插入图片描述

scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=[10, 15], gamma=0.1)

for epoch in range(20):
    # 训练逻辑...
    scheduler.step()
    print(f"Epoch {epoch}, LR: {optimizer.param_groups[0]['lr']:.4f}")

3.6.3 指数学习率衰减

✅ 核心思想:
学习率按指数函数持续衰减,平滑下降。
在这里插入图片描述

scheduler = optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.95)

for epoch in range(10):
    # 训练...
    scheduler.step()
    print(f"Epoch {epoch}, LR: {optimizer.param_groups[0]['lr']:.4f}")

3.6.4余弦退火策略

✅ 核心思想:
学习率按照余弦函数从初始值下降到最小值,可选重启(Warm Restart)。
在这里插入图片描述

scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=10, eta_min=0.001)

for epoch in range(15):
    # 训练...
    scheduler.step()
    print(f"Epoch {epoch}, LR: {optimizer.param_groups[0]['lr']:.4f}")

3.6.5 总结

📌 最佳实践建议:

  • 小项目 → 用 StepLR
  • 中等任务 → 用 MultiStepLR
  • 长周期训练 → 用 ExponentialLR
  • 大模型/复杂任务 → 用 CosineAnnealingLR(尤其带重启)

3.7 正则化

3.7.1 正则项

✅ 核心思想:
在损失函数中加入惩罚项,限制模型复杂度,防止过拟合。
在这里插入图片描述

⚠️ 若 λ 太大 → 模型欠拟合
⚠️ 若 λ 太小 → 无效果

3.7.2 L1正则化(Lasso)

✅ 特点:

  • 加入权重的绝对值之和
  • 能产生稀疏解 → 部分权重变为0,实现特征选择

在这里插入图片描述

import torch
import torch.nn as nn
import torch.optim as optim

model = nn.Linear(10, 1)

# 使用 SGD + L1 正则化(需手动添加)
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 手动添加 L1 惩罚项
l1_lambda = 0.01
for param in model.parameters():
    optimizer.zero_grad()
    loss = criterion(model(x), y_true)
    l1_penalty = l1_lambda * torch.sum(torch.abs(param))
    loss += l1_penalty
    loss.backward()
    optimizer.step()

✅ 注意:PyTorch 的 weight_decay 默认是 L2,L1 需要手动实现!
🎯 适用场景:特征选择、稀疏建模

3.7.2 L2正则化(Ridge)

✅ 特点:

  • 加入权重的平方和
  • 使权重变小但不为零 → 平滑模型输出
    在这里插入图片描述
import torch
import torch.nn as nn
import torch.optim as optim

model = nn.Linear(10, 1)
criterion = nn.MSELoss()

# 使用 Adam + L2 正则化(weight_decay 即 L2)
optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=0.01)

for epoch in range(10):
    optimizer.zero_grad()
    loss = criterion(model(x), y_true)
    loss.backward()
    optimizer.step()

3.7.3 Dropout(重点🔥)

✅ 核心思想:
在训练过程中随机丢弃部分神经元,强制网络不依赖单一路径,增强泛化能力。

🧠 工作机制

  • 训练时:以概率 p 将神经元输出置为0
  • 测试时:所有神经元都保留,但输出乘以 (1−p)

📈 优点:

  • 防止过拟合
  • 提升鲁棒性
  • 类似于集成学习
import torch
import torch.nn as nn
import torch.optim as optim

# 定义含 Dropout 的网络
class Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(10, 50)
        self.dropout = nn.Dropout(p=0.5)  # 50% 概率丢弃
        self.fc2 = nn.Linear(50, 1)

    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = self.dropout(x)  # 训练时生效
        x = self.fc2(x)
        return x

model = Net()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 训练循环
for epoch in range(10):
    optimizer.zero_grad()
    y_pred = model(x)
    loss = criterion(y_pred, y_true)
    loss.backward()
    optimizer.step()

✅ 注意:Dropout 只在训练时启用,测试时自动关闭!
🎯 最佳实践:

  • 全连接层常用 p=0.3~0.5
  • CNN 层可设 p=0.2~0.3
  • 不建议在输入层或输出层使用

3.7.4 批量归一化(BN)

✅ 核心思想:
对每一批数据进行归一化处理,稳定内部协变量偏移(Internal Covariate Shift),加速训练。
在这里插入图片描述

import torch
import torch.nn as nn

class Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(10, 50)
        self.bn1 = nn.BatchNorm1d(50)  # 对50维特征做BN
        self.fc2 = nn.Linear(50, 1)

    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = self.bn1(x)  # BN 在激活后应用
        x = self.fc2(x)
        return x

model = Net()

✅ 优势:
加速收敛
减少对初始化的敏感性
可轻微提升性能

⚠️ 注意:BN 在训练和推理时行为不同!
训练时:使用当前 batch 统计量
推理时:使用全局均值和方差(momentum 参数)

3.7.5 总结

📌 最佳实践建议:

  • 小模型 → 优先用 L2 + Dropout
  • 大模型 → 必须用 BN + Dropout
  • 特征选择任务 → 使用 L1
  • CNN/RNN → 强烈推荐 BN

✅总结:
正则化是防止模型过拟合的"关键"

3.8 搭建神经网络模型

  1. Epoch: 训练轮数,使用全部数据对模型进行一轮完整训练,比如总轮数 Epochs = 100
  2. Batch: 批次大小,使用训练集中的小部分样本的梯度对模型权重进行更新, 比如 batch_size=16
  3. Iteration: 每次训练的批次数,使用一个 Batch 数据对模型进行一次参数更新的过程,比如 iterations=7
"""
案例:
    演示 搭建神经网络 的流程

深度学习的一般工作流:
    1.准备数据集
    2.搭建神经网络:
        前向传播方法forward
    3.模型训练:
        构造损失函数 优化器
        前向传播,获取预测值
        计算损失值
        梯度清零
        反向传播,计算梯度
        更新参数,梯度下降法
    4.模型测试

    
要求:
    第1个隐藏层:权重初始化采用标准化的xavier初始化 激活函数使用sigmoid
    第2个隐藏层:权重初始化采用标准化的He初始化 激活函数采用relu
    out输出层 采用softmax做数据归一化,权重初始化采用标准化的xavier初始化 

搭建神经网络的流程:
    1.定义一个模型类,继承 nn.Module
    2.定义__init__方法,搭建网络层
    3.定义forward方法,实现前向传播

"""

# 导包
import torch
import torch.nn as nn
from torchsummary import summary  # 计算模型参数,查看模型结构,需要安装 pip install torchsummary
# 设置设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 1.搭建神经网络
class ModelDemo(nn.Module):
    # 1.1 定义__init__方法,搭建网络层,并且进行参数初始化
    def __init__(self):
        # 初始化父类成员
        super().__init__()
        # 搭建网络层
        # 隐藏层1,输入特征数 3,输出特征数 3
        self.linear1 = nn.Linear(3, 3)
        # 隐藏层2,输入特征数 3,输出特征数 2
        self.linear2 = nn.Linear(3, 2)
        # 输出层,输入特征数 2,输出特征数 2
        self.output = nn.Linear(2, 2)

        # 参数初始化
        # 初始化隐藏层1
        nn.init.xavier_normal_(self.linear1.weight)
        nn.init.zeros_(self.linear1.bias)
        # 初始化隐藏层2
        nn.init.kaiming_normal_(self.linear2.weight)
        nn.init.zeros_(self.linear2.bias)
        # 初始化输出层
        nn.init.xavier_normal_(self.output.weight)
        nn.init.zeros_(self.output.bias)

    # 1.2 定义forward方法,实现前向传播
    def forward(self, x):
        # 输入隐藏层1,加权求和 + 激活函数
        # # 加权求和
        # y1 = self.linear1(x)
        # # 激活函数
        # y2 = torch.sigmoid(y1)
        # 合并写法
        x = torch.sigmoid(self.linear1(x))
        # 输入隐藏层2,加权求和 + 激活函数
        x = torch.relu(self.linear2(x))
        # 输入输出层,加权求和 + 激活函数
        x = torch.softmax(self.output(x), dim=-1)
        # 返回预测值
        return x

# 2.模型训练
def train():
    # 2.1 创建模型对象ModelDemo
    my_model = ModelDemo()
    print(my_model)

    # 2.2 创建数据集
    data = torch.randn(size=(10, 3))
    print(f"data: {data}")
    print(f"data.shape: {data.shape}")
    print(f"data.requires_grad: {data.requires_grad}")

    # 2.3 前向传播,模型预测
    output = my_model(data) # 底层自动调用forward方法, 执行前向传播
    print(f"output: {output}")
    print(f"output.shape: {output.shape}")
    print(f"output.requires_grad: {output.requires_grad}")
    print("-" * 60)

    # 2.4 计算 查看 模型参数
    print("计算模型参数:")
    # 参数1:模型对象;参数2:输入数据形状,10*3
    my_model.to(device=device)
    summary(my_model, input_size=(10, 3))
    print("查看模型参数:")
    for name,param in my_model.named_parameters():
        print(f"name: {name}")
        print(f"param: {param}")


# 3.测试
if __name__ == '__main__':
    train()

3.9 思路总结

感知机是最简单的神经元模型,神经元主要由树突和轴突组成,树突负责输入,轴突负责输出,它们中间的部分就负责处理。一个个神经元就构成了神经网络ANN的是一切的开始,它的最经典实现就是FNN全连接神经网络,每层神经元与下一层所有神经元都有连接,所以叫全连接。FNN主要由3层构成,分别是输入层,中间层(隐藏层),输出层。在中间层中每个神经元在前向传播的过程中维护着内部加权求和与激活函数的计算公式。激活函数通常以softMaxReLU最为常用,ReLU原本是为了解决梯度消失问题的,但是ReLU函数的左侧部分有时候又会引发神经网络稀疏问题,过于稀疏就会导致较为严重的神经元死亡问题。梯度消失的另一端是梯度爆炸,解决二者的手段通常使用参数初始化,常用的参数初始化就有kaiming初始化和xavier初始化。

将以上整体作为一个基本的神经网络内容,我们还得对其进行网络优化。常见的网络优化办法就是梯度下降法,常用的梯度下降优化办法是动量法momentumAdam以及Adamw。梯度下降法的核心公式为 : w新 = w旧 - 学习率 * 梯度 ,我们对学习率的优化策略通常有指定间隔、固定间隔、指数间隔,余弦退火策略。公式的梯度表示的是损失函数的梯度,常见的损失函数有二分类、多分类的交叉熵、回归任务的MAE、MSE、SmoothL1。对于损失函数,还可以加上正则项对其进行过拟合的优化,常见的正则化方法有L1,L2,Dropout(随机失活),BN(批量归一)

此外,还必须熟悉搭建神经网络的基本步骤
1.准备数据集
2.搭建神经网络模型
3.模型训练
~3.1 创建数据加载器
~3.2 前向传播
~3.3 计算损失值
~3.4 梯度清零
~3.5 反向传播
~3.6 更新参数
4.测试模型


Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐