Anaconda

Anaconda 是一款面向科学计算的开源 Python 发行版本,它集成了众多科学计算所需的库、工具和环境管理系统,旨在简化包管理和部署,提升开发与研究效率。

核心组件:

  • Conda:这是 Anaconda 自带的包和环境管理系统,可在不同环境中轻松安装、更新和卸载软件包,同时支持在 Windows、macOS 和 Linux 系统上创建独立的 Python 环境,避免不同项目间的依赖冲突。
  • Python:Anaconda 自带 Python 解释器,且会跟随版本更新,确保使用的是稳定且功能丰富的 Python 版本。
  • 科学计算库:包含了 NumPy、Pandas、SciPy、Matplotlib 等常用科学计算和数据分析库,为数据处理、数值计算和可视化提供了强大支持。

主要功能:

  • 环境管理:能创建多个相互独立的 Python 环境,每个环境可拥有不同的 Python 版本和软件包。例如,你可创建一个用于深度学习的环境,安装 TensorFlow、PyTorch 等库;再创建一个用于数据分析的环境,安装 Pandas、Matplotlib 等库,避免不同项目间的依赖冲突。
  • 包管理:借助 Conda 命令,可方便地安装、更新和卸载软件包。Conda 会自动解决包之间的依赖关系,避免手动安装时的复杂配置和冲突问题。
  • 跨平台支持:支持 Windows、macOS 和 Linux 等多种操作系统,方便不同平台的用户使用相同的工具和环境进行开发和研究。

应用场景:

  • 数据科学与机器学习:提供了丰富的数据处理、分析和可视化工具,以及常用的机器学习库,如 Scikit-learn、TensorFlow、PyTorch 等,是数据科学家和机器学习工程师的首选工具之一。
  • 科学研究:在物理学、生物学、化学等科学领域,Anaconda 提供了高效的数值计算和模拟工具,帮助科研人员快速实现算法和模型。
  • 软件开发:可用于开发 Python 应用程序,通过环境管理功能,确保项目在不同开发阶段和部署环境中的一致性。

安装与使用

  • 安装:可从 Anaconda 官方网站(Download Anaconda Distribution | Anaconda)下载适合自己操作系统的安装包,然后按照安装向导进行安装。
  • 使用:安装完成后,可通过命令行工具(如 Windows 的 Anaconda Prompt、macOS 和 Linux 的终端)使用 Conda 命令进行环境管理和包管理。例如,创建一个新的 Python 环境(在Anaconda Prompt中输入以下指令):
conda create -n myenv python=3.8 #-n是name的意思,后面跟着环境的名字

 激活环境:

conda activate myenv

在环境中安装包:

conda install numpy

查看环境中有哪些工具包:

pip list

 CUDA

深度学习离不开显卡,有没有显卡其实并不影响PyTorch,显卡主要起到训练加速的作用。像TensorFlow,PyTorch这种常用的深度学习框架在NVDIA显卡上部署相对较容易,除此之外也可以在AMD显卡上部署

以PyTorch在CUDA上部署为例

环境准备:

  • 安装 CUDA:CUDA 是 NVIDIA 推出的并行计算平台和编程模型,PyTorch 借助 CUDA 在 NVIDIA GPU 上运行。你要依据自己的 GPU 型号和系统环境,从 NVIDIA 官方网站下载并安装合适版本的 CUDA。
  • 安装 cuDNN:cuDNN 是 NVIDIA 专门为深度神经网络开发的 GPU 加速库,能进一步提升 PyTorch 在 GPU 上的运行效率。安装时需从 NVIDIA 官方网站下载对应的 cuDNN 库,并将其配置到 CUDA 环境中。
  • 安装支持 CUDA 的 PyTorch:通过 pip 或者 conda 安装支持 CUDA 的 PyTorch 版本。例如,使用 conda 安装支持 CUDA 11.8 的 PyTorch:
conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia

对于Intel核显,PyTorch的部署相对较麻烦。借助英特尔的 OpenVINO 工具包以及相关驱动,能够让 PyTorch 模型在 Intel 核显上进行推理,步骤如下:

1. 安装必要的库

要保证已安装 PyTorch 和 OpenVINO 工具包。可以通过以下命令安装 OpenVINO:

pip install openvino-dev[torch]

2. 导出 PyTorch 模型为 ONNX 格式

ONNX(Open Neural Network Exchange)是一种开放的模型格式,可实现不同深度学习框架间的模型转换。示例代码如下:

import torch
import torchvision.models as models

# 加载预训练的ResNet18模型
model = models.resnet18(pretrained=True)
model.eval()

# 创建一个示例输入
dummy_input = torch.randn(1, 3, 224, 224)

# 导出模型为ONNX格式
torch.onnx.export(model, dummy_input, "resnet18.onnx", export_params=True, opset_version=11)

3. 将 ONNX 模型转换为 OpenVINO 中间表示(IR) 

 使用 OpenVINO 的模型优化器将 ONNX 模型转换为 IR 格式:

mo --input_model resnet18.onnx

 4. 在 Intel 核显上运行推理

利用 OpenVINO 的 Python API 加载 IR 模型并在 Intel 核显上执行推理:

from openvino.runtime import Core

# 初始化OpenVINO核心对象
ie = Core()

# 加载IR模型
model_xml = "resnet18.xml"
model_bin = "resnet18.bin"
net = ie.read_model(model=model_xml, weights=model_bin)
exec_net = ie.compile_model(model=net, device_name="GPU")

# 准备输入数据
import cv2
import numpy as np

image = cv2.imread("test_image.jpg")
image = cv2.resize(image, (224, 224))
image = np.transpose(image, (2, 0, 1))
image = np.expand_dims(image, axis=0)

# 执行推理
output = exec_net([image])

注意事项:

  • 硬件和驱动要求:要保证你的 Intel 核显支持 OpenCL,并且安装了最新的显卡驱动。
  • 模型兼容性:并非所有的 PyTorch 模型都能完美转换为 ONNX 格式,部分复杂的自定义层或操作可能需要额外处理。
  • 性能表现:虽然 Intel 核显可用于推理,但在性能上可能不如独立显卡,特别是对于大规模的深度学习模型。

 安装PyTorch

在官网https://pytorch.org/中下载

下拉可以看到下图所示。根据下载方式选择相应的版本即可。下面是选择PyTorch稳定版Stable,在Windows系统下用conda方式安装,选择完成后,复制下载指令,在新建的myenv(conda create -n myenv python=3.8)环境中输入命令下载即可。

下载完成后,在 myenv环境下输入python3进入python3解释器,输入import torch回车后不报错说明Pytorch下载成功,再输入torch.cuda.is_available(),返回true说明gpu是可以被pytorch使用的

PyCharm

 PyCharm 是由 JetBrains 公司开发的一款专门针对 Python 的集成开发环境(IDE)。它为 Python 开发者提供了丰富的功能和工具,能有效提升开发效率

主要功能:

  1. 代码编辑
    • 智能代码补全:能根据上下文智能提示代码,包括变量名、函数名、类名等,还能补全方法的参数。
    • 代码高亮:对不同的代码元素,如关键字、注释、字符串等采用不同颜色显示,方便阅读和编写代码。
    • 代码格式化:可自动调整代码格式,使其符合 PEP 8 等编码规范,增强代码可读性。
  2. 代码调试
    • 设置断点:能在代码中任意位置设置断点,方便逐行执行代码,观察变量的值和程序的执行流程。
    • 变量监控:调试过程中可实时查看变量的值,帮助定位问题。
    • 多线程调试:支持对多线程程序进行调试,便于处理复杂的并发问题。
  3. 项目管理
    • 创建项目:可轻松创建不同类型的 Python 项目,如 Django、Flask 等 Web 项目,还能自动配置项目的运行环境。
    • 文件导航:提供便捷的文件导航功能,能快速定位到项目中的文件和文件夹。
    • 版本控制集成:集成了 Git 等版本控制系统,方便进行代码的版本管理和团队协作。
  4. 代码分析
    • 语法检查:实时检查代码中的语法错误,并给出相应的提示和建议。
    • 代码质量分析:能分析代码的复杂度、重复度等,帮助开发者优化代码结构。
    • 代码重构:支持多种代码重构操作,如重命名变量、提取方法等,提高代码的可维护性。

使用步骤

  1. 安装:从 JetBrains 官方网站下载适合自己操作系统的 PyCharm 安装包,然后按照安装向导进行安装。下载社区版,社区版免费。
  2. 创建项目:打开 PyCharm,选择 “Create New Project”,设置项目的名称、存储位置和 Python 解释器等信息。
  3. 编写代码:在项目中创建 Python 文件,开始编写代码。
  4. 运行和调试:点击工具栏上的运行或调试按钮,执行代码并进行调试

下载后在Pycharm里选择刚才创建的myenv环境 

Jupyter

Jupyter 是一个开源的 Web 应用程序,允许你创建和共享包含实时代码、方程、可视化和叙述性文本的文档,其名称源于对三种编程语言(Julia、Python、R)的致敬。

主要特点:

  • 交互性强:Jupyter Notebook 以单元格为单位组织代码,你能逐个单元格运行代码,并立即查看输出结果。这种交互方式让你可以快速测试代码片段、调整参数,实时看到代码执行效果,极大地提高了开发和调试效率。
  • 支持多种编程语言:尽管名字源于 Julia、Python 和 R,但 Jupyter 通过内核机制支持超过 40 种编程语言,如 Java、Scala、Ruby 等。这意味着你可以在同一个环境中使用不同语言进行编程,满足多样化的需求。
  • 可视化功能强大:能够直接在 Notebook 中显示各种可视化图表,如 Matplotlib、Seaborn 绘制的统计图表,Plotly 绘制的交互式图表等。这使得数据探索、分析和结果展示变得更加直观和便捷。
  • 文档一体化:除了代码和输出,Jupyter Notebook 还支持 Markdown 文本、LaTeX 公式等,你可以在文档中添加解释说明、注释、理论推导等内容,将代码、数据、分析过程和结果整合在一个文档中,形成完整的分析报告或研究记录。
  • 易于分享和协作:Jupyter Notebook 文件以.ipynb格式保存,这种文件可以轻松地在不同平台和环境中共享。你可以将 Notebook 上传到 GitHub、Jupyter Notebook Viewer 等平台,供他人查看和交互;也可以通过 JupyterHub 实现多用户协作,多个用户可以同时在一个 Notebook 上进行编辑和讨论。

核心组件:

  • Jupyter Notebook:这是 Jupyter 最常用的组件,是一个基于 Web 的交互式开发环境,用户可以通过浏览器访问和操作。在 Notebook 中,用户可以创建、编辑和运行代码单元格,同时添加文本说明和可视化结果。
  • JupyterLab:是 Jupyter Notebook 的下一代用户界面,提供了更强大的功能和更灵活的界面布局。它支持多文档视图、文件浏览器、终端等,类似于传统的集成开发环境(IDE),为用户提供了更丰富的开发体验。
  • Jupyter Kernel内核是 Jupyter 实现多语言支持的关键。每个内核对应一种编程语言,负责执行用户输入的代码,并将执行结果返回给前端界面。例如,Python 内核可以执行 Python 代码,R 内核可以执行 R 代码。

应用场景:

  • 数据科学与数据分析:Jupyter 是数据科学家和分析师的首选工具之一。在数据探索阶段,可以使用 Python 或 R 等语言对数据进行清洗、预处理和可视化;在建模阶段,可以使用机器学习库(如 Scikit-learn、TensorFlow)进行模型训练和评估;最后,将整个分析过程和结果整理成一个 Notebook 文档,方便分享和交流。
  • 学术研究:研究人员可以使用 Jupyter Notebook 记录实验过程、展示研究成果。通过在文档中插入代码、数据和可视化图表,结合 LaTeX 公式进行理论推导,使研究报告更加清晰和直观。
  • 教学:Jupyter 在教育领域也有广泛的应用。教师可以使用 Notebook 编写教学材料,将理论知识与实践代码相结合,让学生在交互式环境中学习编程和数据分析。学生可以通过运行和修改代码,加深对知识的理解和掌握。

使用步骤

安装:可以使用 pip 或 conda 进行安装。一般安装Anaconda时会自动安装Jupyter,可以在Anaconda文件下找到Jupyter Notebook。但是Jupyter默认只安装在base环境中,可以在新建的myenv环境当中再安装Jupyter。在Anaconda Prompt中进入myenv环境,输入以下指令安装

conda install nb_conda

启动:在终端中输入以下命令启动 JupyterNotebook:

jupyter notebook

创建和编辑 Notebook:启动 JupyterLab 后,在浏览器中打开相应的界面,点击 “New” 按钮选择新环境创建一个新的 Notebook,然后就可以开始编写代码和文本了。

运行代码:在代码单元格中输入代码后,按下Shift + Enter组合键即可运行代码,并查看输出结果。

Python包的概念(package)

在 Python 中,package(包)是一种组织和管理模块的方式,它允许将相关的模块组合在一起,形成一个层次化的目录结构,以便更好地进行代码的组织、维护和重用。

一个 Python 包本质上是一个包含__init__.py文件的目录。这个目录下可以包含多个模块文件(.py文件)以及其他子包。例如,要创建一个名为mypackage的包,需要创建一个名为mypackage的目录,在该目录下创建__init__.py文件,还可以根据需要创建其他模块文件,如module1.pymodule2.py等。(在包的目录中,__init__.py文件是必不可少的,它用于标识该目录是一个 Python 包。在__init__.py文件中,可以进行一些包的初始化操作,比如导入包中的其他模块、定义包的全局变量、执行一些初始化代码等。在 Python 3.3 及以上版本中,__init__.py文件不是必需的,但它仍然是一种很好的实践,有助于更好地组织和管理包的内容。)

可以使用import语句来导入包中的模块。例如,如果有一个包mypackage,其中包含module1.py模块,可以使用以下方式导入:

import mypackage.module1
# 或者
from mypackage import module1

导入模块后,就可以使用模块中定义的函数、类、变量等。例如,如果module1.py中定义了一个函数func1,可以通过以下方式调用:

import mypackage.module1

mypackage.module1.func1()
# 或者
from mypackage import module1

module1.func1()

常用的标准库包和第三方包:

  • 标准库包:Python 标准库提供了许多有用的包,如os包用于操作系统相关的操作,sys包用于访问 Python 解释器的相关信息和功能,math包用于数学计算等。
  • 第三方包:在 Python 社区中,有大量的第三方包可供使用。例如,numpy包用于数值计算,pandas包用于数据处理和分析,matplotlib包用于数据可视化,djangoflask是用于 Web 开发的包等。

 dir()与help()

在 Python 中,dir() 和 help() 是两个非常实用的内置函数,它们在探索和理解 Python 对象以及模块方面起着重要作用

  • dir() 函数主要用于快速查看对象的属性和方法名称,它返回的是一个名称列表,不提供具体的使用说明。
  • help() 函数则专注于提供对象的详细帮助信息,对于理解对象的功能和使用方法非常有帮助。两者结合使用,可以更深入地探索和学习 Python 中的各种对象和模块。

1,dir()

dir() 函数用于返回指定对象的所有属性和方法的名称列表。如果不提供参数,它将返回当前作用域内的所有名称列表,包括变量名、函数名、类名等。

dir([object])

其中,object 是可选参数,可以是任何 Python 对象,如模块、类、实例等。

 使用示例:

# 不传入参数,返回当前作用域内的所有名称
result = dir()
print(result)


# 传入math模块对象,返回math模块的所有属性和方法
import math
math_attributes = dir(math)
print(math_attributes)

# 传入类或实例对象
class MyClass:
    def __init__(self):
        self.my_variable = 10

    def my_method(self):
        pass

# 创建MyClass的实例
obj = MyClass()

# 获取实例的属性和方法
instance_attributes = dir(obj)
print(instance_attributes)

2,help()

help() 函数用于查看对象的详细帮助信息,它可以提供对象的文档字符串、用法说明、参数信息等。如果不提供参数,它将进入交互式帮助模式,允许你输入对象名称来获取帮助信息。 

help([object])

其中,object 是可选参数,可以是任何 Python 对象。

使用示例:

# 进入交互式帮助模式
help()

 运行上述代码后,会进入 Python 的交互式帮助模式,你可以在提示符下输入对象名称来获取相应的帮助信息,输入 q 可以退出帮助模式。

import math
# 获取math模块的帮助信息
help(math)

运行这段代码,会输出 math 模块的详细帮助信息,包括模块的概述、包含的函数和常量的说明等。

import math
# 获取math.sqrt函数的帮助信息
help(math.sqrt)

这里使用 help(math.sqrt) 获取 math 模块中 sqrt 函数的详细帮助信息,包括函数的作用、参数和返回值等。

 Numpy

简介

Numpy(Numerical Python)是 Python 生态中专门用于数值计算、数组操作的基础库,诞生的核心目的是解决 Python 原生列表在科学计算中的低效问题,让大规模数值计算(如矩阵乘法、数组统计)比原生列表快成百上千倍。ndarray 全称是N-dimensional Array(N 维数组), 是 Numpy 的核心数据结构,可以把它理解成 Python列表的高性能多维数组升级版

  • 0 阶 ndarray = 标量(如5.0);
  • 1 阶 ndarray = 向量(如[1,2,3]),对应 Python 一维列表;
  • 2 阶 ndarray = 矩阵(如[[1,2],[3,4]]),对应 Python 嵌套列表;
  • 3 阶及以上 = 高维数组(如彩色图片:高度 × 宽度 × 通道)。

Numpy基本操作

1.创建ndarray数组:

创建方式和 PyTorch Tensor 高度相似,常用方法有手动创建、固定值创建、随机值创建,满足不同场景需求

import numpy as np

# 1. 手动创建:从Python列表/嵌套列表转换(最常用)
arr1 = np.array([1, 2, 3])  # 1阶数组(向量)
arr2 = np.array([[1, 2], [3, 4]])  # 2阶数组(矩阵)
arr3 = np.array([[[1,2],[3,4]],[[5,6],[7,8]]])  # 3阶数组

# 2. 固定值创建:全0、全1、全指定值
arr_zeros = np.zeros((2, 3))  # 2行3列全0数组,参数是「形状(tuple)」
arr_ones = np.ones((3, 2))    # 3行2列全1数组
arr_full = np.full((2, 2), 5) # 2行2列全为5的数组

# 3. 随机值创建:均匀分布、标准正态分布(数据分析/建模常用)
arr_rand = np.random.rand(2, 3)  # 2行3列,0~1均匀分布随机数
arr_randn = np.random.randn(2, 3) # 2行3列,标准正态分布(均值0,方差1)
arr_randint = np.random.randint(0, 10, (2,3)) # 2行3列,0~9整数随机数

# 4. 特殊数组:等差数列、单位矩阵
arr_lin = np.linspace(0, 10, 5)  # 0到10,生成5个等距数:[ 0.  2.5  5.  7.5 10.]
arr_eye = np.eye(3)              # 3阶单位矩阵(对角线为1,其余为0)

# 打印查看(以arr2为例)
print("2阶数组arr2:\n", arr2)
print("arr2的形状:", arr2.shape)  # 输出 (2,2),表示2行2列

2.查看 ndarray 的核心属性:

import numpy as np

arr = np.array([[1, 2, 3], [4, 5, 6]])  # 2行3列,int类型

print("形状(行×列):", arr.shape)  # (2, 3) → 核心属性
print("维度数:", arr.ndim)          # 2 → 2阶数组
print("数据类型:", arr.dtype)       # int64 → 可通过dtype参数指定
print("元素总个数:", arr.size)      # 6 → 2×3
print("每个元素字节数:", arr.itemsize) # 8 → int64占8字节

3.索引与切片:

和 Python 原生列表的索引(取单个元素)、切片(取连续元素) 语法几乎完全一致,多维数组只需用逗号分隔不同维度,比原生嵌套列表简洁 10 倍

import numpy as np

arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])  # 3行3列矩阵

# 1. 索引:取单个元素,格式「arr[行索引, 列索引]」(索引从0开始)
print(arr[0, 1])  # 取第0行第1列 → 2
print(arr[2, -1]) # 负索引:第2行最后1列 →9

# 2. 切片:取连续元素,格式「arr[行切片, 列切片]」,切片规则[起始:结束:步长](结束不包含)
print(arr[0:2, :])  # 第0~1行,所有列 → [[1 2 3],[4 5 6]]
print(arr[:, 1:3])  # 所有行,第1~2列 → [[2 3],[5 6],[8 9]]
print(arr[1:, 1:])  # 第1行及以后,第1列及以后 → [[5 6],[8 9]]

# 3. 布尔索引:按条件提取元素(数据分析核心技巧)
arr_boo = arr[arr > 5]  # 提取所有大于5的元素 → [6 7 8 9]
print(arr_boo)

4.向量化运算:

这是 Numpy 最核心的功能,无需写任何 for 循环,直接对整个数组做逐元素运算,速度比原生列表的循环快成百上千倍,语法和普通数值运算一致。

import numpy as np

a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
c = 2  # 标量

# 1. 数组与标量运算:逐元素运算(最常用)
print(a + c)  # 所有元素+2 → [[3 4],[5 6]]
print(a * c)  # 所有元素×2 → [[2 4],[6 8]]

# 2. 数组与数组运算:逐元素运算(要求形状相同,或满足广播)
print(a + b)  # 逐元素加 → [[6 8],[10 12]]
print(a * b)  # 逐元素乘(注意:不是矩阵乘法!)→ [[5 12],[21 32]]

# 3. 矩阵乘法(线性代数核心,和PyTorch的@一致)
print(a @ b)  # 方法1:@运算符(推荐)
print(np.matmul(a, b))  # 方法2:np.matmul函数 → 结果都是[[19 22],[43 50]]

# 4. 常用数学函数:逐元素执行(内置,无需循环)
print(np.sqrt(a))  # 平方根 → [[1. 1.414],[1.732 2.]]
print(np.exp(a))   # 指数运算e^x
print(np.sin(a))   # 正弦函数

5.形状操作(重塑、拼接、分割):

实际使用中,经常需要调整数组形状(如把一维数组转二维)、拼接多个数组、分割一个数组,Numpy 提供了简洁的函数实现,无需手动处理

import numpy as np

# 基础数组
arr1 = np.array([[1,2], [3,4]])  # (2,2)
arr2 = np.array([[5,6], [7,8]])  # (2,2)
arr3 = np.array([1,2,3,4,5,6])   # (6,)

# 1. 重塑形状:reshape(核心,不改变元素,只改变维度)
arr3_23 = arr3.reshape(2, 3)  # 一维转2行3列 → (2,3)
arr3_32 = arr3.reshape(3, 2)  # 一维转3行2列 → (3,2)
arr3_flat = arr1.reshape(-1)  # 任意维度转一维 → (4,),-1表示自动计算元素个数

# 2. 拼接数组:np.concatenate(按行/按列)
arr_concat_row = np.concatenate([arr1, arr2], axis=0)  # 按行拼接(垂直)→ (4,2)
arr_concat_col = np.concatenate([arr1, arr2], axis=1)  # 按列拼接(水平)→ (2,4)

