【项目总结】订单性质识别
最近做的安卓日志告一段落,因此有了一些空闲时间,趁机回顾一下在上家公司做的一些有趣的项目,总结一下~
1.背景
上家公司是一个很大的O2O公司(国内最大吧),我在其一个事业部的数据组负责爬虫工作,爬虫稳定的时候我也会做一些其他项目。我们所做的这个行业目前竞争依然很激烈,基本是前两家分了70%份额,3、4两家紧追其后的局面,并且3、4家都实力不凡。做O2O就会有线下团队,他们主要负责谈商家拉客户做宣传,是公司一线最重要的战斗力。我们把全国划分成城市,每个城市划分成了更小的蜂窝;蜂窝是最小的管理单元,交由线下兄弟管理;其工作经费的发放需要根据其管理区域的情况来定,其工资和奖金则是根据业绩来定,也就是订单数;但是,对于不同的蜂窝,绩效规则可能不同,比如,当公司大力发展白领业务的时候,就会对在写字楼等白领消费的订单,补贴和奖励力度加大;而判断订单属于白领消费的方法,是看订单在哪个蜂窝产生,然后我们人工确定一下这个蜂窝的性质,作为这个订单的性质,比如,一个包含了清华大学的蜂窝,被人工标记为“校园”,那么在这个蜂窝产生的订单,自然不是白领性质。蜂窝的标记之前交由线下团队自己来做,但是这种绩效评判方法实施后,他们就有了将蜂窝性质故意标记错误的动机,以获得更多的补贴和奖金。针对这个问题,大佬们做了一些讨论,最后决定,订单性质由我们总部数据组来计算,替代之前通过蜂窝关联的方法。当时我负责的爬虫刚稳定下来,就接下了这个项目。
2.场景
输入:做这个项目的时候,我们一天的订单数大约220万,现在已经到了300万以上了。当时用户的画像还没有,可用的订单属性就两个,
latitude & longitude; // 用户下单时的经纬度 address; // 用户自己填的配送地址
输出:第一阶段的任务是要统计出每天蜂窝级别各类性质的订单数量,性质一共有4种---白领、校园、住宅、其他;然后交由一个页面来展示。
3. 调研
直观来看,这件事情就需要从地理信息来入手,后来这个事情也确实这么做的。订单可用的属性太少,但是可以把订单放入地图中,关联上地图数据,再从中找一些线索。调研后,发现确实可行。
3.1 地图POI
无论是高德、百度、还是腾讯地图,POI(Point of Interest)都是其核心数据。下图中浮在地图只上的那些 “文字+小标记” 就是POI,例如其中的“同德公园”、“山西大厦”、“洪荫小区”等。从POI的图标可以看到,不同性质的POI有不同的分类,例如“西城苑”和“洪荫小区”都是小区,所以是相同的图标,而“同德公园”是公园,则是另一种图标。从此可以得知,百度地图的POI是有一个自己的类别属性的,高德、腾讯都是如此。那么,思路就来了。我可以把订单关联到POI,然后利用地图POI自带的属性,来判断订单的性质。
3.1.1 获得高德地图poi
最终我选择了高德地图来做这件事,因为我们自己的app就是用的高德。首先,第一步目标我定为:获得所有需要的高德地图poi。高德地图提供在线服务,可以通过http的方式来请求到某一个点附近的k个POI信息(k大约是10或20)。我每天需要处理200万以上的订单,如果在处理每个订单时再临时调用http服务,会有严重的效率问题。所以我要把高德地图的数据搬到本地,在本地做索引,提供服务。
初期的目标,是要把我们自己蜂窝中的地图POI,全部获得。每个蜂窝有明确的多边形边界,全国一共有大约3000个蜂窝。我在这3000多个蜂窝中均匀地、按照相邻500米的密度,全都打上点。大约会产生50w个经纬度点;再将这50万个点调用高德服务(POI搜索->周边搜索,一个典型url是 [todo]),得到每个点附近的POI,调用50万次后,大约会产生1000万的poi数据;然后根据poi的自带id去重,最终我得到了200万左右的POI数据。一个POI的部分内容如下,可以看到,“type”字段标明了该POI的分类,是“餐饮服务;中餐厅;海鲜酒楼”。
{ "id": "B0FFG93MUO", "tag": "白汤河豚,阿拉斯加帝王蟹,象拔蚌,刺身龙虾,刺身澳洲鲍鱼,苏眉,东星斑,海参,基围虾,鲍鱼", "name": "东海海鲜会馆", "type": "餐饮服务;中餐厅;海鲜酒楼", "typecode": "050119", "biz_type": "diner", "address": "贵宾楼饭店一楼西侧(北京饭店北京贵宾楼饭店欧美同学会)", "location": "116.406960,39.908630", "tel": "010-64810079", "pcode": "110000", "pname": "北京市", "citycode": "010", "cityname": "北京市", "adcode": "110101", "adname": "东城区", "gridcode": "5916639201", "distance": "160", "business_area": "东单"
}
3.1.2 高德地图poi的分类
统计一下这200万高德POI的type字段,大约有不到100个值,典型的值包括“写字楼”、“住宅小区”、“医院”、“大学”、“风景名胜”等。和需求方一起,我们把这100个type到订单4种性质的映射关系确定了下来,典型的映射例如,“大学”对“校园”、“中学”对“校园”、“医院”对“其他”等。
3.1.3 订单和POI关联
将地图POI搬到了本地之后,下面要将订单和POI做关联,以利用POI的分类。关联基于两种假设:1. 离订单距离越近的POI,越可能关联上,2. POI的地址和订单地址越相似,越可能关联上。所以,订单和POI的关联步骤如下:
(1) 输入订单经纬度,找到离订单最近的50个POI,作为候选集合
(2) 分别计算订单和候选集合中每个POI的距离
(3) 分别计算订单和候选集合中每个POI的地址相似度
(4) 综合2,4步骤,选出得分最高的POI
(5) 按照3.1.2中的映射关系,查找该POI类型对应的订单性质,即为最终结果
步骤(1)是一个二维K近邻的计算,先对所有POI进行索引,可以使用R树、KD树、4叉树等,这个项目中我使用了R树索引。对200wPOI建立索引后,进行100w次查找,耗时在5分钟以内,完全可以接受。步骤(2)则是两个经纬度点计算测地距离,这个计算方法可以参考 http://www.cnblogs.com/ycsfwhh/archive/2010/12/20/1911232.html。步骤(3)是计算两个字符串的相似度,这里使用了字符串的编辑距离来度量。
这样分类后,发现准确率还是不高,因为经纬度坐标本来就有误差,而且地址也不规范;会产生一些明显的错误,比如一个订单地址是“XXX第4教学楼”,经过距离和地址的加权,可能会和“XXX电影院”匹配上,而错过了正确的POI“XXX中学”。为更大程度避免这种情况,我增加了两种映射关系,来对匹配进行约束:
(1) 订单地址对POI类型的约束
(2) POI类型对订单地址的约束
约束(1)举个例子说明,假如有一个订单地址是“XXXXX公寓”,那么很显然,这个订单对应的POI只有几种可能----住宅小区、学生公寓、职工公寓等,其他类型比如“电影院”、“风景区”是完全不可能的;所以,利用这个约束条件,可以把候选的50个POI,排除掉一部分。约束(2)同样举一个例子,假如有一个POI的类型是“电影院”,那么其关联上的订单地址,一定会是类如“XXX电影”,“XXX影院”等,“影”关键字基本跑不掉,如果不是,那么这个订单和POI的关联关系很可能是错的;再利用这个条件,同样可以排除掉候选50个POI中的一部分。经过这样正反约束,50个候选POI基本上都会降到20个左右,正确率有提高。这两个约束关系大约各有20条规则,都是反复测试总结而来的。
经过这样来做,订单性质的正确率可以到75%左右,也就是说,大约4个订单,会有1个分类错误。这个比例当然不能忍,第一期目标是90%,那么继续。
3.2 地图带区域的POI
在使用百度地图搜索“清华大学”,会看到有清晰的学校边界,如下图。
那么,如果一个订单按照经纬度,落在了这个边界内,那就肯定是属于清华大学了,从而判定为“校园”性质,准确度很高。因此,我可以把地图所有带有区域的POI拿下来,来做订单性质识别。最终我选择了腾讯地图来做这件事,因为高德地图没有找到可用接口,而百度地图的经纬度体系和高德有差异,需要换算。具体的步骤如下:
(1) 将拿到的高德200w的Poi名称,在腾讯地图接口上请求区域信息
(2) 返回的结果中,含有区域信息的,提取出来,作为高德POI的增加属性
经过对所有POI区域的抓取,最终有区域的数量在10w左右,大部分是校园和住宅小区,这是一个非常有用的数据。具体的接口和数据格式,现在我看了一下已经更改了,有时间可以再分析一下。
测试:拿几天的订单(大约一共1000万订单),往这10w区域里面落;可以落到范围内的有250万左右,抽取大约5000来做人工标记,正确率在95%左右。事实证明这是一个非常好的办法,解决了4分之一的订单,并且准确率奇高。但是,结合3.2和3.1,整体的准确率仍然到不了90%,所以继续。
3.3 人工关键词
在前面不停地测试->人工标注->测试->人工标注过程中,对数据质量和方法有了一些把握。人工判断时,基本是通过地址中的关键字眼来做,比如“XXXX中学”,就判断为校园订单,“XXX大厦”,就很有可能是白领订单。自然而然地,我想到用关键词的方法来做。这种方法一开始没有想到,是因为它完全抛弃了经纬度信息。
关键字的方法很简单,就是直接判断订单地址中是否包含关键字,包含则直接根据关键字性质来确定订单的性质。比如“北京大学”中包含“大学”关键字,则直接判定为校园订单,“同济医院”中包含“医院”关键字,则直接判定为“其他”类型订单。但是,有一个先后顺序在其中,例如“北京大学附属医院”既包括“大学”关键字又包括“医院”关键字,那么就取最后的一个,即“医院”作为最终关键字,因为越靠后越接近地址的最终含义。关键词库的创建完全是通过我人工来做,我标记了上万的订单数据后,总结出来大约50个关键词,作为一个宝贵的人工关键词库。
测试:拿1000万订单,用关键词进行分类,可以分类的大约有50%;抽取5000个分类结果人工判定,准确率在95%以上。这是一种最有效的分类方法,也最简单。现在,利用关键词->落POI区域->POI匹配,这个分类流程,经过测试,分类覆盖率有99%,准确率达到了91%左右。目标达成。
3.4 自动关键词
虽然达成了90%的目标,但是还有相当一批订单分类错误,这类订单的地址都不太标准,但是大多数会包含一些楼宇或者小区信息,例如“望京SOHO”、“南湖西园”之类,这类具体的楼宇和小区名称,很难用普遍的方法识别出来,我只能通过词库来做,但是数量又太大,所以,我考虑利用200w的地图poi来自动产生一批关键词。好在这200w地图POI都已经标记上了对应的订单性质,因此,我只要关注怎样提取关键词就可以了。最终,我提取出“白领”、“住宅”这两类自动词库,包含关键词大约50万,基本包含了绝大部分小区名称和相当多的楼宇名称。提取的方式比较简单原始,因为POI对应的地址比较标准,因此可以提取出小区对应的子串,例如,“北京市朝阳区世安家园小区”,直接可以提取出“世安家园小区”,然后把小区关键词去掉,则得到了“世安家园”关键词;楼宇的方法于此相同。
测试:和人工关键词相比,这自动词库关键词质量很一般,因此只能用来做最后的补充;利用这50万自动关键词,对前面分类失败的10%进行分类,可以正确分出五分之一左右,从而将整体的分类准确度提高到了92%~93%,并且每天的计算时间在30分钟以内,达到了第一阶段的要求。
4. 实现
4.1 最终算法
最终的算法流程如4.1所示,需要补充的是。这部分都使用Java来处理,数据库用Mysql;分类结果,被推到HDFS上,用Hive来做后续的ETL。
4.2 子项目和依赖
参考4.1中流程图,整个项目有以下几个子项目
(1) 地图POI抓取项目 (2) 地图POI区域抓取项目 (3) 自动关键词项目 (4) 订单分类主流程项目
第(1)个项目第一次需要全量抓取,后面需要增量维护,每天根据蜂窝区域的变化,来增加地图POI;第(2)个项目与此类似;第(1)和第(2)个项目数据的更新会触发第(3)个项目更新;第(4)个是主项目,每日计算一次。
4.3 测试
流程开发完成后,和PM一起测试,测试基本集中在分类正确率上,两个人每次抽取1000条分类结果进行检查,重复大约有20次左右后,准确率一直稳定在92%,可以上线了。
5. 总结
这个项目比较有意思,主要是使用很土的办法达成了目标,比较爽。研发只有我一个,另外有个PM和我一起讨论和测试;前期数据准备和调研花了3周左右,开发加上线3周左右,一共一个半月时间。后来,来了一个实习生,我和他一起商量了第二阶段的改进办法,准备使用机器学习来处理剩下那一部分相当困难的订单(主要是要从地址模式入手);可是一直有其他的项目在前,就一直没有做。
posted on 2016-01-15 08:57 sparrowjack 阅读(574) 评论(1) 编辑 收藏 举报