基于 GLDv2 数据集的 DELF/DELG 图像特征模型完整训练指南
导读
本文完整讲解 TensorFlow 官方模型仓库中 DELF(Deep Local Features)/DELG(Deep Local and Global Features)图像检索模型的训练流程,涵盖环境安装、GLDv2 数据集下载、TFRecord 数据准备、分布式训练、模型导出与端到端验证的每一个环节。读完本文,你将能够从零训练出用于图像检索的局部特征(DELF)或"局部 + 全局"特征(DELG)模型,并把它接入本仓库的特征提取与图像匹配管线完成实战验证。
本文全部操作与结论均以 training/README.md 为骨架,并对照 training 目录下的真实脚本与模型实现展开,所有命令均可直接复制执行。
概览:完整训练流水线
训练一个 DELF/DELG 模型需要依次完成以下四个阶段:
- 安装 DELF Python 库;
- 下载 Google Landmarks Dataset v2(GLDv2)原始图片;
- 将原始图片打包为 TFRecord 训练/验证数据;
- 启动分布式训练。
接下来各小节将逐步展开说明。
前置条件
训练代码位于本仓库 models/research/delf/delf/python/training 目录。克隆 TensorFlow Models 仓库并进入该目录即可:
git clone https://github.com/tensorflow/models.git
cd models/research/delf/delf/python/training
注意:本文所有命令默认在该 training 目录下执行,下文引用的 train.py、build_image_dataset.py、download_dataset.sh 等都位于此目录(即 research/delf/delf/python/training)。
安装 DELF 库
请严格按照 INSTALL_INSTRUCTIONS.md 完成 DELF Python 库的安装。该文档会指导你完成依赖安装(TensorFlow、TensorFlow Probability、absl、numpy、pandas、matplotlib 等)、delf 包的路径配置以及本地算子编译等步骤。训练/数据准备脚本还会用到两个关键依赖:
- TensorFlow Probability(tfp):训练脚本中用于计算注意力分数的百分位统计,见 train.py 中的
_attention_summaries; - pandas:
build_image_dataset.py读取 GLDv2 的 CSV 元数据时依赖它。
此外仓库还提供了一键安装脚本 install_delf.sh,可用于自动完成环境准备。
下载 GLDv2 训练数据
GLDv2 的图片被划分为 TRAIN、INDEX、TEST 三个子数据集。每个子数据集的图片以 *.tar 压缩包组织,并由 *.csv 文件记录对应的训练元数据与版权信息。各子数据集对应的 tar 文件数量如下:
- TRAIN:500 个文件;
- INDEX:100 个文件;
- TEST:20 个文件。
使用脚本下载
运行 download_dataset.sh 即可下载:
bash download_dataset.sh 500 100 20
脚本按顺序接收三个参数:
| 参数 | 含义 | 上限 |
|---|---|---|
| 第 1 个 | 从 TRAIN 数据集下载的图片文件数量 | 500 |
| 第 2 个 | 从 INDEX 数据集下载的图片文件数量 | 100 |
| 第 3 个 | 从 TEST 数据集下载的图片文件数量 | 20 |
下载目录结构
图片会按如下结构存放:
gldv2_dataset/
├── train/ # TRAIN 数据集的原始图片
├── index/ # INDEX 数据集的原始图片
└── test/ # TEST 数据集的原始图片
每个子文件夹(train/、index/、test/)中都会包含:
- 下载好的
*.tar图片压缩包; - 对应的 MD5 校验文件
*.txt; - 解压后的图片内容(图片按文件名第 1、2、3 个字符组织到多级子目录中);
- 与已下载图片对应的训练/版权元数据 CSV 文件。
从 download_dataset.sh 源码可以看到脚本的具体行为:
- 所有文件从
https://s3.amazonaws.com/google-landmark(图片 tar 包)、.../md5sum(校验和)、.../metadata与.../ground_truth(CSV 元数据)拉取; - 每个
download_image_file会下载images_XXX.tar(编号为 3 位数字)及其 md5 文件,并用md5sum(Linux)/md5 -r(macOS)校验通过后才解压; - 脚本以 6 个并发进程批量下载并解压,中途校验失败会直接
exit 1终止,避免留下损坏数据。
存储与带宽提示
由于 GLDv2 体量巨大,完整下载可能耗时长达 12 小时、占用近 1TB 磁盘空间。为节省带宽与磁盘,建议只下载训练必需的 TRAIN 子集——INDEX 与 TEST 合计约 95GB,可以全部跳过。进一步地,下载并解压后可删除 *.tar 压缩包以释放空间。
准备训练数据
数据准备阶段的目标是把 GLDv2 原始图片转换为 TFRecord 文件,并划分出 TRAIN 与 VALIDATION 两个 split。注意:训练集只保留 GLDv2 数据集中的 clean 子集——clean 子集过滤掉了识别/检索歧义与标注噪声过大的图片,其详细定义见引入 GLDv2 的 CVPR'20 论文。
运行 build_image_dataset.py
生成 clean 子集的 TRAIN/VALIDATION TFRecord 文件,需运行 build_image_dataset.py:
python3 build_image_dataset.py \
--train_csv_path=gldv2_dataset/train/train.csv \
--train_clean_csv_path=gldv2_dataset/train/train_clean.csv \
--train_directory=gldv2_dataset/train/*/*/*/ \
--output_directory=gldv2_dataset/tfrecord/ \
--num_shards=128 \
--generate_train_validation_splits \
--validation_split_size=0.2
参数含义如下(源自脚本中的 flags 定义,详见 build_image_dataset.py 源码):
| 参数 | 说明 |
|---|---|
--train_csv_path |
训练集全量元数据 CSV(train.csv),包含 id 与 landmark_id 列 |
--train_clean_csv_path |
clean 子集 CSV(train_clean.csv),表头为 landmark_id;images;提供后将只保留本文件列出的图片 |
--train_directory |
解压后 TRAIN 图片所在目录,通配符覆盖按文件名分层的多级子目录 |
--output_directory |
TFRecord 输出目录 |
--num_shards |
输出分片数,默认 128 |
--generate_train_validation_splits |
是否将训练集划分为 TRAIN 与 VALIDATION 两个 split(bool) |
--validation_split_size |
VALIDATION split 占训练集的比例,默认 0.2,取值须在 (0, 1) 开区间内 |
--seed |
划分 split 时洗牌用的随机种子,便于结果可复现 |
生成的 TFRecord 文件前缀如下:
- TRAIN split:
train-* - VALIDATION split:
validation-*
clean 子集处理与重标注
从源码可以看清 clean 子集的转换逻辑(_get_clean_train_image_files_and_labels):
- 读取
train_clean.csv,该文件按landmark_id分组列出图片 id(images列以空格分隔); - 与
train_directory下实际存在的图片做交集匹配,跳过 CSV 中存在但未下载的图片; - 将原始 landmark id 重映射为从 0 开始的连续标签(
relabeling = {label: index ...}),因为分类头num_classes需要的是密集整数标签; - 重映射规则会被写入输出目录下的
relabeling.csv(表头new_label,old_label),供后续评估对照。
在未启用 clean 过滤的情况下(不传 --train_clean_csv_path),脚本走 _get_all_image_files_and_labels 直接读取 train.csv 中每个图片 id 的 landmark_id。
Train/Validation 划分的均衡策略
启用 --generate_train_validation_splits 后,脚本按 label 分层划分(_build_train_and_validation_splits):先把每个 landmark 下的图片各自洗牌,再按 validation_split_size 比例切分出验证子集,最后把所有子集拼接并再次全局洗牌。这样能保证验证集与训练集在类别构成上尽量一致,避免小类别全部落入某一侧。
TEST split 的生成(可选)
同一脚本也可为训练后评估生成 TEST split 的 TFRecord,只需追加两个参数:
--test_csv_path=gldv2_dataset/train/test.csv \
--test_directory=gldv2_dataset/test/*/*/*/ \
此时 TEST split 的 TFRecord 文件名符合 test-* 模式。源码中 TEST split 的图片不包含 image/class/label 字段(测试集无公开标签),_convert_to_example 仅在 label 非空时才写入该特征。
TFRecord 内部结构
每个 TFRecord 记录是一个序列化的 Example proto,包含的字段(来自 _convert_to_example,即 build_image_dataset.py):
image/height、image/width:图片像素尺寸;image/colorspace:恒为RGB;image/channels:恒为 3;image/format:恒为JPEG;image/id:图片唯一 id(如97c0a12e07ae8dd5);image/encoded:JPEG 编码的图片字节串;image/class/label:整数地标标签,仅训练数据包含。
耗时与空间提示
同样由于 GLDv2 规模庞大,TFRecord 生成阶段最长可能耗时 12 小时、占用约 500GB 磁盘空间。建议预留足够的临时磁盘,并可在生成完成后删除原始图片以回收空间。
运行训练
为了让模型更快收敛,可以先用 ImageNet 预训练权重初始化 ResNet 主干。ImageNet checkpoint 从 http://storage.googleapis.com/delf/resnet50_imagenet_weights.tar.gz 获取,在 Linux 上解压:
curl -Os http://storage.googleapis.com/delf/resnet50_imagenet_weights.tar.gz
tar -xzvf resnet50_imagenet_weights.tar.gz
训练入口脚本的总体设计
训练主脚本为 train.py。从源码看,其核心设计是:
- 使用
tf.distribute.MirroredStrategy支持多 GPU 同步训练(脚本 docstring 明确说明使用分类损失 + MirroredStrategy); - 模型为 ResNet50 主干 + 注意力模块(Attention)+ 可选自动编码器,按 GLD 数据集版本确定分类头类别数(googlelandmarks.py 中
num_classes = {'gld_v1': 14951, 'gld_v2': 203094, 'gld_v2_clean': 81313}); - 数据集由 googlelandmarks.py 的
CreateDataset构建:读 TFRecord → 解码 JPEG →(像素 - 128) / 128归一化 → 训练时做 ImageNet 风格随机裁剪(sample_distorted_bounding_box,面积占原图 0.08~1.0、宽高比 3/4~4/3)并 resize 到 321×321; - 学习率采用线性衰减调度:
lr = initial_lr * (1 - global_step / max_iters); - 优化器为带 momentum 0.9 的 SGD,全局梯度范数裁剪到 10.0;
- 每 1000 步做一次验证与 checkpoint 保存(
CheckpointManager,保留最近 10 份、每 3 小时至少一份),并把 loss/accuracy/attention 统计写入 TensorBoard。
训练 DELF 局部特征模型
假设 TFRecord 已生成到 gldv2_dataset/tfrecord/ 目录,执行如下命令训练并在 gldv2_training 目录输出结果:
python3 train.py \
--train_file_pattern=gldv2_dataset/tfrecord/train* \
--validation_file_pattern=gldv2_dataset/tfrecord/validation* \
--imagenet_checkpoint=resnet50_weights_tf_dim_ordering_tf_kernels_notop.h5 \
--dataset_version=gld_v2_clean \
--logdir=gldv2_training/
注意:
--use_autoencoder默认值为True,因此默认将启用自动编码器联合训练。自动编码器把 block3 特征图压缩到低维(默认 128 维autoencoder_dimensions)再扩展回去,其重建损失(默认权重 10.0reconstruction_loss_weight)参与总损失,具体逻辑见 train.py 的train_step。
训练 DELG 局部 + 全局特征模型
如需训练 DELG 论文中改进的全局特征头,在启动命令中追加 --delg_global_features 参数:
python3 train.py \
--train_file_pattern=gldv2_dataset/tfrecord/train* \
--validation_file_pattern=gldv2_dataset/tfrecord/validation* \
--imagenet_checkpoint=resnet50_weights_tf_dim_ordering_tf_kernels_notop.h5 \
--dataset_version=gld_v2_clean \
--logdir=gldv2_training/ \
--delg_global_features
delg_global_features 置真后,train.py 的 create_model 会实例化 delg_model.py 中的 Delg 模型而非纯 DELF。从 delg_model.py 源码可见其与 DELF 的差异:
- GeM Pooling:全局特征采用 Generalized Mean pooling(
--delg_gem_power,默认 3.0),替代 DELF 的简单平均池化; - FC 白化/Embedding 层:GeM 输出后接一层 2048 维的全连接白化层(
--delg_embedding_layer_dim,默认 2048); - ArcFace 余弦分类器:backbone 的全局分类头不再用普通
Dense,而是对 L2 归一化后的 embedding 与归一化类别权重计算余弦相似度,再乘可学习/固定的缩放因子(--delg_scale_factor_init,默认 45.25 ≈ sqrt(2048)),并在训练时对正确类别施加 ArcFace margin(--delg_arcface_margin,默认 0.1),见cosine_classifier_logits与apply_arcface_margin; - 在训练时 backbone 的 block3 输出会被
tf.stop_gradient截断梯度(防止全局分支梯度干扰局部注意力分支),这一处理在 delf_model.py 的global_and_local_forward_pass中有明确注释,其依据即 DELG 论文。
训练参数速查
下表汇总了 train.py 中定义的、除数据路径外值得关注的核心 flags(默认值均取自源码):
| Flag | 默认值 | 说明 |
|---|---|---|
--debug |
False | 调试模式:单机 eager 执行,batch_size=4、max_iters=100、每步保存 |
--logdir |
/tmp/delf | TensorBoard 日志与 checkpoint 输出目录 |
--dataset_version |
gld_v1 | 枚举 gld_v1 / gld_v2 / gld_v2_clean,决定类别数 |
--seed |
0 | 训练数据集洗牌种子 |
--initial_lr |
0.01 | 初始学习率(线性衰减) |
--batch_size |
32 | 全局 batch size |
--max_iters |
500000 | 最大迭代步数 |
--block3_strides |
True | 是否在 block3 输出增加 stride(影响特征图分辨率) |
--use_augmentation |
True | 是否使用 ImageNet 风格数据增强 |
--imagenet_checkpoint |
None | ResNet 主干 ImageNet 预训练权重;None 则不加载 |
--attention_loss_weight |
1.0 | 注意力损失在总损失中的权重 |
--delg_global_features |
False | 是否训练 DELG(全局 + 局部)模型 |
--image_size |
321 | 输入图片边长 |
--use_autoencoder |
True | 是否联合训练自动编码器做降维 |
--reconstruction_loss_weight |
10.0 | 自动编码器重建损失权重 |
--autoencoder_dimensions |
128 | 自动编码器压缩后的通道数 |
--local_feature_map_channels |
1024 | 局部特征来源层(block3)的通道数 |
损失函数方面,源码显示总损失 = desc_loss + attention_loss_weight * attn_loss + reconstruction_loss_weight * reconstruction_loss,其中全局分支的 desc_loss 使用 SparseCategoricalCrossentropy(from_logits=True)(DELG 模式为 ArcFace 余弦 logits),注意力分支使用独立的 Dense(num_classes) 分类器计算。
超参数建议
为提升收敛速度,以下超参数组合已在一组测试验证过的硬件环境中取得良好效果(其余 flags 保持默认值):
- 8 张 Tesla P100 GPU:
--batch_size=256 --initial_lr=0.01 - 4 张 Tesla P100 GPU:
--batch_size=128 --initial_lr=0.005
即 batch size 减半时初始学习率也应减半,以维持等效的梯度统计特性。
导出训练好的模型
假设训练产物(TensorFlow checkpoint)位于 gldv2_training 目录,训练脚本会同时保存两种产物:CheckpointManager 管理的 delf_tf2-ckpt 检查点,以及可直接 load_weights 的权重文件 gldv2_training/delf_weights(save_format='tf')。导出命令中的 --ckpt_path=gldv2_training/delf_weights 指向后者。
仅 DELF 局部特征模型
适用于只需要局部特征模型的场景:
python3 model/export_local_model.py \
--ckpt_path=gldv2_training/delf_weights \
--export_path=gldv2_model_local
仅 DELG 全局特征模型
适用于只需要全局特征模型的场景:
python3 model/export_global_model.py \
--ckpt_path=gldv2_training/delf_weights \
--export_path=gldv2_model_global \
--delg_global_features
DELG 局部 + 全局联合模型
适用于需要同时提取局部与全局特征的场景:
python3 model/export_local_and_global_model.py \
--ckpt_path=gldv2_training/delf_weights \
--export_path=gldv2_model_local_and_global \
--delg_global_features
从 export_global_model.py 的实现可以看到导出的本质:以 tf.Module 包装重建的模型结构(delg_model.Delg 或 delf_model.Delf),调用 load_weights(checkpoint_path) 载入权重后,用 tf.saved_model.save 导出,serving_default 签名提供 ExtractFeatures/ExtractFeaturesFixedScales 两个前向函数。导出过程关键点:
- 导出时
block3_strides=False,与训练配置解耦; - 模型输入为
uint8的[None, None, 3]图像,可在服务端灵活处理任意分辨率,并通过input_scales支持多尺度金字塔。
Kaggle 兼容的全局特征模型(特殊格式)
如需导出符合 2020 Landmark Retrieval 挑战赛要求的全局特征模型,可执行:
python3 model/export_global_model.py \
--ckpt_path=gldv2_training/delf_weights \
--export_path=gldv2_model_global \
--input_scales_list=0.70710677,1.0,1.4142135 \
--multi_scale_pool_type=sum \
--normalize_global_descriptor
注意:该命令对参赛很有帮助,但它导出的是 Kaggle 所需格式,与本代码库内部使用的格式不同——按此方式导出的模型无法直接用于 DELG 指令文档 中的命令。若要导出与代码库兼容的格式,请使用上文"仅 DELG 全局特征模型"一节中的命令。
此处三个参数的作用(依据 export_global_model.py 源码):
--input_scales_list:把多尺度输入缩放列表(1/√2 ≈ 0.7071、1.0、√2 ≈ 1.4142)硬编码进模型,导出ExtractFeaturesFixedScales签名,无需运行时动态传入尺度;--multi_scale_pool_type:取值None/average/sum。None时每个尺度分别输出global_descriptors;设为sum(或average)时对各尺度描述子池化,输出单一 1D 向量global_descriptor;--normalize_global_descriptor:是否对最终全局描述子做 L2 归一化。
测试训练好的模型
测试训练好的局部特征模型
导出模型后,可用同一地标的两张图片做"提取特征 → 匹配验证"的冒烟测试,确认模型确实学到了可判别的地标特征。
第一步:下载 Oxford buildings 数据集
mkdir data && cd data
wget http://www.robots.ox.ac.uk/~vgg/data/oxbuildings/oxbuild_images.tgz
mkdir oxford5k_images oxford5k_features
tar -xvzf oxbuild_images.tgz -C oxford5k_images/
cd ../
echo data/oxford5k_images/hertford_000056.jpg >> list_images.txt
echo data/oxford5k_images/oxford_000317.jpg >> list_images.txt
第二步:改写 DELF 提取配置
复制 delf_config_example.pbtxt 作为提取配置的起点,然后做三处修改:
- 将
model_path指向导出模型所在目录(本例为gldv2_model_local); - 在根层级新增
is_tf2_exported属性并置为true; - 将
delf_local_config内的use_pca置为false。
修改后的文件应类似:
model_path: "gldv2_model_local"
image_scales: .25
image_scales: .3536
image_scales: .5
image_scales: .7071
image_scales: 1.0
image_scales: 1.4142
image_scales: 2.0
is_tf2_exported: true
delf_local_config {
use_pca: false
max_feature_num: 1000
score_threshold: 100.0
}
配置项语义对照源码(delf_config.proto 与原始 delf_config_example.pbtxt):
model_path:导出模型(SavedModel)目录;image_scales:图像金字塔多尺度列表,逐尺度提取特征可提升尺度鲁棒性;is_tf2_exported: true:声明加载的是 TF2 导出模型;delf_local_config.max_feature_num:每张图最多保留的特征点数量(默认 1000);delf_local_config.score_threshold:特征点注意力分数阈值(默认 100.0),低于阈值的点被过滤;use_pca: false:新训练的模型不做 PCA 降维,故需关闭该开关。
第三步:提取两幅图的 DELF 特征
python3 ../examples/extract_features.py \
--config_path delf_config_example.pbtxt \
--list_images_path list_images.txt \
--output_dir data/oxford5k_features
第四步:特征匹配
python3 ../examples/match_images.py \
--image_1_path data/oxford5k_images/hertford_000056.jpg \
--image_2_path data/oxford5k_images/oxford_000317.jpg \
--features_1_path data/oxford5k_features/hertford_000056.delf \
--features_2_path data/oxford5k_features/oxford_000317.delf \
--output_image matched_images.png
生成的 matched_images.png 效果应与下图类似:两幅展示同一地标(牛津 Hertford 学院)的图像,通过训练后模型提取的局部特征找到匹配点对并用连线标出,从而直观验证特征的正确性。
测试训练好的全局(或全局 + 局部)特征模型
请遵循 DELG 指令文档 完成全局特征模型的检索测试,唯一需要修改的是:在特征提取时传入指向新训练模型的 delf_config_path。按照 delf_config.proto 中的定义,你需要根据实际使用哪种特征模态正确设置 use_local_features 与 use_global_features;同时记得将 is_tf2_exported 设为 true。
关于纯全局特征(GeM + DELG 头)的训练与评估,仓库还提供了一套不依赖 GLDv2 分类监督的备选流程,可参考 global_features/README.md,其中覆盖了 Siamese/Triplet 式训练、多分辨率评估、Revisited Oxford/Paris 指标评测等进阶内容。
补充说明
- DELF 与 DELG 的论文依据:DELF 模型对应论文 Large-Scale Image Retrieval with Attentive Deep Local Features,核心思路是用注意力对局部特征加权、丢弃背景区域;DELG 模型对应论文 Unifying Deep Local and Global Features for Image Search,在同一网络中以 GeM Pooling + 白化层 + ArcFace 训练全局头、以注意力机制训练局部头,从而统一两类特征。两篇论文的 arxiv 编号分别见 delf_model.py 与 delg_model.py 的 docstring。
- 目录导航:本目录下的其他说明文档包括数据下载与转换细节 datasets/google_landmarks_dataset/README.md、训练后的实际检索流程 DELG_INSTRUCTIONS.md、以及特征提取/匹配最小示例脚本 examples/extract_features.py 与 examples/match_images.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 StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
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
