news 2026/11/2 7:00:13

VGG19迁移学习图像分类:数据管线、冻结微调与错分分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
VGG19迁移学习图像分类:数据管线、冻结微调与错分分析

简介:面向人工智能与深度学习课程学习者及智能计算系统实验实践者,这份资料围绕 VGG19 图像分类任务,提供从模型加载、图像预处理到前向推理与准确率评估的完整实验支撑,适合希望理解卷积神经网络结构、INT8 量化与异构硬件部署的中级学习者。压缩包共 4 个文件,约 95.86MB,由 3 个 Python 评估脚本和 1 个 INT8 量化模型文件组成:脚本分别对应 CPU 与 MLU 平台的推理评估,模型文件可直接加载用于分类实验,便于对照两种硬件环境的运行表现。已有 2154 人学习下载。通过包内材料,读者可复现 VGG19 的推理流程,观察卷积、池化、全连接与 Softmax 各环节的输出,并对比 CPU 与硬件加速器在速度和能效上的差异,进而掌握模型轻量化与部署优化的基本思路。

1. 数据先行的 VGG19 图像分类:别急着把预训练模型塞进 DataLoader

带新人做这个智能计算实验时,最常见的翻车点不是 VGG19 有多深,而是数据目录还没理顺就直接model.fit。这套基于 VGG19 的图像分类实验,核心不是从零训练 19 层卷积,而是借预训练特征做迁移学习:把大规模视觉任务里学到的边缘、纹理、局部形状迁移到自己的类别上。它适合刚接触人工智能实验、需要一份能跑通的训练评估闭环的人,也适合想检查自己迁移学习参数是否合理的熟手。先把数据划分、输入尺寸、归一化这三件事做对,后面调分类头和冻结层才有意义。

2. 数据管线和预处理:把 ImageFolder 拆成可复现的 train/val/test

2.1 先分层划分,再谈 random_split

ImageFolder读目录很方便,但直接把整个目录丢给random_split有两个隐患:类别不平衡时验证集可能缺类,同一张图的不同副本也可能跨集合泄漏。我一般先扫描目录统计每类数量,再按类别分层切 train/val/test,并把划分结果写成固定清单。这样每次实验读的是同一份清单,调参时不会出现“换了个随机种子指标就漂”的玄学问题。

下面脚本只做两件事:统计每个类别文件数,按 8:1:1 分层划分并写出 csv。路径、随机种子、比例都放在参数区,换数据集只改这里。

import csv, random from pathlib import Path from collections import defaultdict data_root = Path("data/raw") # 原始目录:data/raw/类别名/图片 out_csv = Path("data/split.csv") # 划分清单 seed = 42 ratios = (0.8, 0.1, 0.1) # train/val/test random.seed(seed) by_cls = defaultdict(list) for p in data_root.rglob("*"): if p.suffix.lower() in {".jpg", ".jpeg", ".png", ".bmp"}: by_cls[p.parent.name].append(p) rows = [] for cls, files in by_cls.items(): random.shuffle(files) n = len(files) n_train = int(n * ratios[0]) n_val = int(n * ratios[1]) for i, p in enumerate(files): split = "train" if i < n_train else ("val" if i < n_train + n_val else "test") rows.append([str(p), cls, split]) out_csv.parent.mkdir(parents=True, exist_ok=True) with out_csv.open("w", newline="", encoding="utf-8") as f: csv.writer(f).writerows(rows) print(f"images={len(rows)}, classes={len(by_cls)}")

逻辑说明:先按类别聚合文件,再在类内打乱,保证每个类别都按比例进入三个集合。ratios是每类内部的切分比例,不是全量比例;类别极少时int()可能把 val 切成 0,需要把ratios调成 0.7/0.15/0.15 或对每类保底 1 张验证图。seed必须固定,否则同一份数据每次划分不同,指标无法横向比较。

参数建议值说明
输入尺寸224×224VGG19 分类头按 7×7×512 接全连接,尺寸改太大显存涨得快
训练裁剪RandomResizedCrop(224, scale=(0.7, 1.0))保留主体,又带一点尺度扰动
验证裁剪Resize(256) + CenterCrop(224)验证阶段不做随机增强,保证可复现
归一化ImageNet mean/std预训练权重按这套统计量训练,换掉容易掉点
batch size16 或 32显存不够先降 batch,再考虑冻结更多层