# 3. 分割数组:np.split
arr = np.array([1,2,3,4,5,6])
arr_split = np.split(arr, 3)  # 分成3个等长数组 → [array([1,2]), array([3,4]), array([5,6])]

6.数值计算与统计:

Numpy 内置了丰富的统计函数和线性代数函数,可直接对数组做整体 / 按维度的统计计算,无需手动实现,是数据分析的基础

import numpy as np

arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])  # 3行3列

# 1. 全局统计:对所有元素计算
print("求和:", np.sum(arr))       # 45
print("均值:", np.mean(arr))      # 5.0
print("方差:", np.var(arr))       # 6.666...
print("标准差:", np.std(arr))     # 2.581...
print("最大值:", np.max(arr))     # 9
print("最小值:", np.min(arr))     # 1
print("最大值索引:", np.argmax(arr)) # 8 → 扁平化后的索引

# 2. 按维度统计:指定axis,核心!
print("按行求和(axis=1):", np.sum(arr, axis=1))  # [6, 15, 24] → 每一行的和
print("按列求均值(axis=0):", np.mean(arr, axis=0))# [4.,5.,6.] → 每一列的均值

# 3. 线性代数常用操作(和PyTorch线性代数函数一致)
arr = np.array([[1,2], [3,4]])
print("矩阵的逆:\n", np.linalg.inv(arr))  # 求逆矩阵(线性代数核心)
print("矩阵的行列式:", np.linalg.det(arr)) # 求行列式 → -2.0
print("特征值:", np.linalg.eigvals(arr))  # 求特征值 → [-0.372..., 5.372...]

7.Numpy 与 Python 列表 / PyTorch Tensor 互转:

import numpy as np
import torch

# 1. Numpy ↔ Python列表
arr = np.array([1,2,3])
lst = arr.tolist()  # ndarray转列表 → [1,2,3]
arr2 = np.array(lst)# 列表转ndarray → array([1,2,3])

# 2. Numpy ↔ PyTorch Tensor(核心,深度学习数据预处理)
arr = np.array([[1,2], [3,4]])
tensor = torch.from_numpy(arr)  # ndarray转Tensor
arr3 = tensor.numpy()           # Tensor转ndarray(需Tensor在CPU上)

Tensor

简介

Tensor(张量)是 PyTorch最核心的基础数据结构,是实现深度学习计算、GPU 加速、自动求导(反向传播)的载体。它是用于存储数值、执行数学运算的高性能多维数值数组。(如果只是做普通数值计算,Numpy 足够;如果做深度学习,必须用 Tensor—— 它解决了 GPU 加速和反向传播的核心需求)

张量是向量、矩阵的高维推广,维度数称为阶(rank),比如标量(0 阶)、向量(1 阶)、矩阵(2 阶)、3 阶张量(如彩色图片:高度 × 宽度 × 通道)。

Python列表能装整数、字符串、列表、甚至函数,杂乱无章,拿取 / 计算效率低;而Tensor只装数值(整数 / 浮点数),按固定维度排列,支持 GPU 高速运算、自动记录计算过程(用于反向传播),这是列表完全做不到的。

Tensor可以存储任意维度的数值,示例如下:

  • 0维(标量):如损失值、准确率
  • 1维(向量):如单个样本的特征
  • 2维(矩阵):如多个样本的特征
  • 3维(高维数组):如单张彩色图片(通道 × 高 × 宽)
  • 4维(高维数组):如批量图片(批量数 × 通道 × 高 × 宽)

核心作用

所有深度学习操作(数据处理、模型训练、推理)都围绕Tensor展开,核心作用有 4 个:

  1. 数值数据的 “统一存储容器”:把原始数据(图片、文本、音频)转成数值后,用 Tensor 封装 —— 比如图片转成 4 维 Tensor(批量 × 通道 × 高 × 宽)、文本转成词嵌入向量(批量 × 序列长度 × 向量维度),让后续计算有统一的格式。
  2. GPU 加速的 “唯一入口”:深度学习的大规模计算(比如矩阵乘法、卷积)在 CPU 上慢到不可用,而 Tensor 可以一键迁移到 GPU:
     
    # 检查是否有GPU
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    # 将Tensor移到GPU,所有计算自动在GPU上执行(速度提升百倍)
    gpu_tensor = batch_images.to(device)
  3. 自动求导(反向传播)的 “底层支撑”:反向传播核心就是 Tensor 的requires_grad特性,开启该属性后,PyTorch 会自动记录 Tensor 的计算过程,反向传播时一键算出所有参数的梯度,无需手动推导
    # 定义模型参数,开启求导
    w = torch.tensor(0.5, requires_grad=True)
    b = torch.tensor(0.1, requires_grad=True)
    # 正向计算
    y_pred = w * 1.0 + b
    loss = 0.5 * (y_pred - 2.0) **2
    # 反向传播:自动计算w和b的梯度
    loss.backward()
    print("w的梯度:", w.grad)  # 输出tensor(-1.4000),和手动推导一致
  4. 深度学习全流程的 “接口适配”:PyTorch 的模型(如nn.Linearnn.Conv2d)、优化器(如SGDAdam)、数据加载器(DataLoader)都只接收 Tensor 作为输入 —— 比如模型的输入必须是 Tensor,优化器更新的参数也必须是 Tensor,没有 Tensor 就无法完成端到端的训练。

Tensor基本操作

1.基本创建

创建 Tensor 的方式和 Numpy 高度相似,常用torch.tensor()(手动传值)、torch.ones()/torch.zeros()(全 1 / 全 0)、torch.randn()(标准正态分布)

import torch

# 0阶张量(标量)
t0 = torch.tensor(5.0)
# 1阶张量(向量)
t1 = torch.tensor([1, 2, 3])
# 2阶张量(矩阵)
t2 = torch.tensor([[1, 2], [3, 4]])
# 3阶张量(如1张3×3的彩色图片:3通道×3高×3宽)
t3 = torch.ones((3, 3, 3))  # 全1,形状(通道, 高, 宽)
# 四阶张量(存32张224×224的彩色图片)
t4 = torch.randn(32, 3, 224, 224)  # 随机生成

# 查看核心属性:shape(形状)、dtype(数据类型)、device(计算设备)
print("t2形状:", t2.shape)  # 输出 torch.Size([2, 2]),类似Numpy的shape
print("t2数据类型:", t2.dtype)  # 输出 torch.int64(默认整数类型)
print("t2设备:", t2.device)  # 输出 cpu(默认CPU)

2.基本运算

