竞赛圈   > 在水里烧烤 分享 0.40656

在水里烧烤

数据挖掘工程师   |   Python
  • 关注者 3
  • 关注了

在水里烧烤

数据挖掘工程师   |   Python

比赛经历

零零散散参加了不少比赛,因为个人时间安排原因,有时候很忙没空,所以不能拖队友后腿吧?就一直自己耍,经常玩到一半跑路。本次想分享一下前段时间参加的DC的比赛,虽然成绩不咋地,参考交流学习。

比赛介绍

比赛地址:

http://www.dcjingsai.com/common/cmpt/汽车目的地智能预测大赛_竞赛信息.html

比赛数据:

地址:https://pan.baidu.com/s/1Xrrzb6gGkHP0R0ERln94kA

密码:gqx1

竞赛背景

美国东北大学一个科研团队的一项研究表明,人类93%的行为都是可以预测的,想一想我们的规律生活,就会觉得这个数字并不是那么夸张。也正因如此, 交通才得以合理规划,城市才得以有序发展。 在实际应用中,其实并没有那么容易预测,数据的缺乏是一个重要的原因。那么在有限的数据下,我们能够在多大程度上预测出人们的行为呢?

报名开始:

10月12日 11:00

比赛时间:

10月12日 11:00--12月7日 15:00

个人成绩:

12/1411

参赛队伍:

在水里烧烤

参赛人员:

在水里烧烤

评分标准

本次比赛考察预测目的地(经纬度)与实际目的地(经纬度)的球面距离,评分计算方法如下:

其中,di为第i个样本的预测目的地与实际目的地的球面距离(单位:米),n为测试集样本个数。注:score的极限最优值大约在0.01798(即当所有目的地的预测误差均在0米时)。

距离与score的关系如下图,横轴为距离,纵轴为分值。

比赛要求

任务

通过学习一些车辆的历史行程,训练模型,在给定车辆id、时间和出发地点的情况下,预测该辆车在此次行程中的目的地。

数据

*注 : 报名参赛或加入队伍后,可获取数据下载权限。

10月27日更新的训练集和测试集说明:

1.train_new.csv 训练集数据,共1495814条,为2018年1月1日到2018年8月1日的数据(包括原测试集中的数据),字段如下:

2.test_new.csv 测试集数据,为抽取的当年9-10月的数据,除无end_time,end_lat,end_lon字段外,其它字段同训练集数据。共58097条。

比赛分析

总结来说,

给了用户1-8月的数据,预测9-10月用户出行时会去哪?

怎么入手这类题目,首先要问问自己几个问题?

1 . 我们可以从数据获取用户什么信息?

这份数据比较简单,就只有用户行程始终点,对应的时间。

2 . 把这个问题定义成什么问题更好解决?

我们知道用户的起点,想知道用户的终点,终点是一个经纬度,考虑回归还是分类?

考虑回归,效果应该还不错,我个人认为决策树回归应该还可以,但是还没试过。

我个人尝试的是分类问题,第一个想到的就是贝叶斯分类,用户这个地点出发,这个时间出发,很大概率是和往常去的是同个地方。但是提供的是经纬度?怎么进行分类?借鉴了一下摩拜单车的解决方法geohash编码,把地图分为好几块,每一块一个点。

3 . 怎么构建数据进行挖掘,是全数据挖掘?还是分类建模怎么处理?

考虑到数据量大,分类也很细,我的小电脑跑不动,所以我考虑了分类建模,怎么分类?用车辆id作为唯一用户,对每个用户进行建模。

建模流程

建模流程

预处理及构建特征

数据预处理做了如下事情:

  1. 剔除r_key字段;(无用id)
  2. 时间变量衍生;(起始点时间进行特征生成,生成星期、工作日、休息日、上中下旬等)
  3. 起终点经纬度geohash编码;(使用geohash这个包)
  4. 统计最常出现的地点作为Home标记,生成出每个出行点离家距离特征;
  5. 剔除出现次数少于10次的地点;(通过遍历,得到小于10次的地点为临时出行点,认为是干扰数据,进行剔除)

二分类多分类?因为是多个地点,所以选择用多分类器。

第一次使用是贝叶斯分类器、效果一般,线上大概0.45,之后用lightgbm、xgboost,其中xgboost效果较好。

代码示例 https://www.toutiao.com/i6643572922105463300/  完整代码这里获取

