平安驾驶行为预测驾驶风险比赛

最近平安车险的一个数据挖掘比赛,训练集2000w,属于数据不均衡的回归问题,正负例之比大约1:7。

比赛链接
http://www.datafountain.cn/?u=7612745&&#/competitions/284/intro

本赛题提供部分客户1分钟级驾驶行为数据及对应客户的赔付率作为训练集,包括经纬度定位及驾驶状态等(已脱敏),参赛队伍需要对其进行数据挖掘和必要的机器学习训练。另外,我们会提供同期其他部分客户的驾驶行为数据来做评测,检测您的算法是否能准确的识别出当时客户的驾驶风险。

整理一下这个比赛的思路:

  • 特征工程
  • 模型

特征工程

特征工程是机器学习/深度学习里面非常重要的一个环境,觉得的模型效果的上限。特征工程的好坏,对比赛的影响非常大。在这里用pandas来读取csv文件,来做基本的特征处理。

平安驾驶行为预测驾驶风险比赛_第1张图片

从左到右,分别是用户id,轨迹id,时间,经度,维度,方向,高度,速度,通话状态。Y表示当前用户的赔付率。

每一个用户,对应多个轨迹id,每一个轨迹id,对应多行数据。但是每一个用户,只对应一个赔付率。也就是说,特征工程的单位是基于用户,为每一个用户构建一个特征向量。

  • 对于时间特征
    原始数据给出的是unix时间戳,将它转换成正常的时间。然后把时间转换成一个one-hot的向量。
    对于一个用户,如果时间的粒度是1小时,那就是构造一个长度为24的向量。(这个是我随便举的一个例子,在比赛里面这个粒度对分数影响非常大!!!)
    如果一个用户有两条轨迹早上一点的轨迹,这个用户总共有有100条轨迹。那么 one-hot 的表示方式 是 : [2/100, xx , xx , ... ]

      alluser = data['TERMINALNO'].nunique()
      # Feature Engineer, 对每一个用户生成特征:
      for item in data['TERMINALNO'].unique():
          #print('user NO:',item)
          temp = data.loc[data['TERMINALNO'] == item,:]
          temp['hour'] = temp['TIME'].apply(lambda x: (datetime.datetime.fromtimestamp(x).hour))
          hour_state = np.zeros([24,1])
          for i in range(24):
              hour_state[i] = temp[temp['hour']==i].shape[0]/temp.shape[0]
    

    one-hot 是一个处理特征常用的套路,比如对于性别特征,性别有两个属性男和女,那么男可以表示为[1, 0],女可以表示为[0, 1]。

    Pandas 可以使用get_dummies函数把字符特征转换成one hot形式:

    dummies = pd.get_dummies(dframe[feat])     #获取特征
    dummies = dummies.rename(columns=lambda x: feat+str(x)) # 改特征名 
    dframe = pd.concat([dframe,dummies],axis=1) #把新特征加入原始数据集
    
  • 对于速度,高度,方向这三个数值特征

    1. 可以选择他们的均值,方差作为特征(即统计某一个用户,所有轨迹的速度值(高度/方向)的均值,方差等)

    2. 使用频率作为特征
      对于一个用户,统计这个用户的所有的速度的分布的频率作为特征。比如将速度分成三个区间[0,5],[5,15],[15,50] ,然后统计速度落每个区间的数量/总数。这样就得出三个频率值。
      在实际问题里面,我们使用分位点来划分区间。

        #选择6个分位点,将区间划分成7份
        thresold_class1[i] = np.percentile(data2.values,1)
        thresold_class2[i] = np.percentile(data2.values,5)
        thresold_class3[i] = np.percentile(data2.values,90)
        thresold_class4[i] = np.percentile(data2.values,95)
        thresold_class5[i] = np.percentile(data2.values,99)
        thresold_class6[i] = np.percentile(data2.values,100)
      
        def character_class(x,thresold_class0,thresold_class1,thresold_class2,thresold_class3,thresold_class4,thresold_class5,thresold_class6):
                if thresold_class0 <= x < thresold_class1:
                    return 0
                if thresold_class1 < x <= thresold_class2:
                    return 1
                if thresold_class2 < x <= thresold_class3:
                    return 2
                if thresold_class3 < x <= thresold_class4:
                    return 3
                if thresold_class4 < x <= thresold_class5:
                    return 4
                if thresold_class5 < x <= thresold_class6:
                    return 5
      
        #把速度值根据分位点变成0~5的状态值
        temp['speed'] = temp['SPEED'].apply(lambda x: character_class(x,thresold_class0[0],thresold_class1[0],thresold_class2[0],thresold_class3[0],thresold_class4[0]
                                                                      ,thresold_class5[0],thresold_class6[0])) ### 将速度按照10分级,分成14份;
      
        #转换成频率的one-hot
        speed_state = np.zeros([6,1])
        for i in range(6):
            speed_state[i] = temp.loc[temp['speed']==i].shape[0]/float(nsh)
      

      在比赛里面,这个分位点的选择很重要,对分数影响很大。尤其是对于特别大和特别小区间的时候,往往分的细一点会效果更好一些。

  • 对于gps的处理
    1.最早的plan是将gps栅格化,然后给每个方块编码,转换成类似于one-hot的特征。
    但是这个的问题是gps的数据差距很大,并不是同一个城市,这样意味着栅格大了没有用(没有办法把点区分出来),栅格小了编码比较多,不一定有效果。

    2.一个神奇的trick
    在比赛的讨论群里面,有人说给gps减去一个固定的坐标(随意的gps,然后必须是一个固定的值),使用这个距离差作为特征。尝试了一下,效果很好!

    我的理解是有一些地方是事故高发的地方,所以,这么搞一下。可以找到这个地方。

