ICME23 知识蒸馏复现 · 阶段性沉淀
项目:ICME 2023《Collaborative Spatial-Temporal Distillation for Efficient Video Deraining》
数据:NTURain(合成测试集 8 序列 + 真实雨 7 序列)
阶段:链路打通并验证,训练按成本考量主动中止于 epoch 1
日期:2026-10-12 ~ 2026-10-13
环境:AutoDL RTX 3090(14 vCPU / 24 GB 显存)+ Windows 本地
一、一句话结论
这份官方代码不是"下载权重即可推理"的成品 :SLDNet 去雨权重从未公开,必须自训;但训练是自监督的、只需单序列 ,链路已在 3090 上验证跑通(1 epoch / 21 分钟),完整复现成本约 35 小时 / ¥45--70,本轮按性价比主动停止。
二、两条关键事实纠正
2.1 SLDNet 去雨权重不存在(推翻早先判断)
| 原判断 | 实际情况 | 证据 |
|---|---|---|
VDMamba README 的 Google Drive 1D3OpTigv... 是 SLDNet 预训练权重 |
是可视化对比结果图,不是模型 | 该链接位于 VDMamba README 的 # Visual Results 表格中,与 MSCSC / S2VD / MFGAN / MPEVNet 并列;同页第 22--24 行才写明"预训练权重在 checkpoints 文件夹"(指 VDMamba 自身) |
| SLDNet 官方仓库提供去雨权重 | 只提供 FlowNet2 权重 + 数据,去雨网络要自己训 | 官方 README 的 Installation 仅列 FlowNet2 与 NTURain,紧接着即 Train: sh train_video_rain.sh;全仓库无 checkpoints/、无模型发布 |
补充:ICME23 论文说明.md 提到的权重路径 /mnt/hdd/iku/SLDNet_code 是作者内网机器,非公开分发渠道。
结论:所谓"教师模型"必须自行训练得到。
2.2 训练不需要 GT(自监督)
SLDNet 全称 Self-Learned Deraining Network(CVPR 2020)------训练过程完全不需要干净图像:
- 官方训练列表
lists/video_rain_removal_train.txt仅一行b1_Rain(8 字节)→ 单序列训练,这正是官方只要求下载 b1_Rain 的原因; - 数据加载只读
data_NTU/train/b1_Rain/*.jpg; - 损失全部为 warp 一致性与掩膜约束:
overall_loss = loss_fn(frame_pred, frame_target),其中frame_target就是当前带雨帧本身。
三、为什么必须先训练才能推理
3.1 客观限制
"推理"需要被去雨网络的权重,而该权重不存在(见 2.1)→ 物理上不存在"直接推理"这条路径。
3.2 为什么先跑 1 个 epoch 探针
在投入 35 小时前,用 21 分钟验证四件"二值"事项(不跑无法知道):
| 验证项 | 结果 |
|---|---|
| 三个 CUDA 扩展的纯 PyTorch 替换数值是否正确 | ✅ FlowNet2 端到端前向通过,162.52 M 参数与论文一致 |
| FlowNet2 权重能否加载 | ✅ missing keys = 0,unexpected keys = 0 |
| 数据管线是否通 | ✅ 256 帧正确接线(并查出训练集被截断到 20 帧的隐患) |
| 存盘 / 续训机制是否工作 | ✅ model_epoch_1.pth(666 MB)+ opts.pth 自动落盘 |
探针的实际价值:避免盲目投入 35 小时。事后看它确实拦下了"训练集只有 20 帧"这一静默错误(若未发现,等于用 8% 的数据训完全程)。
流程教训:给分阶段方案时应提前量化每一步的时间成本(例:"1 epoch = 21 分钟"),让对方尽早介入决策。
四、技术核心:三个 CUDA 扩展的纯 PyTorch 替换
4.1 为什么必须替换
三个自定义算子(channelnorm / correlation / resample2d)在现代 PyTorch 上编译必然失败,三重叠加:
| 硬伤 | 后果 |
|---|---|
build.py 用 torch.utils.ffi.create_extension |
该 API 在 PyTorch 1.0 即被移除 |
make.sh 写死 -arch=sm_35 |
CUDA 11 起已移除该架构,nvcc 报 Unsupported gpu architecture |
.c 绑定层 #include <THC/THC.h> |
THC 头文件在 PyTorch 1.11 被移除 |
→ 不要走 nvcc 编译路线(会耗费整天且失败)。
4.2 替换实现(逐行对照 .cu 内核语义)
| 算子 | 原始内核语义 | 纯 PyTorch 等价 |
|---|---|---|
| ChannelNorm | output[b,0,y,x] = sqrt(Σ_c x²)。注意:内核忽略 norm_deg 参数,恒为 2 次范数 |
sqrt((x*x).sum(dim=1, keepdim=True)) |
| Resample2d | xf = x + flow[0],yf = y + flow[1],双线性插值 + 下标 clamp 到 [0, W-1] |
grid_sample(mode='bilinear', align_corners=True, padding_mode='border'),其中 g = 2*pixel/(size-1) - 1 |
| Correlation | 零填充 pad_size 后,out[b,tc,oy,ox] = (1/(k²·C))·Σ_c P1[..., oy·s1+md+kr, ox·s1+md+kr]·P2[...+tj·s2, ...+ti·s2];drad=md//s2,dsize=2·drad+1,out=ceil((H+2·pad−2·(kr+md))/s1);corr_type_multiply 传入内核但从未使用 |
双层循环切片 + (p1*sub).sum(dim=1),无需 unfold |
FlowNetC 实际配置 pad=20, k=1, md=20, s1=1, s2=2 → 输出 441 通道,空间尺寸与输入相同。
4.3 验证方式(两层)
- 本地数值对拍 :managed venv 装 CPU 版 torch,用 numpy 朴素实现复刻内核,5 项对拍(含 FlowNetC 真实配置)maxdiff ≤ 4.4e-16(机器精度);
- AutoDL 端到端:FlowNet2 构建 162.52 M 参数(与论文一致)、加载权重零缺失零多余、前向 64×64 输出 shape 正确。
这三个算子无任何可学习参数,替换后
state_dict完全兼容,不影响权重加载。
五、工程坑位清单(全部已解决)
| # | 坑 | 现象 | 处理 |
|---|---|---|---|
| 1 | networks/__init__.py 第 2 行 from .vgg import * |
仓库无 vgg.py → ModuleNotFoundError |
注释该行 |
| 2 | train_video_rain.py 第 30 行硬编码 os.environ["CUDA_VISIBLE_DEVICES"] = "1" |
单卡实例报 device ordinal 错误 | 注释,改用 export CUDA_VISIBLE_DEVICES=0 |
| 3 | 第 195 / 202--204 行初始化 VGG | networks.Vgg16 在缺失的 vgg.py 里;opt.vgg_path 是作者本机路径 |
注释这 4 行------经核实训练循环内从不调用 VGG,无需下载 vgg16 权重 |
| 4 | option.py 覆盖命令行参数 |
第 101--110 行 opts.data_dir = opt.data_dir 等硬编码,命令行传 -data_dir / -model_name / -checkpoint_dir / -crop_size 一律无效 |
不传这些参数,直接改 option.py(其默认值恰与测试脚本路径对齐) |
| 5 | 训练脚本硬编码 data_dir = 'data_NTU/',加载 data_NTU/train/<video> |
SPAC 数据是平铺的 Dataset_Testing_Synthetic/{a1_Rain, a1_GT, ...} |
重组为 data_NTU/train/ 、data_NTU/test/、data_NTU/GT/ 三处软链 |
| 6 | 旧残留目录 data_NTU/train/b1_Rain(仅 20 帧) |
接线逻辑"目录非空即保留"→ 训练只用 8% 数据 | 改为帧数与源不一致即 relink ;prep_step6b_fix_split.sh 专门修此问题 |
| 7 | utils.py 第 15 行反向 import networks |
utils ↔ networks 循环导入 | 导入顺序须为先 utils 后 networks(冒烟脚本已对齐官方顺序) |
| 8 | 训练脚本 import tensorboardX |
未安装 | pip install tensorboardX |
| 9 | 裸装 torchvision |
会连带升降 torch,破坏 1.10.0+cu113 |
必须 --no-deps 且按 torch 版本钉(1.10.0 → 0.11.1);numpy 需 <2 |
| 10 | 服务器装 opencv-python |
libGL.so.1 报错 |
用 opencv-python-headless |
两个"省事"的发现(消除冗余工作):
option.py默认checkpoint_dir='./checkpoints/P401_video_rain_self/'+model_name='derain_self',与test_video_rain.sh要求天然一致;utils.save_model会自动写出opts.pth(第 234--237 行),正是测试脚本第 62--65 行要读的文件 → 无需手工造占位。
六、实测性能与成本
| 指标 | 实测值 |
|---|---|
| 单 epoch(500 iter,batch 8,crop 64) | 21 分钟 |
| 单 iter 墙钟 | 约 2.5 s(数据加载与 GPU 计算并行,日志分别计时各约 2.5 s 属正常) |
| GPU 利用率 / 显存 | 100 % / 8.7 GB(24 GB 显存仅用三分之一) |
| CPU | 14 核,threads=8 |
| 100 epoch 推算 | 约 35 小时 ≈ ¥45--70 |
| 30 epoch 推算 | 约 10.5 小时 ≈ ¥14--21 |
诊断修正记录 :曾判断"数据加载占一半、是瓶颈、调 threads 可省三成"------错误 。GPU 100% 说明算力已吃满,日志中 2.5 s + 2.5 s 是并行发生的两条腿,墙钟由较慢者决定。当前配置已接近该卡吞吐上限,调参空间有限。
可选的加速方向 (未采用):batch 8→16(显存有富余);crop 64→128(但论文原始配置就是 64,改动会偏离复现口径)。建议不改为宜------这是复现任务,配置应与论文一致才有对标意义。
七、资产清单
AutoDL 实例(/root/autodl-tmp/)
| 路径 | 内容 |
|---|---|
CVPR-2020-Self-Rain-Removal/SLDNet_code/ |
已 clone + 打完补丁 + 三个扩展已替换的代码 |
├ pretrained_models/FlowNet2_checkpoint.pth.tar |
软链指向根目录的 620 MB 权重 |
├ checkpoints/P401_video_rain_self/derain_self/model_epoch_1.pth |
666 MB,已验证的训练产物(续训起点) |
├ checkpoints/.../opts.pth |
959 字节,测试脚本必读 |
├ data_NTU/{train,test,GT}/ |
三处软链,各 256 帧 |
└ Dataset_Testing_Synthetic/、Dataset_Testing_RealRain/ |
NTURain 解压后的原始数据 |
prep_step2 ~ step7 系列脚本 |
见下 |
本地(H:\zj\SLDNet Knowledge-Distillation\)
2023 SLDNet会议论文 Collaborative_Spatial-Temporal_Distillation_for_Efficient_Video_Deraining.pdfDistilling the Knowledge in a Neural Network.pdf(Hinton 2015 经典蒸馏原论文)FlowNet2_checkpoint.pth.tar(650,102,505 字节 / 620 MB)Knowledge-Distillation-main.zip(ICME23 代码)NTURain Partly/(SPAC 版,含 rar + spacCNN,663 MB)synthetic_NTURain/+synthetic_NTURain.zip(STVDNet 版,248 MB,额外含真实雨 7 序列)extracted/(ICME23 代码解压结果)
脚本(H:\zj\SLDNet Knowledge-Distillation\ 与工作区)
| 脚本 | 作用 |
|---|---|
prep_step2.sh |
clone 仓库(含 GitHub 镜像兜底)+ 修 vgg bug |
prep_step3_data.sh |
数据定位 / 帧数校验 / 软链 / 生成列表 |
prep_step4_fix_ext.sh |
三个 CUDA 扩展替换为纯 PyTorch + 冒烟测试 |
prep_step5_deps.sh |
只装缺失依赖(保护现有 torch)+ 重跑冒烟 |
prep_step6_train_sldnet.sh |
训练(probe 测速 / full 全量),自动打三处补丁 |
prep_step6b_fix_split.sh |
修训练集截断 + cv2 解码自检 + 跑探针 |
prep_step7_eval.sh |
推理 8 序列 + PSNR/SSIM 评分(含带雨基线对照) |
八、数据说明
NTURain 来源与认证
三种获取渠道(按推荐顺序):
- SPAC 官方 :
github.com/hotndy/SPAC-SupplementaryMaterials------ 用git clone --depth 1 --filter=blob:none --sparse稀疏检出Dataset_Testing_Synthetic,再unrar解压(Linux 上用 7z 解这套 rar 会误报 "Sub items Errors",须用真 unrar); - RainMamba 打包版 :Google Drive
1Yt_0k_OVrBkC1n_F8SeOz_9V160o9UOc,同表有 OneDrive 版; - STVDNet 百度盘 :
https://pan.baidu.com/s/1ltVpPKvdzeh_h-wbubG29A,码sg4n(国内直连,额外含真实雨测试集)。
认证结论(已闭环验证)
本机 synthetic_NTURain.zip(275,859,537 字节)经三重验证 = 正宗官方测试集:
- 大小与本地同名副本一致,且非整数 MiB(无截断特征);
- 八序列帧数 168 / 116 / 125 / 298 / 256 / 250 / 219 / 250,与 SPAC 官方逐序列一致,GT 与 Rain 数量相等;
- 与 SPAC rar 解出的副本
diff -rq全量逐字节比对:16 个序列目录所有 .jpg 完全一致 (唯一差异是 SPAC 版多出 WindowsThumbs.db)。
补充:spacCNN 每序列少 4 帧是该基线方法的固有特征,非数据损坏。
目录结构约定
| 用途 | 路径 | 规模 |
|---|---|---|
| 训练(单序列) | data_NTU/train/b1_Rain |
256 帧 |
| 测试(8 序列) | data_NTU/test/<seq>_Rain |
1634 帧 |
| 真值 | data_NTU/GT/<seq>_GT |
1634 帧 |
| 真实雨(备查) | Dataset_Testing_RealRain/ |
ra1--ra4 / rb1--rb3 共 7 序列 |
训练集(Dataset_Training_Synthetic,t1--t8)尚未下载------L2 蒸馏需要,SLDNet 自监督训练不需要。
九、结果判据与后续路线
9.1 L1 复现的判据
- 训练日志:loss 持续下降、无 NaN、
Batch n/500能推进到 500/500; - 训练后:b1 的 PSNR 从带雨基线(本机实测 27.45 dB / SSIM 0.8995 )提升到 34 dB 上下;
- 论文参考值(CVPR 2020 SLDNet,NTURain 8 序列平均):PSNR 34.89 / SSIM 0.954。
9.2 若继续训练
bash
cd /root/autodl-tmp/CVPR-2020-Self-Rain-Removal/SLDNet_code
sed -i -E "s|^opt\.epoch_max[[:space:]]*=.*|opt.epoch_max = 30|" option.py
nohup python3 -u train_video_rain.py -crop_size 64 > /root/autodl-tmp/train_sldnet_30ep.log 2>&1 &
会自动从 epoch 1 续训(打印 ===> Resuming model from epoch 1)。
训完执行 bash /root/autodl-tmp/prep_step7_eval.sh(自动选最新 epoch 权重)。
9.3 L2 蒸馏(真正的蒸馏训练)
仍需补齐三项,当前不必投入:
- 训练集
Dataset_Training_Synthetic(git sparse-checkout add即可); - 自写
ConvLSTM_sin(约 20 行,照ConvLSTM加时间正弦编码); - 教师改造版权重(ICME23 用的 64 通道 / 7 帧版本)。
9.4 概念澄清:L1 ≠ L2
- L1(本次):教师纯推理 / 自训 ------ 无反向传播、无参数更新、无损失函数、用不到训练集;
- L2:真正的蒸馏训练 ------ 学生先在 GT 上独立训到收敛,再用教师特征微调;监督信号是特征图对齐(3 个 ConvLSTM 桥),而非分类蒸馏的软标签 + 温度。
十、可复用方法论
- 老代码 + 新 PyTorch = 先查扩展编译可行性 。凡是
torch.utils.ffi/THC/THC.h/-arch=sm_35三者任一出现,直接放弃编译路线,改纯 PyTorch 重写算子------扒.cu源码逐行对拍(numpy 朴素实现做基准),比调试 nvcc 报错快一个数量级。 - 下载大文件先看大小是否整数 MiB 。恰为整数 MiB(如 340,787,200 = 325 MiB)是传输截断的典型特征;单个大 tar.gz 走网页下载极易中断,应改传原始小文件或分卷(
split -b 200M),或走 scp。 - 在投入长时任务前先跑最小探针 ,但必须提前量化并告知其时间成本,让决策者早期介入。
- 权重文件用
torch.load先看 keys 再决定。可避免下载数百 MB 冗余权重(如本次 FlowNet2 是否重复下载的判断),也能第一时间发现"链接其实是结果图而非权重"这类误判。 - 多行命令在网页终端会被截断 ------ 一律给单行
&&串联的纯 ASCII 命令,或直接给.sh脚本让其在 JupyterLab 上传后bash xxx.sh。
附:常用命令速查
bash
# 训练(续训,30 epoch)
cd /root/autodl-tmp/CVPR-2020-Self-Rain-Removal/SLDNet_code
sed -i -E "s|^opt\.epoch_max[[:space:]]*=.*|opt.epoch_max = 30|" option.py
nohup python3 -u train_video_rain.py -crop_size 64 > /root/autodl-tmp/train_sldnet_30ep.log 2>&1 &
# 看进度
tail -n 20 /root/autodl-tmp/train_sldnet_30ep.log
grep -E "Rect Loss" /root/autodl-tmp/train_sldnet_30ep.log | tail -n 30
# 查看已存权重
ls -lh checkpoints/P401_video_rain_self/derain_self/
# 推理 + 对分(8 序列)
bash /root/autodl-tmp/prep_step7_eval.sh