2.2 transform 的训练/验证差异要写死

VGG19 的输入是 224×224,预训练权重配套的归一化是mean=[0.485,0.456,0.406]、std=[0.229,0.224,0.225]。训练侧可以做随机裁剪、水平翻转和轻量颜色扰动;验证侧只做 resize、中心裁剪和归一化。很多人验证集指标虚高,不是模型强,而是验证集误用了训练增强,同一张图被随机裁到不同区域,评估结果自然不可比。

from torchvision import transforms train_tf = transforms.Compose([ transforms.RandomResizedCrop(224, scale=(0.7, 1.0)), transforms.RandomHorizontalFlip(p=0.5), transforms.ColorJitter(brightness=0.15, contrast=0.15, saturation=0.1), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) val_tf = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ])

逻辑说明:RandomResizedCrop的scale下限不要压得太低,0.5 以下会把主体裁掉,小数据集上反而让损失震荡。ColorJitter只做轻微扰动,医疗、工业缺陷这类颜色敏感任务可以关掉饱和度项。Resize(256)再CenterCrop(224)是常见做法,比直接Resize((224,224))更接近预训练时的构图。

2.3 DataLoader 的 batch、worker 和均衡采样

数据清单有了,下一步是把 csv 读成 Dataset。这里不建议直接ImageFolder加random_split,因为清单已经固定,用自定义 Dataset 读 csv 更稳。num_workers不是越大越好,Windows 或容器里开到 8 有时会在第一个 epoch 卡住,先从 2 或 4 试。pin_memory=True在 GPU 训练时能减少拷贝等待,CPU 训练就不用开。

import pandas as pd from PIL import Image from torch.utils.data import Dataset, DataLoader class CsvImageDataset(Dataset): def __init__(self, csv_path, split, transform, classes=None): df = pd.read_csv(csv_path) self.df = df[df["split"] == split].reset_index(drop=True) self.transform = transform self.classes = classes or sorted(self.df["label"].unique()) self.cls_to_idx = {c: i for i, c in enumerate(self.classes)} def __len__(self): return len(self.df) def __getitem__(self, idx): row = self.df.iloc[idx] img = Image.open(row["path"]).convert("RGB") return self.transform(img), self.cls_to_idx[row["label"]] train_ds = CsvImageDataset("data/split.csv", "train", train_tf) val_ds = CsvImageDataset("data/split.csv", "val", val_tf, train_ds.classes) train_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4, pin_memory=True, drop_last=True) val_loader = DataLoader(val_ds, batch_size=32, shuffle=False, num_workers=4, pin_memory=True)

逻辑说明:classes从训练集固定后传给验证集,避免验证集出现训练集没有的类别导致索引错位;drop_last=True只在训练集开,防止最后一个 batch 只有 1 张图时 BatchNorm 统计量抖动。convert("RGB")必须加,灰度图或带 alpha 通道的 png 直接进模型会在卷积层报通道数不匹配。类别明显不平衡时,可以给WeightedRandomSampler按类频倒数配权重,但验证集仍然保持自然分布,不要跟着重采样。

2.4 训练前先看一个 batch

正式跑训练循环前,我习惯先取一个 batch 检查三件事:张量形状是不是[B,3,224,224]、标签范围是不是在[0,num_classes)、归一化后的像素均值是否大致在 0 附近。这个检查花不了一分钟,却能提前拦住“通道顺序反了”“标签从 1 开始”“图片全是黑图”这类低级错误。

import torch imgs, labels = next(iter(train_loader)) print(imgs.shape, imgs.dtype, imgs.min().item(), imgs.max().item()) print(labels[:8], labels.min().item(), labels.max().item()) assert imgs.shape[1:] == (3, 224, 224) assert labels.min() >= 0 and labels.max() < len(train_ds.classes)

