DELF Global Features 图像检索全局特征训练全指南:基于 SfM120k 的无人工标注微调与多尺度评估实战
本指南以 TensorFlow 官方 Models 仓库(本仓库 research/delf)中的 CNN 图像检索(CNN Image Retrieval)训练工具箱为主线,完整讲解如何将分类预训练 CNN(ResNet/VGG 等)微调为全局图像检索描述子模型:从 MAC/SPoC/GeM 全局池化原理、Contrastive/Triplet 损失与难负样本挖掘,到 train.py 的完整命令行训练、模型导出为 SavedModel 以及 roxford5k/rparis6k 检索评测。读者读完可掌握一套可直接运行的「无人工标注」图像检索模型训练与部署方案。
一、工具箱背景与要解决的问题
research/delf/delf/python/training/global_features/ 目录下是一套 Python 全局特征(Global Features)训练与测试工具箱,对应两篇核心论文:
- “Fine-tuning CNN Image Retrieval with No Human Annotation”(Radenović, Tolias, Chum,TPAMI 2018)
- “CNN Image Retrieval Learns from BoW: Unsupervised Fine-Tuning with Hard Examples”(Radenović, Tolias, Chum,ECCV 2016)
其核心思想是:不使用任何人工标注(无需 bounding box 或类别标签),仅基于大规模地标检索图像集合上的“图像内/图像间”配对关系(相似/不相似),即可将通用分类 CNN 微调为高质量的全局图像检索特征提取器。训练所用的数据集为 SfM120k(retrieval-SfM-120k)地标图像数据集,微调后的网络输出一个紧凑的全局描述子,直接以**内积(inner product)**度量两幅图像间的相似度。
二、全局检索描述子的工作原理
2.1 全卷积骨干 + 深度局部特征
初始化网络时,以流行的分类预训练网络(如 ResNet 或 VGG)作为 backbone,但丢弃其全连接层,使其成为一个纯全卷积网络。给定尺寸为 [W × H × C] 的输入图像(C 为通道数,W/H 为宽高),网络输出张量 X,形状为 [W' × H' × K],其中 K 为最后一层特征图(feature map)数量。X 可被看作输入图像的深度局部特征集合——每个空间位置对应一个 K 维局部描述子。
2.2 全局池化将局部特征聚合为全局描述子
对深度卷积特征而言,基于全局池化的简单聚合方式通常能取得最好的效果:它速度快、参数量小、过拟合风险低。工具箱支持三种检索系统常用的全局池化(池化层实现):
| 池化 | 全称 / 论文依据 | 实现方式(源码定义) |
|---|---|---|
| MAC | Maximum Activations of Convolutions | 对每个特征图在空间维度上取 tf.reduce_max(见 mac()) |
| SPoC | Sum-pooled convolutional features | 对每个特征图在空间维度上求均值 tf.reduce_mean(见 spoc()) |
| GeM | Generalized Mean Pooling | 广义均值:tf.pow(tf.pow(max(x,eps), p) 的均值, 1/p)(见 gem(),默认 power=3.0) |
以 MAC 为例,聚合后的特征向量由每个特征图的最大激活构成,维度等于 K(即特征图数量)。大多数常见网络(ResNet/VGG)的最终输出维度为 512~2048,因此该图像表示相对紧凑。池化后得到的全局向量再做 L2 归一化。
注:GeM 是平均池化(
power=1)与空间最大池化(power=∞)的一般化形式。power>1时,池化会提升显著特征(salient features)的对比度并聚焦图像中的显著区域。
2.3 白化(Whitening)与再归一化
归一化后的向量可可选地送入全连接(线性)白化层,再做一次 L2 重归一化。最终生成的图像表示可以直接通过向量内积比较两幅图像的相似度。该流程在 GlobalFeatureNet 的 call() 方法中体现为:
feature_extractor(x) → pool → L2 normalize → (whiten → L2 normalize)?
从源码可见,GlobalFeatureNet 是 tf.keras.Model,内部由 feature_extractor(去掉分类头、保留卷积部分的全卷积骨干)、一个全局池化层、L2Normalization 归一化层以及可选的 whiten(tf.keras.layers.Dense)线性层组成。对于多数常用架构,官方已预计算好白化参数(_WHITENING_CONFIG,覆盖 ResNet50/101/152、VGG19 的 gem 配置),训练时可直接下载使用以加速收敛并提升精度;对没有预计算配置的架构或非预训练初始化,白化层则以随机权重初始化(可学习的线性层)。
2.4 支持的骨干架构与输出维度
global_model.py 中 _OUTPUT_DIM 明确列出了支持架构及对应全局描述子维度:
| 架构 | 输出维度 |
|---|---|
| VGG16 / VGG19 | 512 |
| ResNet50 / ResNet101 / ResNet101V2 / ResNet152 | 2048 |
| DenseNet121 / DenseNet169 / DenseNet201 | 1024 / 1664 / 1920 |
| EfficientNetB5 / EfficientNetB7 | 2048 / 2560 |
三、环境安装
使用本套代码前,请按 DELF 官方安装说明 正确安装 DELF 库:直接阅读 INSTALL_INSTRUCTIONS.md。其中最简路径是进入 research/delf/delf/python/training 目录执行一键脚本:
# 在 research/delf/delf/python/training 目录下
bash install_delf.sh
该脚本会依次完成:安装 TensorFlow 2.2(含 GPU 版)→ 从源码安装 TF-Slim → 下载 protoc 并编译 DELF 的 Protocol Buffers → 安装 matplotlib/numpy/scikit-image/scipy/python3-tk 等依赖 → 安装 Object Detection API → 安装 DELF 包。需要 Python 3.6 及以上;protoc 部分目前仅支持 64 位 Linux,其他架构需按说明自行调整(见 install_delf.sh)。
四、训练:命令行参数全解析
4.1 基本用法
进入目录并调用训练脚本(train.py):
cd research/delf/delf/python/training/global_features
python3 train.py [--arch ARCH] [--batch_size N] [--data_root PATH]
[--debug] [--directory PATH] [--epochs N] [--gpu_id ID]
[--image_size SIZE] [--launch_tensorboard] [--loss LOSS]
[--loss_margin LM] [--lr LR] [--momentum M] [multiscale SCALES]
[--neg_num N] [--optimizer OPTIMIZER] [--pool POOL] [--pool_size N]
[--pretrained] [--precompute_whitening DATASET] [--resume]
[--query_size N] [--test_datasets DATASET] [--test_freq N]
[--test_whiten] [--training_dataset DATASET] [--update_every N]
[--validation_type TYPE] [--weight_decay N] [--whitening]
查看所有参数的详细解释(脚本基于 absl flags 实现,故使用 --helpfull):
python3 train.py --helpfull
4.2 核心参数说明(依据 train.py 源码)
下表整理了 train.py 中 flags 定义的参数、默认值与作用:
| 参数 | 默认值 | 可选值 / 说明 |
|---|---|---|
--arch |
ResNet101 |
骨干网络,枚举自 get_standard_keras_models(),如 VGG16/19、ResNet50/101/152、DenseNet、EfficientNet 等 |
--pool |
gem |
全局池化:mac / spoc / gem |
--whitening |
False |
是否在池化后训练可学习白化(线性层) |
--pretrained |
True |
是否用 ImageNet 预训练权重初始化(False 则随机初始化) |
--loss |
contrastive |
损失函数:contrastive / triplet |
--loss_margin |
0.7 |
损失 margin |
--optimizer |
adam |
adam / sgd;配合 --weight_decay(默认 1e-6)、--momentum(默认 0.9)、--lr(默认 1e-6) |
--image_size |
1024 |
训练时图像长边的最大尺寸 |
--batch_size |
5 |
每个 mini-batch 中的 (q, p, n1,…,nN) 元组数量 |
--neg_num |
5 |
每个训练/验证元组中的负样本数量 |
--query_size |
2000 |
每个 epoch 随机抽取的 query 数量 |
--pool_size |
20000 |
难负样本挖掘(hard negative mining)的候选池大小 |
--update_every |
1 |
每 N 个 batch 更新一次模型权重;配合大 batch 使用,等效 update_every × batch_size 的批量 |
--epochs |
100 |
总训练 epoch 数 |
--gpu_id |
0 |
用于训练的 GPU id(映射为 CUDA_VISIBLE_DEVICES) |
--data_root |
data |
训练数据所在目录的绝对路径 |
--directory |
data |
训练网络保存的目录 |
--training_dataset |
retrieval-SfM-120k |
训练数据集(当前枚举仅此一项) |
--validation_type |
None |
验证方式:None / standard / eccv2020 |
--test_datasets |
roxford5k,rparis6k |
逗号分隔的测试集列表(枚举仅支持这两个) |
--test_freq |
5 |
每 N 个 epoch 在测试集上评估一次 |
--multiscale |
[1.] |
测试用多尺度向量,如 1、1,1/2**(1/2),1/2、1,2**(1/2),1/2**(1/2),以逗号分隔字符串传入 |
--precompute_whitening |
None |
用于学习白化的数据集:retrieval-SfM-30k / retrieval-SfM-120k |
--resume |
False |
是否从 logdir 中最新 checkpoint 继续训练 |
--launch_tensorboard |
False |
是否启动 TensorBoard |
--debug |
False |
调试模式(打印每步损失/耗时等) |
4.3 官方标准训练命令
论文标准模型的训练参数(对应 README 中的推荐配置)如下:
python3 train.py \
--directory="DESTINATION_PATH" \
--gpu_ids='0' \
--data_root="TRAINING_DATA_DIRECTORY" \
--training_dataset='retrieval-SfM-120k' \
--test_datasets='roxford5k,rparis6k' \
--arch='ResNet101' \
--pool='gem' \
--whitening=True \
--debug=True \
--loss='triplet' \
--loss_margin=0.85 \
--optimizer='adam' \
--lr=5e-7 --neg_num=3 --query_size=2000 \
--pool_size=20000 --batch_size=5 \
--image_size=1024 --epochs=100 --test_freq=5 \
--multiscale='[1, 2**(1/2), 1/2**(1/2)]'
注意:使用示例训练脚本时,训练/测试所需的数据集与网络权重会自动下载(脚本内置数据集下载器与预训练权重获取逻辑;同时在 train.py 中会校验测试集的 ground truth 文件 gnd_{dataset}.pkl 是否已就位于 data_root,缺失时会提示按 DELF/DELG 说明下载)。
4.4 损失函数底层实现
- Contrastive(对比损失):源自 ECCV 2016 论文,鼓励 query 与正样本距离趋近 0、同时让负样本距离保持在 margin 之上,正负两部分各取 0.5 倍的平方项(见 ranking_losses.py 的
contrastive_loss())。 - Triplet(三元组损失):让所有 query 都比任意负样本更靠近正样本,使用平方距离计算,损失为
max(d_pos − d_neg + margin, 0)求和(见同一文件triplet_loss())。其 margin 默认值不同,标准训练命令中 Triplet 采用0.85。
两者在源码 _LOSS_NAMES = ['contrastive', 'triplet'] 中统一注册,训练时按 --loss 选择。
五、训练逻辑流程(初始化阶段 + 训练阶段)
train.py 主流程与 train_utils.py 辅助函数共同实现如下逻辑:
5.1 初始化阶段
- 检查所需数据集是否已下载,若 data 目录下缺失则自动下载(训练/验证与测试数据集);
- 建立日志与 checkpoint 目录(日志目录与模型存储目录相同);
- 依据用户参数(架构/池化/白化/是否预训练等)初始化模型;
- 依据用户参数定义损失(Contrastive/Triplet);
- 依据用户参数定义优化器(Adam/SGD,含学习率、weight decay、momentum)——源码使用
ExponentialDecay学习率调度(decay_rate 固定为exp(-0.01),decay_steps 为query_size / batch_size),并以 decoupled weight decay(TensorFlow Addons)实现 L2 正则与优化器解耦; - 初始化
CheckpointManager,若设置了--resume则从最新 checkpoint 恢复; - 若设置了
--launch_tensorboard则启动 TensorBoard; - 初始化训练(以及按需的验证)数据集;
- 冻结 BatchNorm 权重更新:由于训练是逐张图像进行的(为降低显存占用),统计量无法按 batch 计算,故选择冻结——即沿用预训练 ImageNet 统计量(源码遍历
feature_extractor将所有BatchNormalization层设为trainable=False); - 训练前先在测试集上评估网络初始性能。
5.2 训练阶段
主训练循环在要求的 epoch 数内依次执行:
- 挖掘难负样本(hard negatives):通过模型一次前向传播在负样本池中寻找当前参数下最难区分的负样本;
- 由每个 epoch 都会变化的生成器构建训练数据集,每个样本由
1 × Positive 图、1 × Query 图、N × Hard negative 图及一个标签数组(Positive=-1、Query=0、Negative=1)组成,N 由--neg_num指定; - 执行一个训练 step 并计算该 epoch 的损失——源码在
train_val_one_epoch()内通过GradientTape逐图前向并concat描述子,随后按target拆分 q/p/n 计算损失与梯度;update_every控制累积梯度若干 batch 后统一apply_gradients; - 若需验证:在验证集上挖掘难负样本(结构与训练集相同),执行一次验证前向并计算损失(无反向传播,无优化器更新);
- 每隔
--test_freq个 epoch 在测试集上评估; - 保存 checkpoint(优化器与模型权重),同时额外保存每个 epoch 的
.h5权重文件(checkpoint_epoch_{epoch}.h5),供后续导出。
5.3 测试评估逻辑
评估函数 test_retrieval()(train_utils.py)会:提取数据库图像与 query 图像(按需裁剪 bounding box)的全局描述子(支持 --multiscale 多尺度,多尺度描述子逐尺度 pow 累加再归一化)→ np.dot(vecs.T, qvecs) 得到相似度分数 → 排序计算 mAP 与 mP@k,并在 TensorBoard 上记录 Easy/Medium/Hard 三档指标。
六、导出训练好的模型
假设训练产物(TensorFlow checkpoint,含 .h5 权重文件)位于 --directory 路径下,即可用导出脚本转换为可用于部署的 SavedModel(脚本实际位于 export_CNN_global.py):
python3 model/export_CNN_global.py \
[--ckpt_path PATH] [--export_path PATH] [--input_scales_list LIST]
[--multi_scale_pool_type TYPE] [--normalize_global_descriptor BOOL]
[--arch ARCHITECTURE] [--pool POOLING] [--whitening BOOL]
注意:--ckpt_path 必须指向包含 .h5 的文件(训练过程中会为每个 epoch 保存 .h5 权重文件)。
导出脚本的关键行为(对应源码):
--arch / --pool / --whitening需与训练时的模型结构一致(whitening布尔值等);- 若不传
--input_scales_list,导出模型会带input_scales输入端点(服务函数ExtractFeatures,输入为图像 + 尺度列表 + 尺度索引),以便部署时动态指定尺度; - 若传入固定尺度列表,则导出
ExtractFeaturesFixedScales服务函数,将尺度硬编码进模型; --multi_scale_pool_type取值None(默认,输出端点global_descriptors,每个尺度分别返回)或average/sum(多尺度描述子合并为一个 1D 向量,输出端点global_descriptor);--normalize_global_descriptor控制是否对全局描述子做 L2 归一化。
七、测试训练好的模型
模型导出后,即可像 DELG 模型一样用它提取全局特征进行检索测试,完整测试流程见 research/delf/delf/python/training 下的 README(“Testing the trained model”一节)。
按标准训练配置(ResNet101 + GeM + triplet,100 个 epoch)训练后,在 Roxford5k 与 RParis6k 数据集上、单尺度评测可获得的检索指标(来自 global_features/README 的记录)为:
>> roxford5k: mAP E: 74.88, M: 58.28, H: 30.4
>> roxford5k: mP@k[1, 5, 10] E: [89.71 84.8 79.07],
M: [91.43 84.67 78.24],
H: [68.57 53.29 43.29]
>> rparis6k: mAP E: 89.21, M: 73.69, H: 49.1
>> rparis6k: mP@k[1, 5, 10] E: [98.57 97.43 95.57],
M: [98.57 99.14 98.14],
H: [94.29 90. 87.29]
其中 E/M/H 分别对应 Oxford/Paris 检索基准中的 Easy / Medium / Hard 三档评估协议。需要说明的是:评测前应显式下载测试集的 ground truth 文件(gnd_roxford5k.pkl / gnd_rparis6k.pkl),并使用 --precompute_whitening 学习白化向量以复现带白化的指标。
八、推荐阅读与后续延伸
- 完整 DELF 框架(局部特征 DELF + 全局特征 DELG)总览:research/delf/README.md
- 全局 + 局部模型联合导出与 DELG 流程:export_global_model.py 与 delg_model.py
- 数据集构建工具(将自建数据整理为 SfM 风格结构):build_image_dataset.py
- 全局特征模型定义与多尺度提取:global_model.py
- 池化层实现:pooling.py;损失函数实现:ranking_losses.py
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00