手把手教学:用MGeo完成两万条地址去重
1. 为什么地址去重不能靠“Ctrl+F”?——从真实业务痛点说起
你有没有遇到过这样的情况:手头有一份两万条用户注册地址的Excel表格,里面充斥着“北京市朝阳区建国路8号”“北京朝阳建国路8号SOHO现代城”“北京朝阳建国路8号”“朝阳建国路8号”……看起来像同一地点,但字符串一模一样?传统方法要么人工一条条比对,耗时三天还漏掉30%;要么用Excel模糊匹配,结果把“上海浦东张江”和“上海浦西中山”也标成相似——这不是去重,是制造混乱。
MGeo不是又一个字符串匹配工具。它是阿里开源、专为中文地址设计的语义相似度模型,能理解“中关村大街1号”和“海淀中关村大厦”虽然字面只重合三个字,但实际指向同一片地理区域;也能分辨“杭州西湖区文三路”和“杭州上城区文三路”,哪怕只差一个区名,它也会果断给出低分。
本文不讲论文、不谈架构,就带你用最短路径,把两万条杂乱地址变成干净、可分析、可对接下游系统的结构化数据。整个过程不需要写一行新代码,所有操作在镜像里点点鼠标、敲几行命令就能完成。如果你正被地址脏数据困扰,接下来的内容就是为你准备的。
2. 环境准备:5分钟完成部署,连GPU驱动都不用装
MGeo镜像已经为你打包好一切:CUDA 11.7、PyTorch 1.12、FAISS-GPU、jieba、transformers……甚至预装了中文地址专用词典。你唯一要做的,就是启动它。
2.1 启动容器并进入交互环境
打开终端,执行这一行命令(确保你的机器已安装Docker且支持GPU):
docker run -it --gpus all -p 8888:8888 mgeo-address-similarity:v1.0 /bin/bash这行命令做了三件事:
--gpus all:自动挂载本机GPU(A4090D单卡完全够用)-p 8888:8888:把容器内Jupyter服务端口映射到本地/bin/bash:直接进入Linux命令行,免去登录步骤
镜像启动后,你会看到类似root@e3f2a1b:/#的提示符——说明你已站在MGeo的“家门口”。
2.2 启动Jupyter Notebook,打开可视化编辑器
在容器内执行:
jupyter notebook --ip=0.0.0.0 --port=8888 --allow-root --no-browser终端会输出一串URL,形如:http://127.0.0.1:8888/?token=abc123def456...
复制这个链接,在你本机浏览器中打开。你将看到一个清爽的Jupyter界面,左侧是文件浏览器,右侧是新建Notebook的按钮——这就是你接下来的操作主战场。
2.3 激活专用Python环境,避免依赖冲突
在Jupyter中新建一个Terminal(顶部菜单 → New → Terminal),输入:
conda activate py37testmaas回车后,提示符前会出现(py37testmaas)字样。这个环境里只有MGeo需要的库,版本全部锁定,不会和你系统里其他项目打架。记住:所有后续Python命令都必须在这个环境下运行。
3. 数据准备:两万条地址怎么喂给MGeo?
MGeo不接受Excel、CSV或数据库直连,它只认一种格式:标准JSON数组。别担心,转换只要3步,5分钟搞定。
3.1 把原始地址整理成“地址对”结构
MGeo的核心任务是判断“两条地址是否指向同一地点”,所以输入必须是成对出现的地址。对于两万条原始地址,我们需要生成所有可能的组合吗?不,那会产生2亿对,根本跑不完。
正确做法是:先做粗筛,再精排。
- 第一步:用省、市、区三级行政区划作为“桶”,把地址分组(比如所有含“北京朝阳”的地址归入同一桶)
- 第二步:只在每个桶内部两两配对,把2万条地址的配对量从2亿降到约50万对(假设平均每桶20条)
你可以用Excel的“筛选+复制”手动分组,也可以用下面这段轻量Python脚本(保存为preprocess.py,在Jupyter Terminal中运行):
import pandas as pd import re # 读取原始Excel(假设第一列为地址) df = pd.read_excel("/root/workspace/原始地址.xlsx", header=None) addresses = df.iloc[:, 0].dropna().tolist() # 简单提取省市区(适用于大部分规范地址) def extract_region(addr): # 匹配“XX省XX市XX区”“XX市XX区”等模式 pattern = r"([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤琼青藏宁新])?([\u4e00-\u9fa5]{0,10}?(?:省|市|自治区|自治州))([\u4e00-\u9fa5]{0,10}?(?:市|区|县|旗))" match = re.search(pattern, addr) if match: return "".join(match.groups()) return "未知区域" # 分组并生成地址对 region_groups = {} for addr in addresses: region = extract_region(addr) if region not in region_groups: region_groups[region] = [] region_groups[region].append(addr) # 写入JSON文件:每对地址一个对象 pairs = [] for region, addrs in region_groups.items(): n = len(addrs) for i in range(n): for j in range(i + 1, n): pairs.append({ "id": f"{region}_{i}_{j}", "address1": addrs[i], "address2": addrs[j] }) # 保存为JSON with open("/root/workspace/address_pairs.json", "w", encoding="utf-8") as f: json.dump(pairs, f, ensure_ascii=False, indent=2) print(f"共生成 {len(pairs)} 对地址,已保存至 /root/workspace/address_pairs.json")运行后,你会在/root/workspace/下看到address_pairs.json——这就是MGeo能读懂的“语言”。
3.2 验证JSON格式是否正确
在Jupyter中新建一个Notebook,运行以下代码检查前3对:
import json with open("/root/workspace/address_pairs.json", "r", encoding="utf-8") as f: data = json.load(f) for i, pair in enumerate(data[:3]): print(f"第{i+1}对:") print(f" ID: {pair['id']}") print(f" 地址1: {pair['address1']}") print(f" 地址2: {pair['address2']}") print()如果输出清晰显示三条地址对,说明数据准备成功。如果报错JSONDecodeError,大概率是中文编码问题——请确认保存时勾选了“UTF-8无BOM”。
4. 批量推理:让两万条地址在10分钟内“自我认识”
现在,真正的去重开始了。我们不用逐条调用,而是用MGeo的批量处理能力,一次喂入上千对地址。
4.1 修改推理脚本,支持JSON文件输入
默认的/root/推理.py是硬编码测试数据。我们需要让它读取你刚生成的address_pairs.json。
在Jupyter中打开/root/workspace/推理.py(如果没复制过,先执行cp /root/推理.py /root/workspace),找到类似pairs = [...]的行,替换成:
import json # 读取你准备好的JSON文件 with open("/root/workspace/address_pairs.json", "r", encoding="utf-8") as f: pairs = json.load(f) print(f"正在加载 {len(pairs)} 对地址进行相似度计算...")同时,确保脚本末尾的predict_similar_pairs(...)调用没有被注释掉。
4.2 调整阈值:0.8不是金科玉律,要根据业务定
MGeo默认用0.8作为“是否为同一地点”的分界线。但在实际业务中:
- 物流场景:要求严格,“北京朝阳建国路8号”和“北京朝阳建国路8号A座”必须算同一地点 → 建议阈值设为0.75
- 政务系统:要求严谨,“杭州市西湖区”和“杭州市上城区”绝不能混淆 → 建议阈值设为0.85
打开/root/workspace/推理.py,找到threshold=0.8这一行(通常在函数定义或调用处),改成你需要的值。例如:
results = predict_similar_pairs(pairs, model, threshold=0.75) # 物流宽松版 # 或 results = predict_similar_pairs(pairs, model, threshold=0.85) # 政务严格版4.3 执行推理,监控进度
回到Terminal,确保还在py37testmaas环境下,执行:
cd /root/workspace python 推理.py你会看到滚动的日志:Processing batch 1/50...Processing batch 2/50...
……Done! Results saved to /root/workspace/results.json
整个过程取决于你的GPU性能。在A4090D上,50万对地址大约耗时8-12分钟。期间你可以喝杯咖啡,或者打开results.json看看实时输出。
5. 结果解析:从“相似度分数”到“可落地的去重方案”
推理完成后,/root/workspace/results.json里存着每一对地址的判定结果。但光有is_match: true还不够——你需要知道哪些地址该合并、哪些该保留。
5.1 把结果转成“地址簇”,一眼看清谁和谁是一伙的
MGeo输出的是扁平化的地址对,而业务需要的是聚类后的“地址组”。用下面这段代码,把结果变成直观的簇:
import json from collections import defaultdict # 读取MGeo结果 with open("/root/workspace/results.json", "r", encoding="utf-8") as f: results = json.load(f) # 构建图:地址为节点,相似对为边 graph = defaultdict(set) all_addresses = set() for r in results: if r["is_match"]: addr1 = r["address1"].strip() addr2 = r["address2"].strip() graph[addr1].add(addr2) graph[addr2].add(addr1) all_addresses.add(addr1) all_addresses.add(addr2) # DFS找连通分量(即地址簇) visited = set() clusters = [] def dfs(addr, cluster): visited.add(addr) cluster.append(addr) for neighbor in graph[addr]: if neighbor not in visited: dfs(neighbor, cluster) for addr in all_addresses: if addr not in visited: cluster = [] dfs(addr, cluster) clusters.append(cluster) # 输出簇信息 print(f"共发现 {len(clusters)} 个地址簇") for i, cluster in enumerate(clusters[:5]): # 只显示前5个示例 print(f"\n簇 {i+1} ({len(cluster)} 条地址):") for addr in cluster[:3]: # 每簇显示前3条 print(f" • {addr}") if len(cluster) > 3: print(f" ... 还有 {len(cluster)-3} 条")运行后,你会看到类似:
簇 1 (5 条地址): • 北京市朝阳区建国路8号 • 北京朝阳建国路8号SOHO现代城 • 朝阳建国路8号 • 北京市朝阳区建国路8号A座 • 建国路8号SOHO现代城这就是你要的“去重单元”——簇内任意一条地址,都可代表整个簇。
5.2 生成最终去重表:选“最规范地址”作为代表
每个簇里有5条地址,选哪条当代表?我们按规则排序:
- 字符数最多(通常最完整)
- 包含“省市区街道门牌号”全要素
- 无口语化缩写(如“北辰”优于“北辰大厦”)
用这段代码自动生成代表地址表:
import re def score_address(addr): score = len(addr) # 长度分 # 要素分:每出现一个关键要素+10分 elements = ["省", "市", "区", "县", "街道", "路", "号", "栋", "单元"] for e in elements: if e in addr: score += 10 # 惩罚口语词 colloquial = ["大厦", "中心", "广场", "SOHO", "公寓"] for c in colloquial: if c in addr: score -= 5 return score final_table = [] for i, cluster in enumerate(clusters): # 按分数排序,取最高分者 scored = [(addr, score_address(addr)) for addr in cluster] best_addr = max(scored, key=lambda x: x[1])[0] final_table.append({ "cluster_id": f"GROUP_{i+1}", "representative": best_addr, "members_count": len(cluster), "all_members": cluster }) # 保存为Excel便于业务使用 import pandas as pd df_out = pd.DataFrame(final_table) df_out.to_excel("/root/workspace/去重结果.xlsx", index=False) print(" 去重结果已生成:/root/workspace/去重结果.xlsx")打开这个Excel,你会看到三列:簇ID、代表地址、成员数量。两万条原始地址,现在变成了几百条精炼的“黄金地址”,可直接导入CRM、用于用户画像、或作为GIS系统底图。
6. 实战避坑指南:那些文档里没写的“血泪经验”
6.1 农村地址太长?截断前先“提纯”
MGeo最大输入64字符,但“云南省文山壮族苗族自治州广南县莲城镇北宁路123号附1号”有32字,还没算乡镇村组。硬截断会丢掉关键信息。
正确做法:用正则提取核心地理链,再送入模型:
import re def extract_core_address(addr): # 优先匹配:省+市+区/县+街道/路+门牌号 pattern = r"(.{0,10}?(?:省|自治区|直辖市))(.{0,10}?(?:市|自治州|地区))(.{0,10}?(?:区|县|旗|市))(.{0,15}?(?:街道|镇|乡|路|道|街))(.{0,10}?(?:号|弄|栋|单元))" match = re.search(pattern, addr) if match: return "".join([g.strip() for g in match.groups() if g]) return addr[:64] # 保底截断 # 在encode_address函数中调用 def encode_address(address: str): core_addr = extract_core_address(address) # 后续编码逻辑不变...6.2 处理速度慢?别单条跑,用向量化批处理
默认推理.py是循环调用compute_similarity,处理50万对要15分钟。改成批量编码,时间压到3分钟:
def batch_compute_similarity(addresses1, addresses2): # 批量编码两个地址列表 vecs1 = batch_encode(addresses1) # 复用前面定义的batch_encode vecs2 = batch_encode(addresses2) # 用numpy向量运算,非循环 from sklearn.metrics.pairwise import cosine_similarity sims = cosine_similarity(vecs1, vecs2).diagonal() # 只取对角线(一一对应) return sims # 使用示例 addrs1 = [r["address1"] for r in pairs] addrs2 = [r["address2"] for r in pairs] similarity_scores = batch_compute_similarity(addrs1, addrs2) for i, r in enumerate(pairs): r["similarity"] = round(similarity_scores[i], 2) r["is_match"] = similarity_scores[i] >= 0.756.3 如何验证去重效果?别信分数,要看人工抽检
MGeo的similarity=0.92不代表100%准确。上线前务必人工抽检:
- 随机抽100对
is_match=true的结果,看是否真为同一地点 - 随机抽100对
is_match=false但similarity>0.6的结果,看是否有漏判
建立一个简单抽检表(Excel即可),记录:
| ID | address1 | address2 | MGeo判定 | 人工判定 | 是否一致 | 备注 |
抽检通过率>98%才建议全量上线。
7. 总结与延伸:从去重到构建地址知识图谱
你已经完成了两万条地址的精准去重——但这只是开始。MGeo的能力远不止于此:
- 地址标准化:把“朝阳建国路8号”统一转为“北京市朝阳区建国路8号”,只需在代表地址上加一层规则映射
- 异常地址预警:对
similarity<0.3且无法归入任何簇的地址,标记为“疑似错误地址”,推动源头治理 - 地址关系挖掘:把所有
is_match=true的地址对导入Neo4j,构建“地址同义词网络”,为搜索推荐提供语义支持
最重要的是,整个流程你只用了镜像自带的工具,没有装新包、没有调参数、没有碰CUDA——这才是AI工程化的理想状态:能力开箱即用,价值所见即所得。
如果你的地址数据量超过十万条,建议下一步把推理封装成REST API(参考文档中的Flask示例),用Nginx做负载均衡,再配合Redis缓存高频查询,轻松支撑日均百万级调用量。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。