逻辑说明:imgs.min()/max()在归一化后通常是负数到正数区间,如果仍然是 0 到 1,说明Normalize没生效;如果全是同一个值,检查图片路径是否读错。标签断言能挡住 csv 里混入空标签或类别名带空格的问题。把这些检查写进脚本,比后面对着损失曲线猜要省时间。

3. VGG19 改造与训练:冻结卷积层、换分类头、分组学习率

3.1 看清 features 与 classifier:25088 维从哪来

torchvision 里的 VGG19 分成features和classifier两段。输入 224×224 经过五段卷积和池化后,features输出[B,512,7,7],展平就是 25088 维,正好接原分类头的第一个全连接层。原分类头是 25088→4096→4096→1000,参数量很大,小数据集上直接全量微调容易过拟合,也会把显存吃满。常见做法是冻结features的前几段,只训练后面的卷积块和新分类头,让模型先学会“用预训练特征分类”,再决定要不要解冻。

import torch.nn as nn from torchvision import models def build_vgg19(num_classes, dropout=0.5, pretrained=True): weights = models.VGG19_Weights.DEFAULT if pretrained else None model = models.vgg19(weights=weights) in_features = model.classifier[0].in_features # 25088 model.classifier = nn.Sequential( nn.Flatten(), nn.Linear(in_features, 512), nn.ReLU(inplace=True), nn.Dropout(dropout), nn.Linear(512, num_classes), ) return model

逻辑说明:in_features直接从原分类头读,避免手写 25088 写错;新分类头用 512 维瓶颈,比直接 25088→类别数多一层非线性,小数据集上更稳。dropout=0.5是 VGG 原配置的常见值,数据量很小可以调到 0.6,数据量较大可以降到 0.3。weights="DEFAULT"会下载预训练权重,离线环境需要提前把权重缓存到本地,否则第一次运行会卡在下载。

3.2 冻结策略:先冻到 block4,再看验证集

冻结不是越少越好。VGG19 的浅层学边缘和颜色,中层学纹理,深层才更偏语义。小数据集上我一般先冻结features[:24]左右,保留最后一段卷积和分类头可训练;中等数据量可以冻到 block3,留 block4、block5 和分类头。判断标准看验证集:如果训练集准确率很快到 95% 而验证集不动,说明可训练参数太多;如果两边都低,可能是学习率太小或数据增强太狠。

model = build_vgg19(num_classes=10) for p in model.features.parameters(): p.requires_grad = False for p in model.features[-4:].parameters(): # 只放开最后一段卷积 p.requires_grad = True trainable = sum(p.numel() for p in model.parameters() if p.requires_grad) total = sum(p.numel() for p in model.parameters()) print(f"trainable={trainable/1e6:.2f}M / total={total/1e6:.2f}M")

逻辑说明:features[-4:]对应最后一段卷积附近的层,具体边界可以用print(model.features)看索引,不要盲抄。先冻结大部分卷积只训练分类头,通常 3 到 5 个 epoch 就能看出方向;验证集 F1 还在涨再解冻后段卷积。BatchNorm 层在冻结时仍会跑滑动统计,如果 batch 很小,建议把冻结部分的 BN 设成eval(),避免统计量被小 batch 带偏。

策略可训练部分适用场景风险
只训分类头classifier数据少、快速验证管线特征不匹配时上限低
冻到 block4block5 + classifier小到中等数据量解冻时机要试
全量微调全部参数数据量接近预训练规模显存高、易过拟合

3.3 分组学习率和训练循环

卷积层用的是预训练权重,学习率要小;新分类头是随机初始化,学习率可以大一个量级。我一般给features设1e-4到3e-5,给classifier设1e-3到3e-4,优化器用 AdamW 或 SGD 加动量都行。训练循环里加混合精度和梯度裁剪,VGG19 参数量大,AMP 能明显降显存;梯度裁剪防个别 batch 梯度爆炸。

