神经网络解析

一:难点知识

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")
相关推荐
TMT星球15 分钟前
虎鲸文娱春苗编剧计划全面升级,已孵化20个剧本进入前期开发
大数据·人工智能
奔跑中的小象7 小时前
统信UOS + 天数AI卡部署SGLang服务手册
人工智能·uos·sglang·天数智芯
DevSecOps选型指南7 小时前
中国版Mythos,为何是悬镜安全灵脉CodeAI?
人工智能·安全
Shockang7 小时前
LangGraph 状态机实战
人工智能
刹那芳华19927 小时前
循环神经网络的从零开始实现(RNN)
人工智能·rnn·深度学习
阿童木写作7 小时前
跨境图片翻译工具多合一,批量图片视频字幕翻译加智能抠图
人工智能·python·音视频·语音识别
科技之门7 小时前
AI3D从建模到贴图、绑骨和动画的完整流程怎么做?V2Fun完整工作流指南
人工智能·3d·贴图
宇宙第一小趴菜7 小时前
二、机器学习的应用领域和发展史
人工智能·机器学习
前端开发江鸟7 小时前
我写过 MCP Server,却一直以为 MCP 只有 Tool
人工智能
天国梦8 小时前
自习室智能化升级避坑指南:天学网AI智习室方案实测与选型建议
大数据·人工智能