人工智能技术应用
项目式教学教材 —— 从零基础到独立完成竞赛项目
面向赛项:2026 金砖国家职业技能大赛 · 人工智能技术应用
覆盖模块:模块A 智能图像处理 / 模块B 智能制造与工业质检 / 模块C 智能客服与舆情分析 / 模块D AI低空创新应用
适用对象:零基础初学者 · 中职 / 高职 / 应用型本科学生
教材版本:v1.0 · 2026年
目录
前言
为什么要编写这本教材
2026年金砖国家职业技能大赛(金砖国家未来技能和技术挑战赛)人工智能技术应用赛项(BRICS-FS-56)聚焦"人工智能+行业"应用场景,涵盖智能图像处理、智能制造与工业质检、智能客服与舆情分析、AI低空创新应用四大模块。然而,对于零基础的学生而言,直接面对竞赛题目往往不知从何入手——缺乏对OpenCV、PyTorch、Transformer、YOLO等框架的基本认知,也不清楚迁移学习、目标检测、情感分析等技术的工作原理。
本教材采用项目式教学(Project-Based Learning, PBL)理念,以竞赛样题为蓝本,将每个模块拆解为可独立完成的子任务,从环境搭建到代码实现逐步引导,确保零基础学生能够跟随教材独立完成全部项目。
教材特色
1. 零基础友好
每个任务先讲"为什么",再讲"怎么做"。所有代码均配有逐行注释,关键概念以"知识链接"形式穿插讲解,不预设读者有任何AI编程经验。
2. 紧贴竞赛真题
教材以选拔赛和国际总决赛两套样题为基础,覆盖全部4个模块共40余道编程任务,每道题提供完整的思路分析、代码实现和易错提示。
3. 全流程覆盖
不仅讲解"填空式"答题代码,更覆盖数据准备、模型训练、结果可视化、模型保存与加载的完整工作流,帮助学生理解项目全貌。
4. 学以致用
每个项目末尾设有"拓展练习",引导学生在掌握基础任务后进行进阶探索,培养独立解决未知问题的能力。
如何使用本教材
- 第一阶段(第0章):跟随环境搭建章节,完成Python、核心库和Jupyter Notebook的安装配置。这是所有项目的前置条件。
- 第二阶段(项目一):从最简单的OpenCV图像处理入手,建立对AI编程的直观感受。建议用时1-2天。
- 第三阶段(项目二~四):按难度递增顺序学习工业质检、舆情分析和低空AI项目。每个项目建议用时2-3天。
- 第四阶段(拓展与附录):完成拓展练习,查阅附录中的速查表和常见错误解决方案,巩固所学知识。
竞赛模块一览
| 模块 | 名称 | 核心技术 | 难度 | 建议学习顺序 |
|---|---|---|---|---|
| 模块A | 智能图像处理应用开发 | OpenCV、matplotlib | 入门 | 第1个 |
| 模块B | 智能制造与工业质检 | PyTorch、MobileNetV2、迁移学习 | 中等 | 第2个 |
| 模块C | 智能客服与舆情分析 | HuggingFace、TinyBERT、NLP | 中等 | 第3个 |
| 模块D | AI低空创新应用 | YOLOv8、Ultralytics、目标检测 | 进阶 | 第4个 |
第0章 开发环境搭建
在开始任何项目之前,我们需要搭建一个完整的Python AI开发环境。本章将手把手引导你完成所有安装步骤。请务必按照顺序操作,遇到问题可参考附录C的常见错误解决方案。
0.1 Python 与 Anaconda 安装
0.1.1 为什么选择 Anaconda
Anaconda 是一个面向数据科学和机器学习的Python发行版,它自带Python解释器、包管理器(conda)和大量预装的科学计算库。对于AI开发初学者来说,使用Anaconda可以避免复杂的依赖管理问题。
0.1.2 下载与安装
Windows 系统
- 访问 Anaconda 官网下载页面:
https://www.anaconda.com/download - 下载 Windows 版本的 Anaconda 安装包(64-Bit Graphical Installer)
- 双击运行安装程序,按提示完成安装。安装时建议勾选"Add Anaconda to my PATH environment variable"选项
- 安装完成后,打开"Anaconda Prompt"验证安装
macOS 系统
- 访问 Anaconda 官网下载页面:
https://www.anaconda.com/download - 下载 macOS 版本的安装包(64-Bit Graphical Installer)
- 双击 .pkg 文件按提示完成安装
- 打开"终端"验证安装
0.1.3 验证安装
打开终端(Windows为Anaconda Prompt,macOS为终端),输入以下命令:
# 检查 Python 版本
python --version
# 预期输出: Python 3.9.x 或更高版本
# 检查 conda 版本
conda --version
# 预期输出: conda 23.x.x 或更高版本
0.1.4 创建虚拟环境
虚拟环境可以为不同项目创建隔离的Python环境,避免库版本冲突。我们为竞赛创建一个名为 brics_ai 的专用环境:
# 创建名为 brics_ai 的虚拟环境,Python 版本为 3.9
conda create -n brics_ai python=3.9 -y
# 激活虚拟环境
conda activate brics_ai
# 激活后,命令行前面会出现 (brics_ai) 标识
# 后续所有操作都请在激活此环境后进行
conda activate brics_ai 激活环境,否则找不到已安装的库。如果忘记了激活环境,运行代码时会报 ModuleNotFoundError 错误。
0.2 核心库安装
竞赛涉及四个模块,每个模块需要不同的Python库。以下按模块列出所需安装的库。
0.2.1 通用基础库
# 确保已激活 brics_ai 环境
conda activate brics_ai
# 安装 Jupyter Notebook(交互式编程环境)
conda install jupyter -y
# 安装 matplotlib(绘图库,各模块都会用到)
conda install matplotlib -y
# 安装 numpy(数值计算库)
conda install numpy -y
# 安装 pandas(数据处理库)
conda install pandas -y
# 安装 psutil(系统信息获取,模块D需要)
conda install psutil -y
0.2.2 模块A:OpenCV 安装
# 安装 OpenCV(opencv-python 是官方推荐的包名)
pip install opencv-python
# 如果需要额外的贡献模块(如扩展算法),可安装 contrib 版本
# pip install opencv-contrib-python
# 验证安装
python -c "import cv2; print('OpenCV版本:', cv2.__version__)"
# 预期输出: OpenCV版本: 4.x.x
0.2.3 模块B:PyTorch 安装
PyTorch 是深度学习框架,模块B(工业质检)和模块C(舆情分析)都需要使用。安装时需要根据你的电脑是否有NVIDIA显卡来选择版本:
有 NVIDIA 显卡(推荐,训练速度快很多)
# 安装支持 CUDA 的 PyTorch
conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia -y
无 NVIDIA 显卡(CPU 版本,训练较慢但功能完整)
# 安装 CPU 版本的 PyTorch
conda install pytorch torchvision torchaudio cpuonly -c pytorch -y
# 验证 PyTorch 安装
python -c "import torch; print('PyTorch版本:', torch.__version__); print('CUDA可用:', torch.cuda.is_available())"
# 预期输出:
# PyTorch版本: 2.x.x
# CUDA可用: True(有显卡)或 False(无显卡)
torch.cuda.is_available() 返回 True,模型训练速度可提升5-20倍。如果没有显卡,CPU版本也能完成所有任务,只是训练时间更长。
0.2.4 模块C:Transformers 安装
# 安装 HuggingFace Transformers 库
pip install transformers
# 验证安装
python -c "from transformers import BertTokenizer; print('Transformers安装成功')"
0.2.5 模块D:Ultralytics (YOLOv8) 安装
# 安装 Ultralytics(YOLOv8 官方库)
pip install ultralytics
# 验证安装
python -c "from ultralytics import YOLO; print('Ultralytics安装成功')"
0.3 Jupyter Notebook 使用入门
0.3.1 什么是 Jupyter Notebook
Jupyter Notebook 是一个交互式编程环境,可以在浏览器中编写和运行Python代码,并即时查看运行结果(包括图像、表格等)。它是AI开发中最常用的工具之一,竞赛也基于此环境进行。
0.3.2 启动 Jupyter Notebook
# 在终端中执行(确保已激活 brics_ai 环境)
conda activate brics_ai
jupyter notebook
执行后,浏览器会自动打开Jupyter Notebook的主页。点击右上角的"New"按钮,选择"Python 3 (ipykernel)"即可创建一个新的Notebook。
0.3.3 基本操作
| 操作 | 快捷键 | 说明 |
|---|---|---|
| 运行当前单元格 | Shift + Enter | 执行代码并跳到下一个单元格 |
| 运行当前单元格(不跳转) | Ctrl + Enter | 执行代码但停留在当前单元格 |
| 新建单元格(上方) | A | 在当前单元格上方插入新单元格 |
| 新建单元格(下方) | B | 在当前单元格下方插入新单元格 |
| 删除单元格 | D, D(按两次D) | 删除当前单元格 |
| 切换为代码模式 | Y | 将单元格设为代码类型 |
| 切换为Markdown模式 | M | 将单元格设为文本类型 |
0.3.4 第一个 Jupyter 程序
在新建的Notebook中,输入以下代码并按 Shift+Enter 运行:
# 第一个程序:验证环境是否正常
import sys
print("Python版本:", sys.version)
import numpy as np
print("NumPy版本:", np.__version__)
import matplotlib.pyplot as plt
plt.figure(figsize=(4,3))
plt.plot([1,2,3], [1,4,9])
plt.title("Test Plot")
plt.show()
如果能看到Python版本信息和一张折线图,说明环境配置成功!
0.4 数据集与项目文件准备
竞赛中每个模块都需要特定的数据集和项目文件。以下是各模块所需的数据准备说明:
| 模块 | 数据集/文件 | 说明 |
|---|---|---|
| 模块A | butterfly.jpg | 蝴蝶图像,用于图像处理练习 |
| 模块B | NEU-DET 钢材表面缺陷数据集 | 含6类缺陷:crazing、inclusion、patches、pitted、rolled、scratches |
| 模块C | 中文电商评论情感分析数据集 | 含正向/负向二分类标注的中文评论 |
| 模块D | 无人机航拍行人数据集 | YOLO格式标注的航拍行人检测数据 |
0.4.1 项目目录结构
建议在电脑上创建如下目录结构来管理项目文件:
brics_ai_projects/ # 项目根目录
├── data/ # 数据文件夹
│ ├── butterfly.jpg # 模块A图像
│ ├── NEU-DET/ # 模块B数据集
│ │ ├── train/
│ │ │ └── images/
│ │ └── val/
│ │ └── images/
│ ├── sentiment_data/ # 模块C数据集
│ │ ├── train.csv
│ │ └── val.csv
│ └── drone_dataset/ # 模块D数据集
│ ├── images/
│ │ ├── train/
│ │ └── val/
│ └── labels/
│ ├── train/
│ └── val/
├── module_a.ipynb # 模块A Jupyter Notebook
├── module_b.ipynb # 模块B Jupyter Notebook
├── module_c.ipynb # 模块C Jupyter Notebook
└── module_d.ipynb # 模块D Jupyter Notebook
***标记处)填写代码即可。本教材的练习环境需要你自行准备数据,可以在网上搜索对应的数据集名称进行下载。
1.1 项目导读与学习目标
项目背景
图像处理是人工智能视觉领域的基础。无论是工业质检中的表面缺陷检测、医疗影像中的病灶识别,还是自动驾驶中的车道线检测,都离不开图像处理技术。OpenCV(Open Source Computer Vision Library)是最流行的开源计算机视觉库,提供了超过2500种图像处理算法。
本项目以经典的 butterfly(蝴蝶)图像为处理对象,通过一系列基础图像处理操作,帮助你建立对图像处理流程的直观理解。这些操作是后续深度学习视觉任务(如模块B的工业质检和模块D的目标检测)的基础。
学习目标
- 掌握 OpenCV 库的导入与基本使用方法
- 能够使用 OpenCV 读取、显示和保存图像
- 理解 BGR 与 RGB 颜色空间的区别并进行转换
- 掌握灰度化、高斯滤波、Canny边缘检测、阈值二值化等核心操作
- 能够使用 matplotlib 展示图像处理结果
1.2 知识准备
1.2.1 数字图像基础
在计算机中,图像是由一个个像素(pixel)组成的二维矩阵。每个像素是一个数值,表示该位置的颜色或亮度。对于彩色图像,每个像素通常由三个通道(Channel)组成——红色(R)、绿色(G)、蓝色(B),即RGB模型。每个通道的取值范围通常是0~255(8位无符号整数)。
关键概念
- 分辨率:图像的宽×高,如1920×1080表示宽1920像素、高1080像素
- 通道:彩色图像有3个通道(RGB),灰度图像只有1个通道
- 像素值:0表示黑色,255表示白色,中间值表示灰色或不同颜色
- 位深:每个像素值用多少位表示,常见为8位(0~255)
1.2.2 OpenCV 的颜色空间:BGR vs RGB
这是一个非常重要的知识点,也是初学者最容易踩的"坑"。在日常生活中,我们习惯用 RGB(红、绿、蓝)顺序来描述颜色。但 OpenCV 读取图像时默认使用 BGR(蓝、绿、红)顺序——也就是说,第一个通道是蓝色,第三个通道是红色。
而 matplotlib(Python最常用的绘图库)显示图像时默认使用 RGB 顺序。如果直接用 matplotlib 显示 OpenCV 读取的图像,红色和蓝色会互换,导致颜色不自然。
1.2.3 灰度图像
灰度图像只包含亮度信息,不包含颜色信息。将彩色图像转为灰度图像是图像处理中常见的预处理步骤,可以减少数据量、突出形状和纹理特征。OpenCV 中常用的转换公式为:
Gray = 0.299 × R + 0.587 × G + 0.114 × B
这个公式反映了人眼对不同颜色的敏感度——对绿色最敏感,对蓝色最不敏感。
1.2.4 图像滤波
图像滤波是图像处理中的基本操作,用于去噪、平滑或增强图像特征。常见的滤波方法有:
| 滤波方法 | 原理 | 适用场景 |
|---|---|---|
| 均值滤波 | 用邻域像素的平均值替代中心像素 | 简单去噪 |
| 高斯滤波 | 用高斯加权平均替代中心像素 | 平滑去噪(最常用) |
| 中值滤波 | 用邻域像素的中位数替代中心像素 | 去除椒盐噪声 |
本项目使用高斯滤波(Gaussian Blur),它对邻域像素进行加权平均,距离中心越近的像素权重越大,符合高斯分布(钟形曲线)。高斯核大小决定了滤波的范围——核越大,模糊效果越强。
1.2.5 边缘检测(Canny算法)
边缘是图像中像素值急剧变化的位置,通常对应物体的轮廓。Canny边缘检测是最经典的边缘检测算法,它包含五个步骤:高斯滤波去噪→计算梯度幅值和方向→非极大值抑制→双阈值检测→滞后边界跟踪。
双阈值机制
Canny算法使用两个阈值:高阈值和低阈值。梯度值高于高阈值的像素被标记为"强边缘"(一定是边缘);低于低阈值的被丢弃;介于两者之间的像素,只有当与强边缘相连时才被保留为边缘。这种机制可以有效减少噪声干扰。
1.2.6 阈值分割(二值化)
阈值分割是将灰度图像转换为二值图像(只有黑和白两种颜色)的过程。设定一个阈值T,像素值大于T的设为255(白色),小于等于T的设为0(黑色)。这种操作可以将目标物体从背景中分离出来。
1.3 环境与数据准备
确保已完成第0章的环境搭建。本项目需要以下文件:
butterfly.jpg:蝴蝶图像文件,放在data/文件夹中- 已安装
opencv-python和matplotlib
创建一个新的 Jupyter Notebook,命名为 module_a.ipynb,在其中编写代码。
1.4 项目实施
以下按照竞赛样题的题目顺序,逐题讲解。每道题包含题目要求、思路分析、代码实现和代码详解四个部分。选拔赛和国际总决赛的题目合并讲解,以"【选拔赛第x题】"或"【总决赛第x题】"标注来源。
思路分析
导入库是所有Python程序的第一步。OpenCV 库在 Python 中的导入名不是"opencv",而是"cv2"。这个名称来源于OpenCV的C++ API版本号(C++ API是第2版,因此叫cv2)。
代码实现
import cv2
代码详解
import cv2 将OpenCV库导入到当前Python环境中。导入后,我们可以通过 cv2.函数名 的方式调用OpenCV的所有功能。
验证
可以在导入后添加一行代码来验证OpenCV是否安装成功:
import cv2
print("OpenCV版本:", cv2.__version__)
ModuleNotFoundError: No module named 'cv2',说明OpenCV未安装或未激活正确的虚拟环境。解决方法:在终端中执行 conda activate brics_ai 后重新运行 pip install opencv-python。
思路分析
读取图像使用 cv2.imread() 函数。该函数接收一个文件路径参数,返回一个NumPy数组(表示图像的像素矩阵)。需要注意路径的正确性——如果文件路径错误,imread 不会报错,而是返回 None,这在后续操作中会导致难以调试的错误。
代码实现
image = cv2.imread('data/butterfly.jpg')
代码详解
cv2.imread('data/butterfly.jpg'):读取data文件夹下的butterfly.jpg文件- 返回值是一个三维NumPy数组,形状为
(高度, 宽度, 3),其中3表示BGR三个通道 - 将读取结果赋值给变量
image,后续操作都基于这个变量
验证
import cv2
image = cv2.imread('data/butterfly.jpg')
# 检查是否读取成功
if image is None:
print("错误:图像读取失败,请检查文件路径!")
else:
print("图像读取成功!")
print("图像形状:", image.shape) # (高, 宽, 通道数)
print("图像数据类型:", image.dtype) # uint8
cv2.imread() 读取失败时返回 None 而不是报错!养成读取后检查 image is not None 的好习惯,可以避免后续操作出现 'NoneType' object has no attribute 'shape' 等令人困惑的错误。
思路分析
这道题考查两个知识点:(1)BGR到RGB的颜色空间转换;(2)使用matplotlib显示图像。
如前所述,OpenCV读取的图像是BGR格式,而matplotlib显示图像时默认使用RGB格式。如果不转换,图像中的红色和蓝色会颠倒。转换使用 cv2.cvtColor() 函数,指定转换码为 cv2.COLOR_BGR2RGB。
matplotlib 显示图像使用 plt.imshow() 函数,配合 plt.show() 显示。
代码实现
import matplotlib.pyplot as plt
# 将 BGR 转换为 RGB
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# 使用 matplotlib 展示图像
plt.imshow(image_rgb)
plt.title('Original Image')
plt.axis('off') # 隐藏坐标轴
plt.show()
代码详解
import matplotlib.pyplot as plt:导入matplotlib的绘图模块,约定俗成缩写为pltcv2.cvtColor(image, cv2.COLOR_BGR2RGB):颜色空间转换函数。第一个参数是输入图像,第二个参数指定转换类型。COLOR_BGR2RGB表示从BGR转到RGBplt.imshow(image_rgb):将图像数据显示在绘图区域中plt.title('Original Image'):设置图像标题plt.axis('off'):隐藏坐标轴刻度,让图像显示更干净plt.show():渲染并显示图像。在Jupyter Notebook中,这行代码会触发图像的内联显示
知识链接:cv2.cvtColor() 常用转换码
| 转换码 | 功能 |
|---|---|
cv2.COLOR_BGR2RGB | BGR → RGB(显示用) |
cv2.COLOR_RGB2BGR | RGB → BGR |
cv2.COLOR_BGR2GRAY | BGR → 灰度 |
cv2.COLOR_GRAY2BGR | 灰度 → BGR |
cv2.COLOR_BGR2HSV | BGR → HSV |
思路分析
灰度化是将三通道的彩色图像转换为单通道的灰度图像。使用 cv2.cvtColor() 函数,转换码为 cv2.COLOR_BGR2GRAY。转换后图像从三维数组(H, W, 3)变为二维数组(H, W)。
代码实现
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
代码详解
cv2.cvtColor(image, cv2.COLOR_BGR2GRAY):将BGR彩色图像转换为灰度图像- OpenCV内部使用公式
Gray = 0.299R + 0.587G + 0.114B进行转换 - 返回的
gray_image是一个二维NumPy数组,形状为(高度, 宽度),没有通道维度 - 赋值给变量
gray_image,后续的滤波、边缘检测等操作将基于灰度图像进行
验证
print("彩色图像形状:", image.shape) # 例如: (400, 400, 3)
print("灰度图像形状:", gray_image.shape) # 例如: (400, 400)
# 显示灰度图像
plt.imshow(gray_image, cmap='gray')
plt.title('Gray Image')
plt.axis('off')
plt.show()
cmap='gray'(颜色映射为灰度),否则matplotlib会默认使用伪彩色(偏绿偏黄),导致灰度图像看起来颜色奇怪。
思路分析
高斯模糊使用 cv2.GaussianBlur() 函数。需要指定三个关键参数:输入图像、高斯核大小(ksize)、X方向标准差(sigmaX)。
题目要求"高斯核大小设置为5×5",即 ksize=(5, 5)。"自动计算X方向标准差"意味着将 sigmaX 设为 0,OpenCV会根据核大小自动计算合适的标准差。
代码实现
blurred = cv2.GaussianBlur(gray_image, (5, 5), 0)
代码详解
cv2.GaussianBlur(src, ksize, sigmaX):高斯模糊函数- 第一个参数
gray_image:输入图像(灰度图像) - 第二个参数
(5, 5):高斯核大小,必须为正奇数。核越大,模糊效果越明显。5×5表示在5×5的窗口内进行高斯加权平均 - 第三个参数
0:X方向标准差 sigmaX。设为0时,OpenCV自动根据核大小计算标准差(公式:σ = 0.3×((ksize-1)×0.5 - 1) + 0.8) - 返回值赋给
blurred,即模糊后的图像
知识链接:为什么核大小必须是正奇数?
高斯核是一个方阵,需要一个中心点。只有奇数大小的方阵才有唯一的中心点(如3×3的中心是(1,1),5×5的中心是(2,2))。偶数大小的核没有唯一中心,因此OpenCV要求核大小必须为正奇数(1, 3, 5, 7, ...)。
验证
# 对比显示原始灰度图和高斯模糊后的图像
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
axes[0].imshow(gray_image, cmap='gray')
axes[0].set_title('Original Gray')
axes[0].axis('off')
axes[1].imshow(blurred, cmap='gray')
axes[1].set_title('Gaussian Blurred (5x5)')
axes[1].axis('off')
plt.show()
思路分析
Canny边缘检测使用 cv2.Canny() 函数,需要指定输入图像、低阈值和高阈值。题目明确给出了阈值参数值:低阈值100,高阈值200。
注意:题目要求对模糊后的图像 blurred 进行边缘检测,而不是原始灰度图像。先滤波再检测边缘是标准流程——滤波可以减少噪声对边缘检测的干扰。
代码实现
edges = cv2.Canny(blurred, 100, 200)
代码详解
cv2.Canny(image, threshold1, threshold2):Canny边缘检测函数- 第一个参数
blurred:输入图像(高斯模糊后的灰度图像) - 第二个参数
100:低阈值(threshold1)。梯度值低于此值的像素被丢弃 - 第三个参数
200:高阈值(threshold2)。梯度值高于此值的像素被标记为强边缘 - 梯度值在100~200之间的像素,只有与强边缘相连时才被保留
- 返回的
edges是一个二值图像:边缘像素为255(白色),非边缘像素为0(黑色)
知识链接:阈值选择经验
高阈值与低阈值的比例通常推荐为 2:1 或 3:1。本题中 200:100 = 2:1,符合推荐比例。如果边缘检测效果不理想,可以尝试调整阈值——阈值越高,检测到的边缘越少但噪声也越少;阈值越低,检测到的边缘越多但可能包含噪声。
思路分析
二值化使用 cv2.threshold() 函数。该函数返回两个值:第一个是使用的阈值(与输入阈值相同),第二个是二值化后的图像。题目要求将二值化图像赋值给 thresh,阈值可以忽略(用 _ 接收)。
参数:阈值=127,最大值=255,二值化类型使用 cv2.THRESH_BINARY(标准二值化:大于阈值设为最大值,小于阈值设为0)。
代码实现
_, thresh = cv2.threshold(gray_image, 127, 255, cv2.THRESH_BINARY)
代码详解
cv2.threshold(src, thresh, maxval, type):阈值分割函数- 第一个参数
gray_image:输入图像(灰度图像) - 第二个参数
127:阈值。像素值大于127的设为最大值,小于等于127的设为0 - 第三个参数
255:最大值。满足阈值条件时赋予的值 - 第四个参数
cv2.THRESH_BINARY:二值化类型。表示标准二值化 - 返回值:函数返回一个元组
(ret, dst)。ret是实际使用的阈值(本题中就是127),dst是二值化后的图像。使用_接收第一个返回值表示忽略它,thresh接收二值化图像
知识链接:cv2.threshold 的二值化类型
| 类型 | 说明 |
|---|---|
cv2.THRESH_BINARY | 大于阈值→maxval,小于阈值→0 |
cv2.THRESH_BINARY_INV | 大于阈值→0,小于阈值→maxval(反转) |
cv2.THRESH_TRUNC | 大于阈值→阈值,小于阈值→不变 |
cv2.THRESH_TOZERO | 大于阈值→不变,小于阈值→0 |
cv2.THRESH_OTSU | 大津法自动阈值(需与BINARY组合使用) |
思路分析
保存图像使用 cv2.imwrite() 函数。该函数接收两个参数:保存路径(含文件名)和图像数据。需要分别保存四张图像。
代码实现
cv2.imwrite('data/gray_image.jpg', gray_image)
cv2.imwrite('data/blurred.jpg', blurred)
cv2.imwrite('data/edges.jpg', edges)
cv2.imwrite('data/thresh.jpg', thresh)
代码详解
cv2.imwrite(path, image):将图像保存到指定路径- 第一个参数是保存路径(字符串),包含文件夹路径和文件名
- 第二个参数是要保存的图像数据(NumPy数组)
- 函数返回
True表示保存成功,False表示失败 - 保存的图像格式由文件扩展名决定:.jpg 为JPEG格式,.png 为PNG格式
cv2.imwrite() 保存灰度图像时直接保存即可(单通道)。如果保存彩色图像,OpenCV默认使用BGR格式保存。如果图像之前做过RGB转换,保存前可能需要转回BGR格式,否则保存的图片颜色会不正确。
1.5 项目总结与拓展
项目总结
通过本项目,我们完成了一个完整的图像处理流程:读取图像→颜色空间转换→灰度化→高斯滤波→边缘检测→阈值分割→保存结果。这是计算机视觉中最基础也是最重要的处理链路,几乎所有的视觉AI项目都会包含其中某些步骤。
本项目学到的核心API
| 函数 | 功能 |
|---|---|
cv2.imread() | 读取图像 |
cv2.cvtColor() | 颜色空间转换 |
cv2.GaussianBlur() | 高斯模糊 |
cv2.Canny() | Canny边缘检测 |
cv2.threshold() | 阈值分割 |
cv2.imwrite() | 保存图像 |
plt.imshow() | 显示图像 |
拓展练习
- 尝试使用
cv2.medianBlur()中值滤波处理图像,对比与高斯滤波的效果差异 - 尝试使用不同的Canny阈值(如50,150 和 30,100),观察边缘检测结果的差异
- 尝试使用
cv2.THRESH_OTSU(大津法)自动计算最优阈值进行二值化,对比与固定阈值127的效果 - 将灰度图像转换为HSV颜色空间,分离出H(色调)、S(饱和度)、V(明度)三个通道并分别显示
2.1 项目导读与学习目标
项目背景
在钢铁制造行业中,钢材表面可能出现各种缺陷(裂纹、夹杂、斑块、麻点、轧入氧化铁、划痕等),这些缺陷会影响钢材的力学性能和使用寿命。传统的人工目视检测效率低、主观性强、容易漏检。利用深度学习技术实现自动化的表面缺陷检测,可以大幅提升检测效率和准确率。
本项目使用东北大学(NEU)发布的钢材表面缺陷数据集 NEU-DET,包含6类缺陷共1800张图像。我们采用迁移学习策略——使用在ImageNet上预训练的 MobileNetV2 作为特征提取器,在其基础上训练一个六分类器来识别缺陷类型。
学习目标
- 理解卷积神经网络(CNN)和迁移学习的基本原理
- 掌握使用 torchvision 进行数据加载与预处理的方法
- 能够加载预训练模型并修改网络结构以适应新任务
- 掌握模型参数冻结与解冻的策略
- 能够定义损失函数、优化器和学习率调度器
- 掌握模型的保存与加载
2.2 知识准备
2.2.1 深度学习与神经网络基础
神经网络是一种模仿人脑神经元连接方式的数学模型。最基本的单元是"神经元"(也叫节点),它接收输入,进行加权求和,再通过一个激活函数输出结果。多个神经元按层组织,就构成了神经网络。
卷积神经网络(CNN)是专门用于处理图像数据的神经网络。它的核心是"卷积层"——用一个小的卷积核(如3×3)在图像上滑动,提取局部特征(如边缘、纹理、形状)。低层卷积提取简单特征(如边缘),高层卷积提取复杂特征(如物体形状)。
CNN 的关键组件
- 卷积层(Conv2d):提取图像特征。通过卷积核在图像上滑动,计算局部特征的加权和
- 池化层(Pool):降维。最大池化取局部区域最大值,平均池化取平均值
- 全连接层(Linear):将特征映射到分类结果。通常是网络的最后一层
- 激活函数(ReLU):引入非线性。ReLU(x) = max(0, x),是最常用的激活函数
- Dropout:随机丢弃部分神经元,防止过拟合
2.2.2 迁移学习(Transfer Learning)
迁移学习是深度学习中最实用的技术之一。核心思想是:将在大规模数据集上训练好的模型,迁移到自己的小数据集任务上。
想象你学会了骑自行车(源任务),再学骑摩托车(目标任务)就会容易很多——因为平衡、转向等技能可以迁移过来。同样,在ImageNet(120万张图片,1000类)上训练好的模型,已经学会了识别边缘、纹理、形状等通用视觉特征,这些特征对于钢材缺陷检测同样有用。
迁移学习的两种策略
- 特征提取(Feature Extraction):冻结预训练模型的卷积层(不更新权重),只训练新添加的分类层。适用于数据量小、与源任务相似的场景
- 微调(Fine-Tuning):解冻部分卷积层,与分类层一起训练。适用于数据量较大、与源任务有一定差异的场景
本项目采用两阶段策略:先冻结卷积基训练分类头(特征提取),再解冻部分层进行微调。
2.2.3 MobileNetV2 架构
MobileNetV2 是Google提出的轻量级CNN架构,专为移动端和嵌入式设备设计。它的核心创新是倒残差结构(Inverted Residual)和线性瓶颈(Linear Bottleneck),在保持较高准确率的同时大幅减少了参数量和计算量。
MobileNetV2 的最后一层卷积输出1280维特征向量,我们将其替换为自定义的分类头来适配6类缺陷检测任务。
2.2.4 NEU-DET 数据集
NEU-DET 数据集由东北大学发布,包含6类钢材表面缺陷,每类300张图像,共1800张。6类缺陷分别为:
| 英文名 | 中文名 | 说明 |
|---|---|---|
| crazing | 裂纹 | 表面细小的龟裂状裂纹 |
| inclusion | 夹杂 | 非金属杂质压入钢材表面 |
| patches | 斑块 | 表面色斑或氧化色斑 |
| pitted | 麻点 | 表面凹坑 |
| rolled | 轧入氧化铁 | 氧化铁皮压入钢材表面 |
| scratches | 划痕 | 表面划伤 |
2.2.5 PyTorch 核心 API
| 模块/类 | 功能 |
|---|---|
torchvision.datasets.ImageFolder | 从文件夹自动加载分类数据集 |
torchvision.transforms | 数据预处理(缩放、归一化等) |
torchvision.models | 预训练模型(MobileNetV2, ResNet等) |
torch.utils.data.DataLoader | 批量数据加载器 |
torch.nn | 神经网络层(Linear, ReLU, Dropout等) |
torch.optim | 优化器(Adam, SGD等) |
torch.nn.CrossEntropyLoss | 交叉熵损失函数(分类任务标配) |
2.3 环境与数据准备
确保已安装 PyTorch 和 torchvision。需要准备 NEU-DET 数据集,目录结构如下:
data/NEU-DET/
├── train/
│ └── images/
│ ├── crazing/ # 裂纹类
│ │ ├── 1.jpg
│ │ ├── 2.jpg
│ │ └── ...
│ ├── inclusion/ # 夹杂类
│ ├── patches/ # 斑块类
│ ├── pitted/ # 麻点类
│ ├── rolled/ # 轧入类
│ └── scratches/ # 划痕类
└── val/
└── images/
├── crazing/
├── inclusion/
└── ...
torchvision.datasets.ImageFolder 要求数据按文件夹组织——每个子文件夹名就是类别名,文件夹内的图片属于该类别。这种组织方式使得数据加载非常方便。
2.4 项目实施
思路分析
这是最基础的导入操作。torchvision 是 PyTorch 的视觉工具库,包含三个核心子模块:datasets(数据集加载)、transforms(数据变换)、models(预训练模型)。
代码实现
from torchvision import datasets, transforms, models
代码详解
datasets:提供常用数据集的下载和加载功能,以及ImageFolder用于加载自定义文件夹数据transforms:提供图像预处理功能,如缩放、裁剪、归一化、转张量等models:提供预训练模型,如 MobileNetV2, ResNet, VGG 等
思路分析
数据预处理是深度学习中非常重要的一步。transforms.Compose() 可以将多个预处理操作串联成一个流程。题目要求三个操作:(1)缩放到200×200;(2)转为张量;(3)用ImageNet统计量归一化。
归一化的目的是将像素值从0~255的范围缩放到以0为中心的范围内,这有助于模型训练时梯度下降的稳定性。使用ImageNet的均值和标准差是因为我们使用的是在ImageNet上预训练的模型。
代码实现
transform = transforms.Compose([
transforms.Resize((200, 200)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
代码详解
transforms.Compose([...]):将列表中的变换操作按顺序组合成一个流水线。图像会依次通过每个变换transforms.Resize((200, 200)):将图像缩放到200×200像素。无论原图是什么尺寸,输出统一为200×200transforms.ToTensor():将PIL图像或NumPy数组转为PyTorch张量。同时会将像素值从0~255缩放到0.0~1.0(除以255),并将通道维度从(H,W,C)调整为(C,H,W)transforms.Normalize(mean, std):对张量进行归一化。公式:(x - mean) / std。使用ImageNet的统计量是因为预训练模型就是在这些统计量上训练的
知识链接:为什么是这三个数?
[0.485, 0.456, 0.406] 是ImageNet数据集所有图片在R、G、B三个通道上的平均值(已归一化到0~1),[0.229, 0.224, 0.225] 是对应的标准差。这些数值已经成为使用ImageNet预训练模型时的标准归一化参数,几乎所有教程和代码都使用这组数值。
思路分析
这道题包含两步操作:(1)用 ImageFolder 加载数据集;(2)用 DataLoader 创建批量加载器。
ImageFolder 会自动扫描目录下的子文件夹,每个子文件夹名作为一个类别。 DataLoader 负责将数据集分成小批量(batch),在训练时逐批提供数据。
代码实现
from torch.utils.data import DataLoader
train_dataset = datasets.ImageFolder('data/NEU-DET/train/images', transform=transform)
train_loader = DataLoader(train_dataset, batch_size=16, shuffle=True)
代码详解
datasets.ImageFolder('data/NEU-DET/train/images', transform=transform):从指定目录加载图像数据。transform参数指定预处理流程,加载时自动应用DataLoader(train_dataset, batch_size=16, shuffle=True):创建数据加载器- batch_size=16:每批16张图片。训练时模型每次处理16张图片而不是全部数据,这既节省内存又能让梯度下降更稳定
- shuffle=True:每个epoch开始时打乱数据顺序。这很重要——如果不打乱,模型可能记住数据的排列顺序而不是学习特征
# 查看数据集信息
print("训练集大小:", len(train_dataset))
print("类别名称:", train_dataset.classes)
print("类别映射:", train_dataset.class_to_idx)
# 获取一个批次的数据
images, labels = next(iter(train_loader))
print("一个批次的图像形状:", images.shape) # torch.Size([16, 3, 200, 200])
print("一个批次的标签:", labels)
思路分析
使用 models.mobilenet_v2(pretrained=True) 加载预训练模型。然后修改模型的第一层卷积层 features[0][0],用新的 nn.Conv2d 替换它。新的卷积层参数:输入通道32(原为3)、输出通道32、卷积核3×3、步长2、填充1。
代码实现
import torch.nn as nn
base_model = models.mobilenet_v2(pretrained=True)
# 修改第一层卷积
base_model.features[0][0] = nn.Conv2d(
3, 32, kernel_size=3, stride=2, padding=1, bias=False
)
代码详解
models.mobilenet_v2(pretrained=True):加载在ImageNet上预训练的MobileNetV2。pretrained=True表示加载预训练权重base_model.features[0][0]:访问模型的第一个特征提取模块中的第一个卷积层。MobileNetV2 的features是一个nn.Sequential,features[0]是第一个块(也是一个Sequential),features[0][0]是其中的卷积层nn.Conv2d(3, 32, kernel_size=3, stride=2, padding=1, bias=False):创建新的卷积层- 3:输入通道数。虽然题目说"将输入通道从3改为32",但实际上输入通道仍然是3(RGB图像),输出通道改为32。这里理解题目意图:修改卷积层的参数配置
- 32:输出通道数(卷积核数量)
- kernel_size=3:卷积核大小3×3
- stride=2:步长2,卷积核每次移动2个像素,使输出尺寸减半
- padding=1:填充1,在图像边缘补一圈0,保证卷积后尺寸计算正确
- bias=False:不使用偏置。MobileNetV2的卷积层后接BN层,BN层已包含偏置功能,因此卷积层不需要额外的偏置
pretrained=True 参数已被弃用,替换为 weights=models.MobileNet_V2_Weights.DEFAULT。但竞赛环境中通常使用旧版本API。如果报错,可以改为 weights='DEFAULT' 或 pretrained=True 中的可用方式。
思路分析
迁移学习的第一阶段是"特征提取"——冻结卷积基的所有参数,只训练新添加的分类头。PyTorch 中通过设置 requires_grad=False 来冻结参数,使优化器在反向传播时不会更新这些参数。
代码实现
for param in base_model.parameters():
param.requires_grad = False
代码详解
base_model.parameters():返回模型所有参数的迭代器param.requires_grad = False:将每个参数的requires_grad属性设为False,表示该参数不需要计算梯度,因此在反向传播时不会被更新- 这样做的好处:(1)减少计算量,训练更快;(2)保护预训练的特征提取能力不被破坏;(3)减少需要训练的参数量,降低过拟合风险
知识链接:requires_grad 机制
PyTorch 使用自动微分机制(autograd)计算梯度。每个张量都有一个 requires_grad 属性——设为 True 时,PyTorch 会跟踪对该张量的所有操作,以便在反向传播时计算梯度。设为 False 则不跟踪,既节省内存又加速计算。在迁移学习中,我们冻结预训练层的 requires_grad,只让新添加的层参与梯度计算。
思路分析
这是一个完整的模型构建任务。使用 nn.Sequential 将各层按顺序串联。模型结构从预训练的base_model开始,经过自适应平均池化、展平、两个全连接层(中间有ReLU激活和Dropout),最终输出6个类别的分数。
代码实现
model = nn.Sequential(
base_model,
nn.AdaptiveAvgPool2d((1, 1)),
nn.Flatten(),
nn.Linear(1280, 256),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(256, 6)
)
代码详解
nn.Sequential(...):按顺序组合各层,数据从第一层开始依次通过每一层base_model:预训练的MobileNetV2特征提取器。输出形状为 (batch, 1280, H, W)nn.AdaptiveAvgPool2d((1, 1)):自适应平均池化,将空间维度压缩到1×1。无论输入H×W多大,输出都是1×1。输出形状变为 (batch, 1280, 1, 1)nn.Flatten():展平多维张量为一维。(batch, 1280, 1, 1) → (batch, 1280)nn.Linear(1280, 256):全连接层,将1280维特征映射到256维。这是第一个分类层的权重矩阵nn.ReLU():ReLU激活函数,引入非线性。ReLU(x) = max(0, x)nn.Dropout(0.5):训练时随机丢弃50%的神经元,防止过拟合。测试时自动关闭nn.Linear(256, 6):最后一个全连接层,将256维特征映射到6维(对应6类缺陷)。输出的是每类的原始分数
知识链接:为什么是1280?
MobileNetV2 的最后一层卷积输出通道数为1280,这是该架构的设计参数。经过自适应平均池化后,1280个通道各取一个平均值,形成1280维特征向量,作为分类器的输入。
思路分析
训练深度学习模型需要三个核心组件:损失函数(衡量预测与真实值的差距)、优化器(根据梯度更新参数)、学习率调度器(动态调整学习率)。
代码实现
import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.9)
代码详解
nn.CrossEntropyLoss():交叉熵损失函数。这是多分类任务的标准损失函数。它内部集成了Softmax操作,因此模型的输出不需要提前经过Softmaxoptim.Adam(model.parameters(), lr=0.001):Adam优化器。- model.parameters():传入需要优化的参数(只有requires_grad=True的参数会被优化)
- lr=0.001:学习率,控制每次参数更新的步长。0.001是Adam的常用默认值
optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.9):指数衰减学习率调度器。每个epoch后学习率乘以gamma(0.9),即学习率按指数逐渐减小
知识链接:学习率调度
训练初期使用较大的学习率可以快速接近最优解,但随着训练进行,大学习率可能导致在最优解附近震荡。学习率调度器在训练过程中逐步减小学习率,使模型在后期更精细地调整参数。指数衰减是最简单的调度策略:lr_new = lr_old × gamma。
思路分析
这是迁移学习第二阶段——微调(Fine-Tuning)的准备工作。先解冻所有参数(requires_grad=True),然后只冻结前50层(保留低层通用特征),让高层特征参与微调以适应钢材缺陷检测任务。
代码实现
# 第一步:解冻所有参数
for param in base_model.parameters():
param.requires_grad = True
# 第二步:冻结前50层
for i, param in enumerate(base_model.parameters()):
if i < 50:
param.requires_grad = False
代码详解
- 第一步遍历所有参数并设为
True,恢复梯度计算 - 第二步使用
enumerate获取参数的序号和值。前50个参数(序号0~49)设为False(冻结),其余保持True(可训练) - 这样做的效果:MobileNetV2的低层(提取边缘、纹理等通用特征)保持冻结,高层(提取更抽象的特征)参与微调,适应钢材缺陷检测任务
思路分析
微调阶段使用更小的学习率(0.0001 vs 第一阶段的0.001),因为此时是在已有预训练权重的基础上做精细调整,大学习率可能破坏已学到的特征。需要只优化 requires_grad=True 的参数。
代码实现
optimizer_fine = optim.Adam(
filter(lambda p: p.requires_grad, model.parameters()),
lr=0.0001
)
代码详解
filter(lambda p: p.requires_grad, model.parameters()):过滤出所有requires_grad=True的参数。filter函数根据条件筛选可迭代对象中的元素lambda p: p.requires_grad:匿名函数,判断参数是否需要梯度。返回True的参数被保留optim.Adam(..., lr=0.0001):创建新的Adam优化器,学习率为0.0001(比第一阶段小10倍)- 只优化需要训练的参数,可以减少计算量并避免意外修改冻结的参数
思路分析
训练完成后需要保存模型参数,以便后续加载使用(如推理预测)。PyTorch 使用 torch.save() 保存模型,推荐只保存参数字典(state_dict)而不是整个模型对象。
代码实现
torch.save(model.state_dict(), 'neu_model.pth')
代码详解
model.state_dict():返回模型所有参数的字典。键是层名,值是对应的参数张量torch.save(obj, path):将对象保存到文件。.pth或.pt是PyTorch模型文件的常用扩展名- 保存的是参数字典而非整个模型,这样更灵活——加载时需要先创建相同结构的模型,再加载参数
知识链接:加载模型参数
# 加载模型参数的完整流程
model = nn.Sequential(...) # 先创建相同结构的模型
model.load_state_dict(torch.load('neu_model.pth'))
model.eval() # 切换到评估模式(关闭Dropout等)
2.5 项目总结与拓展
项目总结
本项目完成了一个完整的迁移学习工作流:数据加载→预处理→加载预训练模型→修改网络结构→冻结参数→定义损失函数和优化器→训练→解冻微调→保存模型。这是深度学习图像分类的标准流程,掌握了这个流程,你就能应对大多数图像分类任务。
两阶段训练策略总结
| 阶段 | 策略 | 学习率 | 目的 |
|---|---|---|---|
| 第一阶段 | 冻结卷积基,只训练分类头 | 0.001 | 快速训练分类头,适应新任务 |
| 第二阶段 | 解冻高层卷积,联合微调 | 0.0001 | 精细调整特征提取,提升准确率 |
拓展练习
- 尝试使用
models.resnet50(pretrained=True)替换 MobileNetV2,对比模型大小和准确率 - 在训练循环中添加验证步骤,每个epoch结束后在验证集上评估模型,保存验证准确率最高的模型
- 使用 matplotlib 绘制训练过程中的损失曲线和准确率曲线
- 实现单张图片的推理预测功能:加载保存的模型,对一张新的钢材图片进行缺陷分类
3.1 项目导读与学习目标
项目背景
随着电商的蓬勃发展,每天产生海量用户评论。人工分析这些评论既费时又费力。利用自然语言处理(NLP)技术自动判断评论的情感倾向(正面/负面),可以帮助商家快速了解用户反馈,优化产品和服务。
本项目聚焦于利用预训练语言模型 TinyBERT,在中文电商评论情感分析数据集上开展迁移学习。TinyBERT 是 BERT 的轻量级压缩版本,参数量小、推理速度快,同时保留了较强的语言理解能力,非常适合教学和实际应用。
学习目标
- 理解自然语言处理(NLP)和预训练语言模型的基本概念
- 掌握 BERT/TinyBERT 的 Tokenizer 和模型使用方法
- 能够构建自定义数据集类并使用 DataLoader 加载
- 掌握 AdamW 优化器和 Warmup 学习率调度的配置
- 理解梯度裁剪、模型保存与加载等训练技巧
3.2 知识准备
3.2.1 自然语言处理(NLP)基础
自然语言处理是让计算机理解和处理人类语言的技术。在深度学习时代,NLP的核心范式是:将文本转换为数值向量→用神经网络处理向量→输出结果。
NLP 处理流程
- 分词:将句子切分成一个个词或子词
- 编码:将每个词映射为一个整数ID
- 嵌入:将ID转换为稠密向量
- 建模:用神经网络处理向量序列
- 输出:根据任务输出分类、生成等结果
3.2.2 BERT 与 TinyBERT
BERT(Bidirectional Encoder Representations from Transformers)是Google于2018年提出的预训练语言模型,它通过"掩码语言模型"(MLM)和"下一句预测"(NSP)两个任务在大规模文本上进行预训练,学习到了丰富的语言表征。
TinyBERT 是 BERT 的知识蒸馏版本,通过将大BERT的知识"蒸馏"到小模型中,在参数量减少约7.5倍的情况下仍能保持大部分性能。本项目使用的中文 TinyBERT 模型参数量约 14.5M,远小于标准 BERT 的 110M。
BERT 的输入格式
BERT 的输入由三部分组成:
- input_ids:词的整数ID序列。[CLS]标记在开头(ID=101),[SEP]标记在结尾(ID=102)
- attention_mask:注意力掩码。实际词为1,填充部分为0
- token_type_ids:句子类型ID(单句任务全为0)
例如:"很好用" → tokens: [CLS] 很 好 用 [SEP] → input_ids: [101, 2523, 1962, 4680, 102]
3.2.3 情感分析
情感分析是NLP中最常见的任务之一,目标是判断文本的情感倾向。本项目是二分类情感分析——判断评论是正面(1)还是负面(0)。使用 BERT 的序列分类头(Sequence Classification Head),在[CLS]标记的输出上接一个线性层进行分类。
3.2.4 HuggingFace Transformers 库
HuggingFace Transformers 是最流行的NLP库,提供了大量预训练模型的统一接口。本项目使用的核心组件:
| 组件 | 功能 |
|---|---|
BertTokenizer | 分词器,将文本转为BERT输入格式 |
BertForSequenceClassification | BERT序列分类模型(内置分类头) |
from_pretrained() | 从预训练模型加载权重 |
3.2.5 AdamW 优化器与 Warmup
AdamW 是 Adam 优化器的改进版本,修正了权重衰减(weight decay)的实现方式,在Transformer模型训练中表现更好。
Warmup(预热)是一种学习率调度策略:训练初期学习率从0线性增长到预设值,然后再按一定策略衰减。这可以防止训练初期因随机初始化的参数导致梯度不稳定。
3.3 环境与数据准备
确保已安装 transformers 和 torch。需要准备中文电商评论数据集(CSV格式,包含text和label两列)。同时需要定义以下全局变量:
# 全局配置(竞赛环境中通常已预定义)
MODEL_NAME = 'huawei-noah/TinyBERT_General_4L_312D' # 预训练模型名称
MAX_LEN = 128 # 最大序列长度
BATCH_SIZE = 32 # 批次大小
EPOCHS = 3 # 训练轮数
WARMUP_RATIO = 0.1 # 预热比例
DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
3.4 项目实施
思路分析
这是深度学习项目的标准开头——检测GPU是否可用,并选择计算设备。有GPU时使用GPU(训练快很多),没有时使用CPU。使用三元表达式在一行代码中完成条件判断和赋值。
代码实现
import torch
DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
代码详解
torch.cuda.is_available():检测系统是否有可用的NVIDIA GPU及CUDA环境。返回True或False'cuda' if torch.cuda.is_available() else 'cpu':Python 三元表达式。条件为真返回'cuda',否则返回'cpu'torch.device(...):创建设备对象。后续可以将模型和数据通过.to(DEVICE)移动到指定设备上- 将设备对象赋值给
DEVICE,在整个项目中统一使用
DEVICE 变量,确保所有操作在同一设备上执行。
思路分析
Tokenizer(分词器)负责将文本转换为模型能理解的数字ID。from_pretrained 方法会自动从HuggingFace Hub下载指定模型的分词器配置和词表。
代码实现
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained(MODEL_NAME)
代码详解
BertTokenizer:BERT的分词器类,专门处理BERT格式的分词from_pretrained(MODEL_NAME):从预训练模型加载分词器。MODEL_NAME是模型名称字符串(如'huawei-noah/TinyBERT_General_4L_312D')。首次使用时会自动下载,之后从本地缓存加载- 赋值给
tokenizer,后续用它将中文评论文本转换为BERT输入格式
知识链接:Tokenizer 的工作过程
# 分词器使用示例
text = "这个商品很好用"
encoding = tokenizer(text, max_length=128, padding='max_length', truncation=True, return_tensors='pt')
print("input_ids:", encoding['input_ids'])
# tensor([[101, 6821, 7028, 765, 3766, 1962, 4680, 102, 0, 0, ...]])
# 101=[CLS], 102=[SEP], 0=[PAD]
print("attention_mask:", encoding['attention_mask'])
# tensor([[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, ...]])
# 1=有效词, 0=填充
思路分析
这是一个简单的对象实例化操作。SentimentDataset 是竞赛环境中预定义的自定义数据集类(继承自 torch.utils.data.Dataset),接收文本列表、标签列表、分词器和最大长度作为参数。
代码实现
val_dataset = SentimentDataset(val_texts, val_labels, tokenizer, MAX_LEN)
代码详解
SentimentDataset(...):创建验证数据集实例val_texts:验证集文本列表,如['很好用', '质量差', ...]val_labels:验证集标签列表,如[1, 0, ...](1=正面,0=负面)tokenizer:上一步加载的BertTokenizer,用于在数据集内部将文本转为ID序列MAX_LEN:最大序列长度,超长截断,不足填充
知识链接:自定义 Dataset 类的结构
class SentimentDataset(torch.utils.data.Dataset):
def __init__(self, texts, labels, tokenizer, max_len):
self.texts = texts
self.labels = labels
self.tokenizer = tokenizer
self.max_len = max_len
def __len__(self):
return len(self.texts)
def __getitem__(self, item):
text = self.texts[item]
label = self.labels[item]
encoding = self.tokenizer(text, max_length=self.max_len,
padding='max_length', truncation=True,
return_tensors='pt')
return {
'input_ids': encoding['input_ids'].flatten(),
'attention_mask': encoding['attention_mask'].flatten(),
'labels': torch.tensor(label, dtype=torch.long)
}
思路分析
与模块B中的DataLoader使用方式相同,只是这里是NLP数据集。创建DataLoader时指定批次大小和是否打乱顺序。
代码实现
from torch.utils.data import DataLoader
train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)
代码详解
DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True):创建训练数据加载器train_dataset:训练数据集(SentimentDataset实例)batch_size=BATCH_SIZE:使用预定义的批次大小(通常为32)shuffle=True:打乱数据顺序,防止模型学习到数据的排列规律
思路分析
BertForSequenceClassification 是HuggingFace提供的BERT序列分类模型,在BERT的基础上内置了一个线性分类头。num_labels=2 指定二分类任务(正面/负面)。
代码实现
from transformers import BertForSequenceClassification
model = BertForSequenceClassification.from_pretrained(MODEL_NAME, num_labels=2)
代码详解
BertForSequenceClassification:BERT序列分类模型类。它在BERT编码器之上添加了一个线性分类层,直接输出分类结果from_pretrained(MODEL_NAME, num_labels=2):加载预训练模型。num_labels=2指定分类头输出2维(对应正面/负面两个类别)- 模型会自动在BERT编码器顶部添加一个线性分类层,将[CLS]标记的隐藏状态映射到2维输出
- 赋值给
model,后续通过model.to(DEVICE)移动到GPU或CPU
from_pretrained 时会从HuggingFace Hub下载模型权重(约50-100MB),需要网络连接。如果下载缓慢,可以提前下载模型到本地,然后传入本地路径。
思路分析
AdamW 是 Transformer 模型训练的标准优化器。2e-5(即0.00002)是BERT微调的推荐学习率,比CNN训练用的学习率小得多,因为预训练语言模型的权重已经很好,只需要轻微调整。
代码实现
from transformers import AdamW
optimizer = AdamW(model.parameters(), lr=2e-5, eps=1e-8)
代码详解
AdamW:Adam优化器的改进版本,修正了权重衰减的实现。Transformers库提供的AdamW与PyTorch内置的torch.optim.AdamW功能相同model.parameters():传入模型所有需要优化的参数lr=2e-5:学习率。科学计数法,2×10⁻⁵ = 0.00002。BERT微调的典型学习率范围是2e-5到5e-5eps=1e-8:数值稳定性常数。防止分母为零的情况,1e-8是默认推荐值
思路分析
Warmup 步数是训练初期学习率从0线性增长到预设值的步数。通常设为总步数的10%(WARMUP_RATIO=0.1)。需要将浮点数结果取整,因为步数必须是整数。
代码实现
warmup_steps = int(WARMUP_RATIO * total_steps)
代码详解
WARMUP_RATIO * total_steps:预热比例乘以总训练步数,得到浮点数结果。例如 total_steps=1000, WARMUP_RATIO=0.1,结果为100.0int(...):将浮点数取整为整数。例如 100.0 → 100- 赋值给
warmup_steps,用于后续构建学习率调度器
知识链接:total_steps 的计算
# 总训练步数 = 每轮步数 × 训练轮数
# 每轮步数 = 数据量 ÷ 批次大小(向上取整)
total_steps = len(train_loader) * EPOCHS
思路分析
训练循环中,每个batch是一个字典,包含 input_ids、attention_mask 和 labels。需要将标签数据移到与模型相同的设备(GPU/CPU)上才能进行计算。
代码实现
labels = batch['labels'].to(device)
代码详解
batch['labels']:从batch字典中获取键为'labels'的张量(真实标签).to(device):将张量移动到指定设备。如果device是'cuda',张量被移到GPU;如果是'cpu',保持在CPU上- 赋值给
labels,用于后续计算损失和评估准确率
思路分析
验证阶段收集所有预测结果。GPU上的张量不能直接转换为Python列表,需要先移到CPU。extend 方法可以将一个列表的所有元素添加到另一个列表中。
代码实现
predictions.extend(preds.cpu().tolist())
代码详解
preds.cpu():将GPU上的预测张量转移到CPU。这一步是必须的——GPU张量不能直接调用.tolist().tolist():将张量转换为Python列表。例如tensor([1, 0, 1])→[1, 0, 1]predictions.extend(...):将列表中的所有元素逐个添加到predictions列表末尾。注意extend和append的区别:extend([1,0,1])添加三个元素,append([1,0,1])添加一个列表作为单个元素
思路分析
训练过程中通常会保存验证集上表现最好的模型权重。训练结束后,需要将最优权重重新加载到模型中,以获得最佳性能的模型用于推理。
代码实现
model.load_state_dict(best_model_state)
代码详解
best_model_state:存储最优模型参数的状态字典(通常在训练循环中通过best_model_state = model.state_dict()保存)model.load_state_dict(best_model_state):将状态字典中的参数值加载到模型的对应层中,覆盖当前参数- 加载后模型恢复到验证集上表现最好的状态,而不是最后一个epoch的状态(最后一个epoch可能过拟合)
3.5 项目总结与拓展
项目总结
本项目完成了一个基于预训练语言模型的NLP情感分析流程:加载Tokenizer→构建数据集→加载预训练模型→配置优化器和调度器→训练与验证→保存最优模型→加载模型推理。这个流程是NLP文本分类的标准范式,适用于情感分析、意图识别、垃圾邮件检测等各种分类任务。
NLP vs CV 迁移学习对比
| 对比项 | CV(模块B) | NLP(模块C) |
|---|---|---|
| 预训练模型 | MobileNetV2(CNN) | TinyBERT(Transformer) |
| 数据预处理 | 缩放、归一化 | Tokenization、填充 |
| 优化器 | Adam | AdamW |
| 学习率 | 0.001 | 2e-5(小得多) |
| 学习率调度 | 指数衰减 | Warmup + 线性衰减 |
拓展练习
- 尝试使用完整的
bert-base-chinese模型替代 TinyBERT,对比准确率和推理速度 - 实现一个完整的推理函数,输入一条评论文本,输出情感预测结果和置信度
- 在训练循环中添加梯度裁剪
torch.nn.utils.clip_grad_norm_,防止梯度爆炸 - 使用 sklearn 的 classification_report 生成详细的评估报告(精确率、召回率、F1值)
4.1 项目导读与学习目标
项目背景
"低空经济"是快速发展的新兴产业,涵盖无人机物流、航拍测绘、安防巡逻、灾害搜救等领域。在无人机应用中,目标检测是最核心的AI能力——无人机需要从高空视角识别地面的人员、车辆等目标。无人机航拍图像的特殊性在于目标尺度极小、视角独特(俯视)、背景复杂,使得常规检测模型效果不佳。
本项目使用轻量级 YOLOv8n 模型,加载COCO预训练权重,通过"冻结主干+微调检测头"的迁移学习策略,在无人机航拍行人数据集上训练小目标检测模型。
学习目标
- 理解目标检测任务的基本概念(边界框、mAP、精确率、召回率)
- 掌握 Ultralytics YOLOv8 的训练、验证和推理方法
- 理解"冻结主干+微调检测头"的迁移学习策略
- 掌握余弦退火学习率调度和 Mosaic 数据增强的作用
- 掌握 psutil 获取系统信息、OpenCV 图像缩放等辅助操作
4.2 知识准备
4.2.1 目标检测基础
目标检测不仅要识别图像中有什么物体(分类),还要定位物体在哪里(定位)。输出是边界框——用矩形框标出物体位置和类别。
核心概念
- 边界框:矩形框,表示为 (x, y, w, h) 或 (x1, y1, x2, y2)
- IoU:交并比,预测框与真实框的交集面积/并集面积
- mAP@0.5:IoU阈值为0.5时的平均精度均值,目标检测核心指标
- 精确率:预测为正的样本中真正为正的比例
- 召回率:真正为正的样本中被正确预测的比例
4.2.2 YOLO 系列与 YOLOv8n
YOLO(You Only Look Once)将目标检测视为单次回归问题,速度极快。YOLOv8 由 Ultralytics 于2023年发布,统一了检测/分割/分类/姿态估计。本项目使用 YOLOv8n(nano版本),参数量仅3.2M,适合资源受限环境。
4.2.3 小目标检测策略
本项目采用的策略
- 加载COCO预训练权重:COCO包含"person"类别,预训练权重已具备行人检测先验
- 冻结主干+微调检测头:保留特征提取能力,只调整检测头
- 高初始学习率+余弦退火:加速检测头适配,后期平滑收敛
- 关闭Mosaic增强:防止小目标在拼接中变得更小
4.2.4 psutil 库
psutil(Python System and Process Utilities)是跨平台系统信息库,可获取CPU核心数、内存使用量等。本项目用 psutil.cpu_count(logical=False) 获取物理CPU核心数(不包括超线程)。
4.3 环境与数据准备
确保已安装 ultralytics、opencv-python、psutil。需要无人机航拍行人数据集(YOLO格式)和数据集配置文件 YAML。竞赛环境预定义了 cfg、YAML_PATH、IMGSZ、BATCH_SIZE、DEVICE、VAL_CONF、IOU_THRESHOLD 等变量。
4.4 项目实施
思路分析
竞赛环境中预定义了 train_model 函数,封装了 YOLOv8 的训练逻辑(包括冻结主干、设置余弦退火调度、关闭Mosaic等)。只需调用该函数即可启动训练。
代码实现
train_model()
代码详解
train_model():调用预定义的训练函数。该函数内部完成:加载YOLOv8n预训练模型→冻结主干参数→配置余弦退火学习率→关闭Mosaic增强→执行训练循环→保存最优权重- 训练过程中会在控制台输出每个epoch的损失、学习率、mAP等指标
思路分析
YOLOv8 的 model.val() 方法用于在验证集上评估模型性能。需要传入多个配置参数,所有参数从配置对象 cfg 中获取。verbose=False 关闭详细输出。
代码实现
metrics = model.val(
data=cfg.YAML_PATH,
imgsz=cfg.IMGSZ,
batch=cfg.BATCH_SIZE,
device=cfg.DEVICE,
conf=cfg.VAL_CONF,
iou=cfg.IOU_THRESHOLD,
verbose=False
)
代码详解
model.val(...):YOLOv8 模型验证方法,在验证集上计算 mAP、精确率、召回率等指标data=cfg.YAML_PATH:数据集配置文件路径,指定验证集位置和类别信息imgsz=cfg.IMGSZ:输入图像尺寸(如640),模型会将图像缩放到此尺寸batch=cfg.BATCH_SIZE:验证时的批次大小device=cfg.DEVICE:计算设备(如 'cuda:0' 或 'cpu')conf=cfg.VAL_CONF:置信度阈值,低于此值的检测结果被过滤iou=cfg.IOU_THRESHOLD:NMS(非极大值抑制)的IoU阈值,用于去除重叠框verbose=False:关闭详细输出,只返回指标不打印日志- 返回值赋给
metrics,包含 mAP@0.5、mAP@0.5:0.95、精确率、召回率等
思路分析
竞赛环境预定义了 evaluate_model 函数,用于在验证集上评估不同epoch保存的权重,返回表现最优的权重文件路径。
代码实现
best_weight_path = evaluate_model()
代码详解
evaluate_model():预定义的评估函数,遍历训练过程中保存的权重文件,在验证集上逐一评估,返回mAP最高的权重路径- 赋值给
best_weight_path,后续用于加载最优模型进行推理
思路分析
使用 cv2.resize() 缩放图像。目标尺寸通过将原始宽高乘以缩放比例并取整得到。int() 确保尺寸为整数(resize函数要求整数尺寸)。
代码实现
display_img = cv2.resize(display_img, (int(w_disp * scale), int(h_disp * scale)))
代码详解
cv2.resize(src, dsize):图像缩放函数。src是输入图像,dsize是目标尺寸元组 (width, height)w_disp * scale:原始宽度乘以缩放比例。例如 w_disp=1920, scale=0.5 → 960.0int(...):取整。resize要求尺寸为整数,960.0 → 960- 目标尺寸元组
(int(w_disp * scale), int(h_disp * scale))同时指定了宽和高 - 缩放结果重新赋值给
display_img,覆盖原图像数据
思路分析
竞赛环境预定义了 predict_and_analyze 函数,用于加载最优权重、对样本图像进行推理预测,并可视化分析结果。需要传入权重路径和样本数量。
代码实现
predict_and_analyze(best_weight_path, num_samples=2)
代码详解
predict_and_analyze(...):预定义的预测分析函数best_weight_path:作为位置参数传入,指定最优权重文件路径num_samples=2:关键字参数,指定分析2个样本图像。关键字参数的格式是参数名=值- 函数内部:加载权重→选取样本→模型推理→绘制检测框→输出分析结果
思路分析
psutil.cpu_count(logical=False) 返回物理CPU核心数。logical=False 表示不计算超线程核心。
代码实现
import psutil
cpu_count = psutil.cpu_count(logical=False)
代码详解
psutil.cpu_count(logical=False):获取物理CPU核心数logical=False:不计算超线程。例如4核8线程的CPU,logical=False返回4,logical=True返回8- 赋值给
cpu_count,用于后续设置 DataLoader 的num_workers参数
思路分析
竞赛环境预定义了环境检查函数(如 check_environment),用于验证GPU、内存、依赖库等是否满足训练要求。
代码实现
check_environment()
代码详解
check_environment():调用预定义的环境检查函数- 该函数检查:PyTorch版本、CUDA可用性、GPU显存、磁盘空间、依赖库完整性等
- 如果环境不满足要求,函数会输出警告信息
思路分析
random.sample(population, k) 从列表中随机抽取k个不重复元素。需要处理数据量不足200的情况——取 min(200, len(train_imgs)) 确保不超过列表长度。
代码实现
import random
sample_imgs = random.sample(train_imgs, min(200, len(train_imgs)))
代码详解
random.sample(population, k):从population中随机选取k个不重复元素,返回新列表train_imgs:训练图像路径列表min(200, len(train_imgs)):取200和列表长度的较小值。如果列表只有150张图,则取150;如果有1000张,则取200- 这样做的目的是防止当数据量不足200时
random.sample报ValueError: Sample larger than population
思路分析
分两步操作:(1)用 cv2.imread() 读取图像(BGR格式);(2)用 cv2.cvtColor() 将BGR转为RGB。这是模块A学过的操作的直接应用。
代码实现
img = cv2.cvtColor(cv2.imread(img_path), cv2.COLOR_BGR2RGB)
代码详解
cv2.imread(img_path):读取指定路径的图像,返回BGR格式的NumPy数组cv2.cvtColor(..., cv2.COLOR_BGR2RGB):将BGR转为RGB。这在用matplotlib显示图像时是必须的- 两步操作可以嵌套在一行中完成:先 imread 再 cvtColor
- 赋值给
img,用于后续的可视化展示
思路分析
竞赛环境预定义了 visualize_ground_truth 函数,用于在图像上绘制标注框(ground truth)并显示。通过关键字参数指定样本数量。
代码实现
visualize_ground_truth(num_samples=2)
代码详解
visualize_ground_truth(...):预定义的可视化函数,在样本图像上绘制真实标注框num_samples=2:关键字参数,指定可视化2个样本。关键字参数格式为参数名=值- 该函数通常用于训练前检查数据标注是否正确
思路分析
使用 Ultralytics 的 YOLO 类加载模型。模型名称存储在配置对象 cfg.MODEL_NAME 中(如 'yolov8n.pt')。
代码实现
from ultralytics import YOLO
model = YOLO(cfg.MODEL_NAME)
代码详解
YOLO(cfg.MODEL_NAME):实例化YOLO模型。cfg.MODEL_NAME是模型名称/路径,如'yolov8n.pt'- 如果传入的是预训练权重文件名(如
yolov8n.pt),Ultralytics 会自动下载(首次使用时)并加载COCO预训练权重 - 赋值给
model,后续调用model.train()、model.val()、model.predict()等方法
知识链接:YOLOv8 模型变体
| 模型 | 参数量 | 速度 | 精度 |
|---|---|---|---|
| YOLOv8n (nano) | 3.2M | 最快 | 最低 |
| YOLOv8s (small) | 11.2M | 快 | 较低 |
| YOLOv8m (medium) | 25.9M | 中 | 中 |
| YOLOv8l (large) | 43.7M | 慢 | 较高 |
| YOLOv8x (xlarge) | 68.2M | 最慢 | 最高 |
4.5 项目总结与拓展
项目总结
本项目完成了一个基于YOLOv8的目标检测全流程:环境检查→加载预训练模型→训练(冻结主干+微调检测头)→验证评估→获取最优权重→推理预测→可视化分析。同时涉及了psutil系统信息获取、random随机抽样、OpenCV图像读取与缩放等辅助操作。
四大模块技术对比
| 模块 | 任务类型 | 核心技术 | 关键指标 |
|---|---|---|---|
| 模块A | 图像处理 | OpenCV | 处理效果 |
| 模块B | 图像分类 | MobileNetV2+迁移学习 | 分类准确率 |
| 模块C | 文本分类 | TinyBERT+微调 | 分类准确率 |
| 模块D | 目标检测 | YOLOv8n+微调 | mAP@0.5 |
拓展练习
- 尝试使用 YOLOv8s 替代 YOLOv8n,对比模型大小、训练时间和mAP
- 开启Mosaic数据增强重新训练,观察对小目标检测的影响
- 实现一个完整的推理脚本:输入一张无人机航拍图像,输出带检测框的结果图
- 使用 matplotlib 绘制训练过程中的 mAP 变化曲线
附录
附录A:Python 基础速查
变量与数据类型
# 基本数据类型
x = 10 # 整数 int
y = 3.14 # 浮点数 float
s = "hello" # 字符串 str
b = True # 布尔值 bool
lst = [1, 2, 3] # 列表 list
d = {'a': 1} # 字典 dict
# 三元表达式
result = 'cuda' if condition else 'cpu'
# 列表推导式
squares = [x**2 for x in range(10)]
import 与模块
import cv2 # 导入整个模块
import numpy as np # 导入并设置别名
from torch import nn # 从模块导入特定子模块
from transformers import BERT # 从包导入特定类
附录B:常用库 API 速查
OpenCV (cv2)
| 函数 | 说明 | 示例 |
|---|---|---|
cv2.imread(path) | 读取图像(BGR) | img = cv2.imread('a.jpg') |
cv2.imwrite(path, img) | 保存图像 | cv2.imwrite('b.jpg', img) |
cv2.cvtColor(img, code) | 颜色空间转换 | cv2.cvtColor(img, cv2.COLOR_BGR2RGB) |
cv2.GaussianBlur(img, ksize, sigmaX) | 高斯模糊 | cv2.GaussianBlur(img, (5,5), 0) |
cv2.Canny(img, t1, t2) | Canny边缘检测 | cv2.Canny(img, 100, 200) |
cv2.threshold(img, t, max, type) | 阈值分割 | _, t = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) |
cv2.resize(img, dsize) | 图像缩放 | cv2.resize(img, (100, 200)) |
PyTorch (torch)
| 函数/类 | 说明 |
|---|---|
torch.device('cuda') | 创建GPU设备对象 |
torch.cuda.is_available() | 检查GPU是否可用 |
tensor.to(device) | 将张量移到指定设备 |
tensor.cpu() | 将张量移到CPU |
model.state_dict() | 获取模型参数字典 |
model.load_state_dict(d) | 加载参数字典 |
torch.save(obj, path) | 保存对象到文件 |
torch.load(path) | 从文件加载对象 |
model.parameters() | 获取模型所有参数 |
param.requires_grad | 参数是否需要梯度 |
Transformers (HuggingFace)
| 类/方法 | 说明 |
|---|---|
BertTokenizer.from_pretrained(name) | 加载分词器 |
BertForSequenceClassification.from_pretrained(name, num_labels=N) | 加载分类模型 |
tokenizer(text, max_length, padding, truncation, return_tensors) | 文本编码 |
Ultralytics (YOLOv8)
| 方法 | 说明 |
|---|---|
YOLO('yolov8n.pt') | 加载模型 |
model.train(data, epochs, imgsz) | 训练模型 |
model.val(data, imgsz, conf, iou) | 验证模型 |
model(image) | 推理预测 |
附录C:常见错误与解决方案
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
ModuleNotFoundError: No module named 'cv2' | OpenCV未安装或环境未激活 | conda activate brics_ai 然后 pip install opencv-python |
ModuleNotFoundError: No module named 'torch' | PyTorch未安装 | conda install pytorch torchvision -c pytorch |
RuntimeError: CUDA out of memory | GPU显存不足 | 减小batch_size,或使用CPU训练 |
'NoneType' object has no attribute 'shape' | 图像读取失败(路径错误) | 检查文件路径是否正确,使用 img is not None 验证 |
ValueError: Sample larger than population | random.sample抽样数超过列表长度 | 使用 min(N, len(list)) 限制抽样数 |
RuntimeError: Expected all tensors on the same device | 模型和数据在不同设备上 | 确保 model.to(DEVICE) 和 data.to(DEVICE) |
| matplotlib显示灰度图颜色异常 | 未指定灰度颜色映射 | 添加 cmap='gray' 参数 |
| matplotlib显示彩色图红蓝颠倒 | OpenCV BGR与matplotlib RGB顺序不同 | 使用 cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 转换 |
TypeError: integer argument expected | cv2.resize尺寸不是整数 | 使用 int() 取整 |
| HuggingFace模型下载缓慢 | 网络问题 | 提前下载模型到本地,传入本地路径 |