import torch from torch.optim import AdamW from torch.optim.lr_scheduler import CosineAnnealingLR device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = model.to(device) criterion = nn.CrossEntropyLoss() optimizer = AdamW([ {"params": model.features.parameters(), "lr": 1e-4}, {"params": model.classifier.parameters(), "lr": 1e-3}, ], weight_decay=1e-4) scheduler = CosineAnnealingLR(optimizer, T_max=20) scaler = torch.cuda.amp.GradScaler(enabled=torch.cuda.is_available()) for epoch in range(20): model.train() for imgs, labels in train_loader: imgs, labels = imgs.to(device), labels.to(device) optimizer.zero_grad(set_to_none=True) with torch.cuda.amp.autocast(enabled=torch.cuda.is_available()): logits = model(imgs) loss = criterion(logits, labels) scaler.scale(loss).backward() scaler.unscale_(optimizer) torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0) scaler.step(optimizer) scaler.update() scheduler.step()

逻辑说明:AdamW的weight_decay=1e-4作用在参数组上,分类头和新卷积层共享这个衰减;如果解冻后掉点,可以把卷积组的 weight decay 调小。T_max=20要和总 epoch 对齐,改 epoch 时别忘了改这里,否则学习率曲线会在训练中途提前归零。clip_grad_norm_的max_norm=5.0是常见起点,出现持续大于 10 的梯度范数再往下调。AMP 只在 CUDA 下开,CPU 训练保持enabled=False。

3.4 第二阶段微调:解冻后段卷积要降学习率

分类头训练几个 epoch 后,如果验证集指标还在涨,可以解冻最后两段卷积做第二阶段微调。此时卷积层学习率要比第一阶段更低,通常降到1e-5到3e-6,分类头也降一个量级。解冻后先跑 1 个 epoch 看验证集,如果掉了就回退,不要一口气解冻全部层。VGG19 的 BN 层对 batch size 敏感,第二阶段 batch 太小会让统计量跳变,显存允许时尽量保持 32。

for p in model.features[-8:].parameters(): p.requires_grad = True for g in optimizer.param_groups: g["lr"] *= 0.1

逻辑说明:features[-8:]是示例边界,实际按print(model.features)的索引调整;解冻后优化器参数组要同步降学习率,否则新解冻的卷积层会用第一阶段的大学习率把预训练特征打乱。第二阶段 epoch 不用多,3 到 5 个足够,配合早停看验证集 macro-F1。

4. 评估与错分分析:别只看 accuracy,先把混淆矩阵和置信度拆开

4.1 评估循环要同时留 logits 和标签

验证集评估不要只算一个 accuracy。类别不平衡时 accuracy 会被多数类带偏,至少同时看 macro-F1 和每类 recall。评估循环把 logits、预测、标签、图片路径一起收起来,后面做混淆矩阵和错分样本落盘都用得上。注意评估时固定model.eval()和torch.no_grad(),否则 dropout 和 BN 还在训练模式,指标会抖。

import numpy as np from sklearn.metrics import accuracy_score, f1_score, confusion_matrix def evaluate(model, loader, device): model.eval() all_logits, all_labels, all_paths = [], [], [] with torch.no_grad(): for imgs, labels in loader: logits = model(imgs.to(device)) all_logits.append(logits.cpu()) all_labels.append(labels) logits = torch.cat(all_logits) labels = torch.cat(all_labels).numpy() preds = logits.argmax(1).numpy() probs = torch.softmax(logits, dim=1).numpy() return { "acc": accuracy_score(labels, preds), "macro_f1": f1_score(labels, preds, average="macro"), "cm": confusion_matrix(labels, preds), "labels": labels, "preds": preds, "probs": probs, }

逻辑说明:torch.softmax只在需要置信度时算,训练循环里不需要额外 softmax,CrossEntropyLoss 内部已经带 log-softmax。macro_f1对每类一视同仁,小类多时比 accuracy 更能反映问题。confusion_matrix的标签顺序要和train_ds.classes对齐,画图前把类别名按同一顺序传入,否则矩阵行列会错位。

4.2 混淆矩阵和错分样本要落盘

混淆矩阵不落盘,调参时只能凭记忆对比。我一般把矩阵存 csv,同时把置信度最高的错分样本复制到badcases/真实类_预测类/下,文件名带上置信度。这样下一轮看错分图时不用再跑一遍推理,也能快速判断是标注问题、相似类混淆,还是预处理把主体裁掉了。

