# 一键演示 61 POI(13 城市簇)+ 全部核心功能
moon run cmd demo
# 语义地址匹配(20k 真实地址评测语料)
moon run cmd addr-match "汉中路180号星汉大厦26楼江苏东方燃油有限公司" "汉中路180号星汉大厦26层A江苏东方燃油有限公司"
moon run cmd addr-eval testdata/data.txt 0 # 全量 20k × 5 candidates 评测
moon run cmd corpus-db 500 # 从真实语料构建空间 DB
# 性能基准(默认 1000,可指定 10000 等更大规模)
moon run cmd benchmark 10000
# 完整测试套件
moon test # 339/339 passed
# 高级空间分析示例
moon run cmd kmeans 3 # K-Means 聚类(3 大宏观区域)
moon run cmd dbscan 50.0 3 # DBSCAN 密度聚类(自动识别 13 城市簇)
moon run cmd tsp-2opt bj-gugong # TSP + 2-opt 路线优化
moon run cmd idw 35.0 115.0 2.0 # IDW 空间插值
moon run cmd kriging 35.0 115.0 12 # Kriging 克里金插值
moon run cmd concave-hull 1.5 # 凹包 (Alpha-Shape)
moon run cmd moran-i-knn # Moran's I (KNN 权重)
moon run cmd sde # 标准差椭圆| 方法 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| find_in_ring | O(n) 全扫描 + 每个 haversine | O(k) 用 bbox 候选集 + 精确过滤 | 10-100x |
| nearest_neighbors_range | O(n) 全计算 + 排序 | O(log n) R-tree KNN | 100x+ |
| tsp_nearest | O(n²) 内部 O(n) 扫描 | O(n·log n) R-tree KNN + 跳过已访问 | 50x |
| tsp_2opt | 内部 O(n²) 嵌套双循环 + 每候选 4 次 HashMap+haversine | 路线坐标/edge 邻接数组,d_before 零三角函数、零 HashMap | 10-30x |
| cluster_dbscan (正确性) | 初始邻居未入去重集合 → 同一点重复入队、簇膨胀、额外扩张 | clustered[] 统一归属/去重,初始邻居先标记再入队 | 正确性 + 1.7x 速度 |
| spatial_join_nearest | O(n·m) 双循环 | O(n·log m) 临时 R-tree | 50x |
| spatial_join_within | O(n·m) 双循环 | O(n + k) bbox 候选集 + haversine 精炼 | 50x |
| spatial_join_knearest | O(n·m·log m) 全距离排序 | O(n·log m) 临时 R-tree | 50x |
| cluster_dbscan | O(n²) 每个点全扫描邻居 | O(n·k) coarse-grid 范围查询 | 10-100x |
| grid 层 | HashMap[String] 每次插值 + 单网格 | HashMap[Int] + dual-grid 细(0.1°)/粗(0.5°) 自适应选择 | 5-10x |
| bulk_insert (insert 批量) | 每条目 HashMap 重复查找 + R-tree 逐步插入 | 一次性构建 + R-tree 集中插入 | 5-20x |
| morans_i_knn | O(n²) 全 n×n 距离矩阵 | O(n·k·log n) 仅 k 最近邻 | 10x+ (大 n) |
| find_neighbors | O(n log n) 全扫描 | O(log n) R-tree 最近邻 | 1000x+ |
| find_sorted_by_distance | O(n log n) 全扫描 | O(log n) R-tree 最近邻 | 1000x+ |
| find_nearest_pairs | O(n² log n²) 全对排序 | O(n log k) R-tree 分批 | 100x+ |
| find_nearby | O(m log m) 全排序 | O(m log k) 堆维护 | 大候选集显著 |
| compute_bbox | O(n) 每次 | O(1) 缓存 | 多次调用显著 |
| voronoi | O(n³) 每次 | O(n³) 缓存 | 2x+ |
| voronoi_neighbors | O(n³·|a|·|b|) | O(n³·(|a|+|b|)) | 邻居查询显著 |
| 操作 | 1k | 10k | 扩展趋势 | 实现 |
|---|---|---|---|---|
| 构建 DB | 10,021 | 134,586 | ~O(n) ✓ | bulk_insert + dual-grid |
| 构建/条目 | 10.0 μs | 13.5 μs | 近常数 ✓ | |
| BBox 查询 | 6 | 42 | O(log n) ✓ | adaptive fine/coarse/R-tree |
| KNN (k=5) | 43 | 84 | O(log n) ✓ | R-tree best-first |
| Within 100km | 9 | 48 | 亚线性 ✓ | unsorted bbox → haversine |
| K-Means (k=3) | 4,380 | 109,800 | O(n·iters·k) | K-Means++ 初始化 |
| DBSCAN (eps=50km) | 7,372 | 237,038 | O(n·k) 扩张 | clustered[] 去重队列 |
| TSP 2-opt | 49,548 | 406,937 | O(n²·rounds),实测亚线性 | edge 数组 + 0 HashMap |
| Moran's I KNN | 27,748 | 634,763 | O(n·k·log n) ✓ | 真 R-tree KNN + 行标准化 |
| Kriging (k=12) | 8,184 | 40,882 | O(n·log n + k²) | R-tree KNN + 变异函数 |
moonbitGEODB/
├── lib/
│ ├── types/ # 核心类型定义 (GeoPoint, Address, GeoEntry, BBox)
│ ├── geo/ # 地理数学运算 (Haversine, Geohash, 坐标转换, 凸包, 多边形)
│ ├── parser/ # 地址解析器 (多格式解析, 中文行政区划识别)
│ ├── index/ # 空间索引 (网格索引, 名称索引)
│ ├── persist/ # 持久化 (二进制编解码, 原子保存, 备份, FFI IO)
│ ├── gen/ # 随机中国地址生成器
│ └── db/ # 主数据库 API (GeoDB)
└── cmd/ # 命令行接口| 模块 | 说明 |
|---|---|
| types | 定义 GeoPoint(地理坐标点)、Address(结构化地址)、GeoEntry(数据库条目)、BBox(边界框)等核心数据类型 |
| geo | 实现 Haversine 公式、Geohash 编解码、WGS84↔GCJ-02 坐标转换、凸包计算、Voronoi 图(泰森多边形)、Moran's I 空间自相关、Getis-Ord Gi* 热点分析、LISA 局部聚类、多边形面积、点-in-多边形、径向密度等地理运算 |
| parser | 支持逗号分隔、制表符分隔、中文行政区划等多种地址格式的解析;核心模块 address_match.mbt 实现语义地址匹配器 — 25 维中间特征提取 + LightGBM 集成(60 棵树投票),在 96,423 对真实中文地址上 Agreement 75.90%,可对数据库内地址做模糊语义检索 |
| index | 网格空间索引加速 BBox 查询;R-tree(rtree.mbt)提供亚线性的 k 最近邻与范围查询;基于 HashMap 的名称/标签/地址索引 |
| persist | 二进制编解码(codec.mbt)、GeoEntry 序列化(binary.mbt)、C FFI IO(persist_native.mbt)、原子写入与备份 |
| gen | 包含 347 个中国城市(含真实坐标)、街道名、门牌号的随机地址生成器 |
| db | 提供完整的数据库操作 API:增删改查、空间查询、标签聚合、持久化、备份恢复、高级分析 |
# 设置链接器路径(解决 pthread 和 libc 找不到的问题)
export LIBRARY_PATH=/usr/lib64
# 强制使用 GCC 而非 tcc(推荐)
export MOON_CC=gcc
# 构建项目
moon build
# 运行测试
moon testmoon run cmd <address> 解析并存储地址
moon run cmd search <keyword> 按名称关键词搜索
moon run cmd fuzzy <kw> [max_dist] 模糊搜索(Levenshtein 距离)
moon run cmd nearby <lat> <lng> [km] 附近查找
moon run cmd within <lat> <lng> <km> 地理围栏查询
moon run cmd tag <tag> 按标签查找
moon run cmd tags 列出所有标签
moon run cmd tag-all <t1> [t2 ...] 多标签 AND 查询
moon run cmd tag-any <t1> [t2 ...] 多标签 OR 查询
moon run cmd province <province> 按省份查找
moon run cmd city <city> 按城市查找
moon run cmd district <district> 按区县查找
moon run cmd stats 地理统计
moon run cmd export [path] 导出 CSV
moon run cmd to-json [path] 导出 JSON
moon run cmd to-geojson [path] 导出 GeoJSON
moon run cmd add-tag <id> <tag> 添加标签
moon run cmd remove-tag <id> <tag> 删除标签
moon run cmd rename <id> <name> 重命名
moon run cmd update-coords <id> <lat> <lng> 更新坐标
moon run cmd seed [n] 生成随机地址(默认 100 条)
moon run cmd save <path> 保存数据库
moon run cmd load <path> 加载数据库
moon run cmd backup <path> 创建备份
moon run cmd check <path> 完整性检查
moon run cmd info <path> 查看数据库信息
moon run cmd pattern <pattern> 通配符搜索
moon run cmd distance <id1> <id2> 两点距离
moon run cmd neighbors <id> [k] k 个最近邻
moon run cmd nearest-pairs [k] k 对最近点
moon run cmd districts [province] 区县分布
moon run cmd tag-count <tag> 标签计数
moon run cmd tag-pairs 标签共现对
moon run cmd centroid <tag> 加权质心
moon run cmd import-csv <csv_str> CSV 导入
moon run cmd import-geojson <gj_str> GeoJSON 导入
moon run cmd duplicates [dist] [edit] 重复检测
moon run cmd cluster [cell_size_km] 网格聚类
moon run cmd matrix <id1> [id2 ...] 距离矩阵
moon run cmd reverse-geocode <lat> <lng> 反向地理编码
moon run cmd dbscan [eps_km] [min_points] DBSCAN 聚类(默认 50km / 3 点)
moon run cmd polygon <lat1>,<lng1> <lat2>,<lng2> ... 多边形操作
moon run cmd geohash-neighbors <hash> [precision] Geohash 邻域
moon run cmd radial-density <lat> <lng> <km> <rings> 径向密度
moon run cmd to-geojson-bbox [path] 带 BBox 的 GeoJSON
moon run cmd distance-histogram [bucket_size_km] 距离直方图
moon run cmd coord-histogram lat|lng [bucket_size] 坐标直方图
moon run cmd distance-summary 距离统计摘要
moon run cmd kmeans <k> [max_iter] K-Means 聚类
moon run cmd elbow [max_k] Elbow 方法选 k
moon run cmd centroid-all 全局质心 + 地理中心
moon run cmd geographic-center 球面地理中心
moon run cmd bbox-query <min_lat> <max_lat> <min_lng> <max_lng> 边界框查询
moon run cmd moran-i Moran's I 空间自相关
moon run cmd sde 标准差椭圆
moon run cmd tsp <start_id> TSP 最近邻路由
moon run cmd tsp-2opt <start_id> TSP + 2-opt 优化(再省 3-10%)
moon run cmd idw <lat> <lng> [power] IDW 空间插值(反距离加权)
moon run cmd concave-hull <alpha> 凹包(Alpha-Shape),alpha=0 → 凸包
moon run cmd benchmark [N] 性能基准测试(默认 1000)
moon run cmd demo 一键演示(K-Means + SDE + TSP + IDW + 凹包)
moon run cmd moran-i-knn Moran's I(KNN 权重,比 O(n²) 快)
moon run cmd voronoi Voronoi 图(泰森多边形)
moon run cmd voronoi-cell <id> 单个条目的 Voronoi 多边形
moon run cmd voronoi-neighbors <id> 条目的 Voronoi 邻居
moon run cmd delaunay Delaunay 三角剖分
moon run cmd delaunay-edges Delaunay 边(唯一)
moon run cmd morans-i-lat 全局 Moran's I(纬度)
moon run cmd morans-i-lng 全局 Moran's I(经度)
moon run cmd hotspot-lat Getis-Ord Gi* 热点/冷点(纬度)
moon run cmd hotspot-lng Getis-Ord Gi* 热点/冷点(经度)
moon run cmd lisa-lat 局部 Moran's I LISA(纬度)
moon run cmd lisa-lng 局部 Moran's I LISA(经度)
moon run cmd kriging <lat> <lng> [k] Kriging 克里金插值
moon run cmd clear 清空所有条目
moon run cmd help 显示帮助
moon run cmd addr-match <q> <c> 语义地址匹配(查询 vs 候选)
moon run cmd addr-eval <path> [N] 评测地址匹配器(JSONL 语料)
moon run cmd addr-feat-dump <path> 导出 25 维特征 TSV(用于 sklearn 重训)
moon run cmd corpus-db [N] 从真实地址语料构建空间 DB# 运行默认演示
moon run cmd
# 解析并存储地址
moon run cmd "北京市海淀区颐和园路5号"
# 搜索关键字
moon run cmd search "颐和园"
# 模糊搜索(最多 2 次编辑距离)
moon run cmd fuzzy "Yuan" 2
# 附近查找(5 公里内)
moon run cmd nearby 39.9 116.4 5
# 地理围栏查询
moon run cmd within 39.9 116.4 10
# 多标签 AND 查询
moon run cmd tag-all "poi" "attraction"
# 按省份查找
moon run cmd province "北京市"
# 生成 500 条随机地址
moon run cmd seed 500
# DBSCAN 聚类(1km 半径,最少 2 点成簇)
moon run cmd dbscan 1.0 2
# 多边形查询
moon run cmd polygon 0,0 0,1 1,1 1,0
# Geohash 邻域查询
moon run cmd geohash-neighbors wx4g0d 6
# 径向密度分析
moon run cmd radial-density 39.9 116.4 1.0 10
# 保存/加载数据库
moon run cmd save geo.db
moon run cmd load geo.db
# 完整性检查
moon run cmd check geo.db
# 数据导出
moon run cmd export output.csv
moon run cmd to-geojson output.json# Exact — suffix 统一化(幢↔栋、层↔楼 自动折叠)
$ moon run cmd addr-match \
"北京市海淀区中关村南大街27号中央民族大学2号楼" \
"北京市海淀区中关村南大街27号中央民族大学2栋"
level: 完全匹配 score: 0.84 dice: 0.97 road: 1 num: 0.5
# Exact — 完整 POI + 门牌号 完全一致
$ moon run cmd addr-match \
"陕西省西安市雁塔区小寨路街道长安中路100号赛高国际" \
"陕西省西安市雁塔区小寨路街道长安中路100号赛高国际"
level: 完全匹配 score: 0.75 dice: 1.00 road: 1 num: 1
# Partial — 同 POI 但楼层/房间号不同(26楼 vs 26层A)
$ moon run cmd addr-match \
"汉中路180号星汉大厦26楼江苏东方燃油有限公司" \
"汉中路180号星汉大厦26层A江苏东方燃油有限公司"
level: 部分匹配 score: 0.84 dice: 0.97 road: 1 num: 1
# Partial — 同道路 + 同门牌段号,但建筑不同
$ moon run cmd addr-match \
"上海市浦东新区世纪大道100号环球金融中心" \
"上海市浦东新区世纪大道88号金茂大厦"
level: 部分匹配 score: 0.56 dice: 0.67 road: 1 num: 0
# Partial — 同门牌号但跨城市同名 POI(LGBM 判 Partial 而非 None,
# 因为 road_b=1 + feature_recall=1.0 + admin_hit=0,属于典型"地名歧义")
$ moon run cmd addr-match \
"浙江省杭州市西湖区文三路90号东部软件园" \
"江苏省南京市玄武区文三路90号东部软件园"
level: 部分匹配 score: 0.65 dice: 0.43 road: 1 num: 1
# None — 完全不相关
$ moon run cmd addr-match \
"北京市朝阳区建国门外大街1号国贸中心" \
"广州市天河区天河路228号正佳广场"
level: 不匹配 score: 0.00 dice: 0.00 road: 0 num: 0# 完整评测:混淆矩阵 + 三分类 F1
$ moon run cmd addr-eval testdata/data.txt 0
=== Address matcher evaluation ===
Records: 20000 pairs: 96423
Throughput: 18284 pairs/s
Agreement: 75.90% (73187/96423)
Confusion matrix (rows = gold, cols = pred):
完全匹配 部分匹配 不匹配
完全匹配 948 2902 2157
部分匹配 440 21305 10146
不匹配 253 7338 50934
# 导出 25 维特征 TSV + score + gold,供 sklearn 重训
$ moon run cmd addr-feat-dump testdata/data.txt | head -3
dice road_b feat name admin_hit road_hit ... score gold
0.296 0.5 0 0 1 0 ... 0.204 2
0.143 0.5 0 0 0 0 ... 0.125 2// 创建内存数据库
let db = @db.GeoDB::in_memory()
// 插入条目(自动解析地址字符串)
let entry = db.insert_raw(
"bj-yiheyuan",
"颐和园",
39.9999,
116.2755,
"北京市海淀区颐和园路5号"
)
// 批量插入(比循环 insert 快 5-20 倍,50k 仅 ~1.1s)
db.bulk_insert([entry1, entry2, entry3])
// 按 ID 查找
match db.get("bj-yiheyuan") {
Some(e) => println(e.name)
None => println("not found")
}
// 按名称查找
let results = db.find_by_name("颐和园")
// 按关键字模糊搜索
let results = db.find_name_contains("海淀")
// 附近查找(5 公里内)
let nearby = db.find_nearby(
@types.GeoPoint::make(39.9999, 116.2755),
radius_km=5.0,
limit=10
)
// DBSCAN 密度聚类(50km 邻域,至少 3 点成簇)
let clusters = db.cluster_dbscan(50.0, 3)
// Geohash 前缀查询
let results = db.find_by_geohash_prefix("wx4g0d", precision=6)
// 路径距离计算
let d = db.path_distance(["id1", "id2", "id3"])
// 分页查询
let page = db.page_all(limit=10, offset=0)
// 地址标准化
let ok = db.normalize_address("bj-yiheyuan")
// 保存到文件
ignore(db.save_to("geo.db"))
// 从文件加载
let loaded = @db.GeoDB::open("geo.db")// Geohash 编码/解码
let gh = @geo.geohash_encode(39.9087, 116.3975, 6)
let pt = @geo.geohash_decode(gh)
// Geohash 邻域(3×3 网格)
let neighbors = @geo.geohash_neighbors(gh)
// 坐标转换(WGS84 → GCJ-02)
let (gcj_lng, gcj_lat) = @geo.wgs84_to_gcj02(116.4, 39.9)
// 凸包计算
let hull = @geo.convex_hull(points)
// 多边形面积(km²)
let area = @geo.polygon_area(polygon_points)
// 点在多边形内测试
let inside = @geo.point_in_polygon(point, polygon)
// 平均最近邻距离
let avg_nn = @geo.average_nn_distance(points)
// Voronoi 图(泰森多边形)
let cells = @geo.voronoi_diagram(points, bbox)
// 单个条目的 Voronoi 多边形
let poly = db.voronoi_cell("id1")
// Voronoi 邻居(共享边的条目)
let neighbors = db.voronoi_neighbors("id1")
// Delaunay 三角剖分(Voronoi 对偶)
let tris = db.delaunay() // 返回三角形索引数组
// Delaunay 边(唯一)
let edges = db.delaunay_edges()
// 空间自相关:Moran's I 全局聚集度检验(纬度)
let moran = db.morans_i_on_lat()
// 热点分析:Getis-Ord Gi* 热点/冷点检测(经度)
let hotspots = db.getis_ord(vals, k = 5, threshold = 1.96)
// LISA:局部 Moran's I 聚类类型(HH/LL/HL/LH)
let lisa = db.lisa(vals, k = 5, threshold = 1.96)
// 重复数据检测
let groups = db.find_duplicates(max_distance_km=0.1, max_edit_dist=2)
// 空间网格聚类
let clusters = db.cluster_by_grid(cell_size_km=1.0)
// 距离矩阵
let matrix = db.distance_matrix(["id1", "id2", "id3"])
// 径向密度分析
let rings = db.radial_density(center, ring_width_km=1.0, num_rings=10)
// 反向地理编码
match db.reverse_geocode(39.9, 116.4) {
Some((entry, dist)) => println("Nearest: " + entry.name)
None => println("Not found")
}
// 性能优化示例:高效查询(O(log n) 通过 R-tree)
// 查找最近 10 个邻居(避免 O(n) 全扫描)
let neighbors = db.find_neighbors(target.location, 10)
// 查找半径内条目(堆维护,避免 O(m log m) 全排序)
let nearby = db.find_nearby(center, 5.0, limit=20)
// 查找最近点对(避免 O(n²) 全对计算)
let pairs = db.find_nearest_pairs(k=5)// 基础用法 — 直接拿 MatchResult
let q = "汉中路180号星汉大厦26楼江苏东方燃油有限公司"
let c = "汉中路180号星汉大厦26层A江苏东方燃油有限公司"
let result = @parser.match_address(q, c)
match result.level {
Exact => println("完全匹配,score=" + result.score.to_string())
Partial => println("部分匹配,dice=" + result.dice.to_string())
None => println("不匹配")
}
// result.dice — 标准化字符串的 Dice 系数
// result.feature_recall, road_recall, num_recall — 三个手工核心指标
// 带特征导出 — 拿 MatchResult + FeatureBundle (25 维中间特征)
let (result2, feats) = @parser.match_address_ex(q, c)
// feats.dice, feats.road_b, feats.feature_recall, feats.admin_hit,
// feats.road_hit, feats.foreign_city, feats.feat_overlap,
// feats.house_missing, feats.veto, feats.main_hit ... 全部可读
// 地址文本标准化(公开 API)
let norm = @parser.normalize_address_text("北京市 海淀区 颐和园路5号 ")
// → "北京市海淀区颐和园路5号"(全角→半角、去标点、trim)
// MatchLevel 枚举工具
Exact.rank() // 2
Partial.rank() // 1
None.rank() // 0
MatchLevel::from_label("完全匹配") // → Exact// 生成 100 条随机中国地址
let entries = @gen.gen_batch(100)
// 预填充数据库
let db = @gen.gen_db(500)
// 也可以通过 CLI 生成并持久化
// moon run cmd seed 500 # 自动保存到 testdata/geo_seeded.db+------------------+
| File Header | 魔数 ("GEO1") + 版本号 + 条目数 + 创建/更新时间戳
+------------------+
| Entry Block 0 | 条目长度 + CRC32 + 序列化数据
| Entry Block 1 | ...
+------------------+
| ... |
+------------------+# 运行所有测试
moon test| 城市 | POI 数 | 代表地标 |
|---|---|---|
| 北京 | 11 | 故宫、天安门、天坛、颐和园、八达岭、鸟巢、水立方、798 |
| 上海 | 8 | 东方明珠、外滩、豫园、南京路、迪士尼、上海中心 |
| 广州 | 5 | 广州塔、陈家祠、越秀公园、白云山、沙面 |
| 深圳 | 5 | 平安金融中心、世界之窗、欢乐谷、莲花山、大梅沙 |
| 成都 | 5 | 宽窄巷子、锦里、武侯祠、熊猫基地、都江堰 |
| 重庆 | 5 | 解放碑、洪崖洞、南山、磁器口、武隆 |
| 杭州 | 4 | 西湖、灵隐寺、西溪湿地、千岛湖 |
| 南京 | 3 | 中山陵、夫子庙、南京博物院 |
| 武汉 | 3 | 黄鹤楼、东湖、武汉大学 |
| 西安 | 3 | 大雁塔、兵马俑、西安城墙 |
| 天津 | 3 | 天津之眼、古文化街、五大道 |
| 青岛 | 3 | 栈桥、崂山、八大关 |
| 昆明 | 3 | 滇池、翠湖、西山 |
$ moon run cmd dbscan 50.0 3
=== DBSCAN (eps=50km, min_points=3) ===
Cluster #1 (3 POIs): # 武汉
Cluster #2 (11 POIs): # 北京
...
Cluster #13 (3 POIs): # 昆明
Clusters: 13 Noise: 3 # 58 + 3 = 61,全部点都有归属判定
Noise points:
- hz-qiandao (千岛湖)
- cq-wulong (武隆天生三桥)
- cd-dujiangyan (都江堰景区)| 类别 | 数量 | 示例 |
|---|---|---|
| 城市 | 347 | 北京、上海、广州、深圳、成都、武汉、西安、杭州、南京、重庆、天津、青岛、郑州、长沙、昆明、厦门、珠海、洛阳、保定、西宁、桂林、三亚、包头、徐州、南通、盐城、扬州、泰州、潍坊、临沂、济宁、德州、淄博、威海、泰安、滨州、菏泽、聊城、东营、襄阳、宜昌、荆州、十堰、黄冈、孝感、咸宁、恩施、株洲、湘潭、衡阳、邵阳、岳阳、常德、张家界、益阳、郴州、永州、怀化、娄底、柳州、梧州、北海、玉林、钦州、鞍山、抚顺、本溪、吉林、四平、齐齐哈尔、大庆、锦州、营口、辽阳、盘锦、铁岭、朝阳、葫芦岛、牡丹江、佳木斯、绥化、大同、运城、临汾、长治、咸阳、宝鸡、渭南、汉中、绵阳、德阳、宜宾、泸州、南充、丽江、大理、玉溪、曲靖、遵义、六盘水、安顺、芜湖、蚌埠、阜阳、马鞍山、安庆、赣州、九江、上饶、宜春、邯郸、廊坊、沧州、开封、新乡、南阳、商丘、长春、通化、延边、鸡西、吴忠、石嘴山、日喀则、林芝、昌都、那曲、海东、眉山、资阳、凉山、达州、广安、巴中、雅安、普洱、临沧、德宏、怒江、迪庆、红河、文山、楚雄、崇左、防城港、贵港、贺州、河池、来宾、朔州、阳泉、忻州、吕梁、晋中、榆林、延安、安康、商洛、毕节、铜仁、昭通、保山、巢湖、池州、宣城、新余、抚州、鹰潭、景德镇、萍乡、鹤壁、济源、三门峡、漯河、信阳、周口、驻马店、随州、仙桃、潜江、天门、神农架、儋州、文昌、万宁、东方、五指山、琼海、乐东、陵水、白沙、昌江、澄迈、临高、定安、屯昌、伊春、黑河、大兴安岭、莱州、滕州、玉树、果洛、海北、黄南、阿里、山南、吐鲁番、哈密、克拉玛依、阿克苏、喀什、和田、伊宁、塔城、阿勒泰、石河子、阿拉尔、图木舒克、北屯、双河、高雄、台北、台中、台南、基隆、新竹、嘉义、彰化、云林、南投、屏东、宜兰、花莲、台东、澎湖、金门、连江、香港、澳门、南沙、天水、庆阳、平凉、酒泉、张掖、武威、定西、陇南、临夏、甘南、嘉兴、湖州、绍兴、金华、衢州、台州、丽水、漳州、三明、南平、龙岩、宁德、惠州、汕头、江门、湛江、茂名、肇庆、揭阳、梅州、清远、潮州、云浮、张家口、承德、衡水、邢台、秦皇岛、辽源、白山、松原、白城、鄂尔多斯、呼伦贝尔、巴彦淖尔、乌兰察布、兴安盟、锡林郭勒、阿拉善盟、固原、中卫、亳州、宿州、六安、镇江、淮安、连云港、宿迁、莆田、黔东南、黔南、黔西南、铜川、渭南、延安、榆林、汉中、安康、商洛、海北、黄南、拉萨、日喀则、林芝、昌都、那曲、阿里、山南、吉林、四平、长春、通化、延边、石家庄、唐山、保定、邯郸、廊坊、沧州、广州、深圳、佛山、东莞、珠海、中山、乌鲁木齐、克拉玛依、吐鲁番、哈密、阿克苏、喀什、和田、伊宁、塔城、阿勒泰、石河子、阿拉尔、图木舒克、五家渠、北屯、双河、昆玉、西宁、海东、西安、铜川、宝鸡、咸阳、渭南、延安、汉中、榆林、安康、商洛、兰州、嘉峪关、金昌、白银、天水、武威、张掖、平凉、酒泉、庆阳、定西、陇南、临夏、甘南等 |
| 行政区划 | 每城市 4-10 个 | 海淀区、朝阳区、天河区、锦江区、武侯区等 |
| 地标/POI 名称 | 100+ | 颐和园、故宫、西湖、兵马俑、鼓浪屿、泰山、张家界等 |
| 街道名称 | 50+ | 中山路、人民路、解放路、幸福路等 |
| 统计量 | 数值 |
|---|---|
| query 数量 | 20,000 |
| 候选地址对 | 96,423 |
| 整体 Agreement | 75.90% (73,187/96,423) |
| 完全匹配 F1 | 24.79% (Prec 57.8%, Rec 15.8%) |
| 部分匹配 F1 | 67.17% (Prec 67.5%, Rec 66.8%) |
| 不匹配 F1 | 83.66% (Prec 80.5%, Rec 87.0%) |
| 吞吐量 | ~19,000 pairs/s |
| 混淆矩阵 | 3×3 (Exact 948 / Partial 21,305 / None 50,934 正确) |
| # | 字段 | 类型 | 含义 | LGBM 重要性 | ||
|---|---|---|---|---|---|---|
| 0 | dice | Double | 标准化字符串 Dice 系数(2 | A∩B | /(|A|+|B|)) | 0.12 |
| 1 | road_b | Double | 道路 token B 分数(匹配 + 加权) | 0.36(最重要) | ||
| 2 | feature_recall | Double | POI/建筑名召回率 | 0.24 | ||
| 3 | name_recall | Double | 公司/机构名召回率 | 0.04 | ||
| 4 | admin_hit | Int | 行政区划 hit 数(省/市/区/街道) | — | ||
| 5 | road_hit | Int | 道路是否命中(0/1) | — | ||
| 6 | town_hit | Int | 乡镇/街道是否命中 | — | ||
| 7 | context_ok | Int | 上下文门控(admin + road 联合 OK) | — | ||
| 8 | house_recall | Double | 门牌号召回率 | 0.04 | ||
| 9 | house_conflict | Int | 门牌号冲突(同 road 不同 num → 1) | — | ||
| 10 | veto | Int | 硬 veto(道路冲突 + POI 不重叠 → 必 None) | — | ||
| 11 | q_structured | Int | query 是否含完整结构(省市区) | — | ||
| 12 | c_structured | Int | candidate 是否含完整结构 | — | ||
| 13 | feat_overlap | Int | POI 子串重叠(3+ chars 跨 token) | 0.04 | ||
| 14 | weak_core_exact | Int | 弱核心词精确匹配 | — | ||
| 15 | fuzzy_poi_overlap | Int | 模糊 POI 重叠(Dice ≥ 0.6) | — | ||
| 16 | reverse_poi_overlap | Int | 反向 POI 子串(candidate→query) | — | ||
| 17 | road_conflict | Int | 道路显式冲突(不同道路名同时存在) | — | ||
| 18 | main_hit | Int | 主干道 + 门牌号 + POI 三项核心全命中 | — | ||
| 19 | house_missing | Int | 一方有门牌号另一方没有 | — | ||
| 20 | foreign_city | Int | 双方属于不同城市(跨城标志) | — | ||
| 21 | q_roads | Int | query 中道路 token 数 | — | ||
| 22 | c_roads | Int | candidate 中道路 token 数 | — | ||
| 23 | q_feats | Int | query 中 POI token 数 | — | ||
| 24 | c_feats | Int | candidate 中 POI token 数 | — | ||
| 25 | score | Double | 手工规则输出的加权总分(仅作为调试参考) | — |
特征重要性 Top 3:road_b (36%) → feature_recall (24%) → dice (12%)。这三个特征占 LightGBM 决策的 72%,而手工规则几乎不使用 road_b 的中间分数——这是 ML 能提升 7%+ 的核心原因。
25 维 FeatureBundle [Double/Int]
│
▼
┌──────────────────────────────┐
│ 60 棵决策树(纯 MoonBit if-else)│
│ n_estimators=20 × 3 classes │
│ max_depth=6, min_child_samples=50 │
│ tree_idx → class: tree_idx % 3 │
└──────────────────────────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
tree0 tree1 tree2 tree3 ... tree59
│ │ │ │ │
s0+L s1+L s2+L s0+L s2+L
│ │ │ │ │
└─────┴─────┴─────┴────────────┘
│
▼
┌──────────────────────────────┐
│ s0, s1, s2 (累加 raw score) │
│ argmax(s0, s1, s2) │
└──────────────────────────────┘
│ │ │
class0 class1 class2
│ │ │
Exact Partial None| 模型 | Holdout Accuracy | 实现复杂度 |
|---|---|---|
| 手工 OR-of-16-rules | ~69% | 2000 行启发式 |
| 最佳单 Decision Tree (depth 20) | 74.41% ± 0.29% | 1996 行 if-else |
| LightGBM (20×3, d=6, lr=0.3) | 75.21% ± 0.41% | 7527 行 if-else |
| Random Forest (500, d=15) | 76.23% | 500+ 棵树,不可嵌入 |
| XGBoost / LightGBM (大集成) | 76.6% | 1000+ 棵树,不可嵌入 |