基于 DINOv2 微调的字体分类系统
特殊字体(手写体、衬线体、等宽体等):识别准确率很高,Top-1 置信度可达 88%~98%普通无衬线体(Roboto、Inter、Overpass 等):风格相近易混淆,Top-1 可能不准,但正确答案通常出现在 Top-1~Top-5 内使用建议:业务上可返回 Top-5 供用户选择,或对 Top-1 置信度低于某阈值时展示 Top-K 候选决策选择权衡数据来源PIL 渲染生成可控且无标注成本
字体分类 ViT 微调项目
基于 DINOv2 微调的字体分类系统,能够识别 214 种字体。从数据生成、模型训练到推理部署全流程自研。
训练状态:已完成(GPU: NVIDIA L40S,20 epochs)
最终训练结果
服务器与训练配置
| 项目 | 配置 |
|---|---|
| GPU | NVIDIA L40S |
| 显存 | 44.4 GB |
| 模型路径 | models/dinov2-base(本地离线) |
| 混合精度 (AMP) | 开启 |
| batch_size | 64 |
| 学习率 (lr) | 3e-5 |
| 权重衰减 (wd) | 0.01 |
| Label Smoothing (ls) | 0.1 |
| 最大 epochs | 20 |
| Early Stopping | patience=5 |
数据与迭代
| 项目 | 数值 |
|---|---|
| 训练样本 | 63,690 |
| 验证样本 | 11,210 |
| 每 epoch batch 数 | 996 (63,690 ÷ 64) |
| 每 epoch 耗时 | ~60s |
| 总训练耗时 | ~20 分钟 (20 epochs) |
| 显存占用 | ~1.8 GB |
训练结果
| 指标 | 数值 |
|---|---|
| val_acc (Top-1) | 67.74% |
| train_acc | 86.1% |
| train_loss (Epoch 20) | 1.39 |
| val_loss (Epoch 20) | 1.94 |
推理测试结果
测试样本
| 测试样本 | 字体类型 | Top-1 预测 | 置信度 | 结果 |
|---|---|---|---|---|
| Overpass_Regular/0036.png | 几何无衬线 | Figtree_Regular | 60.15% | Top-2 正确(Overpass 6.49%) |
| Dancing_Script_Regular/0022.png | 手写体 | Dancing_Script_Regular | 88.12% | ✅ Top-1 正确 |
| Playfair_Display_Regular/0065.png | 衬线体 | Playfair_Display_Regular | 97.79% | ✅ Top-1 正确 |
推理表现总结
- 特殊字体(手写体、衬线体、等宽体等):识别准确率很高,Top-1 置信度可达 88%~98%
- 普通无衬线体(Roboto、Inter、Overpass 等):风格相近易混淆,Top-1 可能不准,但正确答案通常出现在 Top-1~Top-5 内
- 使用建议:业务上可返回 Top-5 供用户选择,或对 Top-1 置信度低于某阈值时展示 Top-K 候选
一、技术选型与理由
Backbone: DINOv2-base (facebook/dinov2-base)
- Meta 开源的自监督视觉基础模型,在 1.42 亿张图片上预训练
- 相比 ViT-ImageNet 分类预训练,DINOv2 学到的是更通用的视觉特征,对字体笔画、结构等细粒度特征有更好的表征能力
- 86M 参数,推理速度适中,适合部署
为什么不用 CLIP / ResNet / 传统 CNN?
- CLIP:偏向图文对齐,字体分类是纯视觉任务,DINOv2 更合适
- ResNet:对全局特征建模较弱,ViT 的 self-attention 能捕捉字体的全局结构特征(如字体的统一风格)
- 传统 CNN:需要从头训练,数据需求量大得多
二、数据工程
数据来源
从 Google Fonts 批量下载 216 种字体文件(含 Regular/Bold 变体),使用 PIL 渲染生成训练数据。
数据规模
- 每种字体生成 350 张图片
- 总计约 75,000 张(训练 63,690 + 验证 11,210)
- 训练/验证拆分比例:85% / 15%
- 多进程并行生成,利用全部 CPU 核心
tests/font_vit/
├── fonts/ ✅ 216 个字体文件
├── dataset/ ✅ 已生成
│ ├── train/ ✅ 214 个类别
│ └── val/ ✅ 216 个类别
├── output/ ✅ 训练输出
│ ├── best_model.pt ✅ 347MB
│ └── class_mapping.json ✅
├── download_fonts.py ✅
├── generate_dataset.py ✅
├── train.py ✅ (已修复)
└── inference.py ✅
数据增强策略
| 增强方式 | 目的 |
|---|---|
| 随机文本内容(中英文混合) | 避免模型记忆文本内容而非字体特征 |
| CJK 字体检测(渲染测试) | 自动判断字体是否支持中文,避免生成空白样本 |
| 随机字号(18-90px) | 适应不同尺度 |
| 随机背景色 + 对比度文字色 | 模拟真实场景 |
| 随机位置偏移(非居中) | 避免位置偏见 |
| 轻微旋转(-5°~5°) | 模拟拍照/扫描倾斜 |
| 高斯模糊(20%概率) | 模拟低质量图片 |
| 随机噪声(15%概率) | 增强鲁棒性 |
| 对比度抖动(10%概率) | 模拟不同显示条件 |
三、模型架构与训练策略
微调策略(参数高效)
DINOv2-base (12层 Transformer Encoder)
├── Layer 0-9: 冻结(不参与训练)
├── Layer 10-11: 解冻(微调)
├── LayerNorm: 解冻
└── Classifier: 新增全连接层(768 → 214)
可训练参数仅占 16.7%(14.5M / 86.9M),大幅减少计算量和过拟合风险。
优化器与调度
| 组件 | 配置 | 理由 |
|---|---|---|
| 优化器 | AdamW | 适合 Transformer,自带权重衰减解耦 |
| 学习率 | 3e-5 | 微调经验值,太大破坏预训练特征 |
| 权重衰减 | 0.01 | 防止过拟合 |
| LR Schedule | Cosine Annealing | 训练后期平滑降低学习率,稳定收敛 |
| Warmup | 前 2 个 epoch 线性预热 | 避免初期大学习率破坏预训练权重 |
| Label Smoothing | 0.1 | 软化标签,防止模型过于自信,提升泛化 |
| 梯度裁剪 | max_norm=1.0 | 防止梯度爆炸 |
Early Stopping
验证集准确率连续 5 个 epoch 无提升则停止,保存最优模型。
四、训练过程与结果
训练环境
- GPU 训练:NVIDIA L40S, 44GB 显存, batch_size=64, AMP 混合精度
Gpu
python train_gpu.py --model models/dinov2-base
Epoch 1 [950/996] loss=4.7552 acc=0.0665 lr=1.43e-05
Epoch 1/20 train_loss=4.7122 train_acc=0.0711 val_loss=3.8577 val_acc=0.1580 lr=1.50e-05 time=60.1s gpu_mem=1.8GB
★ 新最优! val_acc=0.1580
Epoch 2 [150/996] loss=3.7191 acc=0.1862 lr=1.73e-05
这是因为 batch_size 不同:
环境
batch_size
训练样本
每 epoch batch 数
Mac (train.py)
16
63,690
63,690 ÷ 16 = 3981
GPU (train_gpu.py)
64
63,690
63,690 ÷ 64 = 996
GPU 版把 batch_size 设成 64,所以每个 epoch 的 batch 数约为 Mac 的 1/4,训练会更快。
- Mac 训练:M4, MPS(需
PYTORCH_ENABLE_MPS_FALLBACK=1)
★ 新最优! val_acc=0.6417
Epoch 10 [3900/3981] loss=1.6665 acc=0.7438 lr=1.77e-05
Epoch 10/20 train_loss=1.6662 train_acc=0.7440 val_loss=1.9765 val_acc=0.6525 lr=1.76e-05 time=1231.1s
★ 新最优! val_acc=0.6525
收敛曲线(GPU 最终)
| Epoch | train_acc | val_acc | train_loss | 观察 |
|---|---|---|---|---|
| 1 | 9.5% | 18.7% | 4.47 | warmup 阶段 |
| 2 | 27.1% | 29.5% | 3.27 | 快速学习 |
| 3 | 38.6% | 42.8% | 2.82 | 持续上升 |
| 20 | 86.1% | 67.74% | 1.39 | 训练完成 |
train_acc > val_acc 约 18%,存在一定过拟合;相似无衬线体易混淆。
五、推理流程
输入图片 → Resize(224×224) → Normalize → DINOv2 Backbone → Classifier → Top-K 预测
加载类别映射文件 (class_mapping.json),输出 Top-5 字体名称及置信度。
六、关键设计决策总结
| 决策 | 选择 | 权衡 |
|---|---|---|
| 数据来源 | PIL 渲染生成 | 可控且无标注成本,但与真实截图有 domain gap |
| 预训练模型 | DINOv2 (自监督) | 通用特征优于分类预训练,适合细粒度任务 |
| 微调范围 | 只训练最后 2 层 | 参数效率高,防止过拟合,训练快 |
| LR 策略 | Warmup + Cosine | 保护预训练权重,后期稳定收敛 |
| 损失函数 | CrossEntropy + Label Smoothing | 软标签提升泛化,对相似字体更友好 |
七、项目结构
font_vit/
├── fonts/ # 216 个字体文件 (.ttf)
├── models/
│ └── dinov2-base/ # 本地 DINOv2 模型(离线推理用)
├── dataset/
│ ├── train/ # 训练集(214 类,63,690 张)
│ └── val/ # 验证集(214 类,11,210 张)
├── output/
│ ├── best_model.pt # 最优模型权重(347MB)
│ ├── final_model.pt # 最终模型权重
│ ├── class_mapping.json # 类别索引映射
│ └── training_history.json # 训练历史记录
├── download_fonts.py # 从 Google Fonts 批量下载字体
├── get_model.py # 下载 DINOv2 到本地(离线/GPU 用)
├── generate_dataset.py # PIL 渲染生成训练数据
├── train.py # 训练脚本(Mac MPS)
├── train_gpu.py # 训练脚本(GPU CUDA)
├── inference_mac.py # 推理脚本(Mac MPS)
└── inference_gpu.py # 推理脚本(GPU CUDA)
八、使用方式
# 1. 下载字体
python download_fonts.py
# 2. 生成训练数据
python generate_dataset.py
# 3. 下载 DINOv2 到本地(GPU 服务器离线用)
python get_model.py
# 4. 训练
python train_gpu.py --model models/dinov2-base # GPU
python train.py # Mac MPS
# 5. 推理预测
python inference_gpu.py <image_path> # GPU 服务器
python inference_mac.py <image_path> # Mac
九、可延伸方向
- 对比学习:用 triplet loss 学习字体 embedding,支持相似字体检索
- Hard Negative Mining:针对高度相似的字体对(如同一字体 Regular vs Bold)加强训练
- ONNX / TorchScript 导出:部署优化,减少推理延迟
- FAISS 向量检索:提取字体 embedding 构建索引,支持以图搜字体
- 混合精度训练:加速训练,降低显存占用
- TensorBoard 可视化:实时监控训练曲线
更多推荐


所有评论(0)