首页
/ DELF Global Features 图像检索全局特征训练全指南:基于 SfM120k 的无人工标注微调与多尺度评估实战

DELF Global Features 图像检索全局特征训练全指南:基于 SfM120k 的无人工标注微调与多尺度评估实战

2026-09-06 18:26:05作者:庞眉杨Will

本指南以 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)?

从源码可见,GlobalFeatureNettf.keras.Model,内部由 feature_extractor(去掉分类头、保留卷积部分的全卷积骨干)、一个全局池化层、L2Normalization 归一化层以及可选的 whitentf.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.pyflags 定义的参数、默认值与作用:

参数 默认值 可选值 / 说明
--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.] 测试用多尺度向量,如 11,1/2**(1/2),1/21,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.pycontrastive_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 初始化阶段

  1. 检查所需数据集是否已下载,若 data 目录下缺失则自动下载(训练/验证与测试数据集);
  2. 建立日志与 checkpoint 目录(日志目录与模型存储目录相同);
  3. 依据用户参数(架构/池化/白化/是否预训练等)初始化模型;
  4. 依据用户参数定义损失(Contrastive/Triplet);
  5. 依据用户参数定义优化器(Adam/SGD,含学习率、weight decay、momentum)——源码使用 ExponentialDecay 学习率调度(decay_rate 固定为 exp(-0.01),decay_steps 为 query_size / batch_size),并以 decoupled weight decay(TensorFlow Addons)实现 L2 正则与优化器解耦;
  6. 初始化 CheckpointManager,若设置了 --resume 则从最新 checkpoint 恢复;
  7. 若设置了 --launch_tensorboard 则启动 TensorBoard;
  8. 初始化训练(以及按需的验证)数据集;
  9. 冻结 BatchNorm 权重更新:由于训练是逐张图像进行的(为降低显存占用),统计量无法按 batch 计算,故选择冻结——即沿用预训练 ImageNet 统计量(源码遍历 feature_extractor 将所有 BatchNormalization 层设为 trainable=False);
  10. 训练前先在测试集上评估网络初始性能。

5.2 训练阶段

主训练循环在要求的 epoch 数内依次执行:

  1. 挖掘难负样本(hard negatives):通过模型一次前向传播在负样本池中寻找当前参数下最难区分的负样本;
  2. 每个 epoch 都会变化的生成器构建训练数据集,每个样本由 1 × Positive 图、1 × Query 图、N × Hard negative 图 及一个标签数组(Positive=-1、Query=0、Negative=1)组成,N 由 --neg_num 指定;
  3. 执行一个训练 step 并计算该 epoch 的损失——源码在 train_val_one_epoch() 内通过 GradientTape 逐图前向并 concat 描述子,随后按 target 拆分 q/p/n 计算损失与梯度;update_every 控制累积梯度若干 batch 后统一 apply_gradients
  4. 若需验证:在验证集上挖掘难负样本(结构与训练集相同),执行一次验证前向并计算损失(无反向传播,无优化器更新);
  5. 每隔 --test_freq 个 epoch 在测试集上评估;
  6. 保存 checkpoint(优化器与模型权重),同时额外保存每个 epoch 的 .h5 权重文件(checkpoint_epoch_{epoch}.h5),供后续导出。

5.3 测试评估逻辑

评估函数 test_retrieval()train_utils.py)会:提取数据库图像与 query 图像(按需裁剪 bounding box)的全局描述子(支持 --multiscale 多尺度,多尺度描述子逐尺度 pow 累加再归一化)→ np.dot(vecs.T, qvecs) 得到相似度分数 → 排序计算 mAPmP@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)训练后,在 Roxford5kRParis6k 数据集上、单尺度评测可获得的检索指标(来自 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 学习白化向量以复现带白化的指标。

八、推荐阅读与后续延伸

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388