libpostal:国际街道地址NLP
libposal是一个C库,用于使用统计NLP和开放数据解析/规范化世界各地的街道地址。这个项目的目标是理解每种语言中的location-based字符串。有关libposal背后的研究的更全面的概述,请务必查看(冗长的)介绍性博客文章:
原文:OpenStreetMap上的统计NLP
Follow-up1.0版本:OpenStreetMap上的统计NLP:第2部分
?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??
地址和它们所代表的位置对于任何处理地图的应用程序(位置搜索、运输、on-demand/delivery服务、check-ins、评论)都是必不可少的。然而,即使是最简单的地址也包含了本地约定、缩写和上下文,这使得传统的full-text搜索引擎很难对它们进行有效的索引/查询。这个库有助于将人类使用的free-form地址转换成适合机器比较和full-text索引的干净规范化形式。尽管libposal本身并不是一个完整的地理编码程序,但它可以作为一个预处理步骤,使任何地理编码应用程序更智能、更简单、在国际上更加一致。
?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??
核心库是用纯C语言编写的。Python、Ruby、Go、Java、PHP和NodeJS的语言绑定都得到了官方的支持,并且很容易用其他语言编写绑定。
Sponsors
如果您的公司正在使用libpostal,请考虑让您的组织赞助该项目。解释人类所指的地理位置的含义远不是一个解决的问题,赞助有助于我们在地理空间NLP领域开拓新的领域。作为赞助商,您的公司徽标将出现在Github回购页面的显著位置,并附有指向您网站的链接。赞助信息
Backers
个人用户还可以通过每月捐款来帮助支持开放地理NLP研究:
Installation (Mac/Linux)
安装之前,请确保您具备以下先决条件:
On Ubuntu/Debian
sudo apt-get install curl autoconf automake libtool pkg-config
On CentOS/RHEL
sudo yum install curl autoconf automake libtool pkgconfig
On Mac OSX
brew install curl autoconf automake libtool pkg-config
然后安装C库:
git clone https://github.com/openvenues/libpostalcd libpostal./bootstrap.sh./configure --datadir=[...some dir with a few GB of space...]make -j4sudo make install# On Linux it's probably a good idea to runsudo ldconfig
libposal支持pkg-config,因此可以使用pkg-config打印链接程序所需的标志:
pkg-config --cflags libpostal # print compiler flagspkg-config --libs libpostal # print linker flagspkg-config --cflags --libs libpostal # print both
例如,如果您编写了一个名为app.c的程序,可以这样编译它:
gcc app.c `pkg-config --cflags --libs libpostal`
Installation (Windows)
MSys2/MinGW
对于Windows,构建过程当前需要MSys2和MinGW。可以从http://msys2.org。请按照MSys2网站上的说明进行安装。
请运行以下命令确保Msys2是up-to-date:
pacman -Syu
安装以下先决条件:
pacman -S autoconf automake curl git make libtool gcc mingw-w64-x86_64-gcc
然后构建C库:
git clone https://github.com/openvenues/libpostalcd libpostalcp -rf windows/* ././bootstrap.sh./configure --datadir=[...some dir with a few GB of space...]make -j4make install
注意:设置datadir时,C:
驱动器将输入为/c
。libpostal构建脚本会自动在路径的末尾添加libpostal
,因此在Windows上,'/c'将变成C:\libpostal\
。
编译后的.dll将位于src/.libs/
目录中,应该名为libpostal-1.dll
。
如果需要.lib导入库将其链接到应用程序。可以使用Visual Studiolib.exe
工具和libpostal.def
定义文件生成一个:
lib.exe /def:libpostal.def /out:libpostal.lib /machine:x64
解析示例
libpostal的国际地址解析器使用机器学习(条件随机字段),并且在地球上每个有人居住的国家中对超过10亿个地址进行了培训。我们使用OpenStreetMap和OpenAddresses作为结构化地址的源,使用OpenCage地址格式模板:https://github.com/OpenCageData/address-formatting来构造训练数据,并补充包含多边形,生成sub-building组件,如公寓/楼层号和邮政信箱。我们还添加缩写、随机删除组件等,以使解析器对混乱的real-world输入尽可能健壮。
这些示例解析结果来自交互式address_parser程序,该程序在运行make
时使用libposal构建。请注意,解析器可以处理逗号与无逗号,以及组件的各种外壳和排列(如果输入的是城市或城市/邮政编码)。
解析器在held-out数据上实现了非常高的精度,目前99.45%正确的完全解析(这意味着在分子中使用1来获得地址中的每个标记)。
Usage (parser)
下面是一个使用Python绑定的解析器API示例:
from postal.parser import parse_address parse_address('The Book Club 100-106 Leonard St Shoreditch London EC2A 4RH, United Kingdom')
C API示例:
#include <stdio.h>#include <stdlib.h>#include <libpostal/libpostal.h>int main(int argc, char **argv) { // Setup (only called once at the beginning of your program) if (!libpostal_setup() || !libpostal_setup_parser()) { exit(EXIT_FAILURE); } libpostal_address_parser_options_t options = libpostal_get_address_parser_default_options(); libpostal_address_parser_response_t *parsed = libpostal_parse_address("781 Franklin Ave Crown Heights Brooklyn NYC NY 11216 USA", options); for (size_t i = 0; i < parsed->num_components; i++) { printf("%s: %s\n", parsed->labels[i], parsed->components[i]); } // Free parse result libpostal_address_parser_response_destroy(parsed); // Teardown (only called once at the end of your program) libpostal_teardown(); libpostal_teardown_parser(); }
Parser labels
从技术上讲,地址解析器可以使用在训练数据中定义的任何字符串标签,但这些标签是当前定义的,基于OpenCage的address-formatting库中定义的字段,以及libposal为处理特定模式而添加的一些字段:
房子:场地名称如“布鲁克林音乐学院”,建筑名称如“帝国大厦”
类别:用于类别查询,如“餐厅”等。
near:类似“in”、“near”等短语,用于分类短语之后,帮助解析查询,如“Brooklyn的餐馆”
house_number:通常是指外部(street-facing)的建筑编号。在一些国家,这可能是一个复合的,连字符的号码,其中也包括一个公寓号码,或一个街区号码(一个la Japan),但为了简单起见,libposal只将其称为house_number。
道路:街道名称
单元:公寓、单元、办公室、地段或其他次要单元标志
级别:表示楼层号的表达式,例如“3rd floor”、“Ground floor”等。
楼梯:编号/字母楼梯
入口:编号/字母入口
po_box:邮政信箱:通常位于non-physical(mail-only)地址中
邮政编码:用于邮件分拣的邮政编码
郊区:通常是一个非正式的社区名称,如“哈莱姆”、“南布朗克斯”或“皇冠高地”
city_district:这些通常是城市内的行政区或地区,用于某些官方目的,例如“布鲁克林”、“哈克尼”或“布拉迪斯拉发四世”
城市:任何人类住区,包括城市、城镇、村庄、村庄、地方等。
岛屿:命名岛屿,如“毛伊岛”
state_district:通常是second-level行政区划或县。
国家:行政区划。在英格兰,威尔士和北爱尔兰等地都被用在了“英格兰和北爱尔兰”的地图上
country_region:没有任何政治地位的国家的非正式划分
国家:主权国家及其附属领土,任何带有ISO-3166代码的东西。
world_region:目前只用于在国名后面加上“西印度群岛”,这是加勒比海地区经常使用的一种模式,例如“牙买加,西印度群岛”
规范化示例
expand_addressAPI将杂乱的real-world地址转换为适合搜索索引、哈希等的规范化等效地址。
下面是一个使用Python绑定的交互式示例:
libposal包含一个OSM-trained语言分类器,用于检测给定地址中使用的语言,以便应用适当的规范化。唯一需要的输入是原始地址字符串。下面是一些不同语言中不太直接的规范化的简短列表。
Input | Output (may be multiple in libpostal) |
---|---|
One-hundred东96街二十号 | 东96街120号 |
C/Ocho,第4页 | calle 8 polígono工业4 |
V-XX塞特姆布雷,20 | 通过20塞特姆布雷20 |
英语四年级 | 埃格利斯街92号 |
第四、第七条 | 7 |
第四、第七条 | ulitsa karetnyy ryad dom 4 stroyeniye 7号 |
Marktstraße 14 | 马克大街14号 |
libposal目前支持60多种语言的这些类型的规范化,您可以添加更多(不必编写任何C)。
要进一步阅读和一些奇怪的地址edge-cases,请参阅:程序员相信地址的错误。
Usage (normalization)
下面是一个使用Python绑定以简洁的示例(大多数higher-level语言绑定都是类似的):
from postal.expand import expand_address expansions = expand_address('Quatre-vingt-douze Ave des Champs-Élysées') assert '92 avenue des champs-elysees' in set(expansions)
与C API等效的代码多了几行,但仍然相当简单:
#include <stdio.h>#include <stdlib.h>#include <libpostal/libpostal.h>int main(int argc, char **argv) { // Setup (only called once at the beginning of your program) if (!libpostal_setup() || !libpostal_setup_language_classifier()) { exit(EXIT_FAILURE); } size_t num_expansions; libpostal_normalize_options_t options = libpostal_get_default_options(); char **expansions = libpostal_expand_address("Quatre-vingt-douze Ave des Champs-Élysées", options, &num_expansions); for (size_t i = 0; i < num_expansions; i++) { printf("%s\n", expansions[i]); } // Free expansions libpostal_expansion_array_destroy(expansions, num_expansions); // Teardown (only called once at the end of your program) libpostal_teardown(); libpostal_teardown_language_classifier(); }
Command-line用法(展开)
建立libpostal之后:
cd src/ ./libpostal "Quatre vingt douze Ave des Champs-Élysées"
如果您有一个文本文件或流,每行有一个地址,command-line接口还接受来自stdin的输入:
cat some_file | ./libpostal --json
Command-line用法(解析器)
建立libpostal之后:
cd src/ ./address_parser
address_parser是一个交互式shell。只需输入地址,libposal将解析它们并打印结果。
Bindings
libposal是为higher-level语言设计的。如果您看不到您选择的语言,或者您正在编写语言绑定,请让我们知道!
官方支持的语言绑定
Python: pypostal
Ruby: ruby_postal
Go: gopostal
Java/JVM: jpostal
PHP: php-postal
NodeJS: node-postal
R: poster
非官方语言绑定
LuaJIT: lua-resty-postal
Perl: Geo::libpostal
Elixir: Expostal
Database extensions
PostgreSQL: pgsql-postal
Unofficial REST API
libpostalrest:Libpostal REST
Libpostal REST Docker
Libpostal REST Docker Libpostal REST Docker
Libpostal ZeroMQ Docker
Libpostal ZeroMQ Docker image:pasupulaphani/libpostal-zeromq,来源:Github
Tests
libpostal在自动化测试中使用最多。要运行测试,请使用:
make check
{146}你的贡献很容易,如果我们加上82个案例的话。我们主要使用函数测试来检查字符串输入和字符串输出。
libposal还定期从OSM(clean)获取数百万个地址的battle-tested,以及来自生产地理编码器的匿名查询(不太干净)。在此过程中,我们使用valgrind检查内存泄漏和其他错误。
Data files
libpostal需要从S3下载一些数据文件。基本文件是执行扩展所需的数据结构的on-disk表示。对于地址解析,由于模型训练需要几天时间,所以我们将完全训练过的模型发布到S3,并在OSM、OpenAddresses等中添加新地址时自动更新它。语言分类器模型也是如此。
运行make时会自动下载数据文件。要检查和下载任何新的数据文件,您可以运行make
,或者运行:
libpostal_data download all $YOUR_DATA_DIR/libpostal
并将$YOUR_DATA_DIR替换为在安装期间传递给配置的任何内容。
Language dictionaries
libposal包含许多影响扩展、语言分类器和解析器的per-language字典。要探索词典或贡献您的语言中的缩写/短语,请参阅参考资料/词典。
Training data
在机器学习中,大量的训练数据往往是取得良好效果的必要条件。许多open-source机器学习项目要么只发布模型代码(当且仅当你是Google时,结果是可复制的),要么是pre-baked模型,其中训练条件未知。
Libpostal有点不同,因为它是针对所有人都可以使用的开放数据进行培训的,所以我们已经发布了整个培训管道(本次回购中的geodata包),以及由此产生的培训数据本身在Internet存档中。它的解压缩容量超过100GB。
训练数据按创建日期存储在archive.org上。在这个repo的主目录中还存储了一个名为current_parser_training_set
的文件,其中存储了最近创建的训练集的日期。要始终指向最新的数据,请尝试类似于:latest=$(cat current_parser_training_set)
并使用该变量代替日期。
解析器训练集
所有文件都可以在https://archive.org/download/libpostal-parser-training-data-YYYYMMDD/$FILE找到,因为gzip是tab-separated值(TSV)文件,格式如下:language\tcountry\taddress
。
formatted_addresses_tagged.random.tsv.gz(ODBL):OSM地址。公寓、邮政信箱、分类等主要添加到这些示例中
formatted_places_tagged.random.tsv.gz(ODBL):OSM中的每一个地名(即使是以点表示的城市,etc.),reverse-geocoded)给其父管理员,如果它们列在点/多边形上,可能包括邮政编码。每个地方都有一个基本的代表性水平,人口较多的地方按比例得到更多。
formatted_ways_tagged.random.tsv.gz(ODBL):OSM中的每一条街道(ways with highway=*,有一些条件),reverse-geocoded给它的管理员
geoplanet_formatted_addresses_tagged.random.tsv.gz(CC-BY):Yahoo GeoPlanet中的每个邮政编码(包括英国、加拿大等国的几乎所有邮政编码)及其上级管理员。GeoPlanet管理员已被清理并映射到libpostal的标记集
openaddresses_formatted_addresses_tagged.random.tsv.gz(各种许可证,主要是CC-BY)):大多数地址数据集来自OpenAddresses,而OpenAddresses又直接来自政府来源
uk_openaddresses_formatted_addresses_tagged.random.tsv.gz(CC-BY):来自英国OpenAddresses的地址
如果解析器在特定类型的地址上的性能不如您所希望的,那么最好的方法是使用grep/awk查看训练数据,并尝试确定是否有某种模式/样式的地址没有被捕获。
Features
缩写扩展:例如扩展“rd”=>“road”,但几乎适用于任何语言。libposal支持超过50种语言,很容易添加新语言或扩展当前词典。支持表意文字语言(不以空格分隔,例如中文),日耳曼语言也支持,其中通配符类型连接到字符串的末尾,并且可以选择性地分开,这样Rosenstraße和Rosen Straße是等效的。
“{82new-York-Street”:“{82new-York-state:123new-York-state”:“{82new-York-Street”:“解析纽约市主大街123号”,“解析纽约州”。解析器适用于各种国家和语言,而不仅仅是美国/英语。该模型在超过10亿个地址和address-like字符串上训练,使用OpenCage地址格式化repo中的模板为世界上每个有人居住的国家构造格式化、带标记的训练示例。为了使训练数据尽可能接近真实的杂乱的地理编码器输入,需要执行许多类型的规范化。
语言分类:多项式logistic回归训练(使用FTRL-Proximal方法来归纳稀疏性)关于所有OpenStreetMap方式、addr:*标记、地名和格式化地址。标签是使用point-in-polygon测试派生出来的,这些测试分别针对OSM国家和官方/地区语言以及admin 1边界。例如,西班牙语是西班牙的默认语言,但在不同的地区,如加泰罗尼亚、加利西亚、巴斯克地区,各自的地区语言是默认语言。Dictionary-based在区域语言是non-default的情况下,例如威尔士语、布雷顿语、奥克西坦语,可以使用Dictionary-based消歧。字典还用于缩写规范短语,如“Calle”=>“C/”(在语言分类器和地址分析器训练集上执行)
数值表达式解析(“二十一”=>21,"quatre-vingt-douze“=>92,同样使用CLDR提供的数据),支持超过30种语言。处理带有连接表达式的语言,例如milleottocento=>1800。有选择地将罗马数字规范化,而不考虑出现在许多君主、教皇等名字中的语言(IX=>9)。
快速、准确的标记化/词法分析:以大于1M标记/秒的速度运行,实现了用于UTF8分词的TR-29规范,通过字符而不是空格标记东亚语言字符。
UTF8规范化:可选地将UTF8分解为NFD规范化形式,去掉重音符号,例如a=>a和/或应用Latin-ASCII音译。
音译:例如:ulica或ulitsa。使用所有CLDR转换,与ICU使用的源数据完全相同,但是libposal不需要拉入所有的ICU(可能与您的系统版本冲突)。注意:有些语言,特别是希伯来语、阿拉伯语和泰语可能不包括元音,因此通常不会匹配人类的音译。对于这些语言中的某些语言,可能实现统计音译器。
脚本检测:检测给定字符串使用的脚本(可以是多个,例如free-form香港或澳门地址,可以在同一地址中同时使用汉文和拉丁脚本)。在音译中,我们可以为给定的Unicode脚本使用所有适用的音译器(例如,希腊语可以用Greek-Latin、Greek-Latin-BGN和Greek-Latin-UNGEGN)进行音译。
Non-goals
验证位置是否为有效地址
实际上,地理编码地址到lat/lon(这需要数据库/搜索索引)
Raison d'être
libpostal最初是作为openvences项目的一部分创建的,目的是解决场馆重复数据消除问题。在openvences中,我们有一个由数百万个地方组成的数据集,这些数据源于来自公共爬网的tb网页。常见的爬网每月发布一次,因此即使合并两次爬网的结果也会产生显著的重复。
重复数据消除是一个相对well-studied的领域,对于文本文档,如网页、学术论文等,有相当不错的近似相似性方法,如MinHash。
然而,对于物理地址来说,频繁使用诸如Road==Rd、California==CA或newyorkcity==NYC等常规缩写会使问题变得复杂一些。即使使用MinHash这样的技术,它非常适合近似匹配,并且相当于两个集合的Jaccard相似性,我们也必须处理非常短的文本,而且通常情况下,两个等价地址(一个缩写,一个完全指定)在n-gram集合重叠方面不会非常匹配。在non-Latin脚本中,比如一个俄语地址和它的音译等价物,可以想象,两个地址指的是同一个地方,甚至一个字符都不匹配。
作为一个激励人心的例子,考虑以下两种等效的方法来编写一个特定的曼哈顿街道地址,其惯例和冗长程度各不相同:
佛罗里达州第26街西30号7
西30号Twenty-sixth街7号楼
很明显'30 W 26 St Fl#7!=“30 WestTwenty-sixthStreet Floor Number 7”在字符串比较的意义上,但是人类可以摸索出这两个地址指的是同一个物理位置。
libposal的目标是创建标准化的地理字符串,并将其解析为组件,这样我们就可以更有效地判断两个地址实际匹配的程度,并对dups做出自动的server-side决策。
所以不是地理编码器?
如果以上听起来很像地理编码,那是因为在某种程度上,只有在OpenVenues的情况下,我们必须在没有用户界面或用户的情况下进行地理编码,以便在自动完成下拉列表中选择正确的地址。给定一个源地址的数据库,如OpenAddresses或OpenStreetMap(或以上所有内容),libpostal可以用于在MapReduce或stream processing等设置中实现地址重复删除和server-side批处理地理编码。
现在,不再尝试使用巨大的同义词文件、脚本、自定义分析器、标记器等将address-specific约定放入传统的文档搜索引擎,比如Elasticsearch,而地理编码可以是这样的:
通过libposal的expand_address运行数据库中的地址
将规范化字符串存储在您最喜欢的搜索引擎、DB、hashtable等中。
通过libposal运行用户查询或新导入,并使用这些字符串搜索现有数据库
通过这种方式,libpostal可以根据数据集的大小在恒定的时间内执行模糊地址匹配。
Why C?
libpostal用C编写有三个原因(按重要性排序):
可移植性/普遍性:libposal的目标是higher-level人们实际使用的day-to-day语言:Python、Go、Ruby、NodeJS等等。C的优点在于几乎任何编程语言都可以绑定到它,而C编译器无处不在,所以选择你最喜欢的,编写一个绑定,而且,您可以直接在应用程序中使用libpostal,而无需独立的服务器。我们支持Mac/Linux(Windows不是优先考虑的,但很乐意接受补丁),有一个标准的autotools构建和数据文件的endianness-agnostic文件格式。Python绑定作为这个repo的一部分进行维护,因为它们是构造训练数据所必需的。
Memory-efficiency:libposal被设计为在MapReduce设置下运行,根据机器配置,每个进程的RAM可能限制在小于1GB。libpostal尽可能多地使用连续数组、tries(构建在连续数组上)、bloom过滤器和压缩稀疏矩阵来保持低内存使用率。在移动设备上使用libpostal是可能的,在一个国家或少数国家培训过模型。
表现:这是最后一个原因。libposal中的大多数优化都是针对内存使用而不是针对性能。考虑到libpostal的工作量,libpostal的速度相当快。在我们测试过的平台上,它可以在一个线程/进程中每秒处理10-30k个地址(这意味着在一个多小时内处理OSM planet中的每个地址)。查看简单的基准测试程序,测试您的环境和各种类型的输入。在MapReduce设置中,per-core性能没有那么重要,因为所有事情都是并行进行的,但是Mapzen有一些流接收应用程序需要运行in-process。
C conventions
libpostal是用现代的、易读的C99编写的,并使用以下约定:
大约object-oriented,尽可能多地使用C
几乎没有pointer-based数据结构,数组一直向下
使用动态字符数组(受sds启发)以实现更安全的字符串处理
限制了几乎所有的商场名称新和所有自由名称的破坏
对哈希表等简单事物的高效现有实现
通用容器(通过klib)尽可能
数据结构尽可能地利用稀疏性
对大多数字符串字典有效的double-arraytrie实现
Cross-platform尽可能多,尤其是对于*nix
Preprocessing (Python)
libpostalrepo中的geodatapython包包含用于预处理各种地理数据集和为C模型构建训练数据的管道。对于大多数用户来说,这个包不应该是必需的,但是对于那些对生成新类型的地址或改进libpostal的培训数据感兴趣的用户来说,这是一个值得关注的地方。
地址分析器精度
在held-out测试数据上(这意味着模型以前从未见过标记的解析),地址解析器实现了99.45%的完全解析精度。
对于像命名实体识别这样的任务,最好使用F1分数或变体,这主要是因为存在类偏差问题(大多数单词是non-entities,而一个简单地为每个标记预测non-entity的系统实际上在准确性方面做得相当好)。地址解析不是这样。每一个标记都有一个标签,在训练数据中每一个类都有上百万个例子,所以准确度是最好的,因为它是一个干净、简单和直观的性能度量。
这里我们使用完全解析精度,这意味着如果解析器获得地址中的每个标记都正确,那么我们只在分子中给它一个“点”。这应该是一个更好的衡量标准,而不是简单地看每个标记是否正确。
地址分析器的改进
尽管当前的解析器对大多数标准地址都能很好地工作,但仍有改进的余地,特别是在确保我们使用的训练数据尽可能接近实际地址方面。有两种主要方法可以进一步改进地址解析器(按难度排序):
向OSM提供地址。任何带有地址:门牌号将在下次训练时自动合并到解析器中。
如果地址解析器不能很好地用于特定的国家、语言或地址样式,那么在创建训练数据的过程中,可能会丢失或错误标记一些名称变体或位置。有时,修复方法是更新https://github.com/OpenCageData/address-formatting上的格式,在许多其他情况下,我们可以在创建训练数据时进行相对简单的调整,以确保模型经过训练,可以处理您的用例,而不必进行任何手动数据输入。如果您看到明显错误的地址解析模式,最好的做法是将问题发布到Github。
Contributing
欢迎错误报告、问题和请求。请在提交问题、错误报告或请求前阅读贡献指南。
提交问题:https://github.com/openvences/libpostal/issues。
Shoutouts
特别感谢@BenK10最初的Windows构建,以及@AeroXuk将其无缝集成到项目中并建立了Appveyor构建。
License
根据麻省理工学院的许可证条款,该软件是开源的。