一:难点知识
1、要点知识
通道数:卷积核提取图像某方面的特征,作为1个通道
卷积层:提取特征
全连接层:分类
nn.flatten():降成1维的向量(适合全连接层读取)
kernel_size:卷积核大小
学习率:步长大小,每次走多远
偏置:b(告诉网络有偏置即可,初始化为0,自动学习)(卷积核的)
权重:w(正态分布,随机初始化,部分为0)
x:图片具体的每个像素值
激活函数:sigmoid(二分类),softmax(多分类),回归(正值)(ReLU),回归(-1,1)(tanh)
ReLU:取最大值
padding:填充
通道数=卷积核数
输出尺寸=[输入尺寸(单数字)- 卷积核大小(单数字)+(2x填充)]/步长
前向传播-计算loss-反向传播
2、前向传播
- 最后输出层的值logits(打分)(预测值)
- 打分转概率
- 计算损失loss
每一层输出=(wx+b)+...
最后输出层的每个数值对应你的分类标签(数值为预测值)
计算损失:1.分类(交叉熵损失),取对数-ln(); 越小越有信心(对了)
2.回归(均方误差),(预测-真实)*2
3、反向传播
更新w,b
举例说明:简要明了

二:三大模型网络架构
卷积神经网络CNN
1、Lenet-5
1.1层级机构(7层)
输入(灰度图)(固定为32x32)
卷积(5x5核,stride=1)+平均池化(2x2核,stride=2)(C1,S2,C3,S4)*2
卷积/全连接(C5)
全连接
输出(高斯连接)
1.2特点
下采样使用平均池化
Sigmoid/tanh(易梯度消失)激活函数
高斯连接输出,径向基函数RBF(而不是Softmax)
1.3训练细节
损失函数,平均平方误差MSE
优化算法,随机梯度下降SGD(优化器)
预处理,输入归一化、背景归一化至[-1,1],字符位置居中
[定义解释]
下采样:尺寸减小,保留主要特征(省内存,降噪)
梯度消失:梯度即坡度,越大,参数更新越快,学的快;梯度趋近于0,前面层学不到东西,参数不再更新(层数过多)
激活函数:加非线性能力,可学习复杂任务
Softmax:激活/归一化函数,转化为(0-1)概率分布,和为1;全局竞争,一个大,另一个小;分类的最后一层
RBF:距离型核函数,样本距离中心点近,数值越大;局部敏感
MSE:损失函数,回归任务,连续数值;
优化:更新参数,减小损失
SGD:优化器,用少量样本算梯度,更新参数;训练速度快,跳出局部最优,泛化强;震荡剧烈,学习率适当
1.4适用数据集
小尺寸(像素),灰度图(单通道),单一目标
1.5数据先处理
缩放尺寸:统一图片尺寸(该网络输入的要求)
归一化:把 0~255 像素,变成符合神经网络喜欢的标准数值分布,加速收敛、提升精度
python
from torchvision import transforms
transform = transforms.Compose([
# 缩放尺寸
transforms.Resize((32, 32)),
# 转张量,/255,均值方差(标准化)
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
2、Alexnet
2.1层级结构(8层,5卷积+3全连接)
输入(彩色图,224x224x3)
C1(卷积+Relu)
Pool1(最大池化,3x3核,stride=2)
归一化层(LRN[已淘汰])
+1层(卷.池.归一)
C3、4、5(卷积+ReLU)
Pool3
全连接1、2、3(全连接+ReLU+Dropout(1,2中))
输出(softmax)
2.2创新点
6000万参数
ReLU加速收敛
Dropout正则化,随机丢弃50%神经元
重叠池化,3x3核,步长2
数据增强(正则化)
-
随机裁剪:从 256×256 中随机裁剪 224×224
-
水平翻转:概率 0.5
-
PCA 颜色抖动:改变 RGB 通道强度(光照变化模拟)[对 RGB 三个通道做**主成分分析,**随机轻微扰动颜色强度,会破坏图像结构,只改光照]
python
class PCAColorJitter:
def __init__(self, alphastd=0.1):
self.alphastd = alphastd
# ImageNet 数据集预计算的 PCA 主成分与特征值(固定)
self.eigval = np.array([55.46, 4.29, 1.45])
self.eigvec = np.array([
[-0.5675, 0.7192, 0.4009],
[-0.5808, -0.0045, -0.8140],
[-0.5836, -0.6948, 0.4203]
])
def __call__(self, img):
img = np.array(img).astype(float)
alpha = np.random.normal(0, self.alphastd, size=3)
rgb = np.dot(self.eigvec, alpha * self.eigval)
img = img + rgb
img = np.clip(img, 0, 255).astype(np.uint8)
return img
train_transform = transforms.Compose([
# 第一步:先把图像缩放到 256×256
transforms.Resize((256, 256)),
# 第二步:随机裁剪 224×224
transforms.RandomCrop(224),
# 第三步:水平翻转 50% 概率
transforms.RandomHorizontalFlip(p=0.5),
# 第四步:PCA 颜色抖动(AlexNet 原版)
PCAColorJitter(alphastd=0.1),
# 常规操作
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)
])
LRN
双GPU训练
2.3训练细节
|------------|-------------------------------------|
| 优化器 | 动量 SGD(动量 0.9) |
| 学习率 | 初始 0.01,手动,除以 10 三次 |
| 权重初始化 | 高斯初始化(均值为 0,标准差 0.01) |
| 偏置初始化 | C2/4/5 和 全连接 偏置设为 1,其余 0 |
| L2 正则化 | 权重衰减 0.0005 |
| Dropout | 0.5 |
| Batch Size | 128 |
| 训练轮数 | 90(约 5-6 天) |
| 数据集 | ImageNet 2012(120 万训练,5 万验证,15 万测试) |
2.4局限
感受野限制:卷积核较大,计算量较高
梯度问题仍存在
容量还是较小
3、VGGnet
VGG16
3.1结构(16层)
(参数:1亿3千8百万)
输入(彩色图,224x224x3)
卷积+池化(13层,3x3卷积核)
全连接层(3层,最后一层softmax)
3.2特点
全3x3卷积+2x2池化
VGG特征提取器广泛用于分割,检测
3.3缺点
全连接层参数近90%,1亿
训练慢,参数多,层数深
全连接层冗余(后续研究证明可以去掉)
4、GoogLenet
4.1结构
(参数700万)
输入(彩色图,224x224x3)
卷积+池化
Inception1,2,3
池化(平均池化)
输出(全连接+Softmax)
4.2创新点
Inception模块(稀疏连接,多分支卷积)(计算量一定,网络深度,宽度增加)
同一层同时使用**多个不同大小卷积核,**让网络自己选择学习什么特征(提取不同程度特征)
(1x1卷积+ReLU)(增加非线性)
4.3特点
平均池化代替全连接层,参数减少
中间层加两个辅助分类器(浅层监督,缓解梯度消失)
5、Resnet
Resnet-152(残差网络)
(参数6000万)
输入彩色图(224x224)
5.1特点
残差连接(极大解决梯度问题)
- **短路连接:**1x1卷积,将梯度反传到浅层(直接到中间层)
- 瓶颈残差块(节省计算)
- 集成效应:不同子网络
- 残差块:卷积层+短路连接
- 下采样残差块:输出通道增加,尺寸减小1/2
6、Densenet
6.1结构:
密集块(Denseblock)+过渡层(降维,压缩因子0.5,通道减半)+密集块(密集连接)
6.2创新:
密集连接(拼接):通道数增加(每个层拼接前面的所有特征图作为输入)
6.3特点:
每层只学少量新特征
每层直接从损失函数接收梯度(多条短路路径;解决梯度问题)
浅层的边缘、纹理特征可以直接被深层利用,不会被冲淡
每个层都参与最终决策的贡献(隐式深度监督)
6.4缺点:
内存占用较高(需要缓存特征)
7、Mobilenet
7.1适用:
移动端,嵌入式
7.2创新:
深度可分离卷积
- 深度卷积(每个输入通道用独享的单通道卷积核滤波)(输入通道数=输出通道数)
- 逐点卷积(1x1卷积),M个通道映射到N个输出通道
第一层是标准卷积
7.3发展:
- mobilenet V1:
超参数:a(宽度乘数),p(分辨率乘数)[可调]
- mobilenet V2:
倒置残差块(1x1升维,3x3深度卷积,1x1降维)
去掉最后ReLU,线性瓶颈
- mobilenet V3:
神经架构搜索NAS,自动搜索最优结构
SE注意力模块
硬激活函数h-swish
7.4应用:
- 移动端图像分类(手机相册自动分类)
- 实时物体检测
- 语义分割
- 人脸识别Facenet
- 边缘计算
8、shufflenet
8.1适用场景:
计算资源极受限的移动设备
8.2特点:
- 逐点分组卷积
对1x1卷积进行分组(减少计算负担)
- 通道混洗
分组卷积后得到的通道数分为g组,每组n个通道;
组数,通道数转置;
扁平化(打通信息孤立)
9、ConvNext
模仿transformer架构
借鉴swin-Transformer,以Resnet-50为原本
9.1特点:
Patchify层(4x4,stride=4)
引入深层可分离卷积(加宽网络补偿容量,计算量下降)
7x7大卷积核
激活函数GELU(更平滑)
归一化层LN(LayerNorm)
减少归一化层,激活函数的数量
9.2介绍:
ConvNext V2
全局响应归一化层(GRN)
增强通道间特征竞争
自监督学习
循环神经网络RNN
1.LSTM(长短期记忆网络)
1.1简介:
处理长序列梯度消失,梯度爆炸问题
1.2核心:
细胞状态+3个门控结构
- 细胞状态:记忆主线,在上面的信息不易丢失(贯穿整个链)
- 门:sigmoid层+点乘操作(sigmoid输出0-1的值,1让过,0全部不让过)
遗忘门:决定丢弃信息
输入门:决定接受什么新信息
输出门:决定输出什么
1.3擅长领域:
LSTM特别擅长处理序列数据 ,比如:时间序列预测 、自然语言处理中的文本生成 与机器翻译 、语音识别 、视频分析
1.4变体:
双向LSTM:正向,反向捕捉上下文
多层LSTM:多层堆叠,逐层提取更多抽象特点
带窥视孔的LSTM:三个门可以看到细胞状态信息
2、GRU(门控循环单元)
更新门+重置门
1.决定过去多少信息保留到未来(值越大,旧信息越多)
2.如何将新输入与过去记忆结合(值越小,忽略信息)
没有独立细胞状态,直接将隐藏态在时间步之间传递(隐藏状态同时承载了"长期记忆"和"短期输出"的功能。)
收敛更快、更不易过拟合,尤其适合小数据集
3、Bi-LSTM/Bi-GRU
4、SRU
5、TCN
Transformer
自注意力机制
1、纯Encoder(理解)
BERT系列
2、纯Decoder(生成)
GPT
LLaMa
Bloom
3、编解码
T5
BART
PEGASUS
4、视觉CV
ViT
Swin Transformer
5、轻量化
MobileViT
三:分别适用领域
1、适用:图像
2、适用:序列(文字、时间、语音)
3、适用:超长文本、大图、多模态(现有大模型)
四:训练步骤
1、导入库
2、数据集准备
3、模型加载
4、优化器学习率配置
5、训练
6、评估
7、绘图
8、保存模型
数据集过少:小数据集,Lenet-5(举例)直接使用,导致过拟合(死记)(可容纳参数多)
五:经典复现
LeNet-5(MNIST手写数据集)
python
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
# ==================== 1. 定义 LeNet-5 网络结构 ====================
class LeNet5(nn.Module):
"""
LeNet-5 神经网络模型
输入: 32x32 灰度图像 (单通道)
输出: 10 个类别 (数字 0-9)
"""
def __init__(self, num_classes=10):
super(LeNet5, self).__init__()
# 卷积层部分: 特征提取
self.features = nn.Sequential(
# C1: 卷积层, 输入通道1, 输出通道6, 卷积核5x5
nn.Conv2d(1, 6, kernel_size=5, stride=1, padding=0), # 32->28
nn.ReLU(inplace=True),
# S2: 平均池化层, 2x2, 步长2
nn.AvgPool2d(kernel_size=2, stride=2), # 28->14
# C3: 卷积层, 输入6, 输出16, 卷积核5x5
nn.Conv2d(6, 16, kernel_size=5, stride=1, padding=0), # 14->10
nn.ReLU(inplace=True),
# S4: 平均池化层, 2x2, 步长2
nn.AvgPool2d(kernel_size=2, stride=2), # 10->5
)
# 全连接层部分: 分类
self.classifier = nn.Sequential(
# C5: 卷积层(作为全连接), 5x5x16 -> 120
nn.Flatten(),
nn.Linear(16 * 5 * 5, 120),
nn.ReLU(inplace=True),
# F6: 全连接层, 120 -> 84
nn.Linear(120, 84),
nn.ReLU(inplace=True),
# 输出层: 84 -> num_classes (10)
nn.Linear(84, num_classes),
)
def forward(self, x):
x = self.features(x)
x = self.classifier(x)
return x
# ==================== 2. 数据加载与预处理 ====================
def load_data(batch_size=64):
"""
加载 MNIST 数据集并应用必要的预处理
LeNet-5 要求输入为 32x32,而 MNIST 原始大小为 28x28
"""
# 数据预处理: 调整大小到 32x32, 转换为张量, 归一化
transform = transforms.Compose([
transforms.Resize((32, 32)), # 调整到 32x32
transforms.ToTensor(), # 转换为张量 [0,1]
transforms.Normalize((0.1307,), (0.3081,)) # MNIST 均值和标准差
])
# 下载并加载训练集
train_dataset = torchvision.datasets.MNIST(
root='./data',
train=True,
download=True,
transform=transform
)
# 下载并加载测试集
test_dataset = torchvision.datasets.MNIST(
root='./data',
train=False,
download=True,
transform=transform
)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
return train_loader, test_loader
# ==================== 3. 训练函数 ====================
def train(model, train_loader, criterion, optimizer, device, num_epochs=10):
"""训练模型"""
model.train()
for epoch in range(num_epochs):
running_loss = 0.0
correct = 0
total = 0
for i, (images, labels) in enumerate(train_loader):
images, labels = images.to(device), labels.to(device)
# 前向传播
outputs = model(images)
loss = criterion(outputs, labels)
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 统计
running_loss += loss.item()
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
if (i + 1) % 100 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], Step [{i+1}], '
f'Loss: {loss.item():.4f}, Acc: {100.*correct/total:.2f}%')
print(f'Epoch [{epoch+1}/{num_epochs}] 完成, '
f'平均 Loss: {running_loss/len(train_loader):.4f}, '
f'训练准确率: {100.*correct/total:.2f}%')
# ==================== 4. 测试函数 ====================
def test(model, test_loader, device):
"""测试模型"""
model.eval()
correct = 0
total = 0
with torch.no_grad():
for images, labels in test_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print(f'测试集准确率: {100. * correct / total:.2f}%')
return 100. * correct / total
# ==================== 5. 主函数 ====================
def main():
# 设置设备 (GPU 优先)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'使用设备: {device}')
# 超参数
batch_size = 64
learning_rate = 0.001
num_epochs = 10
# 加载数据
train_loader, test_loader = load_data(batch_size)
# 创建模型
model = LeNet5(num_classes=10).to(device)
print(model)
# 损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
# 训练
train(model, train_loader, criterion, optimizer, device, num_epochs)
# 测试
test_accuracy = test(model, test_loader, device)
# 保存模型
torch.save(model.state_dict(), 'lenet5_mnist.pth')
print("模型已保存为 lenet5_mnist.pth")
if __name__ == '__main__':
main()
cifar10
python
# 导入库
import torch
import numpy as np
from torch.utils.data import DataLoader # 数据加载器,负责批量加载和打乱数据
from transformers import ViTImageProcessor, ViTForImageClassification # HuggingFace 的 ViT 模型和图像预处理器
from datasets import load_dataset, load_metric # HuggingFace 的数据集加载和评估指标工具
from torch.optim import AdamW # AdamW 优化器,带解耦权重衰减的 Adam 变体
from torch.optim.lr_scheduler import CosineAnnealingLR # 余弦退火学习率调度器
from tqdm import tqdm # 进度条库,可视化训练循环的进度
import matplotlib.pyplot as plt # 绘图库,用于绘制损失/准确率曲线
# ==================== 2. 数据集准备 ====================
# 从 HuggingFace 云端下载并加载 CIFAR-10 数据集(5 万张训练图 + 1 万张测试图,共 10 个类别)
dataset = load_dataset("cifar10")
# 构建 标签名 → 数字ID 的映射字典(例如:"airplane" → 0, "automobile" → 1 ...)
label2id = {label: i for i, label in enumerate(dataset["train"].features["label"].names)}
# 构建 数字ID → 标签名 的反向映射(例如:0 → "airplane")
id2label = {i: label for label, i in label2id.items()}
# 加载预训练的 ViT 图像处理器(它知道如何将原始图片转为模型需要的张量格式)
# 这里从 HuggingFace 云端下载或从本地缓存读取
processor = ViTImageProcessor.from_pretrained("google/vit-base-patch16-224-in21k")
# 定义数据预处理函数,对每个批次的数据执行以下操作:
def transform(examples):
# 用 processor 批量处理图像:自动调整为 224×224、转为像素值并归一化
# return_tensors="pt" 表示返回 PyTorch 张量格式
inputs = processor(images=examples["img"], return_tensors="pt")
# 将原始标签也保留在返回字典中
inputs["labels"] = examples["label"]
return inputs
# 将 transform 函数"挂载"到数据集上
# 此后每次从数据集中取数据时,都会自动调用 transform 进行图像预处理
dataset = dataset.with_transform(transform)
# 拆分出训练集和测试集(CIFAR-10 已自带 train/test 划分,无需手动分验证集)
train_dataset = dataset["train"]
eval_dataset = dataset["test"]
# 创建训练数据加载器:每个批次 64 张图,训练时随机打乱顺序
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
# 创建验证数据加载器:验证时不需要打乱,只按顺序读取
eval_loader = DataLoader(eval_dataset, batch_size=64)
# ==================== 3. 模型加载与配置 ====================
# 从 HuggingFace 云端加载预训练的 ViT 模型
# "google/vit-base-patch16-224-in21k" 是 Google 在 ImageNet-21k(1400 万张图)上预训练的
# num_labels=10 表示把最后的分类头替换成适配 CIFAR-10 的 10 类分类器
# id2label、label2id 让模型内部能正确地做标签映射
# ignore_mismatched_sizes=True 允许分类头尺寸不匹配(从 21000 类换成 10 类)而不报错
model = ViTForImageClassification.from_pretrained(
"google/vit-base-patch16-224-in21k",
num_labels=10,
id2label=id2label,
label2id=label2id,
ignore_mismatched_sizes=True
)
# 冻结整个预训练骨干网络(ViT 编码器层)的参数
# requires_grad=False 意味着这些参数在反向传播时不会计算梯度、不会被优化器更新
# 这样就能保护大模型辛苦学来的"通用视觉潜意识"不被小数据集破坏
for param in model.vit.parameters():
param.requires_grad = False
# 自动检测是否可用 GPU,优先使用 CUDA,否则用 CPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 将模型的所有参数和张量搬到指定设备(GPU 或 CPU)上
model.to(device)
# ==================== 4. 优化器与学习率调度器配置 ====================
# 配置优化器:AdamW(带解耦权重衰减的 Adam,适合大模型微调)
# 分层学习率设置 ------ 不同层可以有不同的学习率:
optimizer = AdamW(
[
# 分类头是全新的(随机初始化),需要相对较大的学习率快速收敛
{"params": model.classifier.parameters(), "lr": 1e-3},
# 如果后续想解冻骨干的顶层,可以取消下面注释,并给极低学习率
# {"params": model.vit.encoder.layer[-2:].parameters(), "lr": 1e-5}
],
weight_decay=0.01, # 权重衰减系数,等价于 L2 正则化,防止过拟合
)
# 设置余弦退火学习率调度器
# T_max=5 表示在 5 个 epoch 内从初始学习率沿余弦曲线下降到 eta_min
# eta_min=1e-6 是调度器允许的最低学习率
scheduler = CosineAnnealingLR(optimizer, T_max=5, eta_min=1e-6)
# 总的训练轮数(小数据微调通常 3~10 个 epoch 就够,太长容易过拟合)
num_epochs = 5
# 加载准确率评估指标(来自 HuggingFace evaluate 库)
metric = load_metric("accuracy")
# 创建一个字典用来记录每个 epoch 的训练损失和验证准确率,方便最后画曲线
history = {"train_loss": [], "val_accuracy": []}
# ==================== 5. 训练与评估循环 ====================
# 外层循环:对整个数据集反复训练 num_epochs 轮
for epoch in range(num_epochs):
# ============ 5.1 训练阶段 ============
# 将模型设置为训练模式(会启用 Dropout、BatchNorm 等只在训练时起作用的结构)
model.train()
# 累加本 epoch 内所有批次的损失,用来计算平均损失
train_loss = 0.0
# 用 tqdm 包裹训练数据加载器,显示一个带进度的进度条
progress_bar = tqdm(train_loader, desc=f"Epoch {epoch+1}/{num_epochs}")
for batch in progress_bar:
# 从批次字典中取出预处理好的像素值张量,送到 GPU/CPU
pixel_values = batch["pixel_values"].to(device)
# 取出对应的真实标签,同样送到对应设备
labels = batch["labels"].to(device)
# 前向传播:模型接收像素值和标签,内部自动计算交叉熵损失
# outputs.loss 就是计算好的 loss 张量
outputs = model(pixel_values=pixel_values, labels=labels)
loss = outputs.loss
# 反向传播:计算损失相对于所有可训练参数(这里只有分类头)的梯度
loss.backward()
# 优化器根据刚刚算好的梯度,更新可训练参数
optimizer.step()
# 清空梯度缓存,为下一次反向传播做准备(PyTorch 默认会累积梯度,必须手动清零)
optimizer.zero_grad()
# 累加本次批次的损失值(.item() 是把单元素张量转成 Python 浮点数)
train_loss += loss.item()
# 在进度条右侧动态显示当前批次的损失值
progress_bar.set_postfix({"loss": f"{loss.item():.4f}"})
# 计算本 epoch 在整个训练集上的平均损失
avg_train_loss = train_loss / len(train_loader)
# 把本 epoch 的训练平均损失记录到 history 中,为最终画图做准备
history["train_loss"].append(avg_train_loss)
print(f"Epoch {epoch+1} - Average training loss: {avg_train_loss:.4f}")
# 一轮训练结束,学习率沿余弦曲线下降一步
scheduler.step()
# ============ 5.2 评估(验证)阶段 ============
# 将模型切换为评估模式(会关闭 Dropout 等只在训练时起效的机制)
model.eval()
# 分别用来收集所有批次的预测类别和真实标签
all_preds = []
all_labels = []
for batch in tqdm(eval_loader, desc="Evaluating"):
pixel_values = batch["pixel_values"].to(device)
labels = batch["labels"].to(device)
# torch.no_grad() 上下文管理器:在评估时禁用梯度计算,大幅节省显存和加速推理
with torch.no_grad():
# 前向传播,不传 labels 则只返回 logits(原始预测分数),不计 loss
outputs = model(pixel_values=pixel_values)
# 从模型输出中取 logits(形状:[batch_size, 10])
logits = outputs.logits
# argmax(dim=-1) 取每个样本预测分数最高的那个类别索引
preds = torch.argmax(logits, dim=-1)
# 把 GPU 上的张量搬到 CPU,再转成 numpy 数组,追加到列表中
all_preds.extend(preds.cpu().numpy())
all_labels.extend(labels.cpu().numpy())
# 用 HuggingFace 的 accuracy 指标,对比所有预测值和真实标签,计算整体准确率
accuracy = metric.compute(predictions=all_preds, references=all_labels)
val_acc = accuracy["accuracy"]
# 记录验证准确率,为最终画图做准备
history["val_accuracy"].append(val_acc)
print(f"Epoch {epoch+1} - Validation Accuracy: {val_acc:.4f}")
# 打印当前学习率(第一个参数组即分类头),可直观看到余弦退火的递减过程
print(f"当前学习率: {optimizer.param_groups[0]['lr']:.2e}")
print("Training finished.")
# ==================== 6. 绘制训练曲线 ====================
# 创建一张 12×4 英寸的画布
plt.figure(figsize=(12, 4))
# 左边子图:训练损失曲线
plt.subplot(1, 2, 1) # 1 行 2 列,激活第 1 个绘图区
plt.plot(range(1, num_epochs+1), history["train_loss"], marker="o", label="Train Loss") # 画折线图,每个点用圆圈标记
plt.xlabel("Epoch") # x 轴标签
plt.ylabel("Loss") # y 轴标签
plt.title("Training Loss Curve") # 图标题
plt.grid(True) # 显示背景网格
# 右边子图:验证准确率曲线
plt.subplot(1, 2, 2) # 1 行 2 列,激活第 2 个绘图区
plt.plot(range(1, num_epochs+1), history["val_accuracy"], marker="o", color="orange", label="Val Accuracy")
plt.xlabel("Epoch")
plt.ylabel("Accuracy")
plt.title("Validation Accuracy Curve")
plt.grid(True)
# 自动调整子图之间的间距,防止标签重叠
plt.tight_layout()
# 将画好的图保存到当前目录下的 "training_curve.png" 文件
plt.savefig("training_curve.png")
# 在屏幕上显示图片
plt.show()
# ==================== 7. 保存最终模型 ====================
# 将微调后的整个模型(配置 + 权重)保存到 "./cifar10_vit_finetuned" 文件夹
model.save_pretrained("./cifar10_vit_finetuned")
# 将特征提取处理器也保存到同一文件夹(后续推理时需要用它做同样的预处理)
processor.save_pretrained("./cifar10_vit_finetuned")