# 加载包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
import sys, time
import geohash
import copy
import urllib.request
from bs4 import BeautifulSoup
import json
warnings.filterwarnings('ignore')
class ShowProcess():
    """
    显示处理进度的类
    调用该类相关函数即可实现处理进度的显示
    """
    i = 0 
    max_steps = 0 
    max_arrow = 50 
    infoDone = 'done'

    def __init__(self, max_steps, infoDone = 'Done'):
        self.max_steps = max_steps
        self.i = 0
        self.infoDone = infoDone

    def show_process(self, i=None):
        if i is not None:
            self.i = i
        else:
            self.i += 1
        num_arrow = int(self.i * self.max_arrow / self.max_steps) #计算显示多少个'>'
        num_line = self.max_arrow - num_arrow #计算显示多少个'-'
        percent = self.i * 100.0 / self.max_steps #计算完成进度,格式为xx.xx%
        process_bar = '[' + '>' * num_arrow + '-' * num_line + ']'\
                      + '%.2f' % percent + '%' + '\r' #带输出的字符串,'\r'表示不换行回到最左边
        sys.stdout.write(process_bar) #这两句打印字符到终端
        sys.stdout.flush()
        if self.i >= self.max_steps:
            self.close()

    def close(self):
        print('')
        print(self.infoDone)
        self.i = 0
        
# - 计算两点距离
from math import radians, atan, tan, sin, acos, cos
def getDistance(latA, lonA, latB, lonB):  
    ra = 6378140  # radius of equator: meter  
    rb = 6356755  # radius of polar: meter  
    flatten = (ra - rb) / ra  # Partial rate of the earth  
    # change angle to radians  
    radLatA = radians(latA)  
    radLonA = radians(lonA)  
    radLatB = radians(latB)  
    radLonB = radians(lonB)  
  
    try: 
        pA = atan(rb / ra * tan(radLatA))  
        pB = atan(rb / ra * tan(radLatB))  
        x = acos(sin(pA) * sin(pB) + cos(pA) * cos(pB) * cos(radLonA - radLonB))  
        c1 = (sin(x) - x) * (sin(pA) + sin(pB))**2 / cos(x / 2)**2  
        c2 = (sin(x) + x) * (sin(pA) - sin(pB))**2 / sin(x / 2)**2  
        dr = flatten / 8 * (c1 - c2)  
        distance = ra * (x + dr)  
        return distance# meter   
    except:
        return 0.0000001

# - 分解时间特征
import time,datetime
def time_feature(data,col):
    data[str(col)+'tm_hour'] = data[col].map(lambda x:time.strptime(x, "%Y-%m-%d %H:%M:%S").tm_hour)
    data[str(col)+'tm_min'] = data[col].map(lambda x:time.strptime(x, "%Y-%m-%d %H:%M:%S").tm_min)
    data[str(col)+'tm_wday'] = data[col].map(lambda x:time.strptime(x, "%Y-%m-%d %H:%M:%S").tm_wday)
    return data    
    
    
# - 计算得分
def distinct_score(start,end):
    train = pd.merge(start,end,left_index=True, right_index=True)
    train.columns=['true_lat','ture_lon','pred_lat','pred_lon']
    distinct = train.apply(lambda train:getDistance(train['true_lat'],train['ture_lon'],train['pred_lat'],train['pred_lon']),axis=1)
    fdi = 1/(1+np.exp(-(distinct-1000)/250))
    score = fdi.sum()/fdi.shape[0]
    print("Score:",score)   

# - 输出结果  
def get_submit(data,path_csv):
    data['end_code'] = data.end_encode.map(range_dict)
    data['end_lat'] = data.end_code.map(lat_dict)
    data['end_lon'] = data.end_code.map(lon_dict)
    data[['r_key','end_lat','end_lon']].to_csv(path_csv,index=False)    

# - 读取并清洗数据
def get_data():
    print('Get Data start ...')
    
    train = pd.read_csv('train_new.csv')
    train['out_id'] = train.out_id.astype(str)
    del train['r_key']

    test = pd.read_csv('test_new.csv')
    test['out_id'] = test.out_id.astype(str)
    
    train = time_feature(train,'start_time')
    test = time_feature(test,'start_time')    
    
    train['start_encode'] = train.apply(lambda train:geohash.encode(train['start_lat'],train['start_lon'],6),axis=1)
    train['end_encode'] = train.apply(lambda train:geohash.encode(train['end_lat'],train['end_lon'],6),axis=1)
    test['start_encode'] = test.apply(lambda test:geohash.encode(test['start_lat'],test['start_lon'],6),axis=1)
    
    train['date_time'] = pd.to_datetime(train['start_time']).map(lambda x:x.strftime('20%y%m%d'))
    test['date_time'] = pd.to_datetime(test['start_time']).map(lambda x:x.strftime('20%y%m%d'))
    
    print('Get Data over ...')
    return train,test