支持逐元素运算(+/-/*/)和矩阵乘法(@torch.matmul()),语法和 Numpy 一致

import torch

a = torch.tensor([[1, 2], [3, 4]], dtype=torch.float32)  # 转浮点型避免整数截断
b = torch.tensor([[5, 6], [7, 8]], dtype=torch.float32)

# 逐元素加法
c1 = a + b
# 矩阵乘法(核心,神经网络权重计算用这个)
c2 = a @ b  # 等价于 torch.matmul(a, b)
# 标量乘法(张量×数字)
c3 = a * 2

print("逐元素加:\n", c1)
print("矩阵乘:\n", c2)

3.与 Numpy 互转

PyTorch 提供了numpy()from_numpy()方法,实现 Tensor 和 Numpy 数组的无损转换(共享内存,修改一个另一个会变)

import torch
import numpy as np

# Tensor → Numpy数组
t = torch.tensor([1, 2, 3])
n = t.numpy()
print("Tensor转Numpy:", n)  # 输出 [1 2 3]

# Numpy数组 → Tensor
n2 = np.array([4, 5, 6])
t2 = torch.from_numpy(n2)
print("Numpy转Tensor:", t2)  # 输出 tensor([4, 5, 6])

4.开启硬件加速(GPU 迁移)

这是 Tensor 和 Numpy 最核心的区别之一,只需用to()方法即可将 Tensor 移到 GPU,所有计算会自动在 GPU 上执行,速度比 CPU 快成百上千倍

import torch

# 检查是否有可用GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print("使用设备:", device)  # 有GPU输出cuda,否则输出cpu

# 创建Tensor并移到指定设备
t = torch.tensor([1, 2, 3]).to(device)
print("Tensor设备:", t.device)  # 输出 cuda:0(第0块GPU)或 cpu

5.自动求导

import torch

# 1. 定义模型参数:w和b,开启requires_grad=True(需要求导更新)
w = torch.tensor(0.5, requires_grad=True)  # 权重,初始值0.5
b = torch.tensor(0.1, requires_grad=True)  # 偏置,初始值0.1

# 2. 定义输入和真实标签(无需求导,默认requires_grad=False)
x = torch.tensor(1.0)  # 输入x=1
y_true = torch.tensor(2.0)  # 真实标签y=2

# 3. 正向传播:计算预测值和损失(MSE,乘1/2消去求导系数)
y_pred = w * x + b  # 模型预测:y=wx+b
loss = 0.5 * (y_pred - y_true) ** 2  # 损失函数L=1/2*(y_pred-y_true)²

# 4. 反向传播:触发梯度计算(从loss出发,计算w和b的梯度)
loss.backward()

# 5. 查看梯度:loss对w和b的偏导数(即∂L/∂w、∂L/∂b)
print("∂L/∂w =", w.grad)  # 输出 tensor(-1.4000)
print("∂L/∂b =", b.grad)  # 输出 tensor(-1.4000)

过拟合

过拟合指的是模型在训练数据上表现非常好,但在未见过的新数据(测试数据)上表现不佳的现象。简单来说,模型过度学习了训练数据中的细节和噪声,把一些只存在于训练数据中的特征也当作普遍规律来学习,导致模型对训练数据的拟合过于紧密,失去了对新数据的泛化能力

表现:

  • 训练集和测试集性能差异大:在训练过程中,模型在训练集上的损失函数值不断降低,准确率等评估指标不断提高,但在测试集上,损失函数值可能先降低后升高,准确率等指标达到一定程度后不再提升甚至下降
  • 对数据噪声敏感:过拟合的模型会把训练数据中的噪声也学习进来,当遇到新的数据时,即使数据与训练数据只有微小的差异(比如存在一些噪声干扰),模型的预测结果也会出现较大偏差。

原因:

  • 模型复杂度高:如果模型具有过多的参数和复杂的结构,它就有更大的能力去拟合训练数据中的所有细节,包括噪声和异常值。例如,在多项式回归中,使用过高阶的多项式会使模型曲线过于复杂,紧密贴合训练数据点。
  • 训练数据量不足:当训练数据量有限时,模型容易记住训练数据中的每一个样本,而不是学习到数据的普遍规律。例如,在图像分类任务中,如果训练图像的数量太少,模型可能只是记住了这些图像的特征,而无法对新的图像进行准确分类。
  • 数据特征过多:如果输入数据包含大量的特征,其中可能有很多是与目标变量无关或者相关性很弱的特征。模型在训练过程中可能会利用这些无关特征来拟合训练数据,从而导致过拟合。

解决方法

  • 增加训练数据:收集更多的数据可以让模型学习到更广泛的特征和规律,减少对训练数据中噪声和异常值的依赖。例如,在图像识别任务中,可以通过数据增强(如旋转、翻转、缩放等)的方法来增加训练数据的多样性。
  • 正则化:正则化是一种常用的防止过拟合的方法,它通过在损失函数中添加额外的惩罚项来限制模型的复杂度。常见的正则化方法包括 L1 正则化和 L2 正则化。
  • 使用 Dropout 层:在神经网络中,Dropout 层可以随机丢弃一部分神经元,使得模型不能过度依赖某些特定的神经元,从而增强模型的泛化能力。
  • 提前停止训练:在训练过程中,监控模型在验证集上的性能。当验证集上的性能不再提升时,停止训练,避免模型过度拟合训练数据
  • 简化模型结构:减少模型的参数数量和复杂度,例如减少神经网络的层数或神经元数量,选择更简单的模型架构。

欠拟合

欠拟合指的是模型在训练数据上都无法很好地学习数据的特征和规律,导致在训练集和测试集上的表现都不佳的现象。简单来讲,模型过于简单,不能捕捉到数据中的复杂模式,从而无法对数据进行有效的拟合。

表现:

  • 训练集和测试集性能都差:在训练过程中,模型在训练集上的损失函数值较高准确率等评估指标较低,并且在测试集上的表现同样不理想。例如,在一个线性回归任务中,若使用简单的线性模型去拟合一个非线性的数据集,模型将无法准确地拟合数据,训练集和测试集上的误差都会很大。
  • 模型预测结果与真实值偏差大:由于模型没有学习到数据的内在规律,其对数据的预测结果与真实值之间存在较大的差距。比如在图像分类任务中,欠拟合的模型可能无法正确区分不同类别的图像,分类准确率很低。

原因:

  • 模型复杂度不够:如果选择的模型过于简单,它可能无法表达数据中的复杂关系。例如,对于一个具有复杂非线性关系的数据集,使用简单的线性模型就会导致欠拟合。
  • 特征提取不充分:输入数据的特征不能很好地反映数据的本质信息,模型缺乏足够的信息来学习数据的规律。比如在文本分类任务中,如果只提取了文本中的少量关键词作为特征,而忽略了更多的语义和上下文信息,就可能导致模型欠拟合。
  • 训练时间不足:在训练过程中,如果模型没有经过足够的迭代次数进行训练,就可能无法充分学习到数据的特征和规律。例如,在神经网络训练中,过早停止训练可能会使模型还没有收敛到较好的状态。

解决方法:

  • 增加模型复杂度选择更复杂的模型结构来拟合数据。例如,在回归任务中,可以从简单的线性模型转换为多项式回归模型;在神经网络中,可以增加网络的层数或神经元数量。
  • 提取更多有效特征:对原始数据进行更深入的特征工程,提取更多能够反映数据本质信息的特征。比如在图像识别中,可以使用更高级的特征提取方法,如卷积神经网络自动提取图像的特征。
  • 延长训练时间:增加模型的训练迭代次数,让模型有足够的时间来学习数据的特征和规律。同时,可以调整学习率等超参数,使模型能够更有效地收敛。
  • 使用更合适的模型算法:根据数据的特点和问题的性质,选择更适合的模型算法。例如,对于非线性数据,可以选择决策树、支持向量机等非线性模型。

正则化

正则化(Regularization)是机器学习和深度学习中用于防止模型过拟合、提高模型泛化能力的重要技术。正则化是指在模型训练过程中,通过在损失函数中添加额外的惩罚项,来约束模型的复杂度,使得模型在学习数据特征的同时,不会过度拟合训练数据中的噪声和异常值,从而提高模型在未知数据上的泛化能力。

原理:

在机器学习中,模型的目标是最小化损失函数,以使得模型的预测结果尽可能接近真实值。然而,过于复杂的模型可能会在训练数据上表现得非常好,但在新数据上表现不佳,即出现过拟合现象。正则化的基本思想是,在损失函数中加入一个与模型复杂度相关的惩罚项,当模型变得过于复杂时,惩罚项的值会增大,从而增加损失函数的值。这样,模型在训练过程中需要在拟合数据和控制复杂度之间进行权衡,避免过度拟合训练数据。

常见的正则化类型:

1. L1 正则化(Lasso 正则化)

  • 定义:在损失函数中添加模型参数的绝对值之和作为惩罚项。对于线性回归模型,带有 L1 正则化的损失函数可以表示为:
  • 特点:L1 正则化具有特征选择的作用,它可以使部分参数变为 0,从而将一些不重要的特征剔除,得到一个更简洁的模型。

2. L2 正则化(Ridge 正则化)

  • 定义:在损失函数中添加模型参数的平方和作为惩罚项。对于线性回归模型,带有 L2 正则化的损失函数可以表示为:

  • 特点:L2 正则化会使模型参数的值变小,但不会将其变为 0。它可以防止模型参数变得过大,从而减少模型的复杂度。

3. Dropout(适用于神经网络)

  • 定义:在神经网络的训练过程中,Dropout 会以一定的概率随机 “丢弃”(将神经元的输出置为 0)一部分神经元。这样可以使得模型不能过度依赖某些特定的神经元,从而增强模型的泛化能力。在测试阶段,通常会将所有神经元的输出乘以一个缩放因子(, 是丢弃概率)来保证训练和测试阶段的输出期望一致。
  • 特点:Dropout 可以看作是一种特殊的正则化方法,它相当于在训练过程中随机组合不同的子网络进行训练,最终的模型是这些子网络的组合,从而提高了模型的泛化能力。

梯度爆炸/消失

梯度是反向传播时,参数更新的 “方向 + 步长”。模型训练的本质是通过梯度调整参数,让 Loss 变小;而梯度消失 / 爆炸,就是这个 “步长” 出了极端问题:

  • 梯度消失:步长趋近于 0 → 参数几乎不更新。
  • 梯度爆炸:步长趋近于无穷大 → 参数更新幅度过大。 

梯度消失是反向传播过程中,梯度值随着网络层数的传递不断衰减,最终趋近于 0,导致深层网络的参数几乎无法更新。

梯度爆炸是反向传播过程中,梯度值随着网络层数的传递不断放大,最终趋近于无穷大,导致参数更新幅度过大,权重值变得极大,训练崩溃。

一,TensorBoard的使用

1,介绍

在 PyTorch 中,TensorBoard 是一个强大的可视化工具,它最初由 TensorFlow 团队开发,PyTorch 通过torch.utils.tensorboard模块集成了 TensorBoard 的功能。TensorBoard 主要用于帮助用户在深度学习模型的训练和开发过程中进行可视化和监控。

  • 标量可视化(Scalars):在训练过程中,我们通常会关注一些标量值,如损失值(Loss)、准确率(Accuracy)等。TensorBoard 可以将这些标量值随训练步数的变化绘制成折线图,帮助我们直观地观察模型的训练趋势,判断模型是否收敛、是否过拟合等。
  • 图像可视化(Images)TensorBoard 可以展示训练数据、生成的图像、模型的中间特征图等。这对于计算机视觉任务尤为有用,例如在图像分类、目标检测、图像生成等任务中,我们可以通过可视化图像来检查数据的预处理是否正确,或者观察生成模型的输出效果
  • 直方图可视化(Histograms):在深度学习中,我们经常需要观察模型参数(如权重、偏置)和梯度的分布情况。TensorBoard 的直方图功能可以展示这些张量的分布随训练步数的变化,帮助我们了解模型参数的更新情况,判断是否存在梯度消失或梯度爆炸等问题。
  • 模型结构可视化(Graphs):TensorBoard 可以将 PyTorch 模型的计算图可视化,展示模型的结构和数据流。这有助于我们理解模型的架构,检查模型的输入输出是否正确。
  • 嵌入可视化(Embeddings):在处理高维数据时,如文本、图像等,我们通常会使用嵌入(Embedding)技术将数据映射到低维空间。TensorBoard 的嵌入可视化功能可以将这些低维嵌入向量可视化,帮助我们观察数据的聚类情况和分布特征。

打开项目,并将其环境设置为要使用PyTorch的那个环境,这里我们命名为myTorch。

2,安装 

在Anaconda Prompt中激活要安装到的对应环境或者在PyCharm终端输入pip install tensorboard

3,SummaryWriter类 

它主要用于将各种类型的数据写入事件文件(log_dir文件夹),这些文件可以被 TensorBoard 读取并进行可视化展示。

SummaryWriter 类的主要功能是创建一个用于写入事件的对象,通过调用该对象的不同方法,可以将标量、图像、直方图、模型图等不同类型的数据写入指定的日志目录,方便后续使用 TensorBoard 进行可视化分析。

可以看到初始化函数需要我们输入文件夹保存的目录log_dir,如果不输入也是可以的。 后面的参数不常用。

重要的函数是add_image()和add_scalar()和close()函数,其余作为了解即可,也并没有很详细的介绍

(1)实例化类 

from torch.utils.tensorboard import SummaryWriter

# 创建一个 SummaryWriter 对象,指定日志保存的目录
# 如果不指定目录,默认会在 runs 目录下创建一个以当前时间戳命名的子目录
writer = SummaryWriter('runs/my_experiment')

 以下是官方给的示例

关闭 SummaryWriter.在完成数据写入后,建议关闭 SummaryWriter 对象,以确保所有数据都被正确写入磁盘。 

# 关闭 SummaryWriter
writer.close()

 (2)add_scalar()函数

 用于记录训练过程中的标量值,如损失值、准确率等,随着训练步骤的增加,可观察这些值的变化趋势。函数定义如下

add_scalar(tag, scalar_value, global_step=None, walltime=None)
  • tag
    • 类型:字符串(str)。
    • 作用:用于标识这个标量的名称,通常会以一种有层次的命名方式,比如 'Loss/train' 表示训练过程中的损失值,'Accuracy/val' 表示验证集上的准确率。在 TensorBoard 的可视化界面中,不同的 tag 会对应不同的折线图
  • scalar_value
    • 类型:浮点数(float)或整数(int)。
    • 作用:需要记录的标量值,例如训练过程中的损失值、准确率等。
  • global_step
    • 类型:整数(int),可选参数,默认值为 None
    • 作用:表示当前的训练步骤,通常是训练的轮数(epoch)或者迭代次数(iteration)。TensorBoard 会根据 global_step 的值来确定在 x 轴上的位置,从而绘制出标量值随训练步骤的变化曲线。
  • walltime
    • 类型:浮点数(float),可选参数,默认值为 None
    • 作用:表示记录该标量值的实际时间(以秒为单位)。如果不指定,会使用当前系统时间。一般情况下,这个参数可以不设置。
from torch.utils.tensorboard import SummaryWriter

writer = SummaryWriter("logs")

# 模拟训练过程
for epoch in range(100):
    # 模拟损失值
    loss = 1 / (epoch + 1)
    # 将损失值写入 TensorBoard
    writer.add_scalar('y=x', loss, epoch)

write.close()

运行完成后就可以看到在当前目录中产生了logs文件夹,文件夹下就是产生的事件文件

同一个 tag名下的不同事件会集中在一个坐标轴下

 先查看(4)打开事件一节!!!

补充:记录多个不同类型的标量值

from torch.utils.tensorboard import SummaryWriter
import torch

# 创建一个 SummaryWriter 对象
writer = SummaryWriter('runs/multiple_scalars')

# 模拟训练过程
num_epochs = 100
for epoch in range(num_epochs):
    # 模拟训练损失值
    train_loss = 1 / (epoch + 1)
    # 模拟验证集准确率
    val_accuracy = epoch / num_epochs

    # 记录训练损失值
    writer.add_scalar('Loss/train', train_loss, epoch)
    # 记录验证集准确率
    writer.add_scalar('Accuracy/val', val_accuracy, epoch)

# 关闭 SummaryWriter
writer.close()

(3)add_image()函数

可将图像数据(如训练样本、生成的图像等)写入日志,方便直观查看。

add_image(tag, img_tensor, global_step=None, walltime=None, dataformats='CHW')
  1. tag
    • 类型:字符串(str)。
    • 作用:图像的标识名称,用于在 TensorBoard 界面中区分不同的图像。例如,'Training Images''Generated Images' 等。
  2. img_tensor
    • 类型torch.Tensor 或 numpy.ndarray
    • 作用:要记录的图像数据。该张量或数组需要遵循特定的数据格式,具体格式由 dataformats 参数指定,一般采用HWC这种numpy型的格式。
  3. global_step
    • 类型:整数(int),可选参数,默认值为 None
    • 作用:表示当前的训练步骤,通常是训练的轮数(epoch)或者迭代次数(iteration)。TensorBoard 会根据 global_step 的值来确定图像的显示顺序,方便观察图像随训练过程的变化。
  4. walltime
    • 类型:浮点数(float),可选参数,默认值为 None
    • 作用:表示记录该图像的实际时间(以秒为单位)。如果不指定,会使用当前系统时间。一般情况下,这个参数可以不设置。
  5. dataformats
    • 类型:字符串(str),可选参数,默认值为 'CHW'
    • 作用:指定 img_tensor 的数据格式。常见的格式有:
      • 'CHW':表示通道(Channel)、高度(Height)、宽度(Width)的顺序,这是 PyTorch 中图像数据的常见格式。
      • 'HWC':表示高度、宽度、通道的顺序,这是 OpenCV 等库中常用的格式,也是numpy型的数据。
      • 'HW':用于单通道图像,表示高度和宽度。
from torch.utils.tensorboard import SummaryWriter
import numpy as np
from PIL import Image  # 用于读取图像

writer = SummaryWriter("logs")
image_path = "data/train/ants_image/0013035.jpg" # 图像相对路径
img_PIL = Image.open(image_path)  # 读取图像为PIL格式
img_array = np.array(img_PIL)  # 将图像转换为numpy的格式
print(imag_array.shape)  # 打印查看是否为HWC三通道

writer.add_image("test", img_array, 1, dataformats='HWC')

write.close()

 点击进入到image界面,就可以看到图像了

补充:使用 torch.Tensor类型读取单张图像

import torch
from torch.utils.tensorboard import SummaryWriter

# 创建一个 SummaryWriter 对象
writer = SummaryWriter('runs/image_example')

# 模拟一张 3 通道的图像数据,形状为 (3, 224, 224)
'''torch.randn 是 PyTorch 库中一个常用的函数,用于生成服从标准正态分布(均值为 0,标准差为 1)的
随机数张量。torch.randn 函数在 torch 命名空间下,可直接通过 torch.randn() 调用,它主要用于创建随
机初始化的张量,在深度学习模型初始化权重、生成随机噪声等场景中非常有用。如下所示是创建三维张量,这里
只知道是创建一张图像即可'''
img_tensor = torch.randn(3, 224, 224)

# 将图像写入 TensorBoard
writer.add_image('Single Image', img_tensor, global_step=0)

# 关闭 SummaryWriter
writer.close()

补充:写入写入多张图像组成的网格。仅作了解,函数看不懂没关系

import torch
import torchvision
from torch.utils.tensorboard import SummaryWriter
from torchvision import datasets, transforms

# 创建一个 SummaryWriter 对象
writer = SummaryWriter('runs/image_grid_example')

# 加载 MNIST 数据集
transform = transforms.Compose([transforms.ToTensor()])
trainset = datasets.MNIST('mnist_train', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)

# 获取一批图像数据
images, labels = next(iter(trainloader))

# 创建一个图像网格
img_grid = torchvision.utils.make_grid(images)

# 将图像网格写入 TensorBoard
writer.add_image('MNIST Images Grid', img_grid, global_step=0)

# 关闭 SummaryWriter
writer.close()

 (4)打开事件

在终端中输入tensorboard --logdir=logs(logdir=事件文件所在文件夹名),如下,点击http://localhost:6006即可打开

也可以指定端口号

之后每次修改完数据,只要端口号不变,代码运行后进入gttp://localhost:6007然后点击右上角更新即可 。

(5)add_histogram()函数

用于记录张量的直方图,可观察模型参数(如权重、偏置)或梯度的分布情况 

import torch.nn as nn

# 定义一个简单的神经网络
class SimpleNet(nn.Module):
    def __init__(self):
        super(SimpleNet, self).__init__()
        self.fc = nn.Linear(10, 1)

    def forward(self, x):
        return self.fc(x)

# 初始化模型
model = SimpleNet()

# 模拟训练过程
for step in range(100):
    # 模拟输入数据
    inputs = torch.randn(32, 10)
    outputs = model(inputs)
    # 模拟损失计算
    loss = outputs.sum()
    loss.backward()

    # 将模型参数的直方图写入 TensorBoard
    for name, param in model.named_parameters():
        writer.add_histogram(name, param, step)

    # 清空梯度
    model.zero_grad()

(6)add_graph函数

将 PyTorch 模型的计算图写入日志,方便查看模型的结构和数据流。

# 模拟输入数据
inputs = torch.randn(32, 10)

# 将模型的计算图写入 TensorBoard
writer.add_graph(model, inputs)

二,torchvision中的transforms

在 PyTorch 的 torchvision 库中,transforms 模块提供了一系列用于图像预处理和数据增强的工具。这些工具可以帮助我们对图像数据进行各种转换操作,使得数据更适合模型的训练和评估。

常见功能:

  • 数据预处理:对图像进行标准化、归一化、调整大小等操作,使输入数据具有一致的格式和范围,有助于模型的收敛和性能提升。
  • 数据增强:通过随机裁剪、翻转、旋转等操作增加训练数据的多样性,提高模型的泛化能力,减少过拟合的风险。

以下是对transform类的简介。 

1. 转换类

这类主要用于在不同数据类型之间进行转换,例如将 PIL 图像转换为 torch.Tensor,或者将 torch.Tensor 转换回 PIL 图像。

  • ToTensor:将 PIL 图像或 NumPy 数组转换为 torch.Tensor,并将像素值从 [0, 255] 归一化到 [0, 1]
  • ToPILImage:将 torch.Tensor 或 NumPy 数组转换为 PIL 图像。
  • Lambda:应用一个用户自定义的 lambda 函数进行转换。

2. 裁剪类

用于对图像进行裁剪操作,可调整图像的尺寸和保留特定区域。

  • CenterCrop:从图像的中心位置裁剪出指定大小的图像区域。
  • RandomCrop:随机裁剪图像到指定的大小。
  • RandomResizedCrop:随机裁剪图像并调整大小,裁剪区域的大小和长宽比是随机的。
  • FiveCrop:在图像的四个角和中心位置进行裁剪,返回包含五个裁剪图像的元组。
  • TenCrop:在图像的四个角、中心位置进行裁剪,并对这些裁剪图像进行水平翻转,返回包含十个裁剪图像的元组。

3. 调整大小类

改变图像的尺寸,以满足模型输入的要求。

  • Resize:将图像的大小调整为指定的尺寸。可以传入一个整数或元组作为目标尺寸。
  • Pad:在图像的边缘填充指定的像素值,可用于调整图像的大小或添加边框。

4. 翻转和旋转类

通过翻转和旋转操作增加数据的多样性,提高模型的泛化能力。

  • RandomHorizontalFlip:以指定的概率随机水平翻转图像。
  • RandomVerticalFlip:以指定的概率随机垂直翻转图像。
  • RandomRotation:随机旋转图像,旋转角度在指定范围内。
  • RandomAffine:对图像进行随机仿射变换,包括旋转、平移、缩放和剪切等操作。

5. 颜色变换类

对图像的颜色属性进行调整,如亮度、对比度、饱和度和色调等。

  • ColorJitter:随机调整图像的亮度、对比度、饱和度和色调。
  • Grayscale:将图像转换为灰度图像。
  • RandomGrayscale:以指定的概率将图像转换为灰度图像。

6. 归一化类

对图像的像素值进行归一化处理,使数据具有统一的分布。

  • Normalize:对图像的每个通道减去指定的均值并除以指定的标准差。

7. 组合类

用于将多个转换操作组合成一个序列,方便一次性应用多个转换。

  • Compose:将多个转换操作按顺序组合在一起。

8. 其他类

  • RandomChoice:从给定的一组转换操作中随机选择一个进行应用。
  • RandomApply:以指定的概率随机应用一组转换操作。

以下是对常用类的具体介绍。因为都是类,所以用的时候都是先创建对象再使用

 1,常用转换类

(1)ToTensor()

它主要用于把 PIL 图像或者 NumPy 数组转换为 torch.Tensor 类型,并且会对像素值进行一定的处理。

在深度学习任务中,模型通常要求输入数据为 torch.Tensor 类型,而原始的图像数据一般以 PIL 图像或者 NumPy 数组的形式存在。ToTensor() 类就提供了一种方便的方式将这些数据转换为模型可处理的张量格式,同时还会将像素值的范围从 [0, 255] 归一化到 [0, 1]

torchvision.transforms.ToTensor()

ToTensor() 类没有可传入的参数,调用时直接实例化即可。

from PIL import Image
import torchvision.transforms as transforms
import torch

# 打开一张图像
image = Image.open('example.jpg')

# 定义 ToTensor 转换操作
to_tensor = transforms.ToTensor()

# 将 PIL 图像转换为 torch.Tensor
tensor_image = to_tensor(image)

print("转换后的张量形状:", tensor_image.shape)
print("张量的数据类型:", tensor_image.dtype)
print("张量的最小值:", tensor_image.min().item())
print("张量的最大值:", tensor_image.max().item())

在上述代码中,首先使用 PIL 库打开一张图像,然后实例化 ToTensor() 类并将其应用到图像上,将图像转换为 torch.Tensor。最后打印出转换后张量的形状、数据类型以及最小值和最大值,可看到最大值不超过 1,最小值不小于 0。

注意事项:

  • 输入数据格式ToTensor() 接受的输入可以是 PIL.Image.Image 对象或者 NumPy 数组(形状为 (H, W, C),其中 H 是高度,W 是宽度,C 是通道数)。
  • 像素值归一化:转换后的 torch.Tensor 像素值范围会被归一化到 [0, 1],这是因为深度学习模型通常更喜欢处理这种范围的数据。
  • 通道顺序:转换后的张量形状为 (C, H, W),即通道维度在前,与 PIL 图像和 NumPy 数组的 (H, W, C) 顺序不同。

为什么要使用tensor数据类型?

在深度学习中广泛使用 Tensor 数据类型(如 PyTorch 里的 torch.Tensor),主要是因为它具有多方面的优势,能很好地适配深度学习的各类任务和计算需求。而且它包装了神经网络所需要的参数

1. 高效的数值计算

  • 硬件加速Tensor 可以充分利用 GPU(图形处理单元)进行高效的并行计算。现代的深度学习任务通常涉及大量的矩阵运算,如卷积、矩阵乘法等。GPU 拥有数千个小型处理核心,能够同时处理多个数据元素,使得这些矩阵运算的速度大幅提升。例如,在训练一个大型的卷积神经网络(CNN)时,使用 GPU 进行 Tensor 计算可以将训练时间从数天缩短至数小时甚至更短。
  • 优化的底层库支持:许多深度学习框架(如 PyTorch、TensorFlow)都在底层集成了高度优化的数值计算库,如 CUDA(NVIDIA 提供的并行计算平台和编程模型)和 cuDNN(NVIDIA 提供的深度神经网络库)。这些库针对 Tensor 运算进行了专门的优化,能够充分发挥硬件的性能。

2. 自动求导机制

  • 简化模型训练:在深度学习中,训练模型的核心是通过反向传播算法计算梯度,并根据梯度更新模型的参数。Tensor 数据类型支持自动求导(如 PyTorch 中的 autograd 机制),这意味着当你定义好一个计算图(由 Tensor 之间的运算构成)后,框架可以自动计算出每个参数的梯度。例如,在训练一个简单的全连接神经网络时,你只需要定义好前向传播的过程,框架就能自动完成反向传播的计算,大大简化了模型训练的代码实现。
  • 灵活性和可扩展性:自动求导机制使得你可以自由组合各种运算和模型结构,而无需手动推导和实现复杂的求导公式。这为研究人员和开发者提供了极大的灵活性,能够快速尝试不同的模型架构和算法。

3. 统一的数据表示

  • 支持多维数据:深度学习任务中处理的数据类型多样,如图像、音频、文本等。Tensor 是一种多维数组,可以方便地表示这些不同类型的数据。例如,一张彩色图像可以表示为一个形状为 (C, H, W) 的三维 Tensor,其中 C 是通道数(如 RGB 图像的通道数为 3),H 是图像的高度,W 是图像的宽度。这种统一的数据表示方式使得不同类型的数据可以使用相同的计算和处理方法。
  • 易于集成和处理:在深度学习框架中,Tensor 是各个模块之间传递数据的标准格式。无论是数据加载、模型定义还是损失计算,都可以使用 Tensor 进行统一处理,避免了不同数据格式之间转换的复杂性。

4. 分布式训练支持

  • 多设备并行:随着深度学习模型的规模不断增大,单台设备的计算资源可能无法满足训练需求。Tensor 支持分布式训练,可以将数据和计算任务分配到多个 GPU 或多台机器上并行进行。例如,在使用 PyTorch 的 DistributedDataParallel 模块时,可以将一个大型的神经网络模型分布到多个 GPU 上进行训练,每个 GPU 处理一部分数据,从而加速训练过程。
  • 数据同步和通信:在分布式训练中,不同设备之间需要进行数据同步和通信,以保证模型参数的一致性。Tensor 数据类型提供了相应的机制来处理这些问题,使得分布式训练能够高效、稳定地进行。

5. 与深度学习框架的无缝集成

  • 丰富的 API 支持:深度学习框架为 Tensor 提供了丰富的 API,涵盖了各种数值运算、数学函数、数据处理等功能。例如,在 PyTorch 中,你可以使用 Tensor 的 addmul 等方法进行基本的数学运算,也可以使用 torch.nn 模块中的各种层(如 nn.Conv2dnn.Linear)对 Tensor 进行处理。
  • 模型构建和训练流程的整合Tensor 是深度学习框架中模型构建和训练流程的核心数据结构。从数据加载、模型定义、前向传播、损失计算到反向传播和参数更新,整个过程都围绕着 Tensor 展开,使得开发者可以方便地构建和训练复杂的深度学习模型。

(2)ToPILImage()

它的主要作用是将 torch.Tensor 类型的张量或者 numpy.ndarray 类型的数组转换为 PIL.Image.Image 类型的图像对象。

在深度学习任务中,模型处理的数据通常是 torch.Tensor 类型,而在进行图像的可视化、保存或者使用一些基于 PIL(Python Imaging Library)库的图像处理操作时,我们需要将数据转换为 PIL 图像格式。ToPILImage() 就提供了这样一种便捷的转换方式。

torchvision.transforms.ToPILImage(mode=None)
  • mode:可选参数,用于指定 PIL 图像的模式。常见的模式有 'L'(灰度图)、'RGB'(真彩色图)等。如果不指定,ToPILImage 会根据输入张量的通道数自动推断模式。
import torch
import torchvision.transforms as transforms
from PIL import Image
import matplotlib.pyplot as plt

# 生成一个随机的 3 通道图像张量,形状为 (3, 224, 224)
image_tensor = torch.randn(3, 224, 224)

# 将张量的值范围从 [-1, 1] 映射到 [0, 1]
image_tensor = (image_tensor + 1) / 2
image_tensor = image_tensor.clamp(0, 1)

# 定义 ToPILImage 转换操作
to_pil = transforms.ToPILImage()

# 将张量转换为 PIL 图像
pil_image = to_pil(image_tensor)

# 显示 PIL 图像
plt.imshow(pil_image)
plt.axis('off')
plt.show()

 在上述代码中,首先生成了一个随机的图像张量,然后对其进行了简单的归一化处理,确保像素值在 [0, 1] 范围内。接着使用 ToPILImage() 将张量转换为 PIL 图像,并使用 matplotlib 库将其显示出来。

注意事项:

  • 输入的 torch.Tensor 或 numpy.ndarray 数据需要满足一定的要求。对于 torch.Tensor,其形状通常为 (C, H, W),其中 C 是通道数,H 是高度,W 是宽度,且数据类型应为 torch.float32 或 torch.uint8
  • 如果输入的张量像素值范围不在 [0, 255](对于 torch.uint8 类型)或 [0, 1](对于 torch.float32 类型)内,可能会导致转换后的图像显示异常,因此在转换前需要进行适当的归一化或缩放处理。

 (3)Normalize(mean, std)

Normalize(mean, std) 是一个用于对图像数据进行归一化处理的重要转换类。归一化是深度学习中常用的数据预处理步骤,它有助于提高模型的训练效率和稳定性。

Normalize(mean, std) 类的主要作用是对输入的图像张量进行标准化(归一化)操作。具体来说,它会对图像的每个通道分别减去指定的均值 mean,并除以指定的标准差 std。这样做可以将图像数据的分布调整到均值为 0、标准差为 1 的标准正态分布,使得不同样本之间的数据具有可比性,有助于模型更快地收敛,同时也能在一定程度上减少梯度消失或梯度爆炸的问题。 

torchvision.transforms.Normalize(mean, std, inplace=False)

参数说明

  • mean:一个序列(如列表、元组),其长度等于图像的通道数。每个元素表示对应通道的均值。例如,对于 RGB 图像,mean 可以是 [0.485, 0.456, 0.406]
  • std:同样是一个序列,长度与 mean 相同,每个元素表示对应通道的标准差。例如,对于 RGB 图像,std 可以是 [0.229, 0.224, 0.225]
  • inplace:一个布尔值,默认为 False。如果设置为 True,则会直接在输入的张量上进行归一化操作,而不创建新的张量。

归一化后会改变像素值,图像颜色也会改变 

import torch
import torchvision.transforms as transforms

# 模拟一张 3 通道的图像张量,形状为 (3, 224, 224)
image_tensor = torch.randn(3, 224, 224)

# 定义归一化操作,指定每个通道的均值和标准差
normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])

# 应用归一化操作。传入的必须是tensor数据类型
normalized_tensor = normalize(image_tensor)

print("归一化前张量的均值和标准差:")
for i in range(3):
    print(f"通道 {i} 均值: {image_tensor[i].mean().item():.4f}, 标准差: {image_tensor[i].std().item():.4f}")

print("\n归一化后张量的均值和标准差:")
for i in range(3):
    print(f"通道 {i} 均值: {normalized_tensor[i].mean().item():.4f}, 标准差: {normalized_tensor[i].std().item():.4f}")

在上述代码中,首先创建了一个随机的图像张量,然后定义了 Normalize 转换操作,并将其应用到图像张量上。最后分别打印出归一化前后每个通道的均值和标准差,可以看到归一化后的数据更接近标准正态分布。 

(4)Resize(size) 

将图像的大小调整为指定的尺寸。size 可以是一个整数,表示将图像的短边调整为该值,长边按比例缩放;也可以是一个元组 (height, width),表示将图像调整为指定的高度和宽度。 

# 定义调整大小操作,将图像短边调整为 256
resize = transforms.Resize(256)

# 应用调整大小操作
resized_image = resize(image)

(5)RandomCrop(size)

随机裁剪图像到指定的大小。size 可以是一个整数或元组,含义与 Resize 类似。

# 定义随机裁剪操作,裁剪出 224x224 的图像
random_crop = transforms.RandomCrop(224)

# 应用随机裁剪操作
cropped_image = random_crop(image)

 (6)RandomHorizontalFlip(p=0.5)

以概率 p 随机水平翻转图像。默认概率为 0.5。

# 定义随机水平翻转操作
random_flip = transforms.RandomHorizontalFlip()

# 应用随机水平翻转操作
flipped_image = random_flip(image)

 (7)RandomRotation(degrees)

随机旋转图像,degrees 可以是一个整数或元组。如果是整数,表示在 [-degrees, degrees] 范围内随机旋转;如果是元组 (min_degrees, max_degrees),表示在该范围内随机旋转。

# 定义随机旋转操作,在 [-10, 10] 度范围内随机旋转
random_rotation = transforms.RandomRotation(10)

# 应用随机旋转操作
rotated_image = random_rotation(image)

(8)CenterCrop(size)

CenterCrop 是一个用于图像裁剪的转换类。它的主要作用是从输入图像的中心位置裁剪出指定大小的图像区域,这在图像预处理和数据增强中是一个常见的操作。

参数size可以是一个整数,也可以是一个元组 (height, width)

  • 当 size 为整数时,会将图像裁剪成一个正方形,边长为该整数。
  • 当 size 为元组时,会将图像裁剪成指定的高度和宽度。
# 定义 CenterCrop 转换操作,将图像裁剪成高度为 150,宽度为 200 的矩形
center_crop = transforms.CenterCrop((150, 200))

# 应用裁剪操作
cropped_image = center_crop(image)

# 显示裁剪后的图像
plt.imshow(cropped_image)
plt.axis('off')
plt.show()

2,组合多个转换操作

(1)Compose

通常我们会将多个转换操作组合在一起使用,transforms.Compose 类可以帮助我们实现这一点。

# 定义一个组合转换操作
transform = transforms.Compose([
    transforms.Resize(256),
    transforms.RandomCrop(224),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

# 应用组合转换操作
transformed_image = transform(image)

也可以是下图用法,直接将实例化的对象放入compose中

三,transforms用在数据集中

1,torchvision.datasets

torchvision.datasets 是 PyTorch 中 torchvision 库提供的一个模块,它主要用于加载和处理计算机视觉领域中常用的公开数据集(是指那些被广泛公开共享、可供研究人员和开发者免费使用的图像或视频相关数据集)。这个模块为开发者提供了便捷的接口,使得在进行图像分类、目标检测、语义分割等任务时,可以轻松地获取和使用各种标准数据集,而无需手动编写复杂的数据加载和预处理代码。

主要功能:

  • 数据加载:可以直接从网络下载数据集,并将其加载到内存中,方便后续的模型训练和评估。
  • 数据预处理:支持对加载的数据进行各种预处理操作,如调整图像大小、归一化、裁剪等。
  • 数据集划分:可以将数据集划分为训练集、验证集和测试集,以便进行模型的训练、调优和评估。

常见类型:

各个数据集要输入的参数都比较相近,在实例中会演示以CIFAR10为例的使用。

1,图像分类数据集

  • MNIST:由手写数字的图像组成,包含 60,000 张训练图像和 10,000 张测试图像,图像大小为 28x28 像素,共 10 个类别(数字 0 - 9),是图像分类任务的入门级数据集。
  • CIFAR - 10:包含 60,000 张 32x32 的彩色图像,分为 10 个不同的类别,如飞机、汽车、鸟类等,常用于评估图像分类算法的性能。
  • ImageNet:大规模图像数据集,包含超过 1400 万张图像,分为 1000 个不同的类别,是目前计算机视觉领域最具影响力的数据集之一,广泛用于图像分类、目标检测等任务的研究。

2,目标检测数据集

  • PASCAL VOC:包含了 20 个不同类别的目标,如人、汽车、狗等,提供了图像、目标的边界框标注以及分割标注,是目标检测和语义分割领域的经典数据集。
  • COCO:一个大型的目标检测、实例分割和关键点检测数据集,包含了大量的图像和丰富的标注信息,涵盖了 80 个不同的目标类别,广泛应用于目标检测和相关领域的研究。

3,语义分割数据集

  • Cityscapes:主要用于城市街道场景的语义分割任务,包含了来自 50 个不同城市的街道场景图像,提供了详细的像素级标注,涵盖了 30 个不同的类别,如道路、建筑物、行人等。
  • CamVid:由驾驶场景的视频帧组成,用于语义分割任务,包含 32 个不同的类别,如天空、道路、汽车等,数据规模相对较小,但对于研究驾驶场景的语义分割具有重要价值。

4,视频相关数据集

  • UCF101:包含 101 个不同类别的动作视频,如篮球、骑自行车、跳舞等,每个类别有多个视频样本,常用于视频动作识别任务的研究。
  • Kinetics:大规模的视频动作识别数据集,包含了数百万个短视频,涵盖了 400 多个不同的人类动作类别,为视频动作识别模型的训练提供了丰富的数据资源。

2,torchvision. models

(1)介绍

torchvision.models 是 PyTorch 中 torchvision 库提供的一个重要模块,它包含了许多预训练好的计算机视觉模型,同时还提供了这些模型对应的预训练权重。 

预训练模型是指在大规模数据集(如 ImageNet)上进行过训练的模型。这些模型已经学习到了大量图像的通用特征和模式,能够对图像进行有效的特征提取和分类等操作。使用预训练模型可以节省大量的训练时间和计算资源,并且在很多情况下能够提高模型在新任务上的性能。预训练模型有既定的、设计好的网络结构。研究人员和开发者经过大量实验和研究,设计出了一系列适用于不同计算机视觉任务的高效架构,这些架构是预训练模型的基础框架。以Resnet为例,ResNet 预训练模型最初是在 ImageNet 数据集上训练的,能够识别 1000 个类别的物体。然而,我们要训练自己的模型的时候可以使用Resnet作为特征提取器,从而迁移学习,适应新的分类任务。

预训练权重是指预训练模型在大规模数据集上训练后得到的模型参数。这些权重包含了模型学习到的图像特征和模式信息,在使用预训练模型时,可以直接加载这些权重,使得模型能够利用之前学习到的知识来处理新的数据。

(2)预训练权重 

简单来说,预训练权重就是预训练模型在大规模数据集上经过训练后所学习到的模型参数的集合

定义和存储形式:

  • 定义:在神经网络中,权重是连接不同层神经元的参数,控制着信息在网络中的流动和转换。预训练权重就是模型在特定大规模数据集上进行训练后,这些权重所达到的一组特定取值
  • 存储形式:通常以文件的形式保存,不同的深度学习框架有不同的存储格式。例如,在 PyTorch 中,预训练权重一般保存为 .pth 或 .pt 文件;在 TensorFlow 中,常见的保存格式是 .ckpt 或 .h5 文件。

学习过程:

预训练权重是通过在大规模数据集上进行迭代训练学习得到的。以图像分类任务为例,一般步骤如下:

  1. 数据输入:将大规模数据集中的图像样本输入到神经网络模型中。
  2. 前向传播:图像数据在网络中逐层传递,经过卷积、激活、池化等操作,最终得到模型的预测结果。
  3. 损失计算:将模型的预测结果与真实标签进行对比,使用损失函数(如交叉熵损失函数)计算两者之间的差异,得到损失值。
  4. 反向传播:根据损失值,使用优化算法(如随机梯度下降、Adam 等)计算每个权重参数的梯度,以确定如何调整权重能使损失值减小。
  5. 权重更新:根据计算得到的梯度,更新网络中的权重参数。
  6. 迭代训练:重复上述步骤,不断调整权重参数,直到模型在训练集上的性能达到满意的程度,此时得到的权重参数就是预训练权重

作用:

  • 加速模型收敛:使用预训练权重初始化模型后,模型已经具备了一定的特征提取能力,在新的数据集上进行训练时,不需要从头开始学习所有的特征,从而可以大大减少训练时间,加快模型的收敛速度。
  • 提高模型性能:尤其是在新数据集规模较小的情况下,预训练权重能够帮助模型学习到更通用、更有效的特征,避免模型因数据不足而出现过拟合现象,从而提高模型在新任务上的泛化能力和性能。

使用方式:

在不同的深度学习框架中,使用预训练权重的方式有所不同,但总体思路一致,即加载预训练权重文件并将其应用到模型中

import torch
import torchvision.models as models

# 加载预训练的 ResNet18 模型
model = models.resnet18(pretrained=True)  # 设置 pretrained=True 会自动下载并加载预训练权重

# 或者手动加载预训练权重文件
model = models.resnet18(pretrained=False)
pretrained_weights = torch.load('resnet18_pretrained.pth')
model.load_state_dict(pretrained_weights)

(3)常见模型介绍

1,图像分类模型
  • AlexNet:2012 年 ImageNet 图像分类竞赛冠军模型,开启了深度学习在计算机视觉领域的热潮,引入了 ReLU 激活函数、Dropout 等技术。
  • VGG:具有非常深的网络结构,通过堆叠多个小卷积核(如 3x3)来增加网络的深度,提高特征提取能力,有 VGG11、VGG13、VGG16、VGG19 等不同版本。
  • ResNet:引入了残差块(Residual Block)的概念,解决了深度神经网络训练过程中的梯度消失和梯度爆炸问题,能够训练更深的网络,有 ResNet18、ResNet34、ResNet50 等不同深度的版本。
import torchvision.models as models

# 加载预训练的 AlexNet 模型
alexnet = models.alexnet(pretrained=True)

# 加载预训练的 VGG16 模型
vgg16 = models.vgg16(pretrained=True)

# 加载预训练的 ResNet50 模型
resnet50 = models.resnet50(pretrained=True)
 2,目标检测模型
  • Faster R - CNN:一种两阶段的目标检测模型,通过区域建议网络(RPN)生成候选区域,然后对这些候选区域进行分类和边界框回归,实现目标的检测。
import torchvision.models as models

# 加载预训练的 Faster R - CNN 模型
faster_rcnn = models.detection.fasterrcnn_resnet50_fpn(pretrained=True)
3,语义分割模型
  • DeepLabV3:用于语义分割任务,通过空洞卷积(Atrous Convolution)增大感受野,同时引入了多尺度特征融合的机制,提高分割的精度。
import torchvision.models as models

# 加载预训练的 DeepLabV3 模型
deeplabv3 = models.segmentation.deeplabv3_resnet50(pretrained=True)

 3,实例

(1)CIFAR10数据集的使用

以下图片为PyTorch官网给的参数解释

root

  • 类型:字符串
  • 作用:指定数据集的存储路径。若该路径下不存在 CIFAR - 10 数据集,且 download 参数设为 True,则会自动将数据集下载到该路径。
from torchvision import datasets

# 数据集将存储在当前目录下的 data 文件夹中
cifar10_trainset = datasets.CIFAR10(root='./data', train=True, download=True)

train

  • 类型:布尔值
  • 作用:用于指定加载的是训练集还是测试集。设为 True 时加载训练集(包含 50000 张图像);设为 False 时加载测试集(包含 10000 张图像)。
# 加载训练集
train_dataset = datasets.CIFAR10(root='./data', train=True, download=True)
# 加载测试集
test_dataset = datasets.CIFAR10(root='./data', train=False, download=True)

transform

  • 类型:可调用对象(通常是 torchvision.transforms 中的变换组合)
  • 作用:对加载的图像数据进行预处理操作,例如将图像转换为张量、归一化、裁剪、翻转等。可以使用 transforms.Compose 来组合多个预处理操作。
import torchvision.transforms as transforms

# 定义预处理操作
transform = transforms.Compose([
    transforms.ToTensor(),  # 将图像转换为张量
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))  # 归一化处理
])

# 加载数据集并应用预处理
cifar10_trainset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)

target_transform

  • 类型:可调用对象
  • 作用:对图像对应的标签进行预处理操作。不过在 CIFAR - 10 数据集里,标签通常是简单的整数,一般较少用到此参数。
# 定义一个简单的标签转换函数
def target_transform(target):
    return target * 2

cifar10_trainset = datasets.CIFAR10(root='./data', train=True, download=True, target_transform=target_transform)

download

  • 类型:布尔值
  • 作用:决定是否从互联网下载 CIFAR - 10 数据集。设为 True 且 root 指定的路径下没有数据集时,会自动下载;设为 False 则不会下载。
# 若 data 文件夹中没有数据集,会自动下载
cifar10_trainset = datasets.CIFAR10(root='./data', train=True, download=True)

(2) 代码演示

import torchvision

train_set = torchvision.datasets.CIFAR10(root="./dataset",train=True,download=True)
test_set = torchvision.datasets.CIFAR10(root="./dataset",train=False,download=True)

运行后可以看到下方终端就开始下载了

下载完成后目录就出现了这个压缩文件,对压缩文件进行解压 

 

使用索引[i]即可查看数据集中的图片信息等

可以在调试中查看数据集都有那些信息。如下所示,classes,data,meta等就是数据集信息,用.的方式(如test_set.classes的方式就可以访问)

 在使用 torchvision.datasets.CIFAR10 加载测试集后,test_set 是一个 torch.utils.data.Dataset 类型的对象,它主要由图像数据和对应的标签组成。所以可以用两个变量来接收test_set中的一张图片,如下所示

import torchvision

train_set = torchvision.datasets.CIFAR10(root="./dataset",train=True,download=True)
test_set = torchvision.datasets.CIFAR10(root="./dataset",train=False,download=True)

img, target = test_set[0]
print(img)
print(target)

输出结果如下。这里的3是指第1张图片的标签是CIFAR10标签列表的第4个, 即cat

 结合transforms:

import torchvision
from torch.utils.tensorboard import SummaryWriter

dataset_transform = torchvision.transforms.Compose([
    torchvision.transforms.ToTensor()
])

train_set = torchvision.datasets.CIFAR10(root="./dataset",train=True,transform=dataset_transform,download=True)
test_set = torchvision.datasets.CIFAR10(root="./dataset",transform=dataset_transform,train=False,download=True)

writer = SummaryWriter("p10")
for i in range(10):  # 连续显示10张图片
    img, target = test_set[i]
    writer.add_image("test_set", img, i)

writer.close()

四. 神经网络

此章目的是为了学会如何自己搭建一个神经网络结构模型,自己规定神经层数,卷积方法,自己添加线性层,非线性层等等。实际上为了快速训练一个模型,可以直接用上几节说到的torchvison.models torchaudios等PyTorch官网为我们提供的预训练模型直接进行训练。

⭐torch.nn介绍

torch.nn是 PyTorch 中用于构建神经网络的核心模块,它提供了丰富的工具和类,方便用户定义、训练和评估各种类型的神经网络模型。

主要组件(层、损失函数、优化器、容器):

1. 层(Layers)

torch.nn 包含了各种常用的神经网络层,这些层是构建神经网络的基本单元。 以下是一些常用的层:

  • nn.Linear:全连接层,也称为线性层,用于实现输入和输出之间的线性变换,公式为y=wx+b ,其中x是输入,w是权重矩阵,b是偏置向量。常用于分类或回归任务。
  • nn.Conv2d:二维卷积层,主要用于处理图像数据。通过卷积操作提取图像的特征,能够自动学习图像中的局部模式,如边缘、纹理等。在计算机视觉任务中广泛应用,如目标检测、图像分类等。
  • nn.MaxPool2d:二维最大池化层,用于对输入的特征图进行下采样,减少数据的维度,同时保留重要的特征信息。可以提高模型的计算效率,增强模型的鲁棒性。
  • nn.ReLU:修正线性单元(Rectified Linear Unit)激活函数层,它将所有小于 0 的输入值置为 0,大于 0 的输入值保持不变。ReLU 函数具有计算简单、收敛速度快等优点,能够有效缓解梯度消失问题,是深度学习中最常用的激活函数之一。

2. 损失函数(Loss Functions)

损失函数用于衡量模型预测结果与真实标签之间的差异,是模型训练过程中的重要指标。

  • nn.MSELoss:均方误差损失函数,用于回归问题。计算模型预测值与真实值之间的平方误差的平均值,衡量预测值与真实值之间的距离。
  • nn.CrossEntropyLoss:交叉熵损失函数,常用于多分类问题。它结合了 Softmax 激活函数和负对数似然损失,能够有效地衡量模型输出的概率分布与真实标签之间的差异。

3. 优化器 (Optimizers)

优化器用于更新模型的参数,以最小化损失函数。虽然优化器在 torch.optim 模块中,但与 torch.nn 紧密相关。常见的优化器有:

  • torch.optim.SGD:随机梯度下降优化器,是最基本的优化算法。通过计算损失函数关于模型参数的梯度,沿着梯度的反方向更新参数,学习率控制参数更新的步长。
  • torch.optim.Adam:自适应矩估计优化器,结合了 AdaGrad 和 RMSProp 的优点,能够自适应地调整每个参数的学习率,在许多任务中表现良好。

 4. 容器(Containers)

 容器用于组织和管理神经网络的层,方便构建复杂的模型结构。常见的容器有:

  • nn.Sequential:顺序容器,按照传入层的顺序依次执行前向传播。可以将多个层组合成一个序列,简化模型的定义过程。
  • nn.ModuleList:模块列表容器,用于存储多个子模块,可以像列表一样进行索引和操作。适合需要动态添加或管理子模块的场景。 

使用流程

一般来说,使用 torch.nn 构建和训练神经网络的基本流程如下:

  1. 定义模型:继承 nn.Module 类,在 __init__ 方法中定义网络的层,在 forward 方法中定义前向传播的逻辑。
  2. 定义损失函数和优化器:选择合适的损失函数和优化器,并传入模型的参数。
  3. 训练模型:在训练循环中,进行前向传播计算损失,然后进行反向传播计算梯度,最后使用优化器更新模型的参数。
  4. 评估模型:在测试集上评估模型的性能。

⭐nn.Module

在 torch.nn 里,Module 是所有神经网络模块的基类,其本质是 PyTorch 为神经网络打造的“全能管家”,我们继承它,就能直接复用框架封装好的所有核心能力,不用从零手写底层逻辑

(选读)Module 内部封装了构建、训练、管理模型所需的所有核心功能,核心可分为 6 大类(以线性回归为例):

  1. 可训练参数管理:自动收集模型中所有可训练参数(比如线性回归里的wb),优化器能直接通过model.parameters()获取这些参数(具体方法parameters()/named_parameters()
  2. 子模块管理:自动管理模型内的子层,支持嵌套模型(比如 CNN 的多层堆叠)(具体方法add_module()/children()/named_children()
  3. 前向 / 反向传播支撑:封装__call__方法,调用model(x)时自动触发forward(),同时构建计算图;反向传播依赖其维护的参数梯度信息(具体方法forward()__call__()
  4. 设备迁移:一行代码将整个模型(包括所有参数、子层)迁移到 GPU/CPU(如model.to('cuda'))(具体方法to()/cuda()/cpu()
  5. 模型状态管理:切换训练 / 评估模式(影响 Dropout、BN 层行为);state_dict()导出模型参数,用于保存 / 加载模型(具体方法train()/eval()/state_dict()
  6. 梯度管理:一键清空所有参数的梯度(对应之前训练循环里的optimizer.zero_grad(),本质是调用 Module 的梯度清零逻辑)(具体方法zero_grad())

此段代码取自PyTorch实现线性回归-CSDN博客,此文章中有对下面代码的详细解释。

class LinearModel(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = torch.nn.Linear(1, 1)

    def forward(self, x):
        y_pred = self.linear(x)
        return y_pred

model = LinearModel()

criterion = torch.nn.MSELoss(reduction='sum') # 损失函数
optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # 优化器

# 训练过程
for epoch in range(1000):
    y_pred = model(x_data)
    loss = criterion(y_pred, y_data)
    print(epoch, loss.item())

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
  • 当定义class LinearModel(torch.nn.Module)并实例化model = LinearModel()时:
    • Module 自动检测到self.linear = torch.nn.Linear(1, 1)这个子模块,并将其内部的weight(w)bias(b)注册为可训练参数。
    • 当你调用 model(x_data) 时,Module 的__call__方法自动触发你重写的forward(),完成前向计算。
    • 当你调用 loss.backward() 时,Module 会自动跟踪每个参数的梯度,并存储在w.grad/b.grad中。
    • 当你调用 optimizer.step() 时,优化器能通过model.parameters()拿到 Module 管理的wb,完成参数更新

如果不继承Module,你就要手动设置参数并告诉pytorch此参数是否需要计算梯度:

w = torch.tensor([1.0], requires_grad=True)
b = torch.tensor([0.0], requires_grad=True)

如果不继承Module,每次优化器更新时,需要手动把wb传入优化器:

optimizer = torch.optim.SGD([w, b], lr=0.01);

 Containers(容器)

1.nn.Sequential

nn.Sequential 是一个顺序容器,它可以按照顺序依次包装多个神经网络层,形成一个连续的神经网络模块。。当我们构建一个简单的、按顺序执行的神经网络时,使用 nn.Sequential 可以大大简化代码。

在构建神经网络时,我们通常需要按照一定的顺序堆叠多个层,如卷积层、激活函数层、池化层等。使用 nn.Sequential 可以将这些层按顺序组合在一起,避免编写大量重复的前向传播代码,使模型的定义更加简洁和直观。nn.Sequential 会自动按照层的顺序进行前向传播,当输入数据传入 nn.Sequential 封装的模块时,数据会依次通过每一层进行处理,最终输出结果,无需手动编写复杂的前向传播逻辑

import torch
import torch.nn as nn

# 使用 nn.Sequential 构建一个简单的全连接神经网络
model = nn.Sequential(
    nn.Linear(10, 20),  # 输入维度为 10,输出维度为 20 的全连接层
    nn.ReLU(),          # ReLU 激活函数层
    nn.Linear(20, 1)    # 输入维度为 20,输出维度为 1 的全连接层
)

# 生成随机输入数据
input_data = torch.randn(5, 10)  # 输入数据的形状为 (5, 10)

# 进行前向传播
output = model(input_data)

print("输入数据形状:", input_data.shape)
print("输出数据形状:", output.shape)

在上述示例中,我们使用 nn.Sequential 依次堆叠了两个全连接层和一个 ReLU 激活函数层。输入数据 input_data 首先通过第一个全连接层 nn.Linear(10, 20),然后经过 ReLU 激活函数处理,最后通过第二个全连接层 nn.Linear(20, 1) 得到输出结果。 

在 nn.Sequential 中,我们还可以为每一层指定名称,这样可以更方便地访问和操作特定的层 

import torch
import torch.nn as nn
from collections import OrderedDict

# 使用 OrderedDict 构建带名称的 nn.Sequential
model = nn.Sequential(OrderedDict([
    ('fc1', nn.Linear(10, 20)),
    ('relu', nn.ReLU()),
    ('fc2', nn.Linear(20, 1))
]))

# 访问指定名称的层
fc1_layer = model.fc1
print(fc1_layer)

或者通过以下方式为每一层指定名称

import torch
import torch.nn as nn

model = nn.Sequential()
model.add_module('fc1', nn.Linear(10, 20))
model.add_module('relu', nn.ReLU())
model.add_module('fc2', nn.Linear(20, 1))

# 访问指定名称的层
relu_layer = model.relu
print(relu_layer)

nn.Sequential 还可以嵌套使用,即一个 nn.Sequential 模块可以作为另一个 nn.Sequential 模块的一部分。

import torch
import torch.nn as nn

# 定义一个子模块
sub_module = nn.Sequential(
    nn.Linear(10, 15),
    nn.ReLU()
)

# 定义主模块,嵌套使用子模块
model = nn.Sequential(
    sub_module,
    nn.Linear(15, 1)
)

# 生成随机输入数据
input_data = torch.randn(5, 10)

# 进行前向传播
output = model(input_data)

print("输出数据形状:", output.shape)

2.nn.ModuleList(了解)

nn.ModuleList 是一个模块列表容器,它可以像 Python 列表一样存储多个子模块,并且会自动注册这些子模块,使其成为父模块的一部分。与 nn.Sequential 不同的是,nn.ModuleList 不会自动按顺序执行子模块,需要我们在 forward 方法中手动指定执行顺序,适合需要动态添加或管理子模块的场景。 

import torch
import torch.nn as nn

class MyModel(nn.Module):
    def __init__(self):
        super().__init__()
        # 创建一个 nn.ModuleList 并添加两个线性层
        self.module_list = nn.ModuleList([
            nn.Linear(784, 128),
            nn.Linear(128, 10)
        ])

    def forward(self, x):
        x = x.view(-1, 784)
        for module in self.module_list:
            x = module(x)
        return x

model = MyModel()
input_tensor = torch.randn(64, 784)
output = model(input_tensor)
print(output.shape)  # 输出: torch.Size([64, 10])

3.nn.ModuleDict(了解)

nn.ModuleDict 是一个模块字典容器,它允许我们使用字符串作为键来存储和访问子模块。与 nn.ModuleList 类似,nn.ModuleDict 也需要我们在 forward 方法中手动指定子模块的执行顺序,适合需要根据不同条件选择不同子模块的场景。

import torch
import torch.nn as nn

class MyModelWithDict(nn.Module):
    def __init__(self):
        super().__init__()
        # 创建一个 nn.ModuleDict 并添加两个线性层
        self.module_dict = nn.ModuleDict({
            'fc1': nn.Linear(784, 128),
            'fc2': nn.Linear(128, 10)
        })

    def forward(self, x):
        x = x.view(-1, 784)
        x = self.module_dict['fc1'](x)
        x = torch.relu(x)
        x = self.module_dict['fc2'](x)
        return x

model = MyModelWithDict()
input_tensor = torch.randn(64, 784)
output = model(input_tensor)
print(output.shape)  # 输出: torch.Size([64, 10])

Layers(层)

1. Linear Layers(线性层)

 线性层(Linear Layers)是最基础且常用的神经网络层之一,也被称为全连接层(Fully Connected Layers)。线性层的核心作用是对输入数据进行线性变换(乘权重w再加b),将输入特征映射到一个新的特征空间。线性层本身只有线性,表达能力很弱。在深度神经网络中,必须配合激活函数(非线性)使用,才能让模型拟合曲线、分类、识别图像。

利用线性模型可以提取数据中的重要信息,例如一张图 1000 个像素,但有很多像素是黑边、空白,线性层给有用的像素赋大权重,空白像素赋小权重或0权重,加权求和后,输出的特征中有用的特征就会放大。

定义和原理:

线性层执行一个线性变换,对于输入的x,线性层将其与一个权重w相乘,然后加上一个偏置b,可以用以下公式表示:

y=wx+b

  • x为输入。可以是一个标量数值,也可以是一个矩阵。
  • w为权重。与输入x对应,可以是一个标量数值,也可以是一个矩阵。
  • b为偏执(bias)。是一个常数。
  • y为模型输出。

上面的公式写为矩阵形式:(本质上仍是一个X经过线性变换得到一个Y

Y=X\cdot W^{T}+b

  • X为输入矩阵。矩阵的行数表示表示输入矩阵有多少独立的样本(如3张图片为3个样本),矩阵的列数表示每个样本有多少特征(如每张图片1000个像素点代表1000个特征)。
  • W为权重矩阵,W^{T}W的转置。
  • b为偏置向量
  • Y为输出矩阵

输入X、输出Y、权重W、偏置b的行列数遵循严格的矩阵乘法规则。例如输入3个样本,每个样本2两个特征,此时X为3行2列,我们指定输出特征数为5个特征,也就说最终输出Y为3行5列(样本数必须对应,即输入和输出的行数要相等;特征数可以不同,即输入输出的列数可以不等)。这样就需要W^{T}是2行5列的矩阵。如下矩阵乘法示例:

\begin{bmatrix} 1.3 & 1.4& 1.7& 2.1 & 2.2\\ 1.5& 1.8& 1.9 & 2.7 & 2.6\\ 1.5& 1.6 & 2.3 & 2.9 & 3.2 \end{bmatrix}=\begin{bmatrix} 1 &2 \\ 3& 2\\ 1& 4 \end{bmatrix}\cdot \begin{bmatrix} 0.1 & 0.2 & 0.1 & 0.3 &0.2 \\ 0.1& 0.1 & 0.3 & 0.4 & 0.5 \end{bmatrix}+1

nn.Linear:

torch.nn.Linear(in_features, out_features, bias=True)

参数说明:

  • in_features:输入特征的数量,也就是输入张量最后一个维度的大小。例如,若输入5张图片,每张图片100个像素点,那么 in_features 就应设为 100。
  • out_features:输出特征的数量,即输出张量最后一个维度的大小。例如,若希望输出图片像素点为200个,那么 out_features 就设为 200。
  • bias:一个布尔值,指示是否使用偏置向量b。默认为 True,即使用偏置;若设为 False,则不使用偏置。

下图为一个输入样本xd个特征量(x_{1}\sim x_{d})经过中间两个线性层(经过两个箭头,每个箭头表示乘一次权重w)得到输出样本om个特征量(o_{1}\sim o_{m})。

当给torch.nn.Linear()输入参数in_features, out_features后,方法内部会自动将权重矩阵Linear.weight的形状规定为(out_features, in_features),将偏置向量形状规定为(out_features,)。权重矩阵的初始值会从均匀分布(-\sqrt{k},\sqrt{k})中随机取样,其中k=\frac{1}{infeatures};偏置向量的数值同样从(-\sqrt{k},\sqrt{k})中随机取样。

2. Convolution Layers(卷积层)

卷积层(Convolution Layers)是构建卷积神经网络(Convolutional Neural Networks, CNN)的核心组件,在图像、语音等数据处理任务中有着广泛应用。

在学习线性层后,我们构建神经网络使用的都是全连接层(线性层),多个线性层串联连接起来称为全连接网络,如下所示:

全连接层是将输入展平成一维向量,每个神经元和所有输入相连。但是对于图像这种有空间位置、有局部结构、有层级特征的二维数据,如果直接通过全连接层将其转换成一维数据,会有以下问题:

  • 一张图片中的一个像素只和它周围的像素有关系,图片的边缘、角点、纹理、形状等都是局部拼起来的,使整张图片具有结构信息。而全连接层的每个神经元强行把全图所有像素连在一起,把相距很远的像素也强行关联。这会使图像丢失空间信息,像素的上下左右、相邻关系、边缘、形状全部消失,只剩一串无关系的数值,学习起来正确率不高。
  • 每个神经元和所有输入相连,如果是28X28的图像,全连接层只设 100 个神经元,参数数量就达到了28×28×100 = 78400 个权重 + 100 个偏置,如此多的参数极易过拟合,计算量也极大。

卷积层是用一组小的 “特征过滤器(卷积核)”,在输入数据(如图像、特征图)上滑动扫描,通过卷积运算提取局部特征的神经网络层。这是是卷积神经网络(CNN) 的核心。简单来说,它不是 “全连接” 那样把所有神经元乱连一通,而是用小窗口扫一遍数据,抓出有用的局部模式。这可以保留图像的空间结构,并且参数更少(只有卷积核的几个参数)。卷积的具体过程如下:

单通道输入单通道输出二维卷积

对于单通道图片(如灰度图像),假设Input是5X5的特征数据,Kernel(卷积核)是3X3的(9个参数我们先随机指定)。
卷积核从输入的左上角开始,覆盖一个 3×3 的区域(图中红框),输入窗口的每个元素,与卷积核对应位置的元素相乘。相乘过程即结果如下图所示。将所有乘积相加,得到输出特征图的一个元素,这个 211 就是 Output 左上角的第一个元素:

卷积核向右滑动 1 格,重复步骤 2-3,得到下一个输出元素;当一行处理完后,向下滑动 1 格,继续处理下一行:

重复上述步骤,最终得到卷积后的输出Output如下所示:

多通道输入单通道输出二维卷积

当输入是多通道(例如 RGB 图像是 3 通道)时,卷积的逻辑会扩展为逐通道卷积 + 通道求和。

⭐注意,卷积核的通道数必须与输入通道数完全一致。每个卷积核是一个 C_{in}\times K\times K 的三维结构,其中 K 是卷积核的空间尺寸,C_{in}是输入通道数。

如下所示。最左侧的三个5X5矩阵分别是RGB图像的三个通道(各通道数值一般不同),其分别与三通道卷积核的三个通道(各通道参数数值可以设定)进行数乘滑动,最后相加得到一个通道的卷积结果。

上述举例过程是从输入的三个通道(RGB)卷积成输出的一维通道。如果是从输入的n维通道卷积成输出的一维通道,卷积核也一定要是n维的(卷积核的通道数必须与输入通道数完全一致):

多通道输入多通道输出二维卷积

要从输入的n维通道卷积成m维的输出通道,只需要再加几个通道数为n的卷积核即可:

通道数为nwidth_{in}\times height_{in}的输入   先经过  通道数为nK\times K的卷积核filter_{1}   后得到   通道数为1,width'\times height'的输出Feature\, map_{1},这个过程就是上面多通道输入单通道输出二维卷积过程,共m个卷积核分别对输入重复上述操作,得到一维输出Feature\, map_{1}\sim Feature\, map_{m},将这些输出分别作为通道,就得到了  通道数为mwidth_{out}\times height_{out}d的输出。

此时卷积核是四维张量(Tensor):

(扩展阅读1)卷积层的出现:在生物视觉领域,Hubel & Wiesel 做猫视觉实验时发现大脑视觉皮层不是整体看图像,而是分层、分区域提取特征。低层细胞负责抓边缘、线条,高层细胞负责抓形状、物体,每个细胞只负责一小块区域(局部感受野)。受此启发,研究者用小卷积核扫局部来模拟局部感受野,卷积层由此诞生。


(扩展阅读2)卷积公式:在数学领域,卷积是一种线性运算,核心是 “翻转 + 滑动 + 加权求和”,根据信号类型(连续 / 离散、一维 / 二维)有不同表达。

一维连续卷积:

(f*g)(t)=\int_{-\infty }^{+\infty }f(\tau )g(t-\tau )d\tau

f(t)是输入信号,g(t)是卷积核,g(t-\tau )表示将g(t)沿时间轴翻转后,再滑动到位置t

二维连续卷积:

(f*g)(x,y)=\int_{-\infty }^{+\infty }\int_{-\infty }^{+\infty }f(\xi ,\eta )g(x-\xi ,x-\eta )d\xi d\eta

数学卷积的过程为翻转卷积核、滑动窗口、加权求和。描述的是一个函数被另一个函数 “加权叠加” 的过程。以一维连续卷积为例,对每个t,先把g(\tau )翻转成g(-\tau ),再滑动到位置 t(变成g(t-\tau )),然后和f(\tau )相乘,再对\tau积分,物理意义上指在当前滑动位置t下,f(t)与翻转后的g(t-\tau )重叠区域的 “加权面积”。

数学卷积一般用于信号处理或图像滤波。神经网络卷积CNN与之区别是不翻转核,直接用原核做滑窗,并且核由网络通过反向传播自动学习得到。

(扩展阅读3)一维、三维卷积:二维卷积是最常用的,主要用来处理图像。除此之外,一维卷积和三维卷积也是存在的,本质上是同一思想在不同维度数据上的扩展,它们都能提取对应维度的局部特征,只是适配的数据类型和场景不同。

一维卷积:在一维序列数据上,用一维卷积核(长度为 k 的向量)滑动,提取局部序列模式的操作。主要用于时间序列(如IMU 加速度 / 角速度、传感器振动数据、股票价格)、音频波形和文本序列等。使用一维卷积能有效捕捉序列中的局部时序 / 序列模式,比如音频的谐波、文本的短语、传感器数据的突变点,同时通过权值共享减少参数。

三维卷积:在三维体数据(如视频帧序列、3D 体素)上,用三维卷积核(k×k×k 立方体)滑动,提取时空 / 体素级局部特征的操作。核心是同时处理“空间 + 时间 / 深度”维度。主要用于视频(T×H×W×C(时间帧 + 空间像素 + 通道))、3D体素D×H×W×C(深度 + 高度 + 宽度 + 通道,如点云体素化、CT/MRI 影像)等。

1X1卷积:

1X1卷积是用1X1卷积核与每个通道相乘,然后最终将每个通道的计算值加起来得到一个通道的输出,过程与上面讲的卷积过程一样。它是对特征图每个空间位置的所有通道,做「加权求和」,仅改变通道数,不改变特征图的高 / 宽。这相当于给每个像素的通道向量做一次 “全连接”。

1X1卷积后得到的输出中某个位置的像素点包含了原始输入通道中相同位置的像素点的融合信息。即卷积层提取的不同通道对应不同特征(比如通道 1 是 “边缘”、通道 2 是 “纹理”、通道 3 是 “颜色”),1×1 卷积能将这些分散在不同通道的特征融合成新的、更有意义的特征。
 

1X1卷积最主要的作用是改变通道数量,由输入的C_{1}各通道数变为输出的C_{2}个通道数,从而大大降低计算量。具体如下:

如果通过一般的卷积(如图中5X5)将192通道的28X28输入转换为32通道的28X28输出,需要计算的次数:5^{2}\times 28^{2}\times 192\times 32=120422400

如果利用1X1卷积先降低通道数量再使用卷积(5X5),需要计算的次数:1^{2}\times 28^{2}\times 192\times 16+5^{2}\times 28^{2}\times 16\times 32=12433648

利用1X1卷积降低计算量是 ResNet、MobileNet 等网络的核心设计思路,用 “1×1 降维 + 3×3 卷积 + 1×1 升维” 替代直接的 3×3 卷积,大幅减少参数和计算量。

PyTorch 中处理图像类二维数据的核心卷积层是 torch.nn.Conv2d下面先看一个最常用的实例:

import torch
import torch.nn as nn

# 1. 定义卷积层:处理RGB图像(3通道),输出16通道特征图,卷积核3×3,步长1,填充1
conv2d = nn.Conv2d(
    in_channels=3,      # 输入通道数(RGB图是3,灰度图是1)
    out_channels=16,    # 输出通道数(对应16个卷积核)
    kernel_size=3,      # 卷积核尺寸(3表示3×3,也可以写(3,3))
    stride=1,           # 滑动步长
    padding=1           # 输入周围填充0的数量
)

# 2. 构造模拟输入:batch_size=2,3通道,224×224的图像(CV常用尺寸)
input_tensor = torch.randn(2, 3, 224, 224)  # 形状:[batch, channels, height, width]

# 3. 执行卷积运算
output = conv2d(input_tensor)

# 4. 查看输出形状:[2, 16, 224, 224](填充1+步长1,尺寸和输入一致)
print("输出形状:", output.shape)

torch.nn.Conv2d参数说明如下:

  • 必选参数:
    • in_channels:输入数据的通道数。比如RGB图像通道数为3,灰度图像通道数为1。
    • out_channels:输出特征图的通道数。
    • kernel_size:卷积核的空间尺寸。可以是整数(正方形核)或元组(矩形核,如 (3,5) 表示 3 行 5 列)。
  • 可选参数:
    • stride:卷积核滑动的步长,默认值为1。可以是一个整数,表示在高度和宽度方向上的步长相同;也可以是一个元组,元组的两个元素分别表示在高度和宽度方向上的步长。例如stride = 2 表示卷积核在高度和宽度方向上每次滑动 2 个像素(刚才学习的卷积过程都是滑动一个步长)
    • padding:用于在输入特征图的边界添加填充。若为整数,会在高度和宽度方向上都添加相同数量的填充;若为元组,两个元素分别表示在高度和宽度方向上的填充数量;若为字符串,如 'same',会自动计算填充量,使输出特征图的尺寸与输入特征图相同(在 stride = 1 时)。填充可以防止在卷积过程中特征图尺寸过度减小,保留更多边界信息。如下所示padding=1:
    • padding_mode:表示填充方式,默认为'zero'。'zeros'表示填充 0(最常用,适合大部分 CV 任务); 'reflect'表示镜像填充(适合图像边缘保留); 'replicate'表示复制填充(如边缘像素重复);'circular'表示循环填充(适合周期性数据)。
    • bias:是否添加偏置项。True表示每个输出通道加一个可学习的偏置值,False则表示无偏置
    • groups:groups 是分组卷积的组数,作用是把卷积层的输入通道和输出通道分成 groups 个独立的小组,每组内的输入通道只和本组的卷积核、输出通道交互,组与组之间完全隔离。groups必须能整除in_channelsout_channels
      例如某卷积层我们设置in_channels=6、out_channels=12、groups=3、kernel_size=3×3,输入通道分组为6 个输入通道 → 分成 3 组 → 每组 6/3=2 个输入通道;输出通道分组为12 个输出通道 → 分成 3 组 → 每组 12/3=4 个输出通道;为组 1、组2、组3 分别分配 4 个卷积核,每个卷积核尺寸是 2×3×3(仅覆盖每组 的 2 个输入通道),输出组 1 的 4 个通道。
      groups=1为普通卷积,特征融合最好,参数最多;groups=in_channels为深度卷积(DW),参数最少,特征融合最弱;groups为中间值(如groups=3),平衡参数和融合能力,常见于轻量级网络。

 3. Pooling Layers(池化层)

池化层(Pooling Layer,也叫下采样层)是卷积神经网络(CNN) 中紧跟卷积层的核心层,本质是对特征图做局部区域的特征聚合,用一个值代表一片区域的特征,实现降采样

池化层以固定大小的滑动窗口在特征图上移动,对窗口内的特征值按规则计算出一个代表值,输出尺寸更小、信息更凝练的新特征图。它没有可学习参数,只做固定规则的特征压缩。

池化层的主要作用是缩小特征图尺寸,大幅减少计算量和参数量,防止过拟合。除此之外也可以过滤冗余信息,保留区域内最核心的特征。

以下为常见池化层类型:

(1)最大池化层(Max Pooling)

  • 优点:保留强边缘、强纹理、强激活特征,特征区分度高,效果最好
  • 缺点:丢失部分细节信息
  • 适用:图像分类、检测、识别等绝大多数 CV 任务

最大池化层是最常用的池化层,其过程是在输入特征图的每个局部区域(池化窗口)选取最大值作为该区域的输出值。通过这种方式,它能够保留局部区域内最显著的特征,减少数据量。如下所示我们使用一个2X2的池化核(MaxPooling,步长stride默认为2)在原始4X4图像上滑动,共得到图中四个区域(按四种颜色分),选取每个区域的最大值得到缩小后的2X2特征图:

如果是n\times n的池化核,则步长默认为n

在PyTorch中,二维最大池化层使用torch.nn.MaxPool2d调用:

 nn.MaxPool2d参数说明:

必选参数:

  • kernel_size
    • 类型:整数或元组
    • 作用定义池化窗口的大小。若为整数,代表正方形池化窗口的边长;若为元组,元组的两个元素分别表示池化窗口的高度和宽度。
    • 示例kernel_size = 2 表示使用 2x2 的正方形池化窗口;kernel_size = (2, 3) 表示使用高度为 2、宽度为 3 的矩形池化窗口。

可选参数:

  • stride
    • 类型:整数或元组,默认值为 kernel_size
    • 作用:指定池化窗口在输入特征图上滑动的步长。若为整数,代表在高度和宽度方向上的步长相同;若为元组,两个元素分别表示在高度和宽度方向上的步长。
    • 示例stride = 2 表示池化窗口在高度和宽度方向上每次滑动 2 个像素;stride = (1, 2) 表示在高度方向上步长为 1,在宽度方向上步长为 2。
  • padding
    • 类型:整数或元组,默认值为 0
    • 作用:用于在输入特征图的边界添加填充。若为整数,会在高度和宽度方向上都添加相同数量的填充;若为元组,两个元素分别表示在高度和宽度方向上的填充数量。填充可以防止在池化过程中特征图尺寸过度减小,保留更多边界信息。
    • 示例padding = 1 表示在输入特征图的四周各添加 1 个像素的填充;padding = (1, 2) 表示在高度方向上添加 1 个像素的填充,在宽度方向上添加 2 个像素的填充。
  • dilation
    • 类型:整数或元组,默认值为 1
    • 作用:控制池化窗口内元素之间的间距。若为整数,代表在高度和宽度方向上的间距相同;若为元组,两个元素分别表示在高度和宽度方向上的间距。较大的 dilation 值可以扩大池化窗口的感受野。
    • 示例dilation = 2 表示池化窗口内元素之间的间距为 2。
  • return_indices
    • 类型:布尔值,默认值为 False
    • 作用:若设置为 True,在进行最大池化操作时会返回最大值所在的索引。
    • 示例:当需要对特征图进行反池化恢复尺寸时,可将该参数设为 True
  • ceil_mode
    • 类型:布尔值,默认值为 False
    • 作用:控制输出特征图尺寸的计算方式。若为 True,使用向上取整的方式计算输出尺寸;若为 False,使用向下取整的方式计算。分别对应Ceiling和Floor模式
    • 示例:当输入特征图尺寸不能被池化窗口和步长整除时,不同的 ceil_mode 设置会得到不同的输出尺寸。例如对于5*5的输入图像,池化核是3*3的,5不能被3整除,当横向第二次池化时池化核的第三列数据就会缺失(参照下图),如果是Ceiling模式,则会保留此处池数据,如果为Floor模式就不会保留。如果是Floor模式,下图所示的5*5的数据最终只会有2一个数据保留下来

(2)平均池化层(Average Pooling)

  • 优点:保留背景、全局平滑信息,噪声更鲁棒
  • 缺点:弱化强特征,特征区分度不如最大池化
  • 适用:早期 CNN(如 AlexNet 末尾)、需要平滑特征的场景

平均池化层是取滑动窗口内所有特征值的平均值作为输出,保留区域整体平均信息。

在PyTorch中,二维平均池化层使用torch.nn.AvgPool2d调用:

torch.nn.AvgPool2d参数说明:

  • kernel_size/stride/padding/dilation/ceil_mode:同MaxPool2d
  • count_include_pad:计算均值时是否包含 padding 补的 0,默认为True,表示包含。False表示不包含,仅计算原始特征值
  • divisor_override:手动指定均值的除数(覆盖默认的kernel_size²),默认为None。
    如设为 2,均值 = 总和 / 2

(3)自适应池化层(Adaptive Pooling)

  • 优点:可以输入任意尺寸,输出固定尺寸。代码极简,不用手动计算下采样参数

不需要手动设置核大小和步长,只指定输出尺寸,模型自动计算核和步长。它会根据输入特征图的大小自动调整池化操作,以得到指定大小的输出 。

核与步长计算方式为[H_{out}/H_{in}]。如下所示:

假设输入特征图为5X5,我指定输出大小为2X2。输入 H=5,要输出 H=2,则系统自动算出

  • stride = 5 // 2 = 2
  • kernel_size = ⌈5 / 2⌉ = 3
  • padding 自动补 0(这里刚好不用补)

也就是用 3×3 的窗口,步长 2,去滑 5×5 的图,最后刚好得到 2×2 输出。

在PyTorch中,二维自适应池化层又分为torch.nn.AdaptiveMaxPool2d(自适应最大池化),torch.nn.AdaptiveAvgPool2d(自适应平均池化)

AdaptiveMaxPool2d及AdaptiveAvgPool2d参数说明:

  • output_size:指定输出的高 / 宽尺寸。None表示保持该维度不变(如(None, 2)表示高不变,宽 = 2)
  • return_indices:AdaptiveMaxPool2d有,是否返回最大值索引

(4)全局平均池化层(Global Average Pooling, GAP)

全局平均池化层不是对特征图的 “局部窗口” 做平均,而是对整个通道的所有特征值计算平均值,最终每个通道只输出一个数值。

假设输入特征图的维度为:[batch_size, channels, height, width],经过 GAP 后输出维度变为:[batch_size, channels, 1, 1]

GAP通常用于替代全连接层,传统 CNN(如 AlexNet)在末尾用全连接层(FC)将特征图转为分类向量,全连接层参数量极大;而 GAP 可以直接将 2048 通道的特征图转为 2048 维向量,再接一个简单的 1×1 卷积 / 线性层即可分类,参数量几乎可以忽略,彻底缓解过拟合。

在PyTorch中,通过nn.AdaptiveAvgPool2d来使用全局平均池化层,只需指定output_size=1(即输出向量的通道数channels)即可实现 GAP

import torch
import torch.nn as nn

# 1. 定义测试输入(batch=1, channels=2, 3×3)
x = torch.tensor([[[[1,2,3],
                    [4,5,6],
                    [7,8,9]],
                   [[10,11,12],
                    [13,14,15],
                    [16,17,18]]]], dtype=torch.float32)

# 2. 定义全局平均池化层(output_size=1 即GAP)
gap = nn.AdaptiveAvgPool2d(output_size=1)

# 3. 前向传播
output = gap(x)

# 4. 输出结果
print("输入形状:", x.shape)          # torch.Size([1, 2, 3, 3])
print("GAP输出形状:", output.shape)  # torch.Size([1, 2, 1, 1])
print("GAP输出值:\n", output.squeeze())  # squeeze()去掉1维,输出 [5., 14.]

除了上述的四个池化层,还有全局最大池化、随机池化等,因为不常用就不多介绍了。

4. Padding Layers(填充层)

Padding 层(填充层)的主要作用是在输入数据的边界添加额外的元素,以此来改变输入数据的尺寸。在卷积神经网络(CNN)中,卷积和池化操作通常会使特征图的尺寸减小。通过在输入数据周围添加填充,可以控制输出特征图的尺寸,使其与输入尺寸相同或达到期望的大小。例如,在一些需要保持特征图尺寸不变的网络结构中,使用填充可以确保每一层的输出尺寸稳定。 适当的填充可以增加数据的多样性,使得模型在处理不同位置的特征时更加鲁棒,从而提高模型的泛化能力。

填充层不常用,这里仅做简要介绍,了解即可

以下为常用的二维数据类,除此之外还有1d 3d三维类,不做介绍

(1)nn.ZeroPad2d

这是一个二维零填充层,用于在二维输入数据的边界添加零元素

(2)nn.ReflectionPad2d 

 二维反射填充层,它会在输入数据的边界反射其相邻的元素来进行填充,这种填充方式可以更好地保留数据的局部特征

(3)nn.ReplicationPad2d

二维复制填充层,它会复制输入数据边界的元素来进行填充。 

5. Non-linear Activations(非线性激活)

(1)介绍

非线性激活在官网上有Non-linear Activations(weighted sum, nonlinearity)和Non-linear Activations(Other)两种

非线性激活函数是神经网络的重要组成部分,它们为网络引入了非线性特性,使得神经网络能够学习和表示复杂的函数关系

如果神经网络仅由线性层(如全连接层、卷积层)构成,那么无论网络有多少层,其整体仍然是一个线性模型。线性模型只能学习到输入和输出之间的线性关系,对于现实世界中大量存在的复杂非线性关系(如语音、图像、自然语言等数据中的模式),线性模型的表达能力远远不够 。通过在神经网络中引入非线性激活函数,能够打破线性约束,使网络可以学习和表示任意复杂的函数。这样,神经网络就能对各种复杂的模式和关系进行建模,从而大大增强了模型的表达能力。例如,在图像识别任务中,非线性激活函数可以帮助网络学习到图像中物体的边缘、纹理、形状等复杂特征。

非线性激活函数可以对输入的特征进行筛选和提取。不同的激活函数具有不同的特性,能够突出输入特征中的某些部分,抑制其他部分。以 ReLU 激活函数为例,它会将输入中的负值置为 0,只保留正值部分,这可以看作是一种特征筛选的过程,能够提取出对模型有用的特征信息。激活函数将输入特征映射到一个新的空间,在这个新空间中,数据的分布和特征可能更有利于后续的处理和分类。例如,Sigmoid 函数将输入映射到 (0,1)区间,Tanh 函数将输入映射到 (-1,1)区间,这种映射可以使数据的范围更加合适,便于模型进行学习和优化。

以下介绍几个常用的非线性函数,非线性激活函数有很多,其他非线性激活函数可以参考PyTorch官网

(2)Sigmoid

  • 数学公式
  • 特性:将输入值映射到 (0,1)区间,常用于二分类问题的输出层,可表示概率。但它存在梯度消失问题,当输入值很大或很小时,导数趋近于 0。
import torch
import torch.nn as nn

# 创建 Sigmoid 激活函数实例
sigmoid = nn.Sigmoid()

# 生成随机输入张量
input_tensor = torch.randn(10)

# 应用 Sigmoid 激活函数,输入参数为 input_tensor
output = sigmoid(input_tensor)
print(output)

(3)ReLU

  • 数学公式:ReLU(x) = max(0, x)
  • 特性:计算简单,在正区间导数恒为 1,有效缓解了梯度消失问题,被广泛应用于隐藏层。但它存在神经元 “死亡” 问题,当输入为负时,导数为 0,神经元可能不再更新。
import torch
import torch.nn as nn

# 创建 ReLU 激活函数实例
relu = nn.ReLU()

# 生成随机输入张量
input_tensor = torch.randn(10)

# 应用 ReLU 激活函数,输入参数为 input_tensor
output = relu(input_tensor)
print(output)

 在创建实例的时候也可以指定ReLU的inplace参数:假设输入input为-1,指定inplace=True,则input=0;如果指定inplace=False,则input仍等于-1,而input_tensor用于接收的变量为0。inplace默认为False,这样可以保留原始数据。

 (4)Leaky ReLU

  • 数学公式
  • 特性:在负区间引入一个小的斜率 ,避免了神经元 “死亡” 问题。

使用 nn.LeakyReLU 类,创建实例时需要传入 negative_slope 参数(表示负斜率  ),调用实例时输入参数为待激活的张量。 

import torch
import torch.nn as nn

# 创建 LeakyReLU 激活函数实例,指定 negative_slope 参数
leaky_relu = nn.LeakyReLU(negative_slope=0.01)

# 生成随机输入张量
input_tensor = torch.randn(10)

# 应用 LeakyReLU 激活函数,输入参数为 input_tensor
output = leaky_relu(input_tensor)
print(output)

(5)Tanh

  • 数学公式
  • 特性:将输入值映射到 (-1,1)区间,相较于 Sigmoid 函数,它的输出以 0 为中心,在一定程度上缓解了梯度消失问题,但仍然存在该问题。
import torch
import torch.nn as nn

# 创建 Tanh 激活函数实例
tanh = nn.Tanh()

# 生成随机输入张量
input_tensor = torch.randn(10)

# 应用 Tanh 激活函数,输入参数为 input_tensor
output = tanh(input_tensor)
print(output)

(6)代码案例

import torch
from torch import nn
from torch.nn import ReLU

input = torch.tensor([[1,-0.5],
                      [-1, 3]])

input = torch.reshape(input, (-1, 1, 2, 2))

class MyModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.relu1 = ReLU()
    
    def forward(self, input):
        output = self.relu1(input)
        return output

mymodel = MyModel()
output = mymodel(input)

6. Normalization Layers(归一化层)

归一化层(Normalization Layers)是深度学习模型中非常重要的组件,它们有助于加速模型的训练过程、提高模型的稳定性和泛化能力。

内部协变量偏移(Internal Covariate Shift):在神经网络训练过程中,随着网络层数的增加,各层输入的分布会不断发生变化,这种现象被称为内部协变量偏移(Internal Covariate Shift),即在深度神经网络训练时,前面层的参数一更新,后面所有层的输入数据分布就会跟着变,而且越往深层,飘得越厉害。(输入分布指一批数据的整体统计特征,即均值、方差)

内部协变量偏移(Internal Covariate Shift)出现的原因:

  1. 深度网络是链式依赖的。对于“输入 → Layer1 → Layer2 → … → LayerN → 输出”,Layer2 的输入 = Layer1 的输出、Layer3 的输入 = Layer2 的输出,任何一层参数变了,后面所有层的输入都会跟着变
  2. 参数更新带来连锁扰动。训练用反向传播,每训练一个 batch,所有层的权重 W、偏置 b 都会更新一点点。哪怕只是 Layer1 的 W 改了 0.001,但经过 Layer2、Layer3…… 一层层叠加、复合、放大到 Layer10 时,输入分布可能已经极大改变。即前面微变,后面剧变。

可以把每一层网络都看作一个独立的小模型,它的任务是基于当前的输入分布,学习一个映射函数,把输入变成对后续层有用的特征。
参数是根据梯度来更新的,但梯度的计算高度依赖于当前的输入分布。梯度\frac{\partial L}{\partial w}告诉你的是在当前这一批数据下,参数往哪个方向调能让损失变小,如果输入分布稳定,每一批数据的梯度方向是一致的,参数会稳步向全局最优收敛;如果输入分布剧烈变化,这一批数据告诉你 “w 要调大”,下一批数据又告诉你 “w 要调小”,梯度方向来回震荡,参数就会在最优解附近反复横跳。所以,对于这个小模型而言它的参数 w,b本质上是在拟合当前输入分布下的统计规律,一旦输入分布变了,之前学到的规律就不再适用,必须重新调整参数来适应新分布。
如下所示以两层全连接网络为例,

  • 输入x \in \mathbb{R}^{d},批量大小为 B
  • 损失函数L=\frac{1}{2B}\sum_{i=1}^{B}(y_{i}-\hat{y}_{i})^{2}

第二层参数W_{2}的梯度:

可以看出梯度的计算公式里有\sum_{i=1}^{B}h_{i},这意味着梯度的大小合方向直接依赖于当前批量h的统计分布(均值、方差、协方差)。


 

内部协变量偏移会导致训练极慢,收敛巨难,下面我们通过一个简单的例子说明这个过程:

  1. 首先我们先搭建一个简单的神经网络:
    输入 x → Layer1(全连接 + 激活) → h1(Layer1 输出) → Layer2 → 输出。
    批次大小 batch=2,输入就两个数:x_{1}=2,x_{2}=3
    Layer1:h_{1}=sigmoid(w_{1}x+b_{1})
    Layer2 的输入 = h1
    拟合目标是让输出接近[0.9,0.9]        
  2. 初始时Layer1层参数w_{1}=0.5,b_{1}=0,代入输入数据x_{1}=2,x_{2}=3,得:
    z_{1}=w_{1}x_{1}+b_{1}=0.5\times 1+0=0.5,z_{2}=w_{1}x_{2}+b_{1}=0.5\times 3+0=0.15
    h_{11}=sigmoid(0.5)\approx 0.62,h_{12}=sigmoid(1.5)\approx 0.82
    所以Layer2的输入为h_{1}=[0.62,0.82],均值\mu \approx 0.72,方差\sigma ^{2}\approx 0.01
  3. 如果将Layer1的权重w_{1}=0.5,b_{1}=0\rightarrow w_{}=2.0,b_{1}=0,此时:
    h_{11}\approx 0.88,h_{12}\approx 0.997,所以Layer2的输入为h_{1}=[0.88,0.997],均值\mu \approx 0.94,方差\sigma ^{2}\approx 0.0068
    可以看到Layer1参数一变,后续神经网络层的输入分布就会变化。
  4. 我们的拟合目标是输出接近[0.9,0.9]。第一轮Layer2的输入为h_{1}=[0.62,0.82],假设此时Layer2 输出为[0.8,0.7]w_{2}=0.5),此时它会将自己的参数w_{2}增大(比如0.5增大为0.6)来增大输出的值使其更接近[0.9,0.9]。由于梯度更新是反向传播,Layer2参数更新后Layer1的参数也会得到更新,Layer1更新后得到Layer2输入h_{1}=[0.88,0.997],此时Layer2发现输入变大,又会将参数w_{2}调小,这会导致w_{2}反复横跳,收敛极慢,根本没法稳定靠近最优解。

简单来说,整个过程就是:分布一直变 → 后面层权重永远在 “适应新分布”,更新方向乱跳、梯度容易消失 → 收敛巨慢

归一化的出现就是为了解决内部协变量偏移的问题,常见的归一化层有:

  • Batch Norm(BN):按 batch 归一化 → 适合 CNN、大 batch
  • Layer Norm(LN):按单个样本归一化 → 适合 NLP、小 batch
  • Instance Norm(IN):按单张图单通道归一化 → 风格迁移
  • Group Norm(GN):分组归一化 → 小 batch 下替代 BN

这里我们仅介绍BN(批量归一化):

  1. 假设当前 batch 有 m 个样本,该层的输入为 \vec{h}=\begin{Bmatrix} h_{1} ,& h_{2}, & \cdots & ,h_{m} \end{Bmatrix}
    当前 batch 均值及方差计算如下
  2. 标准化(零均值、方差为1):

    这一步会把输入分布强行拉到 均值≈0,方差≈1,避免分布偏移。
  3. 可学习的缩放与偏移:

对 “可学习的缩放与偏移” 的解释:

  • \hat{h}_{i}:已经被标准化到 均值≈0、方差≈1 的特征
  • \gamma:可学习的缩放参数(初始化为 1)
  • \beta:可学习的偏移参数(初始化为 0)

如果没有这一步,所有层的输入都会被强制变成 均值 0、方差 1,这会导致不同特征本应有的分布差异被抹平,网络无法学习到更丰富的模式。比如某层特征原本应该是 均值 = 2、方差 = 3,这样才能让后续层更好地区分两类样本,但纯标准化会把它变成 均值 = 0、方差 = 1,反而丢失了关键的区分信息,在加上 \gamma 和 \beta 后,网络可以通过学习让 \gamma =3\beta =2,恢复原本的分布形态,同时又保留了 “相对稳定” 的特性(因为是在标准化基础上线性变换,不会回到之前剧烈偏移的状态)。


总结来说:

在理想收敛状态下,每一层的输入分布都应该是任务相关的、稳定的 “特征分布”,它本身就是网络学到的关键信息(比如某些特征均值为正、方差较大,才能区分不同类别),但是在未收敛时,浅层参数的更新会导致深层输入分布剧烈、非线性地跳变,这种跳变是无意义的、干扰性的。BN做的就是先把所有跳变的分布 “按住”,强制拉到均值 = 0、方差 = 1 的稳定分布,消除训练过程中无意义的分布震荡,然后加入 \gamma 和 \beta把 “最终要学习的特征分布” 的决定权交还给网络,让它通过梯度下降自主学习 γβ,恢复出任务所需的最优分布形态(比如均值 = 2、方差 = 3),同时这个恢复过程是线性、平滑、可控的,不会再出现之前的剧烈跳变。

7. Recurrent Layers(循环层)

PyTorch 的循环层(Recurrent Layers)主要用于处理具有序列结构的数据

传统的前馈神经网络(如多层感知机)在处理输入时,每个输入样本都是独立的,不考虑样本之间的顺序和上下文信息。然而,像文本、语音、时间序列等数据都具有明显的序列特征,元素之间的顺序和依赖关系对于理解数据至关重要。循环层通过引入循环结构,能够保存和利用之前的信息。在每个时间步,循环层接收当前的输入和上一个时间步的隐藏状态,经过计算得到当前时间步的隐藏状态,并将其作为下一个时间步的输入。这种机制使得循环层可以对序列中的每个元素进行处理时考虑到其上下文信息,从而更好地捕捉序列数据中的模式和规律。

以下是几个应用场景:

  • 文本分类:例如垃圾邮件分类、新闻分类等。可以将文本看作一个单词或字符的序列,使用循环层对文本进行编码,提取文本的语义信息,然后将编码后的结果输入到分类器中进行分类。
  • 机器翻译:将源语言的句子翻译成目标语言的句子。循环层可以用于对源语言句子进行编码,然后解码生成目标语言句子。常见的模型如基于 LSTM 或 GRU 的编码器 - 解码器架构。 
  • 情感分析:判断文本所表达的情感倾向(如积极、消极、中性)。通过循环层对文本进行处理,提取情感相关的特征,从而实现情感分类。
  • 股票价格预测:股票价格随时间变化形成一个时间序列,循环层可以学习到价格的历史走势和变化规律,从而对未来的股票价格进行预测。
  • 天气预测:气象数据(如温度、湿度、气压等)是按时间顺序记录的时间序列数据。使用循环层可以分析气象数据的变化趋势,预测未来的天气情况。
  • 语音识别:语音信号是一种典型的序列数据,循环层可以对语音信号进行特征提取和建模,将语音转换为文本。例如,使用 LSTM 或 GRU 处理语音特征序列,结合声学模型和解码算法实现语音识别任务。
  • 视频分析:在视频中,每一帧图像可以看作序列中的一个元素。循环层可以用于分析视频帧之间的动态变化,例如视频分类(判断视频的类别,如动作、喜剧等)、行为识别(识别视频中人物的行为,如跑步、跳舞等)。

 具体要用的话可以参考官网定义的各种类

8. Transformer Layers(Transformer 层)

PyTorch 的 Transformer Layers 提供了构建 Transformer 架构所需的核心组件

Transformer 架构专门用于处理序列数据,广泛应用于自然语言处理(NLP)、时间序列分析等领域

  • 自然语言处理:语言本质上是序列结构,单词按顺序组成句子和段落。Transformer Layers 能够捕捉文本中单词之间复杂的依赖关系,无论是短距离还是长距离的依赖。例如在翻译任务里,能精准理解源语言句子中各单词的语义和语法关系,进而生成高质量的目标语言翻译。在文本生成任务(如自动写作、对话系统)中,它可以根据前文生成符合逻辑和语境的后续文本。
  • 时间序列分析:时间序列数据(如股票价格、气象数据)随时间变化呈现一定规律。Transformer Layers 可学习时间步之间的长期依赖,对未来数据进行准确预测。比如预测股票价格走势、气象变化等

Transformer Layers 的核心是自注意力机制(Self - Attention)

  • 捕捉长距离依赖:传统的循环神经网络(RNN)在处理长序列时,难以捕捉远距离元素之间的关系,存在梯度消失或爆炸问题。而自注意力机制允许模型在处理序列中任意位置的元素时,直接关注序列中其他位置的元素,从而有效捕捉长距离依赖。例如在分析一篇长文章时,能关联到前文很远位置的相关信息。
  • 并行计算:RNN 是按顺序逐个处理序列元素,难以并行化,计算效率较低。Transformer Layers 中的自注意力机制可以并行计算序列中所有元素的表示,大大提高了训练和推理的速度。这使得模型能够在大规模数据集上更高效地训练

 Transformer Layers和Recurrent Layers的区别:

  • 在结构设计上,循环层具有循环结构,在处理序列数据时,会按顺序逐个处理序列中的元素。在每个时间步,循环层接收当前输入和上一个时间步的隐藏状态,通过特定的计算更新当前隐藏状态;而Transformer 层完全基于注意力机制,摒弃了循环结构。它采用多头自注意力(Multi - Head Self - Attention)机制并行处理序列中的所有元素,同时结合前馈神经网络(Feed - Forward Network)进行特征变换。这种结构使得模型能够同时考虑序列中的所有位置,而不需要按顺序逐个处理。
  • 在计算效率上,循环层在处理长序列时计算效率较低,尤其是在训练大规模模型时,训练时间会显著增加;Transformer 层的多头自注意力机制可以并行计算序列中所有元素的表示,大大提高了计算效率。在现代 GPU 等硬件设备上,并行计算能够充分发挥硬件的性能优势,减少训练时间,使得模型可以在更短的时间内处理大规模的序列数据。
  • 在长序列处理能力上,循环层存在梯度消失或梯度爆炸的问题。随着序列长度的增加,早期时间步的信息在传递过程中会逐渐丢失或变得不稳定,导致模型难以捕捉长距离的依赖关系。虽然 LSTM 和 GRU 等改进的循环结构在一定程度上缓解了这个问题,但仍然存在局限性。Transformer 层通过自注意力机制可以直接关注序列中任意位置的元素,有效地捕捉长距离依赖关系。无论序列长度如何,模型都能在处理每个元素时考虑到序列中的所有其他元素,从而更好地处理长序列数据。
  • 在应用场景上,由于其具有一定的时序建模能力,循环层在一些对序列顺序敏感的任务中仍然有应用,例如语音识别中的语音信号处理,它可以较好地处理语音信号的时序特征;在一些小规模的序列数据处理任务中,循环层也能取得不错的效果,并且其结构相对简单,易于理解和实现。Transformer 层在自然语言处理领域取得了巨大的成功,广泛应用于机器翻译、文本生成、问答系统等任务。在计算机视觉领域,Transformer 也逐渐成为主流架构,用于图像分类、目标检测、图像生成等任务。此外,在时间序列分析、音频处理等领域,Transformer 层也展现出了强大的性能优势,尤其适用于处理大规模、长序列的数据。

  具体要用的话可以参考官网定义的各种类

9. Dropout Layers(丢弃层)

在 PyTorch 中,Dropout Layers(丢弃层)是一种常用的正则化技术,用于防止神经网络过拟合

Dropout 是由 Hinton 等人在 2012 年提出的一种正则化方法。在训练过程中,Dropout 层会以一定的概率随机 “丢弃”(将神经元的输出置为 0)一部分神经元,这样可以使得模型在训练时不会过度依赖某些特定的神经元,从而增强模型的泛化能力。

具体来说,假设一个 Dropout 层的丢弃概率为p ,那么在每次前向传播时,每个神经元都有p的概率被丢弃,即其输出被置为 0,而有1-p的概率保持不变。在测试阶段,Dropout 层通常不进行丢弃操作,而是会将所有神经元的输出乘以1-p进行缩放,以保证训练和测试阶段的输出期望一致。

通过随机丢弃神经元,使得模型不能依赖于某些特定的特征组合,迫使模型学习更鲁棒的特征表示,从而减少过拟合的风险。让模型在不同的神经元子集上进行学习,相当于训练了多个不同的子模型,最终的模型是这些子模型的组合,从而提高了模型在未知数据上的泛化能力。 

具体要用的话可以参考官网定义的各种类

 10. Sparse Layers(稀疏层)

在 PyTorch 中,Sparse Layers(稀疏层)用于处理稀疏数据,稀疏数据是指大部分元素为零的数据。

稀疏数据在很多实际场景中广泛存在。例如,在自然语言处理里的词袋模型,一个文档的词向量表示中大部分维度对应词汇在该文档中未出现,值为 0;在推荐系统中,用户 - 物品交互矩阵里大部分用户和物品之间没有交互,矩阵元素为 0;在生物信息学中,基因表达数据也常常是稀疏的。

 作用:

  • 节省内存:稀疏数据中大量的零元素占用了不必要的内存空间。PyTorch 的稀疏层采用特殊的数据结构(如 COO、CSR 等)来存储稀疏矩阵,只存储非零元素及其对应的索引,从而显著减少内存使用。
  • 提高计算效率:在进行矩阵运算时,稀疏层可以避免对大量零元素进行无意义的计算。例如,在矩阵乘法中,当一个矩阵是稀疏的,只需要计算非零元素的乘积和累加,从而减少计算量,提高计算速度。
  • 处理大规模数据:由于稀疏层节省了内存和提高了计算效率,使得模型能够处理更大规模的稀疏数据。在实际应用中,很多数据集规模非常大,如果使用普通的稠密表示和计算方式,可能会导致内存溢出或计算时间过长。

具体要用的话可以参考官网定义的各种类

11. Vision Layers(视觉层)

PyTorch 的 Vision Layers(视觉层)主要用于构建和训练计算机视觉相关的神经网络模型。计算机视觉涉及到对图像和视频数据的理解、分析和处理,而 PyTorch 的视觉层提供了丰富的工具和组件,帮助开发者高效地实现各种视觉任务。

用途概述:

1. 特征提取

视觉层可以从图像或视频中提取有意义的特征。在卷积神经网络(CNN)中,卷积层是核心的视觉层之一,它通过卷积核在图像上滑动进行卷积操作,提取图像的局部特征,如边缘、纹理等。不同层次的卷积层可以提取不同抽象程度的特征,从底层的简单特征逐渐过渡到高层的复杂语义特征。这些特征是后续进行图像分类、目标检测等任务的基础。

2. 数据变换

Vision Layers 提供了各种数据变换的功能,用于对输入的图像数据进行预处理和增强。例如,通过调整图像的大小、裁剪、旋转、翻转等操作,可以增加数据的多样性,提高模型的泛化能力。同时,对图像进行归一化处理可以使数据具有相似的分布,有助于模型的训练和收敛。

3. 模型构建

视觉层包含了构建复杂视觉模型所需的各种组件,如卷积层、池化层、全连接层等。开发者可以根据具体的任务需求,将这些组件组合成不同的网络架构,如 LeNet、AlexNet、VGG、ResNet 等。此外,还可以使用预训练的模型进行迁移学习,在已有模型的基础上进行微调,以快速适应新的任务。

4. 空间信息处理

视觉层能够有效地处理图像中的空间信息。例如,池化层可以对特征图进行下采样,减少特征图的尺寸,同时保留重要的特征信息,降低模型的计算量和参数数量。而反卷积层(转置卷积层)则可以对特征图进行上采样,恢复图像的空间尺寸,常用于图像生成、语义分割等任务。

常见应用场景:

1. 图像分类

图像分类是计算机视觉中最基础的任务之一,其目标是将输入的图像分类到不同的类别中。PyTorch 的视觉层可以用于构建分类模型,通过卷积层提取图像特征,然后使用全连接层将特征映射到不同的类别上。例如,在花卉分类任务中,可以使用预训练的 ResNet 模型,经过微调后对不同种类的花卉图像进行分类。

2. 目标检测

目标检测是指在图像中定位和识别多个目标的任务。Vision Layers 可以用于构建目标检测模型,如 Faster R - CNN、YOLO 等。这些模型通常包含卷积层用于特征提取,以及特定的检测头用于预测目标的位置和类别。例如,在智能安防系统中,可以使用目标检测模型实时检测监控画面中的人物、车辆等目标。

3. 语义分割

语义分割是将图像中的每个像素分配到不同的类别中,实现对图像的像素级分类。PyTorch 的视觉层可以用于构建语义分割模型,如 U - Net、DeepLab 等。这些模型通常采用编码器 - 解码器结构,通过卷积层提取特征,然后使用反卷积层进行上采样,恢复图像的空间尺寸并输出像素级的分类结果。例如,在医学图像分析中,可以使用语义分割模型对肿瘤等病变区域进行分割。

4. 图像生成

图像生成任务包括生成逼真的图像、图像风格迁移等。Vision Layers 可以用于构建生成对抗网络(GAN)或变分自编码器(VAE)等生成模型。例如,在图像风格迁移任务中,可以使用卷积层提取图像的内容和风格特征,然后通过生成网络将一种风格的图像转换为另一种风格的图像。

5. 姿态估计

姿态估计是指估计图像或视频中人体或物体的姿态信息。PyTorch 的视觉层可以用于构建姿态估计模型,通过卷积层提取图像特征,然后预测人体关节点的位置。例如,在运动分析、人机交互等领域,姿态估计模型可以用于分析运动员的动作姿态或实现手势控制等功能。

 具体要用的话可以参考官网定义的各种类

12. Shuffle Layers(混洗层)

在 PyTorch 里,Shuffle Layers(混洗层)通常指的是通道混洗层(Channel Shuffle Layer),它主要用于提升卷积神经网络(CNN)的效率和性能,尤其在轻量级网络架构中应用广泛。

通道混洗层的核心思想是打乱特征图中通道的顺序。在很多 CNN 架构中,为了提高计算效率,会采用分组卷积(Grouped Convolution)的方式。分组卷积将输入通道分成多个组,每个组独立进行卷积操作,这样可以减少参数数量和计算量。然而,分组卷积也带来了一个问题:不同组之间的通道缺乏信息交流,这可能会限制模型的表达能力。通道混洗层就是为了解决这个问题而设计的,它通过打乱通道顺序,使得不同组的通道能够进行信息交互,从而在不显著增加计算量的情况下提升模型性能。

具体作用:

1. 促进通道间信息交流

  • 原理:分组卷积中,每个卷积组只处理自己组内的通道信息,导致不同组的通道之间缺乏信息共享。通道混洗层会重新排列通道,让原本属于不同组的通道在后续的卷积操作中有机会相互作用,增强了特征图的信息融合。
  • 示例:假设在一个分组卷积中,将输入的 64 个通道分成 4 组,每组 16 个通道。经过通道混洗层后,原本属于不同组的通道会被重新组合,在后续卷积时就能实现跨组的信息交流。

2. 提高模型效率

  • 原理:通道混洗层的操作非常简单,计算成本很低。它可以在不增加太多计算量和参数的情况下,提升分组卷积网络的性能。通过促进通道间的信息交流,使得模型能够更有效地学习特征,减少了因为分组卷积带来的信息隔离问题,从而提高了整体的计算效率。
  • 示例:在轻量级的 CNN 架构中,如 ShuffleNet 系列,通道混洗层是核心组件之一。这些网络需要在有限的计算资源下实现较好的性能,通道混洗层通过高效的通道信息融合,帮助模型在减少计算量的同时保持较高的准确率。

3. 增强模型表达能力

  • 原理:信息在通道间的充分交流使得模型能够学习到更丰富、更复杂的特征表示。不同组的通道可能捕捉到不同类型的特征,通道混洗层将这些特征进行整合,让模型能够更好地理解输入数据的特征分布,从而增强了模型的表达能力。
  • 示例:在图像分类任务中,模型需要学习到图像的各种特征,如颜色、纹理、形状等。通道混洗层促进了不同特征信息的融合,使得模型能够更准确地对图像进行分类。

应用场景

1. 移动设备和嵌入式系统

在移动设备(如手机、平板电脑)和嵌入式系统(如智能摄像头、无人机)中,计算资源和功耗是重要的考虑因素。轻量级的 CNN 架构结合通道混洗层可以在有限的资源下实现高效的图像识别、目标检测等任务,例如在手机端的图像分类 APP 中,使用包含通道混洗层的模型可以快速准确地对图像进行分类,同时减少电池消耗。

2. 实时计算机视觉任务

对于需要实时处理的计算机视觉任务,如实时视频监控、自动驾驶中的环境感知等,模型的计算速度至关重要。通道混洗层能够提高模型的计算效率,使得模型可以在更短的时间内完成图像或视频的处理,满足实时性的要求。例如,在自动驾驶系统中,车辆需要实时识别道路上的各种目标,包含通道混洗层的模型可以快速准确地完成目标检测任务,保障行车安全。

  具体要用的话可以参考官网定义的各种类

Loss Functions(损失函数)

在 PyTorch 中,损失函数(Loss Functions)是深度学习模型训练过程中的关键组件,它用于衡量模型预测结果与真实标签之间的差异。

通过计算损失值,我们可以直观地了解模型在当前训练阶段的表现。损失值越小,说明模型的预测结果越接近真实值,模型性能越好。在训练过程中,优化器的目标是最小化损失函数。损失函数为优化器提供了优化的方向,通过反向传播算法计算损失函数关于模型参数的梯度,然后根据梯度更新模型参数,使得损失函数的值不断减小,从而逐步提高模型的性能。

以下是常用的损失函数,PyTorch还提供了很多其他的损失函数,按照自己项目的具体要求在官网选择一个合适的损失函数即可。

PyTorch 中,所有损失函数 / 激活函数都有 “类版本(nn.XXX)” 和 “函数版本(nn.functional.xxx)” 两种形式:

  • 第一种通过torch.nn.调用,比如均方误差损失MSELoss可以通过torch.nn.MSELoss()调用。
  • 第二种通过torch.nn.functional.调用,比如均方误差损失MSELoss可以通过torch.nn.functional.MSELoss()调用。
  • 两种调用方式最终得到的结果是一样的。区别在于“类版本”继承自nn.Module的类,需要先实例化再调用,可作为nn.Module子类(模型类)的成员变量,和模型一起管理(如移到GPU);“函数版本”是一个纯函数,直接调用(无需实例化),好处是更高(每次调用可灵活修改参数)。类版本的调用方式较为常用,所以下面只介绍类调用。

1. nn.L1Loss(平均绝对误差)

nn.L1Loss 是 PyTorch 中用于计算损失的一个类,它实现了 L1 损失函数,也被称为平均绝对误差(Mean Absolute Error,MAE)损失函数。

使用场景:

  • 回归任务:在回归问题中,目标是预测一个连续的数值,如房价预测、股票价格预测等。L1 损失对异常值的敏感性相对较低,因为它只考虑误差的绝对值,而不是误差的平方。这使得模型在存在异常值的数据集上训练时,不会过度受到异常值的影响,从而得到更稳健的预测结果。
  • 模型鲁棒性要求较高的场景:当数据中可能存在噪声或异常值,并且希望模型对这些异常情况具有一定的鲁棒性时,L1 损失是一个不错的选择。与均方误差损失(MSE)相比,L1 损失在处理异常值时更加温和,不会因为个别异常样本的误差过大而使整个损失值大幅增加。
torch.nn.L1Loss(reduction='mean')
  • reduction:指定损失的计算方式,有三种可选值:
    • 'mean':默认值,对所有样本的损失求平均值,得到一个标量值。
    • 'sum':将所有样本的损失相加,得到一个标量值。
    • 'none':不进行任何缩减操作,返回每个样本的损失值,输出的形状与输入的预测值和真实值相同。
import torch
import torch.nn as nn

# 创建 L1Loss 实例
l1_loss = nn.L1Loss()

# 生成随机的预测值和真实值
y_pred = torch.randn(10, 1)  # 预测值,形状为 (10, 1)
y_true = torch.randn(10, 1)  # 真实值,形状为 (10, 1)

# 计算 L1 损失
loss = l1_loss(y_pred, y_true)

print("L1 损失值:", loss.item())

# 使用 'sum' 缩减方式
l1_loss_sum = nn.L1Loss(reduction='sum')
loss_sum = l1_loss_sum(y_pred, y_true)
print("使用 'sum' 缩减方式的 L1 损失值:", loss_sum.item())

# 使用 'none' 缩减方式
l1_loss_none = nn.L1Loss(reduction='none')
loss_none = l1_loss_none(y_pred, y_true)
print("使用 'none' 缩减方式的 L1 损失形状:", loss_none.shape)

2. nn.MSELoss(均方误差损失)

MSE全称Mean Squared Error(均方误差损失),本质是量化模型预测结果和真实结果的平均偏离程度,是回归任务中最基础、最标准的损失度量。MSE 越小,代表模型预测的越准确;MSE 越大,代表模型整体预测偏差越大。

  • 适用场景:常用于回归任务,如房价预测、股票价格预测等,目标是预测一个连续的数值。
  • 公式:

MSE=\frac{1}{N}\sum_{i=1}^{n}(y_{i}-\hat{y}_{i})^{2}

y_{i}是真实值,\hat{y}_{i}是预测值,N是样本数量

PyTorch中使用torch.nn.MSELoss(reduction='mean')调用:

criterion = torch.nn.MSELoss(reduction='sum')
  • MSELoss是PyTorch中的一个类,同样继承自nn.Module
  • 参数说明:reduction='sum'指计算所有样本损失的总和;reduction='mean'指计算所有样本损失的平均值;若reduction='none'指保留每个样本的损失值(张量),最终得到的 loss 是一个和样本数量对应的张量。

在实例 PyTorch实现线性回归-CSDN博客 中用到此损失函数

 3. nn.BCELoss(二元交叉熵损失)

BCE全称Binary Cross Entropy(二元交叉熵)。

  •  适用场景:专门用于二分类任务,即类别只有两种(0/1)的情况。
  • 公式(单个样本):

BCE=-(ylog\hat{y}+(1-y)log(1-\hat{y}))

y是真实标签(0/1),\hat{y}是模型预测的概率(范围0~1)

  • 公式(批量样本):

BCE=-\frac{1}{N}\sum_{i=1}^{N}[y_{i}log\hat{y_{i}}+(1-y_{i})log(1-\hat{y}_{i})]

y_{i}是真实标签(0/1),\hat{y}_{i}是模型预测的概率(范围0~1),N为样本数

二分类的真实标签只有 0 或 1,代入公式后会发现公式只会计算 “真实类别对应的概率的对数”,另一项会被消为 0,这是 BCELoss 适配二分类的关键。以单个二分类样本为例,如下所示:

  • y=1时,公式简化为BCE=-log\hat{y}。只关注 “预测为 1 类的概率\hat{y}”,\hat{y}越接近 1,log\hat{y}越接近 0,损失越小;\hat{y}越接近 0,损失越大。
  • y=0时,公式简化为BCE=-log(1-\hat{y})。只关注 “预测为 0 类的概率1-\hat{y}”,1-\hat{y}越接近 1(即\hat{y}越接近 0),损失越小;反之越大。

BCELoss的梯度特性友好,对其求导可以得到梯度公式\frac{\partial Loss}{\partial z}=p-y,梯度只和 “预测率p” 与 “真实标签y” 的差值相关,计算高效,且不会出现复杂的梯度爆炸 / 消失(只要学习率合适)。

PyTorch中使用torch.nn.BCELoss(reduction='sum')调用:

criterion = torch.nn.BCELoss(reduction='sum')
  • 参数reduction的选择与MSELoss相同。

在实例PyTorch实现二分类-CSDN博客中用到此损失函数

(选读)二元交叉熵公式由来:

BCELoss是从极大似然估计(MLE)推导而来,核心目标是 “让模型预测的概率分布,尽可能接近真实标签的分布”。


1. 定义二分类的概率模型:二分类中,样本的真实标签y服从伯努利分布(0-1 分布),概率公式为:

P(y|x)=p^{y}\cdot (1-p)^{1-y}

  • y=1时,P(y|x)=p(预测为 1 类的概率);
  • y=0时,P(y|x)=1-p(预测为 0 类的概率);

2. 极大似然估计(最大化样本出现的总概率):对于N个独立样本,总似然(所有样本同时出现的概率)是每个样本概率的乘积:

L(\theta )=\prod_{i=1}^{N}p_{i}^{y_{i}}\cdot (1-p_{i})^{1-y_{i}}

  • \theta是模型参数(权重w、偏置b),我们要找\thetaL(\theta )最大。

极大似然估计:全称Maximum Likelihood Estimation,简称MLE,本质上是最大化样本出现的概率,让样本的出现看起来更合理。比如我有5个样本,它们所属的类别是确定的,这是已经观测到的事实,不会改变,把样本特征输入模型,模型里的参数(w、b)和特征做运算,输出每个样本属于对应类别的概率(0~1 之间),既然这 5 个样本已经实实在在发生了,最合理的模型参数,就是让这组 “已经发生的事实” 的概率最大 —— 这类似于 “用样本估计总体” 的思想。因为样本之间独立,所以这 5 个样本作为一组整体数据出现的总概率,就是每个样本单独概率相乘,于是有了上面的公式,而极大似然估计的目的就是最大化概率L(\theta )

3. 对数变换(把乘积变求和,简化计算):对数函数是单调递增的,最大化log(L(\theta ))等价于最大化L(\theta )

log(L(\theta ))=\sum_{i=1}^{N}[y_{i}log(p_{i})+(1-y_{i})log(1-p_{i})]

4. 转化为损失函数(最小化负对数似然):机器学习中习惯 “最小化损失” 而非 “最大化似然”,因此加负号,得到:

BCE=-\frac{1}{N}\sum_{i=1}^{N}[y_{i}log\hat{y_{i}}+(1-y_{i})log(1-\hat{y}_{i})]

4. nn.NLLLoss(负对数似然损失)

NLLLoss,全称Negative Log Likelihood Loss(负对数似然损失),是 PyTorch 中专门用于单标签多分类任务的损失函数。

  • 适用场景:常用于单标签多分类任务。
  • 输入要求:必须是模型输出经过LogSoftmax(对数 + Softmax)后的对数概率(而非原始得分logits,也非 Softmax 后的普通概率)。如下图所示三分类问题,基础神经网络层的三个输出要经过Softmax再经过Log后才能输入到NLLLoss中。
  • 公式:

NLL=-Ylog\hat{Y}

Y为实际标签向量,如[1,0,0]\hat{Y}为估计概率值向量,如[0.38,0.34,0.28]

Ylog\hat{Y}只有正确的标签(1)项可以留下,其余的标签值为0,与log\hat{Y}相乘仍为0。这和二元交叉熵的思路一致,都是只留下标签正确的一项。所以最终最小化损失函数就是缩小当前输入与其正确标签值之间的差距。

(选读)具体数据示例(三分类问题,类别标签分别为0、1、2,输入样本的正确标签值是0):

  1. 输入样本值经过基础神经网络层计算后得到原始得分:[1.0,2.0,3.0]
  2. 计算所有类别的 exp 值及和:
    e^{1.0}\approx 2.718,e^{2.0}\approx 7.389,e^{3.0}\approx 20.085
    \sum e^{x}=2.718+7.389+20.085=30.192
  3. 计算 LogSoftmax:log(\frac{e^{x}}{\sum e^{x}})=x-log(\sum e^{x})
    类别0:1.0-log(30.192)\approx 1.0-3.4079=-2.4039
    类别1:2.0-3.4079=-1.4079
    类别2:3.0-3.4079=-0.4079
  4. 计算NLLLoss:
    真实标签 0,只有这一项留下,其余的类别1和类别2均为0:
    -ylog\hat{y}=-1\cdot (-2.4079)=2.4079
  5. 接下来就通过梯度下降不断减小NLLLoss即可。

在PyTorch中,通过torch.nn.NLLLoss(reduction='mean')实例化NLLLoss损失函数类,其参数reduction与之前介绍完全相同。

criterion = torch.nn.NLLLoss(reduction='mean')

输入要求上,此损失函数的输入必须是模型输出经过LogSoftmax(对数 + Softmax)后的对数概率(而非原始得分logits,也非 Softmax 后的普通概率)。所以在使用之前还要使用PyTorch的log_softmax函数来将原始得分计算成对数概率,完整使用步骤如下:

import torch
import torch.nn as nn
import torch.nn.functional as F

# 1. 构造实例数据
logits = torch.tensor([[1.0, 2.0, 3.0]])  # 模型原始输出
labels = torch.tensor([0,])                             # 真实标签
log_softmax_output = F.log_softmax(logits, dim=1)         # 计算LogSoftmax

nll_loss_module = nn.NLLLoss(reduction='mean')  # 实例化,默认reduction='mean'

 5. nn.CrossEntropyLoss(交叉熵损失)

交叉熵损失CrossEntropyLoss = LogSoftmax + NLLLoss,也就是说交叉熵损失函数与NLLLoss相比多了LogSoftmax这一步,不需要再单独调用log_softmax() 方法。

在PyTorch中,通过torch.nn.NLLLoss(reduction='mean')实例化NLLLoss损失函数类

criterion = nn.CrossEntropyLoss(reduction='mean')

在实例PyTorch实现多分类-CSDN博客中用到此损失函数。

Optimizer(优化器)

在 PyTorch 中,优化器(Optimizer)是用于更新模型参数以最小化损失函数的工具。优化器根据计算得到的梯度信息,按照特定的优化算法来更新模型的参数。 

在深度学习中,训练模型的目标是找到一组最优的模型参数,使得模型在给定的数据集上的损失函数值最小。损失函数用于衡量模型预测结果与真实标签之间的差异,而优化器的作用就是根据损失函数的梯度信息,不断调整模型的参数,逐步降低损失函数的值,从而让模型的预测能力不断提升。

  • 参数更新:在每次迭代中,优化器根据计算得到的梯度,按照特定的算法对模型的参数进行更新,使得模型朝着损失函数减小的方向前进。
  • 自适应调整:一些优化器(如 Adam、RMSprop)能够自适应地调整每个参数的学习率,从而在不同参数的更新过程中更加灵活和高效。
  • 加速收敛:不同的优化器采用了不同的优化策略,能够在一定程度上加速模型的收敛速度,减少训练所需的时间和迭代次数。

 优化器的类有很多种,以下仅以SGD类做举例,讲解如何在模型中使用优化器。如果需要使用优化器,建议在PyTorch官网找到torch.optim中的各种优化器,根据需求选择。

torch.optim.SGD(随机梯度下降):

选择优化器需要根据原理选择适合自己需求的优化器,并参考官方文档查找各参数含义。

随机梯度下降(Stochastic Gradient Descent,SGD)是最基础的优化算法,其核心思想是在每次迭代中,随机选择一个或一小批样本计算损失函数的梯度,然后根据梯度的反方向更新模型的参数。

torch.optim.SGD(params, lr=<required parameter>, momentum=0, dampening=0, weight_decay=0, nesterov=False)

这里仅介绍常用的两个参数 ,因为不同优化器基本只有前两项参数相同,其他参数根据不同优化器的算法各有不同

params:

  • 类型:可迭代对象,通常为 model.parameters()
  • 说明:需要优化的模型参数。在 PyTorch 中,当你定义一个继承自 nn.Module 的模型后,可以通过调用 model.parameters() 方法获取模型中所有需要训练的参数,将其作为 params 传入优化器。
import torch
import torch.nn as nn

# 定义一个简单的线性模型
model = nn.Linear(10, 1)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

lr:

  • 类型:浮点数
  • 说明:学习率(Learning Rate),它控制了每次参数更新的步长。学习率过大可能导致模型无法收敛,在最优解附近震荡;学习率过小则会使模型收敛速度过慢。这是一个必需的参数。

⭐神经网络搭建训练过程

1. 导入必要的库

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

2. 定义模型

class LinearRegression(nn.Module):
    def __init__(self):
        super(LinearRegression, self).__init__()
        # 定义一个线性层,输入维度为 1,输出维度为 1
        self.linear = nn.Linear(1, 1)  

    def forward(self, x):
        # 前向传播,将输入 x 通过线性层得到输出
        out = self.linear(x)
        return out

# 实例化模型
model = LinearRegression()

3. 定义损失函数

选择合适的损失函数来衡量模型预测结果与真实标签之间的差异。对于线性回归问题,常用的损失函数是均方误差损失(Mean Squared Error, MSE)。

# 定义均方误差损失函数
criterion = nn.MSELoss()

4. 定义优化器

 选择合适的优化器,并传入模型的参数和学习率等超参数。这里我们使用随机梯度下降(SGD)优化器。

# 定义 SGD 优化器,传入模型参数和学习率
optimizer = optim.SGD(model.parameters(), lr=0.01)

5. 准备数据

准备训练数据,包括输入特征和对应的真实标签。在这个示例中,我们使用随机生成的数据。

# 生成一些随机训练数据
x_train = torch.randn(100, 1)
y_train = 2 * x_train + 1 + 0.1 * torch.randn(100, 1)

6. 训练模型

通过多次迭代训练模型,在每次迭代中执行以下操作:

  • 前向传播:将输入数据传入模型,得到预测结果,并计算损失。
  • 反向传播:调用 loss.backward() 计算损失函数关于模型参数的梯度。
  • 更新参数:调用 optimizer.step() 根据梯度更新模型的参数。
  • 清空梯度:调用 optimizer.zero_grad() 清空上一次迭代的梯度信息,避免梯度累积。
# 设置训练的轮数
num_epochs = 100

for epoch in range(num_epochs):
    # 前向传播
    outputs = model(x_train)
    loss = criterion(outputs, y_train)

    # 反向传播和参数更新
    optimizer.zero_grad()  # 清空梯度
    loss.backward()  # 反向传播计算梯度
    optimizer.step()  # 更新参数

    # 打印训练信息
    if (epoch + 1) % 10 == 0:
        print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')

 7. 保存模型

在训练完成后,你可以选择将训练好的模型保存下来,以便后续使用。

# 保存模型
torch.save(model.state_dict(), 'linear_regression_model.pth')

五,使用PyTorch提供的网络模型

我们主要研究的是图像方面的网络模型,如果你要使用语音网络模型的话就可以在torchaudio里找,如果你要是用文字网络模型的话就可以在torchtext里面找。如下图所示官网,都可以找到对应的模型

本节只讲与图像有关的torchvision.models

1,介绍

torchvision.models 是 PyTorch 中 torchvision 库的一个子模块,它提供了许多预训练好的深度学习模型,涵盖了图像分类、目标检测、语义分割等多个计算机视觉任务。这些预训练模型在大规模图像数据集(如 ImageNet)上进行了训练,能够为用户提供强大的特征提取和预测能力,极大地简化了模型开发的过程。 

以下仅对图像分类模型展开讲解,目标检测模型和语义分割模型大同小异

2,图像分类模型 

除了AlexNet,ResNet模型外,还有VGG,SqueezeNet,DenseNet,ResNext等等模型,不同模型在不同场景和任务下各有优势,可以参考官网找到适合自己的模型再具体学习。以下仅以AlexNet和ResNet模型做介绍学习

(1)AlexNet

1,背景及原理

AlexNet 是由 Alex Krizhevsky、Ilya Sutskever 和 Geoffrey Hinton 在 2012 年提出的深度卷积神经网络模型,它在当年的 ImageNet 大规模视觉识别挑战赛(ILSVRC)中取得了巨大的成功,Top - 5 错误率相比之前的方法大幅降低。AlexNet 的出现标志着深度学习在计算机视觉领域的崛起,开启了深度卷积神经网络在图像识别等任务上的广泛应用。

AlexNet 网络结构相对较深,共有 8 层,其中包含 5 个卷积层和 3 个全连接层。以下是其详细结构:

  • 输入层:输入图像的大小为 224×224×3(高度 × 宽度 × 通道数)。
  • 卷积层 1:使用 96 个大小为 11×11 的卷积核,步长为 4,填充为 0。经过卷积操作后,输出特征图的大小为 55×55×96。接着使用 ReLU 激活函数引入非线性。之后使用最大池化层,池化窗口大小为 3×3,步长为 2,输出特征图大小变为 27×27×96。
  • 卷积层 2:使用 256 个大小为 5×5 的卷积核,步长为 1,填充为 2。经过卷积和 ReLU 激活后,使用最大池化层(3×3 窗口,步长 2),输出特征图大小为 13×13×256。
  • 卷积层 3:使用 384 个大小为 3×3 的卷积核,步长为 1,填充为 1,经过 ReLU 激活。
  • 卷积层 4:使用 384 个大小为 3×3 的卷积核,步长为 1,填充为 1,经过 ReLU 激活。
  • 卷积层 5:使用 256 个大小为 3×3 的卷积核,步长为 1,填充为 1。之后使用最大池化层(3×3 窗口,步长 2),输出特征图大小为 6×6×256。
  • 全连接层 1:将卷积层 5 的输出展平为一维向量,输入到包含 4096 个神经元的全连接层,经过 ReLU 激活和 Dropout(丢弃率为 0.5)。
  • 全连接层 2:同样有 4096 个神经元,经过 ReLU 激活和 Dropout(丢弃率为 0.5)。
  • 输出层:包含 1000 个神经元,对应 ImageNet 数据集的 1000 个类别,使用 Softmax 函数输出每个类别的概率。

创新点: 

  • ReLU 激活函数:传统的神经网络使用 Sigmoid 或 Tanh 作为激活函数,这些函数在输入值较大或较小时容易出现梯度消失的问题。ReLU(Rectified Linear Unit)激活函数的公式为f(x)=max(0,x) ,它在正区间内梯度恒为 1,有效缓解了梯度消失问题,加快了网络的训练速度。
  • 数据增强:通过随机裁剪、水平翻转等方式对训练数据进行扩充,增加了数据的多样性,减少了过拟合的风险。
  • Dropout:在全连接层中使用 Dropout 技术,以一定的概率随机丢弃部分神经元,使得网络在训练过程中不会过度依赖某些特定的神经元,增强了模型的泛化能力。
  • GPU 加速:AlexNet 是第一个充分利用 GPU 并行计算能力进行训练的深度神经网络,大大缩短了训练时间。
2,使用方法

参数说明

pretrained:

  • 类型:布尔值(bool
  • 默认值False
  • 说明:用于指定是否加载在 ImageNet 数据集上预训练好的模型权重。
    • 当 pretrained = True 时,会自动从 PyTorch 的官方模型库中下载预训练的权重并加载到模型中。这样做的好处是可以利用在大规模数据集上学习到的特征表示,在自己的任务上进行微调,通常能加快收敛速度并提升模型性能。
    • 当 pretrained = False 时,会创建一个随机初始化权重的 AlexNet 模型,适合从头开始训练模型的场景。
import torchvision.models as models

# 加载预训练模型
pretrained_alexnet = models.alexnet(pretrained=True)

# 加载随机初始化模型
random_alexnet = models.alexnet(pretrained=False)

progress:

  • 类型:布尔值(bool
  • 默认值True
  • 说明:仅在 pretrained = True 时有效。用于控制在下载预训练权重时是否显示下载进度条。
    • 当 progress = True 时,下载过程中会在控制台显示下载进度信息。
    • 当 progress = False 时,下载过程将静默进行,不会显示进度条。
      # 加载预训练模型,不显示下载进度条
      alexnet = models.alexnet(pretrained=True, progress=False)

num_classes

  • 类型:整数(int
  • 默认值:1000
  • 说明:指定模型输出层的类别数量,即模型可以进行分类的类别总数。在 ImageNet 数据集上,有 1000 个不同的类别,所以默认值为 1000。如果你的任务涉及的类别数量不同,需要修改这个参数。通常在使用预训练模型进行迁移学习时,会根据具体任务调整输出层的类别数。
import torch
import torchvision.models as models

# 假设我们的任务是进行 10 分类
num_classes = 10
alexnet = models.alexnet(pretrained=True)

# 修改输出层

#在 AlexNet 模型中,classifier 是一个包含多个全连接层的序列模块(nn.Sequential)。
#alexnet.classifier[6] 指的是 classifier 序列中的第 7 个模块(索引从 0 开始),也就是原始的输出
#层。in_features 是 nn.Linear 层的一个属性,它表示该层的输入特征数量,即前一层输出的神经元数量。
#通过这行代码,我们获取了原始输出层的输入特征数量,并将其存储在变量 num_ftrs 中。
num_ftrs = alexnet.classifier[6].in_features
#这行代码将原始的输出层替换为一个新的全连接层。torch.nn.Linear 是 PyTorch 中用于定义全连接层的
#类,它接受两个参数:in_features 和 out_features。这里我们将 in_features 设置为 num_ftrs,确保
#新的输出层接收的输入特征数量与原始输出层相同;将 out_features 设置为 num_classes,也就是我们前
#定义的 10,表示新的输出层将输出 10 个神经元,对应 10 个分类类别。
alexnet.classifier[6] = torch.nn.Linear(num_ftrs, num_classes)

(2)ResNet

1,背景及原理

在深度神经网络的发展过程中,人们发现随着网络深度的增加,模型的训练误差和测试误差反而会增大,这种现象被称为 “退化问题”,它并非由过拟合导致。传统的神经网络在层数加深时,梯度在反向传播过程中容易出现消失或爆炸的情况,使得模型难以收敛到较好的结果。 

ResNet 有多种不同的架构,如 ResNet18、ResNet34、ResNet50、ResNet101 和 ResNet152 等,数字代表网络的层数。以 ResNet18 和 ResNet50 为例:

  • ResNet18 和 ResNet34:使用基本的残差块(Basic Block),每个基本残差块包含两个 3x3 的卷积层。
  • ResNet50、ResNet101 和 ResNet152:使用瓶颈残差块(Bottleneck Block),瓶颈残差块由一个 1x1 卷积层进行降维、一个 3x3 卷积层进行特征提取和一个 1x1 卷积层进行升维组成,这样可以减少参数量。

ResNet 的整体结构通常包括:

  • 输入层:接收输入图像,一般会进行一些预处理操作,如归一化等。
  • 初始卷积层:通常使用一个较大的卷积核(如 7x7)进行初步的特征提取,并使用最大池化层进行下采样。
  • 多个残差块组:由多个残差块堆叠而成,每个组内的残差块结构相同,不同组之间的通道数会逐渐增加,同时可能会进行下采样操作。
  • 全局平均池化层:将特征图在空间维度上进行平均池化,将每个通道的特征图变为一个标量,减少参数量。
  • 全连接层:将全局平均池化层的输出连接到一个全连接层,输出最终的分类结果。
2,使用方法

参数说明

pretrained:

  • 类型:布尔值(bool
  • 默认值False
  • 说明:指定是否加载在 ImageNet 数据集上预训练好的模型权重。当 pretrained = True 时,会自动下载并加载预训练权重;当 pretrained = False 时,模型权重将随机初始化。
import torchvision.models as models

# 加载预训练的 ResNet50 模型
resnet50_pretrained = models.resnet50(pretrained=True)

# 加载随机初始化的 ResNet50 模型
resnet50_random = models.resnet50(pretrained=False)

progress

  • 类型:布尔值(bool
  • 默认值True
  • 说明:仅在 pretrained = True 时有效。用于控制在下载预训练权重时是否显示下载进度条。当 progress = True 时,会显示进度条;当 progress = False 时,下载过程将静默进行。

num_classes

  • 类型:整数(int
  • 默认值:1000
  • 说明:指定模型输出层的类别数量。由于预训练的 ResNet 模型是在 ImageNet 数据集上训练的,该数据集有 1000 个类别,所以默认值为 1000。如果你的任务类别数不同,需要修改这个参数。
import torch
import torchvision.models as models

# 假设我们的任务是进行 10 分类
num_classes = 10
resnet50 = models.resnet50(pretrained=True)
num_ftrs = resnet50.fc.in_features
resnet50.fc = torch.nn.Linear(num_ftrs, num_classes)

3,目标检测模型

  • Faster R-CNN:一种两阶段的目标检测模型,由区域生成网络(Region Proposal Network, RPN)和 Fast R-CNN 组成,能够同时完成目标的定位和分类。
  • Mask R-CNN:在 Faster R-CNN 的基础上增加了一个用于实例分割的分支,能够同时完成目标的检测、分类和分割任务。

4,语义分割模型

  • FCN:全卷积网络(Fully Convolutional Networks),是最早提出的用于语义分割的深度学习模型,将传统的卷积神经网络中的全连接层替换为卷积层,能够输出与输入图像大小相同的分割结果。
  • DeepLabV3:采用了空洞卷积(Dilated Convolution)和空间金字塔池化(Atrous Spatial Pyramid Pooling, ASPP)等技术,在语义分割任务上取得了很好的效果。

5,模型的保存

import torchvision

vgg16 = torchvision.models.vgg16(pretrained=False)
# 保存方式1(既保存模型结构,也保存参数)
torch.save(vgg16, "vgg16_method1.pth") # ""里指定路径。运行后即可在当前目录下看到保存的模型

# 保存方式2(只将模型的参数保存为字典)
torch.save(vgg16.state_dict(), "vgg16_method2.pth")

 对于自己定义的模型保存也是一样的,如下

import torch
from torch import nn

class MyModel(nn.Module):
    def __init__(self):
        super().__init()
    def forward(self, input):
        output = input + 1
        return output

mymodel = MyModel()
torch.save("mymodel1.pth")

6,模型的加载

import torch

# 保存方式1加载模型
model = torch.load("vgg16_method1.pth") # 输入模型路径即可加载模型

# 保存方式2加载模型
vgg16 = torchvision.models.vgg16(pretrained=False)
vgg16.load_state_dict(torch.load("vgg16_method2.pth"))

 对于自己的模型保存后再加载需要注意一个点:加载此模型时必须将class定义的模型粘贴复制到要加载模型的文件中。可以不用再实例                当然也可以通过from  ...  import *将定义了模型的文件内容都引用过去

import torch
from torch import nn

class MyModel(nn.Module):
    def __init__(self):
        super().__init()
    def forward(self, input):
        output = input + 1
        return output

model = torch.load("mymodel1.pth")

六,完整模型训练过程

以CIFAR10数据集为例进行训练

import torchvision
from torch import nn
import torch
from torch.utils.tensorboard import SummaryWriter
from torch.utils.data import DataLoader

# 准备训练集
train_data = torchvision.datasets.CIFAR10(root="../data", train=True, transform=torchvision.transforms.ToTensor(), download=True)

# 准备测试集
test_data = torchvision.datasets.CIFAR10(root="../data", train=False, transform=torchvision.transforms.ToTensor(), download=True)

# DataLoader来加载数据集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)

# 搭建神经网络
class MyModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(64*4*4, 64),
            nn.Linear(64, 10)
        )
    def forward(self, x):
        x = self.model(x)
        return x

# 创建网络模型
mymodel = MyModel()

# 损失函数
loss_fn = nn.CrossEntropyLoss()

# 优化器
optimizer = torch.optim.SGD(mymodel.parameters(), lr=0.01)

# 设置训练网络的一些参数
total_train_step = 0  # 记录训练次数变量
total_test_step = 0  # 记录测试的次数
epoch = 100  # 训练的轮数

# 添加tensorboard
writer = SummaryWriter("../logs_train")

for i in range(epoch):
    print("第{}轮训练开始".format(i+1))
    for data in train_dataloader:
        imgs, targets = data
        outputs = mymodel(imgs)
        loss = loss_fn(outputs, targets)
        
        # 优化器优化模型
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
        total_train_step = total_train_step + 1
        if total_train_step % 100 == 0: # 100轮打印一次训练误差
            print("训练次数:{}, Loss:{}".format(total_train_step, loss.item()))
            writer.add_scalar("train_loss", loss.item(), total_train_step)
    
    # 每次训练完后在测试数据集上跑一遍,以测试数据集的正确率来评估模型是否训练好
    total_test_loss = 0
    with torch.no_grad():
        for data in test_dataloader:
            imgs, targets = data
            outputs = mymodel(imgs)
            loss = loss_fn(outputs, targets)
            total_test_loss = total_test_loss + loss.item()
    print("整体测试集上的Loss:{}".format(total_test_loss))
    total_test_step = total_test_step + 1
    writer.add_scaler("test_loss", total_test_loss, total_test_step)

    # 保存每一轮的模型
    torch.save(mymodel, "mymodel_{}.pth".format(i))

writer.close()
    


以下为优化代码,对于分类问题一般采用测试集正确率来衡量模型训练的效果

import torchvision
from torch import nn
import torch
from torch.utils.tensorboard import SummaryWriter
from torch.utils.data import DataLoader

# 准备训练集
train_data = torchvision.datasets.CIFAR10(root="../data", train=True, transform=torchvision.transforms.ToTensor(), download=True)

# 准备测试集
test_data = torchvision.datasets.CIFAR10(root="../data", train=False, transform=torchvision.transforms.ToTensor(), download=True)

# DataLoader来加载数据集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)

# 搭建神经网络
class MyModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(64*4*4, 64),
            nn.Linear(64, 10)
        )
    def forward(self, x):
        x = self.model(x)
        return x

# 创建网络模型
mymodel = MyModel()

# 损失函数
loss_fn = nn.CrossEntropyLoss()

# 优化器
optimizer = torch.optim.SGD(mymodel.parameters(), lr=0.01)

# 设置训练网络的一些参数
total_train_step = 0  # 记录训练次数变量
total_test_step = 0  # 记录测试的次数
epoch = 100  # 训练的轮数

# 添加tensorboard
writer = SummaryWriter("../logs_train")

for i in range(epoch):
    print("第{}轮训练开始".format(i+1))
    for data in train_dataloader:
        imgs, targets = data
        outputs = mymodel(imgs)
        loss = loss_fn(outputs, targets)
        
        # 优化器优化模型
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
        total_train_step = total_train_step + 1
        if total_train_step % 100 == 0: # 100轮打印一次训练误差
            print("训练次数:{}, Loss:{}".format(total_train_step, loss.item()))
            writer.add_scalar("train_loss", loss.item(), total_train_step)
    
    # 每次训练完后在测试数据集上跑一遍,以测试数据集的正确率来评估模型是否训练好
    total_test_loss = 0
    total_accuracy = 0 
    with torch.no_grad():
        for data in test_dataloader:
            imgs, targets = data
            outputs = mymodel(imgs)
            accuracy = (outputs.argmax(1) == targets).sum()
            total_accuracy = total_accuracy + accuracy
    print("整体测试集上的正确率:{}%".format((total_accuracy / len(test_data)))*100)
    total_test_step = total_test_step + 1
    writer.add_scaler("test_accuracy", total_accuracy / len(test_data), total_test_step)

    # 保存每一轮的模型
    torch.save(mymodel, "mymodel_{}.pth".format(i))

writer.close()
    


有些人会在训练开始时加入mymodel.train()函数,在测试开始时加入mymodel.eval()函数。这两个函数是让模型进入训练模式或者测试模式,只有在模型用到了某些层(Dropout,BatchNorm等)时才有作用,保险起见一般我们写代码的时候都加上。

七,使用GPU进行训练

在上一章的代码基础上修改几处即可

GPU的训练方式有两种

下面介绍第一种,第一种GPU的训练方式只要找到网络模型,数据(imgs,targets),损失函数,然调用他们的.cuda()进行返回即可

import torchvision
from torch import nn
import torch
from torch.utils.tensorboard import SummaryWriter
from torch.utils.data import DataLoader


# 准备训练集
train_data = torchvision.datasets.CIFAR10(root="../data", train=True, transform=torchvision.transforms.ToTensor(), download=True)

# 准备测试集
test_data = torchvision.datasets.CIFAR10(root="../data", train=False, transform=torchvision.transforms.ToTensor(), download=True)

# DataLoader来加载数据集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)

# 搭建神经网络
class MyModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(64*4*4, 64),
            nn.Linear(64, 10)
        )
    def forward(self, x):
        x = self.model(x)
        return x

# 创建网络模型
mymodel = MyModel()
if torch.cuda.is_available():
    mymodel.cuda()

# 损失函数
loss_fn = nn.CrossEntropyLoss()
if torch.cuda.is_available():
    loss_fn.cuda()

# 优化器
optimizer = torch.optim.SGD(mymodel.parameters(), lr=0.01)

# 设置训练网络的一些参数
total_train_step = 0  # 记录训练次数变量
total_test_step = 0  # 记录测试的次数
epoch = 100  # 训练的轮数

# 添加tensorboard
writer = SummaryWriter("../logs_train")

for i in range(epoch):
    print("第{}轮训练开始".format(i+1))
    for data in train_dataloader:
        imgs, targets = data
        if torch.cuda.is_available():
            imgs = imgs.cuda()
            targets = targets.cuda()
        outputs = mymodel(imgs)
        loss = loss_fn(outputs, targets)
        
        # 优化器优化模型
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
        total_train_step = total_train_step + 1
        if total_train_step % 100 == 0: # 100轮打印一次训练误差
            print("训练次数:{}, Loss:{}".format(total_train_step, loss.item()))
            writer.add_scalar("train_loss", loss.item(), total_train_step)
    
    # 每次训练完后在测试数据集上跑一遍,以测试数据集的正确率来评估模型是否训练好
    total_test_loss = 0
    total_accuracy = 0 
    with torch.no_grad():
        for data in test_dataloader:
            imgs, targets = data
            if torch.cuda.is_available():
                imgs = imgs.cuda()
                targets = targets.cuda()
            outputs = mymodel(imgs)
            accuracy = (outputs.argmax(1) == targets).sum()
            total_accuracy = total_accuracy + accuracy
    print("整体测试集上的正确率:{}%".format((total_accuracy / len(test_data)))*100)
    total_test_step = total_test_step + 1
    writer.add_scaler("test_accuracy", total_accuracy / len(test_data), total_test_step)

    # 保存每一轮的模型
    torch.save(mymodel, "mymodel_{}.pth".format(i))

writer.close()
    


下面介绍第二种也是更常用的GPU使用方式 。使用.to(device)方法

import torchvision
from torch import nn
import torch
from torch.utils.tensorboard import SummaryWriter
from torch.utils.data import DataLoader

# 定义训练设备
device = torch.device("cuda")  # 在GPU上运行,也可以填cpu。可以填cuda:0选择第一张显卡
                               # 也可以填cuda:1选择第二张显卡(对于多显卡设备)
# device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 准备训练集
train_data = torchvision.datasets.CIFAR10(root="../data", train=True, transform=torchvision.transforms.ToTensor(), download=True)

# 准备测试集
test_data = torchvision.datasets.CIFAR10(root="../data", train=False, transform=torchvision.transforms.ToTensor(), download=True)

# DataLoader来加载数据集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)

# 搭建神经网络
class MyModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(64*4*4, 64),
            nn.Linear(64, 10)
        )
    def forward(self, x):
        x = self.model(x)
        return x

# 创建网络模型
mymodel = MyModel()
mymodel.to(device)

# 损失函数
loss_fn = nn.CrossEntropyLoss()
loss_fn.to(device)

# 优化器
optimizer = torch.optim.SGD(mymodel.parameters(), lr=0.01)

# 设置训练网络的一些参数
total_train_step = 0  # 记录训练次数变量
total_test_step = 0  # 记录测试的次数
epoch = 100  # 训练的轮数

# 添加tensorboard
writer = SummaryWriter("../logs_train")

for i in range(epoch):
    print("第{}轮训练开始".format(i+1))
    for data in train_dataloader:
        imgs, targets = data
        imgs = imgs.to(device)
        targets = targets.to(device)
        outputs = mymodel(imgs)
        loss = loss_fn(outputs, targets)
        
        # 优化器优化模型
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
        total_train_step = total_train_step + 1
        if total_train_step % 100 == 0: # 100轮打印一次训练误差
            print("训练次数:{}, Loss:{}".format(total_train_step, loss.item()))
            writer.add_scalar("train_loss", loss.item(), total_train_step)
    
    # 每次训练完后在测试数据集上跑一遍,以测试数据集的正确率来评估模型是否训练好
    total_test_loss = 0
    total_accuracy = 0 
    with torch.no_grad():
        for data in test_dataloader:
            imgs, targets = data
            imgs = imgs.to(device)
            targets = targets.to(device)
            outputs = mymodel(imgs)
            accuracy = (outputs.argmax(1) == targets).sum()
            total_accuracy = total_accuracy + accuracy
    print("整体测试集上的正确率:{}%".format((total_accuracy / len(test_data)))*100)
    total_test_step = total_test_step + 1
    writer.add_scaler("test_accuracy", total_accuracy / len(test_data), total_test_step)

    # 保存每一轮的模型
    torch.save(mymodel, "mymodel_{}.pth".format(i))

writer.close()
    


八,模型验证(利用已经训练好的模型测试)

from PIL import Image
import torchvision

image_path = "../imgs.png"  # 要测试的图片所在路径
Image.open(image_path)  # 读取图片
image = image.convert('RGB')  # 确保图片为三颜色通道,可以适应各种格式的图片

transform = tochvision.transforms.Compose([torchvision.transforms.Resize(32,32),
                                           torchvision.transforms.ToTensor()])

image = transform(image)

# 加载模型
model = torch.load("mymodel_0.pth")

image = torch.reshape(image, (1, 3, 32, 32))
model.eval()
with torch.no_grad():
    output = model(image)
outputs = model(image)

print(output.argmax(1))

注意使用GPU训练的模型,在CPU中加载时要用map_location=torch.device('cpu')对应到cpu上 

from PIL import Image
import torchvision

image_path = "../imgs.png"  # 要测试的图片所在路径
Image.open(image_path)  # 读取图片
image = image.convert('RGB')  # 确保图片为三颜色通道,可以适应各种格式的图片

transform = tochvision.transforms.Compose([torchvision.transforms.Resize(32,32),
                                           torchvision.transforms.ToTensor()])

image = transform(image)

# 加载模型
model = torch.load("mymodel_0_gpu.pth", map_location=torch.device('cpu'))

image = torch.reshape(image, (1, 3, 32, 32))
model.eval()
with torch.no_grad():
    output = model(image)
outputs = model(image)

print(output.argmax(1))

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