简介:面向深度学习与计算机视觉进阶开发者,提供一份基于DilateFormer的完整图像分类实战资源。模型采用多尺度扩张注意力与滑动窗口扩张注意力,加上金字塔架构设计,在植物幼苗分类任务中达到89%以上的准确率,资源围绕DilateFormer的tiny版本展开,可直接用于复现实验或迁移到其他分类场景。压缩包共2000个文件,以1987个png图像为主,用于训练、验证与结果展示;辅以7个python脚本、4个pyc、1个json类别映射和1个txt配置说明,整体大小约736.93MB,覆盖从数据组织到模型评估的完整流程。目前已累计118人学习,适合希望快速上手新型ViT结构并落地图像分类任务的读者。资源内含完整代码与配置,理清DilateFormer在真实数据集上的训练细节,减少复现弯路,同时理解注意力机制在视觉任务中的实际调优思路。
1. DilateFormer:把ViT的全局注意力“删”一半,图像分类还是能打
很多人第一反应是“Transformer做图像分类,就该老老实实算全局注意力”。但如果你真的把ViT每层block的注意力矩阵拉出来看,会发现浅层真正有响应的连接非常稀疏,绝大多数patch只和周围几个patch有强关联,远处位置的注意力权重趋近于0。全局注意力在前几层留下的大量计算,基本是浪费。DilateFormer就是从这个反直觉观察出发,用多尺度扩张注意力(MSDA)和滑动窗口扩张注意力(SWDA)替换浅层的全局注意力,只在深层保留全局多头自注意力,从而在更低计算量下把图像分类精度稳住。我用dilateformer_tiny在植物幼苗分类任务上跑到89%+的ACC,单张V100就能训练,属于典型的“中等精度、低显存开销”方案,适合数据集规模不大、又不想放弃Transformer结构的图像分类场景。
2. DilateFormer的核心机制:MSDA与SWDA为什么能省计算还不掉点
2.1 从全局注意力到稀疏注意力:ViT浅层的斑块交互
ViT把图片切成patch序列后,每个patch都是一个token,标准自注意力让所有token两两交互,复杂度是token数量的平方。224×224输入切成16×16 patch后是196个token,单层注意力矩阵196×196,层数一多,计算和显存都扛不住。DilateFormer论文里做了一个很关键的分析:把ViT每一层的注意力矩阵按patch位置摊开,发现前几个block的attention非常“局部化”,大部分patch只对邻近8~12个patch有明显attention分数,远处基本是噪声。这说明浅层阶段不需要全局视野,全局注意力对浅层来说既贵又冗余。
那能不能完全不看远处?也不能。因为物体识别需要从局部边缘逐步扩大到整体轮廓,如果浅层只看严格相邻的几个patch,感受野增长太慢。MSDA的巧妙之处在于:它借鉴空洞卷积的扩张采样思路,在局部窗口内按一定间隔取patch,窗口尺寸不变,但参与计算的patch之间隔着距离。比如窗口是7×7,扩张率d=2时,实际取到的坐标是0、2、4、6,感受野覆盖到约13×13的区域,但参与计算的token数只有4×4=16个,直接砍掉一大半注意力计算。同时设置多组扩张率,就能在一个block里并行捕获“近、中、远”三种尺度依赖,这就是“多尺度扩张注意力”名字的由来。
从选型角度看,这个设计比Swin Transformer更简洁。Swin要周期性地做窗口shift来引入跨窗口交互,实现里还要维护窗口mask。MSDA用扩张采样在不同尺度上覆盖更大范围,不需要等周期性的shift,代码更干净。代价是窗口边界处的信息利用率不如Swin均匀,但实际训练时只要配合稍大的输入分辨率,这个劣势并不明显。
2.2 SWDA:滑动窗口扩张注意力如何组织局部邻域
SWDA是MSDA内部的一个必要组件。它把一个stage的特征图划分成不重叠的窗口,在窗口内部执行自注意力,同时使用扩张率扩大实际感受野。实现上,先按窗口大小切块,再在窗口内按扩张率做稀疏采样。为了避免读者被完整的MSDA多头实现绕晕,我写一段只保留“窗口切分 + 扩张采样”的简化逻辑,重点展示边界处理:
import torch import torch.nn.functional as F def sparse_window_sample(x, window_size=7, dilation=2): # x: [B, C, H, W] B, C, H, W = x.shape # 补零,保证 H 和 W 能被 window_size 整除 pad_h = (window_size - H % window_size) % window_size pad_w = (window_size - W % window_size) % window_size if pad_h or pad_w: x = F.pad(x, (0, pad_w, 0, pad_h)) _, _, Hp, Wp = x.shape # 窗口内坐标,扩张率决定采样间隔 h_idx = torch.arange(window_size, device=x.device) w_idx = torch.arange(window_size, device=x.device) h_slice = h_idx[::dilation] w_slice = w_idx[::dilation] Hq, Wq = len(h_slice), len(w_slice) # 用 unfold 取出全部不重叠窗口块 blocks = F.unfold(x, kernel_size=(window_size, window_size), stride=window_size) blocks = blocks.view(B, C, Hp // window_size, Wp // window_size, window_size, window_size) blocks = blocks.permute(0, 2, 3, 1, 4, 5).contiguous() # 在窗口内按扩张率取子集 sampled = blocks[..., h_slice[:, None], w_slice[None, :]] return sampled, B, Hq * Wq x = torch.randn(2, 64, 28, 28) sampled, _, token_count = sparse_window_sample(x, window_size=7, dilation=2) print(token_count) # 16逻辑说明:F.unfold以window_size为kernel和stride,把特征图切成一堆7×7窗口。随后将窗口块组织成[B, gh, gw, C, 7, 7]的形状,再通过索引h_slice和w_slice把每个窗口内参与注意力计算的patch缩减到4×4。注意补零策略只在H/W不是window_size整数倍时需要,实际DilateFormer一般会先确保特征图尺寸能够被窗口整除,避免补零引入边缘噪声。
参数说明:window_size常见取7或9,越大单窗口感受野越宽,计算量也随之上升。dilation决定“隔几个patch采一个点”,dilation=1等价于普通窗口注意力;dilation≥2时感受野变大但采样密度下降。MSDA里通常并行使用多组(dilation)配置,比如在同一个block内三个head分别用dilation=1、2、3,然后把各自的注意力输出拼接回原始通道维度。
2.3 金字塔架构:浅层用MSDA,深层用全局注意力
DilateFormer采用金字塔结构,意味着不同stage输出分辨率递减、通道数递增。我用的dilateformer_tiny变体,大致配置如下表:
| stage | 输出尺寸 | 通道数 | 堆叠的DilateBlock数 | 注意力类型 |
|---|---|---|---|---|
| stem | H/4 × W/4 | 64 | 卷积降采样 | —— |
| stage1 | H/4 × W/4 | 128 | 3 | MSDA,扩张率组(1,2,3) |
| stage2 | H/8 × W/8 | 192 | 4 | MSDA,扩张率组(2,4,6) |
| stage3 | H/16 × W/16 | 384 | 2 | SWDA + 少量全局多头自注意力 |
| stage4 | H/32 × W/32 | 512 | 1 | 全局多头自注意力 |
为什么这么排?因为浅层分辨率高,patch数量大,全局注意力复杂度高,适合用MSDA这种稀疏窗口注意力来节省算力。到了stage3以后,特征图分辨率降到H/16或H/32,实际patch数量已经很少,全局多头自注意力的计算量可以接受,这时再补上全局交互,能把前面几个stage积累的局部特征整合成全局语义。这个设计与图像识别的直觉一致:先看局部纹理,再逐步整合全局形状。
从参数配置上看,stage1的扩张率组不要设太大。植物幼苗这类边缘密集的图像,如果浅层扩张率太大,注意力会跳过关键叶片边缘,导致特征粗糙。我一般把stage1的扩张率固定在(1,2,3),stage2才放开到(2,4,6)。如果数据集物体尺度很大,比如卫星图像,反而应该把两组的扩张率都调大一档。
3. 数据准备与class.json:让图像分类项目跑起来的第一步
3.1 class.json在图像分类项目里的角色
在拿到这份DilateFormer实战资源后,第一个要注意的文件不是模型,而是class.json。它是一个类别映射文件,负责把模型的输出索引翻译成可读的类别名。常见格式有两种:第一种是类别名到索引的字典,例如{"Black-grass": 0, "Charlock": 1};第二种是索引到类别名的字典,例如{"0": "Black-grass", "1": "Charlock"}。加载时我建议统一转成按索引排列的list,避免后面每次预测都重写一次映射逻辑:
import json with open("class.json", "r") as f: class_dict = json.load(f) # 如果 class.json 是索引到类别名的字典 id_to_name = [class_dict[str(i)] for i in range(len(class_dict))] print(id_to_name[:3])逻辑说明:按字符串形式的索引从小到大取类别名,得到list后,模型输出第i个类别的置信度就直接对应id_to_name[i]。这里有个隐藏坑:如果class.json里某个索引编号不是从0开始,或者中间有跳跃,直接range(len(class_dict))会顺序错位。保险做法是先检查所有key是否能转成连续整数。
参数说明:len(class_dict)就是类别总数,后续定义全连接分类头、计算混淆矩阵都用这个值。如果class_dict是反向的类别名到索引,需要先做一次反转,代码是:
name_to_id = {v: int(k) for k, v in class_dict.items()} id_to_name = [None] * len(name_to_id) for name, idx in name_to_id.items(): id_to_name[idx] = name3.2 从文件夹图片到DataLoader:可用的数据流水线
植物幼苗数据集的目录结构一般是每个类别一个子文件夹,子文件夹里是采集到的幼苗照片,比如“1.png”“0367e0199.png”这类命名。用torchvision的ImageFolder可以直接加载,但它的类别顺序是按文件夹名的字母排序,而不是按class.json定义。如果两者不一致,训练和验证时标签就会“张冠李戴”。我最常做的处理是:先让ImageFolder自己扫一遍,随后用class.json里的id_to_name覆盖它的class_to_idx:
from torchvision import datasets, transforms from torch.utils.data import DataLoader train_transform = transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomResizedCrop(224), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.3), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) valid_transform = transforms.Compose([ transforms.Resize(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) train_dataset = datasets.ImageFolder("dataset/train", transform=train_transform) print("ImageFolder默认顺序:", train_dataset.class_to_idx) # 用 class.json 覆盖默认顺序 train_dataset.classes = id_to_name train_dataset.class_to_idx = {c: i for i, c in enumerate(id_to_name)} train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=4, pin_memory=True) valid_dataset = datasets.ImageFolder("dataset/valid", transform=valid_transform) valid_dataset.class_to_idx = train_dataset.class_to_idx valid_loader = DataLoader(valid_dataset, batch_size=64, shuffle=False, num_workers=4, pin_memory=True)逻辑说明:训练集用先Resize到256再随机裁剪到224的方式做尺度增强;验证集只做Resize,不做随机裁剪,保证输出稳定可复现。覆盖class_to_idx的目的是让“文件夹名 → 索引”的映射强制对齐class.json,这样模型训练过程中使用的标签顺序,和最后评估时的id_to_name完全一致。
参数说明:Resize与RandomResizedCrop的搭配是图像分类通用做法。如果数据集原始图像偏大,Resize到256裁剪到224会丢失一些细节;如果偏小,先放大再裁剪容易产生模糊,我一般会根据图片分辨率微调:原图边长在300像素以下的,直接Resize到224就行,不要做256再裁224的多余放大。
3.3 类别不平衡:用权重让植物幼苗小类不再被吃掉
植物幼苗数据集中不同类别样本数量差异可能很大,有些类别只有几十张,训练时模型会倾向把难分样本分到大类里。交叉熵损失本身对类别不平衡没有抵抗力,有两个常用解决办法:一是给损失函数传class_weight,二是用WeightedRandomSampler。我优先用后者,因为它不改变损失函数的计算逻辑:
from torch.utils.data import WeightedRandomSampler from collections import Counter labels = [s for _, s in train_dataset.samples] counter = Counter(labels) sample_weight = [1.0 / counter[l] for l in labels] sampler = WeightedRandomSampler(sample_weight, num_samples=len(sample_weight), replacement=True) train_loader = DataLoader(train_dataset, batch_size=64, sampler=sampler, num_workers=4, pin_memory=True)逻辑说明:每个样本的权重是1/该类样本数,类别样本越少,单样本被抽中的概率越高。replacement=True允许重复采样,保证每个epoch都能看到所有类别。这个操作在植物幼苗分类里经常把小类准确率从60%拉回85%以上。
参数说明:WeightedRandomSampler的num_samples一般设为训练集总长度,过大会让少数类重复太多导致过拟合,过小则每个epoch数据量偏少。如果你发现验证集小类上有明显过拟合,可以把num_samples调成总长度的0.8倍。
4. 训练DilateFormer_tiny:优化器、学习率与评估指标
4.1 优化器与学习率:AdamW + Cosine + Warmup的配置
DilateFormer是纯Transformer结构,用SGD调参效率很低,batch size和个人机器差异都容易让精度抖动。我用的是AdamW,weight decay独立于梯度更新,对Transformer的范数控制更干净。下面是常用的一组配置:
from torch.optim import AdamW from torch.optim.lr_scheduler import CosineAnnealingLR, LinearLR, SequentialLR learning_rate = 5e-4 weight_decay = 0.05 total_epochs = 60 warmup_epochs = 5 optimizer = AdamW(model.parameters(), lr=learning_rate, weight_decay=weight_decay) warmup_scheduler = LinearLR(optimizer, start_factor=0.1, end_factor=1.0, total_iters=warmup_epochs) cosine_scheduler = CosineAnnealingLR(optimizer, T_max=total_epochs - warmup_epochs, eta_min=1e-6) scheduler = SequentialLR(optimizer, schedulers=[warmup_scheduler, cosine_scheduler], milestones=[warmup_epochs])逻辑说明:前5个epoch用线性warmup把学习率从5e-5提升到5e-4,目的是让MSDA里扩张采样产生的梯度噪声不至于在训练初期把模型带偏。随后切换到cosine退火,把学习率平滑降到1e-6,最后几个epoch相当于在局部极值附近精细搜索。milestones=[warmup_epochs]表示warmup结束时立刻切换到cosine。
参数说明:weight_decay从0.01到0.1都能跑,但我发现0.05在植物幼苗这种偏中小规模的数据上更稳,既限制过拟合,又不会让浅层MSDA学不到细节。如果训练loss下降特别慢,把start_factor改成0.5,warmup缩短到2个epoch,优先让模型先跑起来再谈稳定。
4.2 训练循环与验证循环:每步该干什么
整套训练流程里最常翻车的地方是混合精度和梯度裁剪的顺序。用PyTorch的AMP时有两点必须注意:clip之前先unscale_,否则clip的阈值和真实梯度不一致;scaler.step后再scaler.update,不能在loss.backward后立刻update。一个可复制的训练循环如下:
from torch.cuda.amp import GradScaler, autocast criterion = torch.nn.CrossEntropyLoss() scaler = GradScaler() best_acc = 0.0 for epoch in range(1, total_epochs + 1): model.train() train_correct, train_total = 0, 0 train_loss = 0.0 for images, labels in train_loader: images, labels = images.cuda(), labels.cuda() optimizer.zero_grad() with autocast(): logits = model(images) 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() train_loss += loss.item() * images.size(0) train_correct += (logits.argmax(1) == labels).sum().item() train_total += images.size(0) model.eval() val_correct, val_total, val_loss = 0, 0, 0.0 with torch.no_grad(): for images, labels in valid_loader: images, labels = images.cuda(), labels.cuda() with autocast(): logits = model(images) loss = criterion(logits, labels) val_loss += loss.item() * images.size(0) val_correct += (logits.argmax(1) == labels).sum().item() val_total += images.size(0) val_acc = val_correct / val_total if val_acc > best_acc: best_acc = val_acc torch.save(model.state_dict(), "dilateformer_tiny_best.pth") scheduler.step() print(f"Epoch {epoch}: train_acc={train_correct/train_total:.4f}, " f"val_acc={val_acc:.4f}")逻辑说明:autocast只包住forward和loss计算,backward保持在float32下完成,梯度更稳。clip_grad_norm_放在scaler.unscale_之后,这样才能把混合精度中的梯度统一放大到正常尺度后再限制范数。最后保存验证集ACC最高的权重,而不是最后一轮权重。
参数说明:max_norm=5.0是我默认值。如果训练过程中loss曲线一直震荡不停,把这个值降到1.0;如果模型收敛很慢、梯度范数始终小于1,可以放宽到10.0。batch size为64以上时,这些参数基本不用动。
4.3 植物幼苗分类的评估:ACC之外还要看混淆矩阵
整体ACC达到89%只是一个起点。植物幼苗数据集中“Black-grass”和“Loose Silky-bent”外观非常接近,如果模型只靠颜色或叶片形状的偶然特征,可能整体不差但小类崩盘。所以我每次训练完都会额外统计“每个类别的准确率”,这一步非常耗时但必不可少:
all_preds, all_labels = [], [] model.eval() with torch.no_grad(): for images, labels in valid_loader: images = images.cuda() logits = model(images) preds = logits.argmax(1).cpu().tolist() all_preds.extend(preds) all_labels.extend(labels.tolist()) from collections import defaultdict correct_by_class = defaultdict(int) total_by_class = defaultdict(int) for pred, label in zip(all_preds, all_labels): total_by_class[label] += 1 correct_by_class[label] += (pred == label) for idx in range(len(id_to_name)): total = total_by_class.get(idx, 0) correct = correct_by_class.get(idx, 0) if total > 0: print(f"{id_to_name[idx]}: total={total}, acc={correct / total:.4f}")逻辑说明:这段代码把验证集所有样本的预测和标签先存起来,再按类别统计。不直接打印整体accuracy,是因为整体结果无法告诉你哪一类在拖后腿。保存后的all_preds和all_labels也可以直接送给sklearn.metrics.confusion_matrix做可视化。
参数说明:如果某个类别acc低于70%,大概率是样本数太少或者该类与别的类太像。解法有两个方向:一是给该类增加数据增强强度,比如额外做RandomAffine;二是把该类样本的损失权重调高,比如CrossEntropyLoss里传入weight参数。
5. 避坑与排查:DilateFormer训练中的常见问题
5.1 训练loss卡在0.7左右,验证acc不涨
现象:训练到第20个epoch,loss降到0.7附近就再也不动,验证ACC一直徘徊在0.6。
原因:学习率过大,加上MSDA中不同扩张率分支的梯度互相干扰,模型卡在局部平坦区。另一个常见原因是warmup太短,模型一开始就用大步长跳过了解空间里的关键路径。
解决:把初始学习率降到5e-4以下,warmup从2个epoch延长到8个epoch;同时检查数据增强里RandomResizedCrop的scale参数,默认(0.08, 1.0)在植物幼苗上容易裁到太多背景,改成(0.5, 1.0)让模型先专注前景。
5.2 显存溢出(OOM),batch size调到16还是不行
现象:一进stage1训练就报CUDA out of memory,batch size从64一路降到16仍报错。
原因:部分MSDA实现会在窗口内先构造[B, num_windows, heads, Nq, Nk]五维张量再算softmax,中间峰值显存非常大。stage1输出分辨率是H/4×W/4,窗口数量多,这个中间张量直接占满显存。
解决:优先使用梯度累积,用两到三个mini batch模拟一个batch做一次参数更新,比如:
scaler = GradScaler() for step, (images, labels) in enumerate(train_loader): with autocast(): loss = criterion(model(images), labels) scaler.scale(loss).backward() if (step + 1) % accumulation_steps == 0: scaler.unscale_(optimizer) clip_grad_norm_(model.parameters(), 5.0) scaler.step(optimizer) scaler.update() optimizer.zero_grad()另外可以把输入分辨率从224降到192,感受损失相对较小,显存却能省下20%。推理时再换回224,精确度差距很小。
5.3 训练集acc>99%,验证集只有85%
现象:训练集轻松跑到99%以上,但验证集只有85%,且随着训练推进,验证loss先降后升。
原因:DilateFormer的MSDA在局部窗口内有很强的拟合能力,如果数据增强不够,模型会把背景纹理当作类别特征。植物幼苗数据集本身背景复杂,颜色分布也有偏,不做色彩扰动很容易过拟合。
解决:在transform里加入ColorJitter和RandomGrayscale,MixUp的alpha设0.2。同时把weight_decay从0.05提到0.1,dropout仅在分类头加一行,不要加到注意力里。
5.4 输入尺寸不是正方形,模型推理报shape mismatch
现象:训练时用224×224,部署时输入一张320×240图片,forward到某个DilateBlock时报张量维度对不上。
原因:MSDA的窗口划分要求H和W都能被window_size整除。320不能被7整除,补零后stage输出的token数变化,后续池化或全连接层维度就错位了。
解决:推理阶段务必先做Resize((224,224))或CenterCrop(224)。如果不想丢宽高比,也可以Resize到224×320再做CenterCrop,但前后一致性比是否保留宽高比更重要。对于训练阶段,建议在sparse_window_sample里预留pad逻辑,同时把pad掉的区域对应的注意力mask置为-inf,避免补零区参与softmax。
5.5 class.json和ImageFolder的类别顺序不一致,验证时混淆矩阵完全错乱
现象:验证集整体acc看起来很高,但混淆矩阵对角线乱得离谱,A类预测全被算到B类。
原因:ImageFolder的class_to_idx按文件夹名字母排序,class.json却是数据集作者按采集顺序定义,两者的索引编码不一致。如果只改了一套代码里的映射,另一套没改,标签就错位了。
解决:在训练开始前打印“ImageFolder默认顺序”和“class.json顺序”核对。更稳妥的做法是手动校验:读取valid_loader里任意一个batch的label值,再结合id_to_name输出类别名,和图片文件夹名对照。如果发现不一致,立即用第3.2节的方式覆盖class_to_idx。
6. 进阶:从89%到90%+:MSDA注意力图可视化与超参调优
6.1 可视化注意力图,找出DilateFormer真正关注的区域
当验证ACC稳定在89%附近时,调学习率已经不是最有效的手段。我习惯用forward_hook把stage2第一个DilateBlock的注意力权重抠出来,按每个patch被“关注总量”画成热力图,再叠加到原图上。代码大概长这样:
def hook_fn(module, input, output): # output取当前block输出的注意力权重,形状依据具体实现 attn = output.detach().cpu().numpy()[0] # [heads, N, N] attn_map = attn.mean(axis=0).sum(axis=0) # [N] attn_map = attn_map.reshape(14, 14) # 对应stage2输出分辨率 import torch.nn.functional as F attn_map = F.interpolate( attn_map.unsqueeze(0).unsqueeze(0), size=(224, 224), mode="bilinear" ).squeeze().numpy() # 归一化后保存 attn_map = (attn_map - attn_map.min()) / (attn_map.max() - attn_map.min()) cv2.imwrite("attn_map.png", (attn_map * 255).astype("uint8")) model.stage2[0].register_forward_hook(hook_fn)逻辑说明:hook拿到的是一个block attention输出,先对所有head取平均,再对target patch侧求和,得到每个patch累计被关注的强度,最后重采样成原图尺寸。保存成灰度热力图后,一眼能看出模型是盯着幼苗叶片还是背景。
参数说明:如果热力图大部分高亮都在图像边缘,说明扩张率太大,模型在浅层就跳到远处背景上;如果高亮集中在叶片中心但没有覆盖边缘,说明窗口太小,叶片边缘细节被skipped掉。这两种情况需要分别调整扩张率窗口大小,而不是盲目加数据。
6.2 调整扩张率与窗口大小:从89到90的直接手段
基于注意力图判断后,我会把stage2的window_size从7改成9,扩张率从(2,4,6)改成(1,3,5),给局部位置更高采样密度,同时保留一定的远程跨度。这个操作在植物幼苗分类上通常带来0.5~1.5个百分点的提升,代价是stage2推理速度下降约6%。stage1不要动,浅层保持小窗口小扩张,否则训练初期梯度噪声会变大。配合调整,我把warmup从5epoch延长到8epoch,让新增的窗口采样模式稳定落地。从那以后,我每次用DilateFormer做一个新数据集,第一件事不是调学习率,而是先打印stage2的attention map。很多看似玄学的精度瓶颈,画完图就知道是背景干扰还是扩张率失配,又或者深层全局注意力根本没学到东西。希望帮到你。
本文还有配套的精品资源,点击获取