最后,返回生成的特征

        feature = [item, num_of_trips, num_of_records,num_of_state_0,num_of_state_1,num_of_state_2,num_of_state_3,num_of_state_4
            ,float(speed_state[0]),float(speed_state[1]),float(speed_state[2]),float(speed_state[3]),float(speed_state[4]),float(speed_state[5]),float(height_state[0])
            ,float(height_state[1]),float(height_state[2]),float(height_state[3]),float(height_state[4]),float(height_state[5]),float(direction_state[0]),float(direction_state[1])
            ,float(direction_state[2]),float(direction_state[3]),float(direction_state[4]),float(direction_state[5]),float(hour_state[0]),float(hour_state[1])
            ,float(hour_state[2]),float(hour_state[3]),float(hour_state[4]),float(hour_state[5])
            ,float(hour_state[6]),float(hour_state[7]),float(hour_state[8]),float(hour_state[9]),float(hour_state[10]),float(hour_state[11])
            ,float(hour_state[12]),float(hour_state[13]),float(hour_state[14]),float(hour_state[15]),float(hour_state[16]),float(hour_state[17])
            ,float(hour_state[18]),float(hour_state[19]),float(hour_state[20]),float(hour_state[21]),float(hour_state[22]),float(hour_state[23])
            ,float(hour_state[24]), float(hour_state[25]), float(hour_state[26]), float(hour_state[27]),float(hour_state[28]), float(hour_state[29])
            ,float(hour_state[30]), float(hour_state[31]), float(hour_state[32]), float(hour_state[33]),float(hour_state[34]), float(hour_state[35])
            ,float(hour_state[36]), float(hour_state[37]), float(hour_state[38]), float(hour_state[39]),float(hour_state[40]), float(hour_state[41])
            ,float(hour_state[42]), float(hour_state[43]), float(hour_state[44]), float(hour_state[45]),float(hour_state[46]), float(hour_state[47])
            ,hdis1,target]
数据补全,异常处理 和 归一化
  1. 对于缺失数据,可以使用均值,中位数,或者众数填充。

  2. 归一化
    2.1 Z-Score标准化
    2.2 Min-Max归一化

min_max_scaler = preprocessing.MinMaxScaler() 
minmax_train = min_max_scaler.fit_transform(train_x) 
minmax_test = min_max_scaler.transform(test_x) 
train_x, test_x = pd.DataFrame(minmax_train), pd.DataFrame(minmax_test)
  1. 不平衡数据的处理
    一般是上采样或下采样。我们这次比赛是为0的数据和非0的数据是7:1的关系。我们尝试了上采样和下采样,但是效果没有明显变化。感觉这个也和测试集数据的分布有关系。可以尝试,当不一定有效果。
模型

我们尝试的模型有随机森林,lgb,线性回归,svm等。综合下来,最好的效果是lgb。对于模型的参数是一个很麻烦的事情,尤其是新添加了几个特征之后,模型的参数就全变了。没有办法判断是特征没有效果还是参数没有效果。我们的想法是把特征都推上去,然后调参。

  • 特征选择
    由于比赛每天只有三次的提交机会,而调参又是需要大量的尝试,当我们添加了一个特征的时候,往往用原来的参数是没有办法证明这个特征的有效性(换句话说,就是原来模型的参数无效了,有需要重新调参)。这个显然是一个无解的死循环。所以,我们的想到的解决办法是。当添加新的特征的时候,使用线性回归的模型进行提交,因为线性回归的模型是不需要很多参数,可以迅速的验证添加的特征是否可以提高分数。
    如果添加特征之后,分数没有下降,那么可以基本上可以认为这个特征是一个有效的特征。这就是我们后期选择特征的方法。然后在把模型换成lgb,进行调参。

  • 调参
    调参的思路是根据每一次提交的成绩得分,来估计模型是处于一个过拟合还是一个欠拟合的状态。(比如当前的分数是0.15 , 试着把迭代次数调高,再提交一次,如果分数下降了。那可以说明模型现在处于过拟合的状态。)

    如果是过拟合,采用的调参思路是:

    1. 提高迭代的次数
    2. 提升num_leaves

    如果是过拟合,采用的办法是:

    1. L1 和 L2正则
    2. 更少的num_leaves
    3. 减少迭代次数
    4. 使用early_stop(ealry_stop的实现是,如果cost不再下降的时候,就提前结束,来避免过拟合)

比赛很遗憾,没有杀进决赛,最后的名次是初赛15名,复赛16名。领了3000元的奖金,作为伙食费散场了。

你可能感兴趣的:(平安驾驶行为预测驾驶风险比赛)