前言
提到快递,必然关联到地址。对于用户来说,我们往往只根据自己的理解来填地址,我们觉得填地址时,可以把省份省略掉,因为知道是哪个市,必然就知道它属于哪个省了;但是对于物流公司,系统里肯定不能存放没有省份的数据,这时就需要对这样的地址进行识别并补全省份。
如果你使用过电子运单,那你可能用过“智能识别”的功能,就是你粘贴一段文字,然后系统帮你识别联系人、电话、地址等。我这里想说的就是如何来识别那地址的省市县等。
正文
由于受系统框架限制,所以我使用的是盘古分词,和Lucene.net全文检索来实现这个功能。
盘古分词
由于我们的目的是地址识别,那对用户输入的句子来分词时,就不能按我们平常的方法来分词了,比方说“深圳南山科技园南区”,我肯定不希望它分成“深圳、南山、科技、南区”,而我希望它能分成“深圳、南山、科、技、南、区”,如果是按第一种常规的方法来分词,那用这组词来检索时,肯定就会检索出更多的结果,而用后一种分词,我在检索时,把单个字的过滤点,就只用“深圳、南山”去检索,其精确率必然更高。
盘古分词是可以自定义字典的,那我这儿就是以省市县乡四级地址及四级地址去掉末尾的“省、市、区、街道”等字眼来作为字典(这儿我是为了牺牲部分准确率,换来其识别率),同时把盘古分词自带的字典给移除,我们在使用盘古分词时,就只使用我们自己的字典就可以了。
private static void LoadMyDict()
_WordDictionary._WordDict.Clear();
_WordDictionary.ChineseName = null;
List<string> wordList=new List<string>();
string path = Path.Combine(Environment.CurrentDirectory, "address.txt");
var lines = File.ReadAllLines(path);
wordList.AddRange(lines);
foreach (string line in lines)
string word = line.TrimEnd(new[] {'省', '市', '区', '镇', '乡', '县'}).Replace("街道","");
if (!wordList.Contains(word))
wordList.Add(word);
foreach (var word in wordList)
if(word.Length<2)continue;
_WordDictionary.InsertWord(word, 999, POS.POS_D_N);
_WordDictionary.Save(Path.Combine(Environment.CurrentDirectory, "dict.dct"));
}
注:盘古分词源代码:https://github.com/JimLiu/Lucene.Net.Analysis.PanGu;
上面代码中的address.txt文档是四级地址的名称,以换行作为分隔;另外下载源代码,在PanGu.Segment中的LoadDictionary方法最后再调用上面代码即可得到dict.dct,然后把这个dict.dct放到你使用盘古分词项目的exe目录下的dict文件夹即可。
Lucene.net
Lucene文档就只有这5个字段,为什么需要由四级地址拼接成的DetailAddress呢?因为如果我以“广东”、“深圳”,这两个关键词查询,会查找到包含这两个词的所有地址,比方说“广东省深圳市南山区”,而我想要的是“广东省深圳市”,所以这儿必须加DetailAddress,不然无法把“广东省深圳市”这一项排在搜索结果的最前面。
另外Lucene的数据源是由只有Province、Province+Prefecture、Province+Prefecture+County、Province+Prefecture+County+Village组成的。
在搜索时,我设置了个搜索字段的权重,四级地址的各自的权重按其级别来定,另外由于Village存在很多同名的,所以把其权重设为最小。
fileterList为需要过滤的搜索关键词,因为这类词匹配的地址不够准确,当然,这可以写成一个配置文件来维护,我这儿只是作为Demo,就直接写在代码里了。
words.Take(4):由于我们这儿最多只查询四级,所以只去4个关键词进行查询就可以了。