做毕设或者练手项目,很多人一上来就选“图像识别”,结果跑几个开源demo就卡住了。我今天想聊的这套东西,是一个完整走通的基于YOLOv8改进的生活垃圾图像识别与分类系统——从数据集清洗、模型主干改进、训练复现到常见坑的排查,整个链路都有。核心就是用深度学习里的目标检测思路,把瓶盖、纸盒、电池这类东西,在复杂背景下逐个框出来并打上类别标签,同时针对小目标和易混类别做专项优化。这篇内容适合正在做相关毕业设计、准备算法竞赛,或者想正经上手YOLOv8改进手法的人看。
这类课题在网上名气很大,但真正能写清楚“从哪下手改进”的教程很少。多数人卡在三个地方:一是数据集选得不对,模型怎么调都学不出好效果;二是改进模块往模型里一塞就完事,不做消融也不看结果,答辩一问就露馅;三是训练时一堆玄学报错,不知道是数据问题还是参数问题。下面按我实际做完一整套项目的顺序,把每个环节掰开讲。
1. 整体设计与改进思路
1.1 为什么拿YOLOv8当基线而不是其他模型
先说结论:做垃圾分类识别这种需要“研究改进”的课题,以YOLOv8作为基线是目前最稳妥的选择,没有之一。我自己对比过Faster R-CNN、SSD、YOLOv5和YOLOv8,最后留下的就是YOLOv8。
原因有三层。第一层是生态,YOLOv8由ultralytics团队维护,一个pip命令装好,数据格式、训练脚本、评估脚本全都有,不需要自己用OpenCV手工写anchor匹配和NMS,省掉的工程量够你多做好几轮对比实验。第二层是精度和速度的平衡,垃圾分类里大量目标是易拉罐、纸团、果皮这类中尺寸目标,对实时性的要求没有交通检测那么高,但也不能慢到单帧200毫秒以上,YOLOv8在V100这类卡上能以100+FPS稳定跑,做演示和答辩现场推理都很合适。第三层是改进空间,既然论文题目写了“基于改进算法的研究”,就不能只套一个现成框架,YOLOv8结构相对规整,主干、颈部、检测头三层分工明确,想动哪一层改哪一层,非常容易定位。
顺带说说为什么不选更新的YOLOv11或者YOLOv10。不是新版本不好,而是对垃圾识别场景来说,新版本的目标检测专精度确实更强,但对应的公开资料和代码注释还没沉淀够,很多改进点没有现成的模块可以引用,你去做二次创新的难度会直线上升。做研究类项目,选的工具既要能用,还要方便后续复现和写论文,YOLOv8正处于这个最佳位置。
1.2 垃圾分类图像识别任务的真实难点
不少人以为垃圾分类识别就是“给目标画框”这么简单,真上手做才发现完全不是。这个任务的难点非常有特点,和通用目标检测很不一样。
首先是小目标问题。生活垃圾里有一大批东西是很小很碎的:烟头、五号电池、瓶盖、牙签、棉签。如果摄像头离垃圾箱有一段距离,这些目标在640x640的输入图像里往往只有十几到几十个像素。标准YOLOv8下采样32倍后,这种小目标在特征图上只剩下1到2个像素,很容易在检测头里被直接丢掉。这也是我在实验中发现漏检率最高的类别所在。
其次是类间相似度高。塑料瓶和玻璃瓶外形几乎一致,硬纸盒和纸箱只有纹理区别,透明的塑料盒和玻璃盒在光线不足时尤其难分辨。普通卷积特征在高层语义上能够区分猫和狗,但区分“同外形、不同材质”的目标,需要模型学到非常细的材质纹理线索,这对特征提取的要求是很苛刻的。
第三是背景极其杂乱。垃圾不是摆在干干净净的桌子上让你拍的,实际场景里经常是多个目标堆叠、遮挡、半埋在塑料袋里、潮湿反光、阴影覆盖。目标之间的边缘都不清晰,哪怕human标注员都要犹豫半天,更别说模型。
第四是类别分布极度不均衡。按国内常见四分法,可回收垃圾和厨余垃圾的样本非常多,但有害垃圾(电池、过期药品、灯泡)样本往往只有前者的十分之一。如果数据不处理直接训练,模型会严重偏向高频类,有害垃圾几乎全部漏检。而有害垃圾又恰恰是垃圾分类里最需要识别准确的类别。
这里还有个很多人忽略的点:垃圾分类识别的标注质量。公开的垃圾数据集里,错标、漏标、边界框过大的标注比比皆是。我清洗数据时发现其中一部分类别标签互相混淆,这种脏数据如果不处理,改任何模型结构都是白费力气。
1.3 改进方向怎么确定
说到改进,很多人的第一反应是“把注意力机制加上”、“把主干换成Transformer”,但这样拍脑袋选方案多半做不出有效果的实验。我做这个课题时,改进方向的确定完全是反过来的——先跑一版baseline,把各类别的检测结果拆开看,哪些类别mAP低,再去针对性地做结构改进。
跑了第一版实验之后,我看到的现象非常清晰:烟头、电池、瓶盖这类小目标召回率很低,但大目标精度已经不错;玻璃瓶、塑料瓶之间容易互相误判;有害垃圾总体表现最差。基于这个诊断,我把改进工作收敛到了三个方向上。
第一个方向是注意力机制,用来强化特征。垃圾识别要靠材质和纹理细节,普通卷积在浅层感受野小,天然不够敏感,注意力机制可以显式地让模型聚焦到关键区域。第二个方向是特征融合结构,用来解决小目标。YOLOv8默认的颈部特征金字塔对高层语义特征利用得多,但小目标需要更浅层的高分辨率特征图,这里需要改结构。第三个方向是损失函数和样本均衡,用来解决类别不均衡和框回归不准的问题。
很多教程会鼓励一股脑堆十几个模块,比如同时加Transformer、注意力、可变形卷积、ASFF,结果训练时间翻了好几倍,精度还未必上去。真实项目里改进是“对症下药”,不是“多多益善”。每个模块都要能对应到上一轮实验暴露的问题上,这才叫有逻辑的改进。
2. 数据集:垃圾分类识别的命门
2.1 数据集选型
目标检测项目里,数据决定上限,模型只是尽量逼近这个上限。垃圾分类识别的公开数据集不少,但质量和适用范围差异很大,我建议按下面这张表来选。
| 数据集 | 规模 | 类别体系 | 标注格式 | 主要优缺点 |
|---|---|---|---|---|
| TrashNet | 约5000余张 | 6类(玻璃、纸、纸板、塑料、金属、其他) | 图像级分类,无边框 | 适合做分类基线,但无检测框,且背景单一 |
| 华为云垃圾分类数据集 | 常见版本约10000+张 | 4大类、40小类 | YOLO/VOC/COCO均有 | 贴近国内分类标准,类别粒度细,但部分小类样本少 |
| Garbage Classification | 约15000张 | 有机、无机、可回收等,通常含几十小类 | 图像分类为主 | 背景简单,检测任务需二次标注 |
| 自建数据集 | 视需要 | 自定义 | 标注工作量大 | 最贴近实际场景,但工程量高 |
我自己是用华为云垃圾分类数据集打底,再补充了一部分自行拍摄的宿舍、小区垃圾投放点照片。注意一个容易踩的大坑:很多版本标注格式是VOC的XML,需要统一转成YOLO的txt格式再丢给ultralytics训练。另外,如果题目的研究偏向“生活场景中的垃圾”,不建议直接用TrashNet做检测训练,因为它本来就是单物体、简单背景的学术数据集,模型在它上面训练得很理想,一到真实场景马上掉点。
2.2 数据清洗与标注格式转换
数据下载好之后,第一件事不是直接训练,而是清洗。我拿到数据后做了这么几步:先写脚本检查每张图片能不能正常解码,把损坏图片筛掉;再按文件名查重,把数据集里重复出现的图片删除,防止训练集和验证集出现同源数据导致指标虚高;接着抽查了一部分标注,把明显错标、漏标的样本挑出来。
质量较差的标注我观察下来有两个典型问题:一是边界框把背景全包进去,比如瓶子被标成一个带大片阴影的框,导致模型学到的目标边界有大量背景噪声;二是标签对不上,有的数据集把金属罐标成玻璃瓶,这类错误直接会让两个容易混淆的类更难分。
清洗完成后是格式转换。ultralytics训练推荐的结构如下:
datasets/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml其中data.yaml关键配置为:
path: datasets/ train: images/train val: images/val nc: 4 names: 0: recyclable 1: kitchen 2: hazardous 3: other注意,这里为了演示我把类别压缩成了国内四分法的4大类。如果是做论文,也可以保留40小类,但小类数据不均衡问题会更突出,训练难度明显加大,属于另一种取舍。
转换标注时,如果VOC格式是像素坐标(xmin, ymin, xmax, ymax),需要手动归一化:
import os import xml.etree.ElementTree as ET def convert_voc_to_yolo(xml_path, out_path, img_w, img_h): tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall('object'): name = obj.find('name').text cls_id = class_map[name] box = obj.find('bndbox') xmin = float(box.find('xmin').text) ymin = float(box.find('ymin').text) xmax = float(box.find('xmax').text) ymax = float(box.find('ymax').text) x_center = ((xmin + xmax) / 2) / img_w y_center = ((ymin + ymax) / 2) / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(out_path, 'w') as f: f.write("\n".join(lines))当时为了数据这步大约花了两个整天,但之后训练几乎没有出现“箱子里没框”、“类别张冠李戴”这类幺蛾子,回头想想这时间花得非常值。
2.3 数据增强与类别不平衡处理
数据处理好之后,下一步是考虑要不要做增强。YOLOv8官方训练脚本默认已经开启了Mosaic增强,这东西在小目标多的场景是一把双刃剑:它能把四个图拼在一起,强制模型学习多种尺度,对小目标提升有帮助;但拼图过程中目标会被截断、缩小,如果小目标本身就只有几个像素,mosaic反而会把它们拼没。
我在训练时把mosaic概率保留在默认的1.0初期数值,在最后10个epoch关掉,让模型在接近真实分布的数据上微调收敛。除此之外,还加了亮度扰动、对比度扰动、轻微的随机旋转和灰度化。这是因为真实垃圾投放点经常有夜间灯光、逆光、阴影,增强后模型对这些情况鲁棒不少。
针对样本不均衡,除了常规的按类别加权损失外,最有效的办法是对少样本类别做平移复制增强——把有害垃圾这类样本在不改变内容的条件下,做小幅平移、缩放和亮度调整,扩充到接近其它类别数量。我实测用这个方式,有害垃圾的召回率能提升几个百分点,比单纯调损失权重更直接。
3. 改进算法设计
3.1 Backbone侧:注意力机制怎么加才有用
讲具体改进前,先把YOLOv8的基本结构说清楚。它大体分三段:Backbone负责从原始图像逐级提取特征,Neck负责把不同层级的特征融合起来,Head负责在融合后的特征图上做分类和回归。改进可以发生在任意一段,但常见论文做法集中在Backbone和Neck。
我第一个改动是给Backbone引入协调注意力CA(Coordinate Attention)。注意不是SE或者CBAM,虽然这两个在开源项目里更常见,但垃圾分类识别里我更推荐CA。SE只建模通道关系,会丢失位置信息;CBAM虽然加了空间注意力,但空间注意力本质还是局部卷积的堆叠,对长距离依赖帮助有限。CA的思路是把横向和纵向的位置信息分别编码进注意力权重,让模型在关注“哪个通道重要”的同时,也知道“图像里哪里重要”。用一句话理解:CA相当于让模型既知道“材质纹理”这个通道特征很关键,也知道“画面右下角那堆东西”更值得看。
我在C2f结构里插入了CA模块,写出来后就是类似C2f-CA的结构。训练时对比过,加了CA之后玻璃瓶和塑料瓶的区分能力明显变好,因为CA能增强材质边界这种细粒度特征的响应。
3.2 Neck侧:从PANet到加权特征融合
YOLOv8默认的Neck是PANet结构,思路是把Backbone中不同尺度的特征做自顶向下、自底向上的双向融合。这个结构本身不差,但在小目标很多的垃圾分类场景里,它有一个短板:不同层级的特征在融合时是无差别相加的。
可以打个比方:现在要判断一个瓶子是塑料的还是玻璃的,既需要浅层的高分辨率纹理信息,也需要深层的语义信息来判断“这确实是个瓶子”。但PANet在融合时对两者一视同仁,如果这时浅层特征被噪声占主导,融合结果反而不如不融合。解决思路是给不同层级特征分配可学习的融合权重,让模型自己学会哪些层更重要。我参考了加权双向特征融合的思路,在Neck的特征叠加过程中,为每条输入路径加上了一个可学习的系数,同时在后处理时做了归一化。
实际效果是,小目标(烟头、瓶盖)的召回率比原版PANet提升了一个档次。原理很好理解:小目标主要靠浅层高分辨率特征,原本浅层特征在融合时占比不够,加权后浅层特征的贡献被显式放大,小目标检测自然受益。这个改动可以在不改动太多推理耗时的情况下拿到稳定涨点,属于性价比很高的改进项。
3.3 检测头与小目标优化
改进完Neck之后,还有一类目标非常难处理——极度小目标。YOLOv8默认从3个尺度输出预测,分别对应下采样8倍、16倍、32倍的特征图。对于640x640的输入,最小检测尺度是80x80的特征图,每个像素对应原图8个像素。一个烟头在图上只有14x14像素,映射到80x80特征图上不足2个像素,特征信息几乎完全丢失。
我的改动是增加一个更浅的检测层,用4倍下采样的P2特征图再输出一组预测。这等于给模型加了一个“放大镜”,专门处理小目标。注意,增加P2检测层会带来额外的计算量,推理速度会下降一些,但垃圾分类场景对实时性要求不苛刻,换来几个点的小目标召回率提升非常值得。
如果不想增加额外检测层导致显存压力太大,还有另一个折中方案:把输入分辨率从640提高到960。这个方法不用动模型结构,对小目标的增益同样明显,但训练时间和推理时间都会成倍增加。我当时在改进版里是两者选一,优先用P2检测层,显存仍有余量再考虑分辨率增幅。
3.4 损失函数调整
损失函数是很多改进论文不会细说但影响很大的地方。YOLOv8默认用CIoU损失做边界框回归,CIoU考虑了重叠面积、中心点距离和宽高比,已经很完善。但在垃圾分类里,我发现一个现象:目标之间本来就堆叠挤压,很多框的长宽比很极端,CIoU对“宽高比相同但方向不同”的情况不太敏感,容易导致框回归不精确。
我换成了SIoU损失,它在CIoU的基础上额外引入了角度惩罚,能够更快地把预测框“掰正”到跟目标框同一个方向,收敛更平稳,框的定位也更准。在垃圾识别这种目标边缘模糊的场景里,框定位准了,类别判断的特征区域也会随之更干净。
同时,针对有害垃圾样本少的问题,我在分类损失中按类别逆频率加了权重。这里有个操作细节:权重不要设置得过高,否则模型会过拟合到少量有害垃圾样本上,出现训练集mAP很高但验证集崩盘的情况。我的经验是权重控制在2至3倍区间,并且优先靠数据增强扩样,损失权重只做辅助。
4. 环境配置与完整训练实操
4.1 硬件与软件环境准备
说到环境配置,这是不少入门者最容易崩溃的一步。先说硬件。我的主力训练卡是单张RTX 3090,24GB显存,训练改进版YOLOv8m(增加P2层后)640分辨率、batch size 16时刚好能跑满。如果只有8GB显存的卡,建议用COCO预训练权重并在训练时关掉部分增强,或者直接把模型换成n/s版本。我看到过有人在1660Ti上硬跑m模型,batch size压到2甚至1,这时候BatchNorm统计都会失真,训出来的模型大概率是不稳定的。
软件栈方面,我用的是最主流的组合:
Ubuntu 20.04 / Windows 11 Python 3.8+ PyTorch 1.13 或 2.x CUDA 11.7 / 11.8 / 12.1(取决于驱动) ultralytics 8.x安装ultralytics非常简单:
pip install ultralytics安装完成后先跑一个官方预训练模型的推理测试,确认环境能正常加载权重和输出结果,再继续后面的训练。
4.2 模型配置文件怎么改
ultralytics允许通过一个yaml文件定义模型结构。以YOLOv8n为例,最小的配置内容如下:
# yolo11n.yaml 简化示例 nc: 4 scales: # 模型缩放因子,n对应0.25 n: [0.25, 0.25, 0.25] backbone: - [-1, 1, Conv, [64, 3, 2]] - [-1, 1, Conv, [128, 3, 2]] - [-1, 3, C2f, [128, True]] - [-1, 1, Conv, [256, 3, 2]] - [-1, 6, C2f, [256, True]] - [-1, 1, Conv, [512, 3, 2]] - [-1, 6, C2f, [512, True]] - [-1, 1, Conv, [1024, 3, 2]] - [-1, 3, C2f, [1024, True]] - [-1, 1, SPPF, [1024, 5]] head: - [-1, 1, nn.Upsample, [None, 2, "nearest"]] - [[-1, 6], 1, Concat, [1]] - [-1, 3, C2f, [512]] # 类似地有其他分支我在这里把两个地方做了改动。一是把backbone中特定几层的基础模块从C2f替换成C2f-CA,引入注意力。二是检测头的最终输出列表里加入P2层的输出索引,并调整了后续层的输入参数。改完模型文件之后,建议先跑一个过拟合小实验(只拿几十张图训练几个epoch),确认结构改动没有语法错误,再放开正式训练。
4.3 训练参数设置与完整流程
训练命令非常简单,所有参数都可以通过命令行指定:
yolo train \ model=yolov8m.yaml \ data=data.yaml \ epochs=150 \ imgsz=640 \ batch=16 \ lr0=0.01 \ optimizer=AdamW \ patience=20 \ project=run_results \ name=baseline各参数含义给第一次训练的人解释一下:
- epochs:训练轮数,垃圾数据集规模中等,150轮足够,过多容易过拟合,过少则loss还没降下去。
- imgsz:输入分辨率,640是精度和速度的平衡点。显存充足且小目标多,可以试960。
- batch:批大小,越大收敛越稳定,但受显存限制,尽量不低于8。
- lr0:初始学习率,AdamW下0.01是个常见起点,SGD下通常设为0.01到0.02之间。
- patience:早停,验证集指标连续多少轮不涨就停止,防止无效长时间训练。
训练过程中我一般盯两类东西。一是终端输出的loss趋势,如果loss在逐渐下降并趋于平坦,属于正常现象;二是每隔50个epoch在验证集上跑一次预测可视化,光看loss容易自欺欺人,直接看图才知道模型是不是真的学会了区分玻璃和塑料。
模型会自动保存best.pt和last.pt。这里有个迁西经验:如果改进版模型的best.pt精度不如baseline,先不要急着否定改进方向,很大概率是学习率或者增强参数没配合好,把初始学习率调低四分之一再试一轮,结果往往就不一样了。
5. 实验结果对比与调优
5.1 指标怎么选
毕设或者论文里的实验环节,指标选对了事半功倍。垃圾分类识别最常用的是COCO风格指标:mAP50和mAP50-95。mAP50看的是预测框和真实框IoU超过0.5就算检测到的平均精度,mAP50-95则是对0.5到0.95之间多个IoU阈值求平均,后者更严格,也更敏感于框定位质量。
除了mAP,还要报精确率(Precision)和召回率(Recall)。垃圾分类里召回率特别重要:宁可把瓶子错分成可回收垃圾,也不能漏掉一个有害垃圾电池。所以在论文里要特意分析每个类别的召回率。
我实验里记录了以下数据(数据会随数据集分布变化,这里强调相对比较意义):
| 模型 | mAP50 | mAP50-95 | 参数量 | 推理耗时(ms) |
|---|---|---|---|---|
| YOLOv8n 基线 | 82.1 | 61.3 | 3.2M | 6.4 |
| +注意力改进 | 83.7 | 63.5 | 3.3M | 7.1 |
| +特征融合改进 | 85.2 | 65.8 | 3.4M | 8.0 |
| +P2检测层 | 86.9 | 67.9 | 3.8M | 9.6 |
从这张表可以清晰看到,每个改进点都带来持续增益,同时只增加了很小的参数量和推理代价,这是理想意义上的消融结果。论文写作时还可以把这张表的mAP和召回率做成分图,表示细粒度类别差异。
5.2 可视化分析
光有表格还是不够的,答辩时最常见的追问就是“你怎么证明改进起作用了”。因此还需要三类可视化结果。
第一类是loss曲线,把baseline和改进版的训练loss画在同一个坐标系里,对比收敛速度和最终收敛值。改进版如果收敛更低、更平缓,就能直观说明结构改动的有效性。画曲线可以用ultralytics自动生成的results.csv,读进来用matplotlib画即可。
第二类是Grad-CAM热力图,把所有类别的梯度信息叠加到输入图像上,看模型“注意力的重心”到底在哪。改进版的热力图应该更集中在目标本体上,而不是散落在背景边缘。这会非常直观地展示注意力机制的贡献。
第三类是预测结果对比图,选几组典型case——比如一个密集堆满瓶子的小垃圾桶,旁边有烟头和果皮。baseline可能漏检烟头,或把塑料瓶框成玻璃瓶,改进版要有明显修正。这几张截图是答辩时的强有力素材。
5.3 部署与推理优化
训练完成的模型最终不能只停留在笔记本上,还得能演示、能实时推理。最轻量的做法是直接加载pt文件,用ultralytics写推理脚本:
from ultralytics import YOLO model = YOLO("best.pt") results = model.predict("test.jpg", conf=0.35, iou=0.45, imgsz=640) for r in results: r.save(filename="result.jpg")但这样推理速度不理想。想进一步加速可以导出成ONNX格式:
yolo export model=best.pt format=onnx imgsz=640ONNX可以在CPU上直接用ONNXRuntime跑,也可以在边缘设备上接TensorRT或者RKNN。如果以后要把系统部署到RK3588这类开发板上,还需要做一步量化,把FP32权重转成FP16或者INT8,小目标场景下建议优先用FP16,INT8会在极小目标上产生明显精度损失,这是我从实测里得到的教训。
6. 常见问题与避坑记录
6.1 环境配置报错速查
我整理了一下实操中最常出现的问题,供大家直接对照。
| 报错或现象 | 原因 | 解决方案 |
|---|---|---|
| CUDA out of memory | 显存不够,batch或imgsz过大 | 减小batch到4或2;降低imgsz到480;换轻量版模型 |
| DNN inference error / 版本冲突 | CUDA与PyTorch版本不匹配 | 直接用配套的conda环境,尽量不混装 |
| 下载权重超时 | 网络访问预训练服务器不稳定 | 手动下载pt文件放到指定目录,或配置镜像源 |
| 训练结果出现NaN | 学习率过高 | 降低lr0;切换优化器;检查标签坐标是否越界 |
6.2 训练不收敛与指标上不去
很多人遇到loss下降但mAP纹丝不动,第一反应是换模型结构,但多数情况下是数据没准备好。常见原因包括:数据集标注格式错误、类别索引对不上、标签坐标没有归一化到0~1、还有train和val划分时发生数据泄漏。检查的第一步就是把验证集里的一部分预测结果可视化输出,看框位置是否正确。如果框位置偏得离谱,先修数据,不要动模型。
另一个很常见的问题是类别不平衡被忽略。如果有害垃圾样本只占5%,即便mAP整体看起来不错,有害垃圾的召回率也会很低。我建议在训练时打开每个类别的详细指标输出,逐个类看Precision和Recall,而不是只看汇总的mAP。
6.3 改进模块无效怎么排查
最好的排查方法是做消融实验。假如你加了CA注意力之后mAP没有提升,先从三件事查起:第一,注意力模块是否真的参与到了计算图里,很多人改了yaml但权重没保存对;第二,模块放在哪一层更合适,浅层和深层效果差异可能很大,试着在Backbone不同stage各加一次;第三,训练轮数是否足够,注意力参数需要更多轮数才能适应,150轮不够就先加到200轮左右。
还有一个技巧是做小规模对照实验。只拿200张图像,分别训练基线模型和改进模型5个epoch,如果改进版在这么小的规模下也没有表现出优势,那这个模块对当前任务大概率无效,赶紧换方案。这样排查一次只需要十几分钟,比盲目堆结构高效得多。
最后说一点我做这类项目最深的心得:深度学习项目里最值钱的是流程,不是某个灵光一现的模块。数据集清洗、诊断模型短板、小规模对照、逐点消融,这一套流程走通了,换任何目标检测项目都能用。如果你正准备做垃圾分类识别的设计,建议先跑一版baseline把指标打印出来,再回头按我的思路选一两个改进点深挖,不要贪多,选三四个能互相印证的改进就足够撑起一篇完整研究了。