import pandas as pd, shutil from pathlib import Path res = evaluate(model, val_loader, device) cm_df = pd.DataFrame(res["cm"], index=train_ds.classes, columns=train_ds.classes) cm_df.to_csv("outputs/confusion_matrix.csv") bad_root = Path("outputs/badcases") bad_root.mkdir(parents=True, exist_ok=True) val_df = pd.read_csv("data/split.csv") val_df = val_df[val_df["split"] == "val"].reset_index(drop=True) for i, (y, p) in enumerate(zip(res["labels"], res["preds"])): if y != p: conf = res["probs"][i, p] src = Path(val_df.iloc[i]["path"]) dst_dir = bad_root / f"{train_ds.classes[y]}_to_{train_ds.classes[p]}" dst_dir.mkdir(parents=True, exist_ok=True) shutil.copy(src, dst_dir / f"{conf:.3f}_{src.name}")

逻辑说明:val_df的顺序必须和val_loader完全一致,所以验证集shuffle=False,且 csv 读取后不要再次打乱。conf是预测类的 softmax 概率,文件名前缀方便按置信度排序。错分样本目录按“真实类_to_预测类”命名,类多时目录会多,但排查相似类非常直观。

4.3 高置信错分比低置信错分更值得看

验证集里有一类错分很值得警惕:模型给出 0.95 以上的置信度,但标签错了。这种样本要么是标注噪声,要么是训练集和验证集分布不一致,要么是类别定义本身有重叠。低置信错分反而正常,模型在边界样本上犹豫是合理的。把置信度分桶看一遍,比盯着总 accuracy 更能找到数据问题。

import numpy as np wrong = res["preds"] != res["labels"] conf = res["probs"].max(axis=1) for lo, hi in [(0.5, 0.7), (0.7, 0.9), (0.9, 1.01)]: mask = wrong & (conf >= lo) & (conf < hi) print(f"conf[{lo},{hi}) wrong={mask.sum()}, ratio={mask.sum()/max(wrong.sum(),1):.2%}")

逻辑说明:conf取每张图的最大 softmax 概率,只统计错分样本的置信度分布。高置信错分占比高时,先复查这些图的标签和原图,不要急着调模型。如果标签没问题,再考虑类别定义是否太细,或者验证集里混入了训练集近重复图。

4.4 验证集虚高的三个来源

验证集指标异常高时,先查三件事:训练集和验证集是否有重复图或近重复图;验证集 transform 是否误用了训练增强;早停是否在验证集上反复挑 epoch 导致过拟合。重复图可以用文件哈希快速查,近重复图可以看感知哈希,实验阶段至少做一次文件名和哈希去重。早停可以保留,但最终报告要用独立 test 集,不要拿 val 反复调完再当最终指标。

import hashlib from pathlib import Path seen = {} for p in Path("data/raw").rglob("*"): if p.suffix.lower() in {".jpg", ".jpeg", ".png"}: h = hashlib.md5(p.read_bytes()).hexdigest() if h in seen: print("duplicate:", p, seen[h]) seen[h] = p

逻辑说明:MD5 只能抓完全重复文件,裁剪、压缩过的近重复图抓不到;实验阶段先用它排掉最明显的泄漏。更稳的做法是把训练集和验证集的图片都过一遍感知哈希,距离小于阈值的配对人工抽查。验证集 transform 单独写一个变量,不要和训练 transform 复用同一个Compose。

5. 常见问题与排查:损失不降、显存炸、验证集虚高、保存加载不一致

5.1 损失不降:先看标签和归一化,再看学习率

现象:训练 loss 前几个 epoch 几乎是一条直线,准确率停在多数类比例附近。原因通常不是 VGG19 不够强,而是标签索引错位、归一化没生效、分类头输入维度不对,或者学习率小到梯度更新可忽略。先打印一个 batch 的图片均值、标签范围和模型输出 logits 的均值,确认输入不是全零、标签不是全 1、logits 不是常数。解决顺序是:查classes映射是否训练验证一致,查Normalize是否在ToTensor之后,查分类头in_features是否等于512*7*7。如果这些都正常,把分类头学习率调到1e-3跑 20 个 step,看 loss 是否动;不动再查梯度,named_parameters()里找requires_grad=True但grad is None的层。

