神经网络解析

一:难点知识

1、要点知识

通道数:卷积核提取图像某方面的特征,作为1个通道

卷积层:提取特征

全连接层:分类

nn.flatten():降成1维的向量(适合全连接层读取)

kernel_size:卷积核大小

学习率:步长大小,每次走多远

偏置:b(告诉网络有偏置即可,初始化为0,自动学习)(卷积核的)

权重:w(正态分布,随机初始化,部分为0)

x:图片具体的每个像素值

激活函数:sigmoid(二分类),softmax(多分类),回归(正值)(ReLU),回归(-1,1)(tanh)

ReLU:取最大值

padding:填充

通道数=卷积核数

输出尺寸=[输入尺寸(单数字)- 卷积核大小(单数字)+(2x填充)]/步长

前向传播-计算loss-反向传播

2、前向传播

  1. 最后输出层的值logits(打分)(预测值)
  2. 打分转概率
  3. 计算损失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创新:

深度可分离卷积

  1. 深度卷积(每个输入通道用独享的单通道卷积核滤波)(输入通道数=输出通道数)
  2. 逐点卷积(1x1卷积),M个通道映射到N个输出通道

第一层是标准卷积

7.3发展:
  1. mobilenet V1:

超参数:a(宽度乘数),p(分辨率乘数)[可调]

  1. mobilenet V2:

倒置残差块(1x1升维,3x3深度卷积,1x1降维)

去掉最后ReLU,线性瓶颈

  1. mobilenet V3:

神经架构搜索NAS,自动搜索最优结构

SE注意力模块

硬激活函数h-swish

7.4应用:
  1. 移动端图像分类(手机相册自动分类)
  2. 实时物体检测
  3. 语义分割
  4. 人脸识别Facenet
  5. 边缘计算

8、shufflenet

8.1适用场景:

计算资源极受限的移动设备

8.2特点:
  1. 逐点分组卷积

对1x1卷积进行分组(减少计算负担)

  1. 通道混洗

分组卷积后得到的通道数分为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个门控结构

  1. 细胞状态:记忆主线,在上面的信息不易丢失(贯穿整个链)
  2. 门: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")
相关推荐
清水白石0081 小时前
从“能装上”到“可复现”:Python 团队如何正确使用 requirements.txt、锁定文件与依赖分组
开发语言·人工智能·python
Agent产品评测局1 小时前
传统RPAvsAI Agent,制造业生产场景能力对比详解 —— 2026智能制造自动化选型全景盘点
人工智能·ai·chatgpt·自动化·制造
元智启1 小时前
企业AI如何开发:从“野生智能体”到“平台化治理”
大数据·人工智能
godspeed_lucip1 小时前
LLM和Agent——专题2: LLM as Judge 入门(2)
人工智能·python
沪漂阿龙1 小时前
面试题:激活函数是什么?为什么必须非线性,Sigmoid、ReLU、Softmax 怎么选,一文讲透深度学习高频考点
人工智能·深度学习
沪漂阿龙1 小时前
AI大模型面试题:模型求解和优化全解析——梯度下降、BGD、SGD、MBGD、学习率、Batch Size、损失函数、优化器一文讲透
人工智能·学习·机器学习
科技AI训练师1 小时前
B2B行业AI搜索优化卓越案例:GEO特工队助力芯片推荐率突破75%
人工智能·搜索引擎·百度
老王谈企服1 小时前
实在Agent智能体视频生成节点实战:多模型调度、Jinja模板与动态参数,打造自动化视频生产线
人工智能·自动化·音视频
XD7429716361 小时前
科技晚报|2026年5月12日:Claude 进 AWS,AI 落地拼控制面
人工智能·科技·aws·科技新闻·科技晚报