13 地址对齐 — 序列标注 + 数据库对齐全栈项目
学习理念:这是第 12 章的"升级版"——从整句分类(一个标题→一个类别)升级到序列标注(每个字→一个标签),从手写 Trainer 升级到 HuggingFace 官方 Trainer,并新增了数据库查询 + 编辑距离匹配的后处理环节。30 分钟通过文档 + 图表吃透,有疑问用 AI 插件问答式解惑。
乐高定位:第 10 章(从零训练)→ 第 12 章(BERT 整句分类)→ 第 13 章(BERT 序列标注 + 数据库对齐) → 第 19 章(大型项目)
本节 AI 替代率:~72% | 人工干预率:~28%
| 角色 | 能力范围 |
|---|---|
| 🤖 AI 擅长 | 生成序列标注代码、HF Trainer 配置、FastAPI 路由、编辑距离匹配 |
| 👤 人类需理解 | BIO 标签体系的设计、序列标注的标签对齐原理、数据库对齐的流程设计 |
技术栈健康度速览(OPC 7.4)
🟢🟡🟠🔴 = 学习优先级 | 🔥🟢⏳⚠️💀 = 技术健康度
| 技术 | 健康度 |
|---|---|
BertForTokenClassification | 🔥 巅峰 |
HuggingFace 官方 Trainer | 🔥 巅峰 |
HF DataCollatorForTokenClassification | 🔥 巅峰 |
| FastAPI + Pydantic | 🔥 巅峰 |
| RapidFuzz 编辑距离 | 🟢 稳定 |
| MySQL + pymysql | 🟢 稳定 |
| BIO/BIOSO 标注体系 | 🟢 稳定 |
一、项目全景(3 分钟看懂)
解决什么问题
电商/物流场景的地址信息通常是非结构化文本:
输入:"高霞 13139243427北京市市辖区东风街道27号楼"
期望输出(结构化):
{
"name": "高霞",
"phone": "13139243427",
"prov": "北京市",
"city": "北京市",
"district": "市辖区",
"town": "东风街道",
"detail": "27号楼"
}两阶段管线
25 种 BIO 标签
采用 BIOSO 标注体系(B=开始, I=内部, E=结束, S=单字, O=无关):
| 实体类型 | 标签示例 | 含义 |
|---|---|---|
| 省 | B-prov, I-prov, E-prov | 省份/直辖市 |
| 市 | B-city, I-city, E-city | 城市 |
| 区 | B-district, I-district, E-district, S-district | 区/县 |
| 镇 | B-town, I-town, E-town, S-town | 镇/街道 |
| 详情 | B-detail, I-detail, E-detail, S-detail | 门牌号/楼栋 |
| 姓名 | B-name, I-name, E-name | 收件人 |
| 电话 | B-phone, I-phone, E-phone | 手机号/座机 |
| 无关 | O | 空格等 |
二、🔥 第 12 章 → 第 13 章升级对比
这是本文档最有价值的部分——看"升级了什么"比"写了什么代码"重要 10 倍。
| 对比维度 | 第 12 章(商品分类) | 第 13 章(地址对齐) | 为什么升级 |
|---|---|---|---|
| 任务类型 | 整句分类 | 序列标注 | 序列标注能提取实体位置,不是只给一个类别 |
| 模型 | AutoModelForSequenceClassification | BertForTokenClassification | 输出每个 token 的标签分布 |
| Trainer | 手写 Trainer 类(学习用途) | HF 官方 Trainer | 生产级用法,自带 checkpoint/分布式/混合精度 |
| 数据对齐 | DataCollatorWithPadding | DataCollatorForTokenClassification | 序列标注需要把标签和子词对齐([CLS]/[SEP] 位设 -100) |
| 后处理 | 无 | MySQL 查询 + RapidFuzz | 把模型输出和真实数据库做匹配,保证结果准确 |
| 预训练模型 | bert-base-chinese | roberta-small-wwm | RoBERTa 训练更充分,small 版本推理更快 |
序列标注 vs 整句分类(代码级对比)
python
# 第 12 章:整句分类 — 输出一个类别
model = AutoModelForSequenceClassification.from_pretrained(..., num_labels=12)
outputs = model(**inputs) # 输出: (batch_size, 12)
pred = torch.argmax(logits, dim=-1) # 取概率最高的类别
# 第 13 章:序列标注 — 每个字输出一个标签
model = BertForTokenClassification.from_pretrained(..., num_labels=25)
outputs = model(**inputs) # 输出: (batch_size, seq_len, 25)
preds = torch.argmax(logits, dim=-1) # 每个位置取概率最高的标签三、🔥 核心流程设计
3.1 数据流
3.2 标签对齐(最难理解的部分)
序列标注的训练数据中,标签需要和分词后的 token 一一对应。但分词器会把一个词切成多个子词,需要 word_ids() 来对齐:
python
text = "北京市" # 3 个字 → 3 个 token
labels = ["B-city", "I-city", "E-city"]
# 分词后(假设 "市" 被切成两个子词)
tokenized = tokenizer(["北", "京", "市"], is_split_into_words=True)
# tokens: [CLS] 北 京 市 [SEP]
# word_ids: None 0 1 2 None
# 标签对齐:word_ids 相同的 token 继承同一个标签
# [CLS] → -100(忽略)| 北 → B-city | 京 → I-city | 市 → E-city | [SEP] → -100
labels = [
label_seq[j] if j is not None else -100
for j in tokenized.word_ids(batch_index=i)
]3.3 地址对齐
python
def address_check(text, address, mysql_config):
# 1. 用 NER 提取的省/市/区/镇 去 MySQL 查 full_name
# 比如 address["prov"]="北京" → SELECT full_name FROM region
# WHERE region_type=2 AND name LIKE '%北京%'
# 2. 查询结果构造成树
address_tree = {"北京市": {"市辖区": ["东风街道", ...]}}
# 3. 展平成 ["北京市", "市辖区", "东风街道"] 形式的列表
address_chains = flatten_address_tree(address_tree)
# 4. 对每个候选计算编辑距离
scores = [fuzz.ratio(candidate, original_text) for candidate in address_chains]
# 5. 取分数最高的
return address_chains[scores.index(max(scores))]四、🟢 关键技术点
4.1 HF 官方 Trainer(对比第 12 章手写 Trainer)
第 12 章手写 Trainer 是为了理解原理,第 13 章用 HF 官方 Trainer 才是生产级做法:
python
training_args = TrainingArguments(
output_dir="finetuned",
num_train_epochs=20,
per_device_train_batch_size=64,
learning_rate=2e-5,
warmup_ratio=0.2, # 学习率预热
lr_scheduler_type="cosine", # 余弦退火调度
weight_decay=0.01, # 权重衰减
bf16=True, # 混合精度(自动检测硬件支持)
eval_strategy="steps",
save_strategy="steps",
eval_steps=500,
save_steps=500,
metric_for_best_model="eval_f1", # 自动选最佳模型
greater_is_better=True,
save_total_limit=1, # 只保留最新检查点
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=valid_dataset,
compute_metrics=compute_metrics,
data_collator=DataCollatorForTokenClassification(tokenizer),
)
trainer.train()手写 Trainer vs HF Trainer 的区别:
| 功能 | 第 12 章手写 | 第 13 章 HF 官方 |
|---|---|---|
| 早停 | 手写 _should_stop | TrainingArguments 配置 |
| 混合精度 | 手写 scaler | bf16=True 一行开启 |
| 检查点 | 手写 dict save/load | 内置,自动保存恢复 |
| 学习率调度 | 无 | warmup + cosine + weight_decay |
| 最佳模型选择 | 手写逻辑 | metric_for_best_model 自动选 |
4.2 RapidFuzz 编辑距离
python
from rapidfuzz import fuzz
# 编辑距离:把一个字符串变成另一个需要的最少操作次数
# 插入、删除、替换各算 1 步
# RapidFuzz 的 ratio() 返回 0~100 的相似度分数
score = fuzz.ratio("北京市市辖区东风街道", "北京市市辖区东风街道")
# score = 100.0(完全一致)
score = fuzz.ratio("北京市市辖区东风街道", "北京市拱辰街道")
# score = 较低(不匹配)核心类图(补 §18 标准)
API 调用时序(补 §18 标准)
🛠️ 从"看代码"到"跑起来"
本文档定位是理解项目架构。如果要在本地跑起来:
1️⃣ 环境检测
把下面的提示词发给 AI:
text
我的环境是 [Windows/CUDA 12.x/Python 3.12],
这个项目的依赖有 torch、transformers、datasets、fastapi、pydantic、uvicorn、
pymysql、rapidfuzz、scikit-learn,
帮我生成兼容的 requirements.txt,torch 版本要和 CUDA 匹配。2️⃣ MySQL 配置
本项目依赖 MySQL 地区表。发给 AI:
text
我有一个 region.sql 文件(MySQL 地区数据),
本地 MySQL root/123456 在 3306 端口,
帮我写一个初始化脚本:创建 region 数据库并导入 region.sql。3️⃣ 一键部署
发给 AI:
text
根据这个项目的入口:
python token_classification.py(训练,执行 train() 函数)
python app.py(启动 FastAPI,端口 8089)
帮我生成 Dockerfile + docker-compose.yml + .dockerignore,
基础镜像用 pytorch/pytorch:2.2-cuda12.1,
需要包含 MySQL 服务。4️⃣ 跑通验证
bash
# Step 1: 训练(如果有 GPU)
python -c "from token_classification import train; from config import ROBERTA_SMALL; train(ROBERTA_SMALL)"
# Step 2: 测试推理
python address_alignment.py
# Step 3: 启动服务
python app.py
# 浏览器访问 http://localhost:80895️⃣ 代码审查
发给 AI:
text
帮我审查 token_classification.py 中的训练流程:
1. DataCollatorForTokenClassification 的标签对齐是否正确?
2. compute_metrics 中 -100 掩码的处理是否正确?
3. 最佳模型保存逻辑有没有 bug?技术栈深度评估
| 编号 | 技术 | 健康度 | 说明 |
|---|---|---|---|
| T1 | BertForTokenClassification | 🔥 巅峰 | 序列标注的标准做法 |
| T2 | HF 官方 Trainer | 🔥 巅峰 | 生产级训练器,相比手写 Trainer 是正式升级 |
| T3 | DataCollatorForTokenClassification | 🔥 巅峰 | 序列标注的标签对齐方案 |
| T4 | FastAPI + Pydantic | 🔥 巅峰 | 现代 API 框架 |
| T5 | HF Datasets | 🔥 巅峰 | 数据处理标准库 |
| T6 | RapidFuzz | 🟢 稳定 | 轻量级模糊匹配 |
| T7 | MySQL + pymysql | 🟢 稳定 | 传统数据库查询 |
| T8 | BIO/BIOSO 标注体系 | 🟢 稳定 | 序列标注的标注标准 |
海外对标
| 企业 | 应用场景 | 技术方案 |
|---|---|---|
| 美团 | 地址解析 + POI 匹配 | BERT + CRF 序列标注 + 数据库对齐 |
| 菜鸟/京东物流 | 快递地址标准化 | 序列标注 + 地址库匹配 |
| Google Maps | 地址自动补全 | 序列标注 + 知识图谱 |
学习路径
| 优先级 | 内容 | 时间 | 说明 |
|---|---|---|---|
| 🔥 | 理解"第 12 章 → 第 13 章"升级对比 | 5 min | 核心价值,看懂对比表格和 Mermaid 图 |
| 🔥 | 序列标注管线 + 标签对齐 | 10 min | 理解 BIO 体系和 DataCollatorForToken |
| 🟢 | 地址对齐后处理(MySQL + RapidFuzz) | 5 min | 理解树构建 + 编辑距离 |
| 🟢 | HF 官方 Trainer 配置 | 5 min | 对比第 12 章手写 Trainer |
| 🟡 | 跑通完整流程(可选) | 30 min | 需要 MySQL + GPU |
AI 协作指南
本文档看完后,以下问题直接问 AI 插件:
Q: "BIO 和 BIOSO 标注体系有什么区别?"
Q: "为什么序列标注里 [CLS] 和 [SEP] 位的标签要设成 -100?"
Q: "DataCollatorForTokenClassification 和 DataCollatorWithPadding 的区别是什么?"
Q: "编辑距离和余弦相似度在什么时候该用哪个?"
AI 能为你做的:
- 把地址对齐流程翻译成其他框架(如 spaCy、Stanford NER)
- 给出一份"把 MySQL 换成 Redis 搜索引擎"的改进方案
- 解释 CRF 层在序列标注中的作用(本项目没用 CRF,是纯 BERT)附录:原始资料处理说明
| 原始文件 | 处理方式 |
|---|---|
地址对齐V1.0.2.docx + 地址对齐总结.txt | 内容已整合到本文档,以图表和代码骨架为主 |
3.代码/ | 核心代码骨架已提取展示 |
region.sql(MySQL 地区表) | 在实操指引中说明用途 |
pretrained/roberta-small-wwm/ | 预训练模型路径保留 |
| 视频(day01 + day02) | 跳过 |
修复情况:
- ✅ 技术栈健康度标签(OPC 7.4)
- ✅ 🟢🟡🟠🔴 优先级颜色
- ✅ Mermaid 流程图 + 对比图
- ✅ 第 12 章 vs 第 13 章逐项对比表
- ✅ 序列标注 vs 整句分类的代码级对比
- ✅ 核心代码骨架(标签对齐 + 地址对齐)
- ✅ 技术栈评估 + 海外对标
- ✅ AI 问答指引
- ✅ 实操引导(MySQL 配置 + 环境检测 + 一键部署)
- ✅ 学习路径表(最短 25 分钟读完)