5.2 显存炸:先降 batch,再冻结,最后才考虑改模型

现象:第一个 epoch 还能跑,第二个 epoch 或验证阶段报 CUDA out of memory。原因可能是 batch 太大、验证阶段没加torch.no_grad()、中间变量没释放,或者 DataLoader 的pin_memory在内存紧张时反而添乱。解决顺序:把 batch 从 32 降到 16 或 8,验证循环加with torch.no_grad(),训练循环里optimizer.zero_grad(set_to_none=True),不再保留上一个 batch 的计算图。还不行就冻结更多features层,减少可训练参数和激活缓存。VGG19 的浅层激活很大,输入从 224 提到 320 会让显存明显上涨,实验阶段不要随手改输入尺寸。

5.3 验证集虚高:查重复图、查 transform、查早停

现象:验证集 accuracy 到 0.98,换 test 集掉到 0.75。原因常见的有三种:训练集和验证集存在重复或近重复图;验证集误用了RandomResizedCrop等训练增强;调参时反复看验证集挑 epoch,等价于在验证集上训练。解决:先跑文件哈希去重,再人工抽查高置信错分和验证集前 32 张图;把验证 transform 单独定义,确保只有 resize、center crop、normalize;最终指标用独立 test 集,验证集只用于早停和选模。类别少时,验证集每类至少留 20 到 30 张,否则指标波动很大。

5.4 保存加载不一致:类别顺序和预处理要一起存

现象:训练时验证集 0.9,重新加载state_dict后预测全乱。原因通常是保存模型时没存classes顺序,加载时用sorted(目录名)重新生成,顺序和训练时不一致;或者推理脚本用了不同的归一化参数。解决:保存 checkpoint 时把classes、输入尺寸、mean/std、模型构造参数一起写进字典;加载时先构造模型再load_state_dict,不要只存model对象。推理脚本的 transform 直接从 checkpoint 里读参数,别手写第二套。

ckpt = { "model": model.state_dict(), "classes": train_ds.classes, "input_size": 224, "mean": [0.485, 0.456, 0.406], "std": [0.229, 0.224, 0.225], } torch.save(ckpt, "outputs/vgg19_best.pt")

逻辑说明:classes必须和训练时CsvImageDataset的classes完全一致,推理时用ckpt["classes"][pred_idx]还原类别名。input_size和归一化参数一起存,避免换机器后靠记忆复现。保存state_dict比保存整个模型更稳,加载时模型结构由代码控制,不会受 pickle 路径影响。

5.5 固定排查顺序,别同时改三个参数

出现指标异常时,我一般按“数据→模型→优化”的顺序排查:先看一个 batch 的图片和标签,再看模型输出形状和 logits 范围,最后才动学习率和冻结层。一次只改一个变量,改完记录到实验表里。VGG19 实验里最容易犯的错是同时换 batch、改学习率、解冻层,最后指标涨了也不知道是哪一项起作用。把随机种子、数据划分、transform 版本、模型配置写进同一个 yaml 或 json,复现时直接读配置,比翻聊天记录可靠。

6. 进阶技巧:用特征缓存和分层学习率把实验跑稳

数据量不大、又反复调分类头时,可以把冻结卷积层的输出提前算好缓存起来。VGG19 的features不参与训练时,同一张图每次前向结果一样,缓存[N,25088]特征后,分类头训练从“每步跑 19 层卷积”变成“每步跑两层全连接”,调参速度快很多。做法是先model.features.eval(),用torch.no_grad()遍历训练集和验证集,把特征和标签存成.pt;训练分类头时只加载特征,学习率可以放大胆一点。注意缓存只对冻结阶段有效,一旦解冻卷积层就必须重新算特征,否则缓存和当前权重不一致,指标会骗人。

def cache_features(model, loader, device, path): model.eval() feats, labels = [], [] with torch.no_grad(): for imgs, y in loader: f = model.features(imgs.to(device)).flatten(1) feats.append(f.cpu()) labels.append(y) torch.save({"feat": torch.cat(feats), "label": torch.cat(labels)}, path) cache_features(model, train_loader, device, "outputs/train_feat.pt") cache_features(model, val_loader, device, "outputs/val_feat.pt")