def get_dict():
    encode_list = pd.concat([train.start_encode,train.end_encode,test.start_encode],axis=0)
    encode_dict = dict(zip(set(encode_list),list(range(0,len(set(encode_list))))))
    out_id_dict = dict(zip(set(train.out_id),list(range(0,len(set(train.out_id))))))
    range_dict = dict(zip(list(range(0,len(set(encode_list)))),set(encode_list)))
    start_ = train[['start_lat','start_lon','start_encode']]
    end_ = train[['end_lat','end_lon','end_encode']]
    end_.columns=['start_lat','start_lon','start_encode']
    encode2 = pd.concat([start_,end_],axis=0)
    lat_dict = dict(encode2.groupby('start_encode').start_lat.mean())
    lon_dict = dict(encode2.groupby('start_encode').start_lon.mean())
    
    # - 构建节假日字典(后面再考虑假日时间长短)
    set_date = set(pd.concat([train['date_time'],test['date_time']]))
    max_steps = len(set_date)
    process_bar = ShowProcess(max_steps, 'Get dict OK')
    date_lable = []
    for date_s in set_date:    
        date_lable.append(Getdatatype(date_s))
        process_bar.show_process()
        time.sleep(0.01)
    date_dict = dict(zip(set_date,date_lable))
    
    return encode_dict,range_dict,out_id_dict,lat_dict,lon_dict,date_dict

# - 获取是否节假日及工作日
def Getdatatype(datetime_):
    url = 'http://api.goseek.cn/Tools/holiday?date='+datetime_
    urllib.request.urlopen(url)  # 打开url
    html = urllib.request.urlopen(url).read()  # 读取内容
    return json.loads(html)['data']

# - 进行编码
def FeatureEncode(data,istrain=True):
    encodeData = copy.deepcopy(data)
    encodeData['date_time'] = encodeData['date_time'].map(date_dict)
    encodeData['out_id'] = encodeData.out_id.map(out_id_dict)
    encodeData['start_encode'] = encodeData.start_encode.map(encode_dict)
    if istrain == True :
        encodeData['end_encode'] = encodeData.end_encode.map(encode_dict)
    return encodeData

# - 获得家庭地址
def Gethomedict(data,col1,col2):
    Fdata =  copy.deepcopy(data)
    
    Fdata[str(col1)+str(col2)] = Fdata[col1].map(lambda x:str(x)).str.cat(Fdata[col2].map(lambda x:str(x)))
    place_dict = dict(Fdata[str(col1)+str(col2)].value_counts())
    Fdata['place_time'] = Fdata[str(col1)+str(col2)].map(place_dict)
    
    Fdata['rank_'] = Fdata['place_time'].groupby(Fdata[col1]).rank(ascending=0,method='dense') # - 排序打标签
    unually_dist = dict(zip(Fdata[Fdata.rank_==1][[col1,col2]].drop_duplicates()[col1], Fdata[Fdata.rank_==1][[col1,col2]].drop_duplicates()[col2]))
    return unually_dist
# - 计算出行点离家距离
def FeatureHomeDist(data,dict_):
    Fdata =  copy.deepcopy(data)
    Fdata['usual_dist'] = Fdata['out_id'].map(dict_)
    Fdata['D_lat'] = Fdata['usual_dist'].map(range_dict).map(lat_dict)
    Fdata['D_lon'] = Fdata['usual_dist'].map(range_dict).map(lon_dict)
    Fdata['usual_dist'] = Fdata.apply(lambda Fdata:getDistance(Fdata['start_lat'],Fdata['start_lon'],Fdata['D_lat'],Fdata['D_lon']),axis=1)
    return Fdata

提升方向

进行简单分析,出错较多的还是周六日和节假日,对于这些特殊时间点的出行。

对于该类型,可以考虑通过时间和空间特征:

做一些比如假期剩余时间、离家地点,当离家地点变化趋势等,理由如下:

假期剩余时间:当假期剩余时间较少,则倾向回家

假期离家距离趋势:当离家地点越来越近,则代表正在回家路上

节假日周六日出游倾向:构建用户附近的人群密集点,加上时间判断作为出游景点,构建用户离景点距离特征

参数、交叉验证轻微提高一些准确性。

总结

这次比赛过程中,就做了两个多星期,最近才开始闲下来。因为工作原因当时没法继续。还有很多提升的方向有idea但是没时间去做。另外就是代码薄弱,我是学统计出身,有机会还是需要恶补代码。若有什么意见,可以私信或留言交流,谢谢。

最后麻烦大家来我的头条号看看我 https://www.toutiao.com/i6643572922105463300/


2条评论

分享

2条评论
意见反馈
关注微信公众号 关注微信公众号

扫一扫分享给周围朋友