逻辑说明:flatten(1)把[B,512,7,7]压成[B,25088],和分类头输入对齐;缓存文件按 train/val 分开存,标签顺序跟各自 loader 一致。缓存前确认features全部冻结且处于eval(),否则 BN 统计量会变。分类头训练时把optimizer的参数组只留classifier,学习率可以设1e-3到3e-3,配合 cosine 调度跑 30 到 50 个 epoch,通常比端到端快一个数量级。

分层学习率还有一个实用变体:解冻后按层深给不同学习率,浅层最小,深层稍大,分类头最大。VGG19 的features可以按索引切三段,分别放进optimizer.param_groups,每段乘 0.3 的衰减。这个做法在数据量中等、又想榨一点精度时比较稳。验证时不要只看 best accuracy,固定看 macro-F1 和每类 recall,再抽查 20 张高置信错分图。我吃过一次亏:缓存特征后忘了重新算验证集,端到端解冻后指标还拿着旧缓存对比,白调了一下午;从那以后每次切换冻结/解冻状态,我都强制先跑一遍特征版本校验,再开始训练。需要这套实验包的话,从文末入口取,先跑通数据管线和评估脚本,再动模型参数。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/2 6:59:57

流式计算技术解析:从原理到Flink/Kafka实战

1. 流式计算技术概述流式计算是一种实时处理无界数据流的技术范式。与传统的批处理模式不同&#xff0c;流式系统在数据产生时就立即进行处理&#xff0c;典型延迟在毫秒到秒级。这种技术最早可以追溯到1990年代的金融交易系统&#xff0c;但直到2010年后随着Apache Storm、Fli…

作者头像 李华
网站建设 2026/11/2 6:59:32

智能理论目录:AI知识体系的结构化导航

1. 智能的理论《目录》项目概述第一次看到"智能的理论《目录》"这个标题时&#xff0c;我脑海中浮现的是一个系统化整理智能相关理论的框架体系。这让我想起十年前刚开始研究人工智能时&#xff0c;面对海量理论却无从下手的困境。当时如果有这样一个结构化的知识目录…

作者头像 李华
网站建设 2026/11/2 6:59:21

奇安信IPS透明模式部署与配置指南

1. 奇安信IPS入侵防御系统概述奇安信网神入侵防御系统&#xff08;IPS&#xff09;是一款企业级网络安全防护产品&#xff0c;采用在线部署方式&#xff0c;能够实时检测和阻断各类网络攻击行为。作为网络安全防护体系中的关键一环&#xff0c;IPS系统通过深度内容检测、协议分…

作者头像 李华
网站建设 2026/11/2 6:59:09

信息检索技术大赛:跨模态与小样本学习的实战解析

1. 检索大赛的行业背景与价值定位信息检索技术作为数字时代的核心基础设施&#xff0c;其发展水平直接决定了信息获取效率和质量。当前主流搜索引擎虽然已经实现基础检索功能&#xff0c;但在垂直领域精准度、长尾需求覆盖、多模态内容理解等方面仍存在明显短板。检索大赛的举办…

作者头像 李华
网站建设 2026/11/2 6:58:27

国产科研检测仪器技术瓶颈与突围路径分析

1. 项目概述&#xff1a;民用科研检测仪器的现状与挑战在实验室工作十几年&#xff0c;我见过太多科研人员对着进口检测仪器又爱又恨的眼神。我们能把火箭送上太空&#xff0c;能造出世界领先的核电站&#xff0c;却在实验室里离不开那些贴着外国商标的检测设备。这不是个例&am…

作者头像 李华
网站建设 2026/11/2 6:58:21

DC-3靶机渗透测试:Joomla漏洞利用与防御实战

1. DC-3靶机渗透测试实战指南作为网络安全领域的经典训练环境&#xff0c;DC-3靶机为安全从业者提供了绝佳的学习平台。这款基于Joomla CMS构建的靶机系统&#xff0c;完美复现了真实世界中的Web应用漏洞场景。1.1 环境准备与基础侦查首先需要搭建测试环境。推荐使用VMware Wor…

作者头像 李华