金融风控实战——信贷特征衍生与筛选(中国移动人群画像赛TOP1)

运营商变量的深度挖掘

我们以本次比赛的特征为例进行展开的描述:

1、用户实名制是否通过核实 1为是0为否 目前国内基本上是手机卡绑定身份证,当然有部分落后地区仍旧存在着买卖所谓流量卡这类的经营活动,一般这类特征常常作为反欺诈特征作为反欺诈规则的一部分,除此之外,用户的实名制使用的身份证数据一般是可以获取的,对于身份证进行分割提取变量也是比较常见的衍生手段:
在这里插入图片描述
首先,这里的11代表了所在省份的代码,我们常通过码表,来对将编码转化为具体的明文:(下面列出部分码表)
在这里插入图片描述
这里我们就可以衍生出用户所在省份为北京

同样,3、4位代表了所在城市代码、第5,6位数字表示所在区县的代码、第7到14位表示出生年,月,日、第15,16位数字表示所在地派出所的代码、第17位数字表示性别,奇数表示男性,偶数表示女性、第18位数字是校验码,由号码编制单位按统一的公式计算出来的、如果尾号是0到9,那就不会出现X,如果尾号是10,那么就会用X代替。

针对于市、区、县一般都会有对应的码表用于提取用户的所在地特征;

针对于公民的出生年月可以计算出其年龄、星座;

通过性别编码可以得到其性别

2、根据用户的套餐情况可以知道用户是否大学生

现在高校很多都推出了运营商针对于在校大学生的优惠套餐,因此可以据此判断出用户是大学生还是上班族;

3、是否黑名单客户

关于运营商黑名单用户的定义:

1、逾期欠费停机被销户的用户;

2、恶意欠费用户(如恶意骗取国际信息台话费、恶意骗取异地漫游话费、盗打等);

3、公安机关确定的涉嫌短信欺诈、诈骗等犯罪行为的用户;

4、无主黑名单用户:符合上述条件的无客户资料的用户等。

一般我们在进行评分卡开发的过程中,这类特征都是作为反欺诈规则的一部分进行用户的筛选居多

4、运营商高危用户

指的是超套、双卡、双降、低消4类高危客户

5、用户在网时长

一般运营商都会又用户在网时间长度的特征,这个人工去相应的营业厅或者电话查询都是可以查询到的

6、用户缴费记录

运营商一般都保留有用户的缴费记录情况,也就是用户的消费流水信息,这种特征的处理方式是非常多的:

(1)、时间窗函数,统计用户最近一次消费金额,用户最近一周、一个月、三个月、半年、一年、三年的消费记录的求和、均值、最大值、最小值、标准差等;

(2)、用户的最近一次消费距今时间间隔

(3)、用户的账户余额的流水信息,我们每个月都会有欠费,不同用户的行为模式不同,有的用户喜欢多交,有的用户喜欢交的刚刚好,因此每个用户的账户余额都会存在一定差异,那么针对于账户余额的序列数据,我们仍旧可以使用时间窗函数,统计用户最近一次账户余额、最近一周。。。。。三年等,如果用户在网时长较短则计算结果常常为空值,针对空值我们要单独进行记录;

(4)、用户缴费情况,即有的用户会在欠费之后缴费,有的则在欠费之前缴,二者的行为模式存在区别,可以通过计算用户每月缴费日期和话费账单日的时间差来刻画用户的缴费行为模式,然后按照时间窗来进行不同时间周期内的求和、均值、最大值、最小值、标准差等等

注,上述的序列特征,我们可以尝试使用CNN、RNN、attention、transformer等自动化学习特征的手段来提取其潜在特征,例如使用LSTM进行有监督建模然后去其hidden representation或者直接使用模型输出的得分作为深度学习自动获取的更加高阶的抽象特征

7、用户通话记录

用户通话记录可以说是一个能够玩出花的数据类型,最基本的:

(1)、用户每周、月、三月、半年、一年、三年等的通话人数的均值、总和、标准差等;

(2)、用户每周、月、三月、半年、一年、三年等的通话时间的均值、总和、标准差等;

(3)、用户每周、月、三月、半年、一年、三年等的外地、国内外通话时间/人数的均值、总和、标准差等;

针对于用户的通话记录可以进行非常多的特征衍生手段

除此之外,更加重要的是,根据用户的通话时间和通话情况,我们可以非常简单直观的构造出用户通话记录的社交图谱:在这里插入图片描述
然后结合传统的图算法甚至是graph embedding,GNN等尝试进行特征抽取,得到用户隐式的社交特征用于增强下游传统机器学习任务的效果

不过很可惜,运营商通话记录已经越来越难以获取了

常用特征工程手段

1、连续特征之间的加减乘除平方开方对数变换分箱等;

2、类别特征之间的交叉、特征编码、分箱、embedding等

3、时间窗,适用于上述两种特征,连续特征的不同时间窗下的统计特征,类别不同时间窗下的记数等等;

4、高阶的特征工程往往结合深度学习进行,序列特征的embedding、id 特征的embedding等等,适用于连续和类别

这里关于id特征的处理做一下展开吧,因为id类特征的处理,推荐系统领域是非常成熟的,风控里也有常常出现id类特征,也是风控中难得的一些有技术含量的东西:

基础属性特征,用户和商品的原始特征,以风控为例,用户属性包括用户的职业、性别、工作城市、公司属性等,商品属性典型的例如用户手机安装的app的所属类别、用户借贷的贷款产品的类目等等

统计类特征,常见的时间窗函数(这玩意儿的学名原来叫histogram regression,具体可见这位大佬的文章:https://zhuanlan.zhihu.com/p/76415842), 用户过去不同时间窗口内对商品发生行为的统计,如点击/查看/下载/购买等;同样地,物品在不同时间窗口内的以上行为的统计,除此之外也包括了全部数据的各类groupby count min max mean median 等等处理;

上下文特征,如用户当前所处地理位置,交易时间上中下午,当天是否为休息日、节假日等描述交易发生的时候用户所处“状态”的特征;

高阶交叉特征,多阶类别特征交叉,gbdt特征交叉为典型代表;

表征学习,文本特征embedding,图像特征embedding,序列特征embedding等,万物皆可embedding;

textgcn 这篇文本

具体比赛流程

数据清单

train_dataset.zip:训练数据,包含50000行

test_dataset.zip:测试集数据,包含50000行

数据说明

本次提供数据主要包含用户几个方面信息:身份特征、消费能力、人脉关系、位置轨迹、应用行为偏好。字段说明如下:

字段列表 字段说明

  • 用户编码 数值 唯一性
  • 用户实名制是否通过核实 1为是0为否
  • 用户年龄 数值
  • 是否大学生客户 1为是0为否
  • 是否黑名单客户 1为是0为否
  • 是否4G不健康客户 1为是0为否
  • 用户网龄(月) 数值
  • 用户最近一次缴费距今时长(月) 数值
  • 缴费用户最近一次缴费金额(元) 数值
  • 用户近6个月平均消费话费(元) 数值
  • 用户账单当月总费用(元) 数值
  • 用户当月账户余额(元) 数值
  • 缴费用户当前是否欠费缴费 1为是0为否
  • 用户话费敏感度 用户话费敏感度一级表示敏感等级最大。根据极值计算法、叶指标权重后得出的结果,根据规则,生成敏感度用户的敏感级别:先将敏感度用户按中间分值按降序进行排序,前5%的用户对应的敏感级别为一级:接下来的15%的用户对应的敏感级别为二级;接下来的15%的用户对应的敏感级别为三级;接下来的25%的用户对应的敏感级别为四级;最后40%的用户对应的敏感度级别为五级。
  • 当月通话交往圈人数 数值
  • 是否经常逛商场的人 1为是0为否
  • 近三个月月均商场出现次数 数值
  • 当月是否逛过福州仓山万达 1为是0为否
  • 当月是否到过福州山姆会员店 1为是0为否
  • 当月是否看电影 1为是0为否
  • 当月是否景点游览 1为是0为否
  • 当月是否体育场馆消费 1为是0为否
  • 当月网购类应用使用次数 数值
  • 当月物流快递类应用使用次数 数值
  • 当月金融理财类应用使用总次数 数值
  • 当月视频播放类应用使用次数 数值
  • 当月飞机类应用使用次数 数值
  • 当月火车类应用使用次数 数值
  • 当月旅游资讯类应用使用次数 数值

评价方式

竞赛评价指标采用MAE系数。

平均绝对差值是用来衡量模型预测结果对标准结果的接近程度的一种衡量方法。计算方法如下:

M A E = 1 n ∑ i = 1 n ∣ p r e d i − y i ∣ M A E=\frac{1}{n} \sum_{i=1}^{n}\left|p r e d_{i}-y_{i}\right| MAE=n1i=1nprediyi

其中$ p r e d_{i} 为 预 测 样 本 , 为预测样本, y_{i}$为真实样本。MAE的值越小,说明预测数据与真实数据越接近。

最终结果为:$ Score =\frac{1}{1+M A E}$

M S E = 1 n ∑ i = 1 n ( pred i − y i ) 2 M S E=\frac{1}{n} \sum_{i=1}^{n}\left(\text {pred}_{i}-y_{i}\right)^{2} MSE=n1i=1n(prediyi)2

MSE对预测偏差越大的样本惩罚越大,因为加上平方之后数据会越来越大。

最终的结果越接近1分数越高

全面探索

万变不离其宗,首先我们作为一名数据竞赛选手,拿到数据应该进行分析观察,让自己对竞赛题型、数据大致了解,下面开始数据整体探索。

""" 导入基本库 """
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt


#plt.style.use("bmh")
#plt.rc('font', family='SimHei', size=13) #显示中文
#pd.set_option('display.max_columns',1000)
#pd.set_option('display.width', 1000)
#pd.set_option('display.max_colwidth',1000)
plt.rcParams['font.sans-serif'] = ['Heiti TC'] # 步骤一(替换sans-serif字体)
plt.rcParams['axes.unicode_minus'] = False

在数据清单我们知道本次竞赛有一个训练集压缩包和一个预测集压缩包,在文件夹中解压后直接进行合并,以便后续数据内容变换的统一处理。

pd.concat合并训练集和测试集

""" 导入数据 """
train_data = pd.read_csv('/Users/zhucan/Desktop/train_dataset.csv')
test_data = pd.read_csv('/Users/zhucan/Desktop/test_dataset.csv')
df_data = pd.concat([train_data, test_data], ignore_index=True)
df_data

在这里插入图片描述

""" 数据属性 """
df_data.info() #可以看出没有缺失值
#<class 'pandas.core.frame.DataFrame'>
#Int64Index: 100000 entries, 0 to 49999
#Data columns (total 30 columns):
# #   Column           Non-Null Count   Dtype  
#---  ------           --------------   -----  
# 0   用户编码             100000 non-null  object 
# 1   用户实名制是否通过核实      100000 non-null  int64  
# 2   用户年龄             100000 non-null  int64  
# 3   是否大学生客户          100000 non-null  int64  
# 4   是否黑名单客户          100000 non-null  int64  
# 5   是否4G不健康客户        100000 non-null  int64  
# 6   用户网龄(月)          100000 non-null  int64  
# 7   用户最近一次缴费距今时长(月)  100000 non-null  int64  
# 8   缴费用户最近一次缴费金额(元)  100000 non-null  float64
# 9   用户近6个月平均消费值(元)   100000 non-null  float64
# 10  用户账单当月总费用(元)     100000 non-null  float64
# 11  用户当月账户余额(元)      100000 non-null  int64  
# 12  缴费用户当前是否欠费缴费     100000 non-null  int64  
# 13  用户话费敏感度          100000 non-null  int64  
# 14  当月通话交往圈人数        100000 non-null  int64  
# 15  是否经常逛商场的人        100000 non-null  int64  
# 16  近三个月月均商场出现次数     100000 non-null  int64  
# 17  当月是否逛过福州仓山万达     100000 non-null  int64  
# 18  当月是否到过福州山姆会员店    100000 non-null  int64  
# 19  当月是否看电影          100000 non-null  int64  
# 20  当月是否景点游览         100000 non-null  int64  
# 21  当月是否体育场馆消费       100000 non-null  int64  
# 22  当月网购类应用使用次数      100000 non-null  int64  
# 23  当月物流快递类应用使用次数    100000 non-null  int64  
# 24  当月金融理财类应用使用总次数   100000 non-null  int64  
# 25  当月视频播放类应用使用次数    100000 non-null  int64  
# 26  当月飞机类应用使用次数      100000 non-null  int64  
# 27  当月火车类应用使用次数      100000 non-null  int64  
# 28  当月旅游资讯类应用使用次数    100000 non-null  int64  
# 29  信用分              50000 non-null   float64
#dtypes: float64(4), int64(25), object(1)
#memory usage: 23.7+ MB
print("共有数据集:", df_data.shape[0])
print("共有测试集:", test_data.shape[0])
print("共有训练集:", train_data.shape[0])
#共有数据集: 100000
#共有测试集: 50000
#共有训练集: 50000

结论:数据集情况与数据清单相对应,说明我们数据没有下载错误,合并后100000行,可以看到合并数据集特征列中全为数值型特征并且不存在缺失值。在这里,比赛刚开始的时候,一个新手与数据敏感高手的区别就开始体现出来,新手通常会直接忽略该信息。但是我们试着推理一下,中国移动公司获取一个从不开定位的手机信息和一个从不移动的家用手机信息商场出现次数,是应该有区别的,那么从不开定位就是一种缺失值,但在赛题中并没有出现。经过资料搜索和调查,发现实际上主办方是直接将所有缺失值填补为0,导致数据集中不存在缺失值;

""" 统计每个特征有多少个类别 """
for i,name in enumerate(df_data.columns):
    name_sum = df_data[name].value_counts().shape[0] 
    print("{}、{}      特征类别个数是:{}".format(i + 1, name, name_sum))
#1、用户编码      特征类别个数是:100000
# 2、用户实名制是否通过核实      特征类别个数是:2
# 3、用户年龄      特征类别个数是:88
# 4、是否大学生客户      特征类别个数是:2
# 5、是否黑名单客户      特征类别个数是:2
# 6、是否4G不健康客户      特征类别个数是:2
# 7、用户网龄(月)      特征类别个数是:283
# 8、用户最近一次缴费距今时长(月)      特征类别个数是:2
# 9、缴费用户最近一次缴费金额(元)      特征类别个数是:532
# 10、用户近6个月平均消费值(元)      特征类别个数是:22520
# 11、用户账单当月总费用(元)      特征类别个数是:16597
# 12、用户当月账户余额(元)      特征类别个数是:316
# 13、缴费用户当前是否欠费缴费      特征类别个数是:2
# 14、用户话费敏感度      特征类别个数是:6
# 15、当月通话交往圈人数      特征类别个数是:554
# 16、是否经常逛商场的人      特征类别个数是:2
# 17、近三个月月均商场出现次数      特征类别个数是:93
# 18、当月是否逛过福州仓山万达      特征类别个数是:2
# 19、当月是否到过福州山姆会员店      特征类别个数是:2
# 20、当月是否看电影      特征类别个数是:2
# 21、当月是否景点游览      特征类别个数是:2
# 22、当月是否体育场馆消费      特征类别个数是:2
# 23、当月网购类应用使用次数      特征类别个数是:8382
# 24、当月物流快递类应用使用次数      特征类别个数是:239
# 25、当月金融理财类应用使用总次数      特征类别个数是:7232
# 26、当月视频播放类应用使用次数      特征类别个数是:16067
# 27、当月飞机类应用使用次数      特征类别个数是:209
# 28、当月火车类应用使用次数      特征类别个数是:180
# 29、当月旅游资讯类应用使用次数      特征类别个数是:934
# 30、信用分      特征类别个数是:278
""" 数据统计 """
df_data.describe()

在这里插入图片描述

df_data['信用分'].isnull()
#0        False
#1        False
#2        False
#3        False
#4        False
#         ...  
#99995     True
#99996     True
#99997     True
#99998     True
#99999     True
#Name: 信用分, Length: 100000, dtype: bool
""" 观察训练/测试集数据同分布状况 """
df_data[df_data['信用分'].isnull()].describe()

在这里插入图片描述

df_data[df_data['信用分'].notnull()].describe()

在这里插入图片描述

初级特征探索(数据预处理)

接下来开始分析特征与信用分的关联性、开展相关的初级特征探索。很多新手经常在特征探索容易出现无从下手的情况,在这里推荐,按照连续、离散、非结构化特征顺序来进行特征探索.

连续+连续

类别+类别 复杂一些进行join然后转化为文本

连续+类别 类似特征编码

非结构化

计算每个特征和标签的特征相关度

# np.corrcoef(df_data['当月是否看电影'].values,df_data.信用分.values)
df_train=df_data[df_data['信用分'].notnull()]
df_train.head()

在这里插入图片描述

np.corrcoef(df_train['当月是否看电影'].values,df_train['信用分'].values)[0,1]
#0.1653765230842276
df_train.columns
#Index(['用户编码', '用户实名制是否通过核实', '用户年龄', '是否大学生客户', '是否黑名单客户', '是否4G不健康客户', '用户网龄(月)', '用户最近一次缴费距今时长(月)', '缴费用户最近一次缴费金额(元)', '用户近6个月平均消费值(元)', '用户账单当月总费用(元)', '用户当月账户余额(元)', '缴费用户当前是否欠费缴费', '用户话费敏感度', '当月通话交往圈人数', '是否经常逛商场的人', '近三个月月均商场出现次数', '当月是否逛过福州仓山万达', '当月是否到过福州山姆会员店', '当月是否看电影', '当月是否景点游览', '当月是否体育场馆消费', '当月网购类应用使用次数', '当月物流快递类应用使用次数', '当月金融理财类应用使用总次数', '当月视频播放类应用使用次数', '当月飞机类应用使用次数', '当月火车类应用使用次数', '当月旅游资讯类应用使用次数', '信用分'], dtype='object')
from matplotlib.font_manager import FontProperties
myfont=FontProperties(fname='SimHei.ttf',size=14)
plt.figure(figsize=(28,15))
ax = df_train.corr()['信用分'].sort_values().plot(kind="bar")
ax.set_title('Correlation coefficient of the variables')
ax.set_ylabel('Correlation coefficient')

df_train.corr()['信用分'].sort_values()

x_cols=[col for col in df_train.columns if col not in ['信用分'] if df_train[col].dtype!='object']

labels=[]
values=[]
for col in x_cols:
    labels.append(col)
    values.append(np.corrcoef(df_train[col].values,df_train['信用分'].values)[0,1])
    
corr_df=pd.DataFrame({'col_labels':labels,'corr_values':values})
corr_df=corr_df.sort_values(by='corr_values')

fig,ax=plt.subplots(figsize=(12,40))
ind=np.arange(len(labels))
ax.barh(ind,corr_df.corr_values.values,color='r')
ax.set_yticks(ind)
ax.set_yticklabels(corr_df.col_labels.values,rotation='horizontal',fontproperties=myfont)
ax.set_xlabel('Correlation coefficient')
ax.set_title('Correlation coefficient of the variables')

在这里插入图片描述

# 连续值特征
name_list=['用户网龄(月)','用户近6个月平均消费值(元)','当月通话交往圈人数','用户账单当月总费用(元)',
           '缴费用户最近一次缴费金额(元)','近三个月月均商场出现次数','当月金融理财类应用使用总次数','用户当月账户余额(元)']
f, ax = plt.subplots(4, 2, figsize=(20, 15))
for i,name in enumerate(name_list):
    sns.scatterplot(data=df_train, x=name, y='信用分', color='r', ax=ax[i // 2][i % 2])
plt.show()

在这里插入图片描述

name_list=['当月视频播放类应用使用次数','用户年龄','当月网购类应用使用次数','当月火车类应用使用次数',
           '当月旅游资讯类应用使用次数','当月飞机类应用使用次数','当月物流快递类应用使用次数','当月物流快递类应用使用次数']
f, ax = plt.subplots(4, 2, figsize=(20, 15))
for i,name in enumerate(name_list):
    sns.scatterplot(data=df_train, x=name, y='信用分', color='r', ax=ax[i // 2][i % 2])
plt.show()

在这里插入图片描述

# 离散值特征
name_list = ['当月是否景点游览','当月是否体育场馆消费','用户最近一次缴费距今时长(月)', '当月是否看电影',
             '是否经常逛商场的人','是否黑名单客户','缴费用户当前是否欠费缴费','当月是否到过福州山姆会员店',
             '当月是否逛过福州仓山万达', '用户实名制是否通过核实','是否大学生客户','是否4G不健康客户']

f, ax = plt.subplots(4, 3, figsize=(20, 15))

for i,name in enumerate(name_list):
    sns.boxplot(data=df_data, x=name, y='信用分',ax=ax[i // 3][i % 3])
plt.show()

在这里插入图片描述

# 离散值特征
f, ax = plt.subplots( figsize=(20, 6))
sns.boxplot(data=df_train, x='用户话费敏感度', y='信用分', color='r')
plt.show()

在这里插入图片描述

对数据做基本处理,再次观察单变量相关关系

df_train.describe()

在这里插入图片描述

def base_process():
    transform_value_feature=['用户年龄','用户网龄(月)','当月通话交往圈人数',
                             '近三个月月均商场出现次数','当月网购类应用使用次数','当月物流快递类应用使用次数',
                             '当月金融理财类应用使用总次数','当月视频播放类应用使用次数', '当月飞机类应用使用次数',
                             '当月火车类应用使用次数','当月旅游资讯类应用使用次数']
    
    user_fea=['缴费用户最近一次缴费金额(元)','用户近6个月平均消费值(元)','用户账单当月总费用(元)','用户当月账户余额(元)']
    
    log_features=['当月网购类应用使用次数','当月金融理财类应用使用总次数','当月视频播放类应用使用次数']
    
    #处理离群点.这里我们将大于99.9%的数据直接赋值对应99.9%的值,将小于0.1%的数据直接赋值0.1%对应的值
    for col in transform_value_feature+user_fea+log_features:
        ulimit=np.percentile(df_train[col].values, 99.9) #计算一个多维数组的任意百分比分位数
        llimit=np.percentile(df_train[col].values, 0.1)
        df_train.loc[df_train[col]>ulimit,col]=ulimit  # 大于99.9%的直接赋值
        df_train.loc[df_train[col]<llimit,col]=llimit
        
            
    for col in user_fea+log_features:
        df_train[col]=df_train[col].map(lambda x: np.log1p(x)) #取对数变化
        
    return df_train

train_df=base_process()
train_df.head()

在这里插入图片描述

经过数据处理之后再次进行相关度测量

train_df['交通次数']=train_df['当月飞机类应用使用次数']+train_df['当月火车类应用使用次数']

x_cols=[col for col in train_df.columns if col not in ['信用分'] if train_df[col].dtype!='object']

labels=[]
values=[]
for col in x_cols:
    labels.append(col)
    values.append(np.corrcoef(train_df[col].values,train_df.信用分.values)[0,1])
corr_df=pd.DataFrame({'col_labels':labels,'corr_values':values})
corr_df=corr_df.sort_values(by='corr_values')

ind=np.arange(len(labels))
width=0.5
fig,ax=plt.subplots(figsize=(12,40))
rects=ax.barh(ind,corr_df.corr_values.values,color='y')
ax.set_yticks(ind)
ax.set_yticklabels(corr_df.col_labels.values,rotation='horizontal')
ax.set_xlabel('Correlation coefficient')
ax.set_title('Correlation coefficient of the variables')

在这里插入图片描述

所有连续变量之间两两相关关系

corrmat=train_df.corr(method='spearman')

f,ax=plt.subplots(figsize=(12,12))
sns.heatmap(corrmat,vmax=1,square=True)
plt.title('Important Variables correlation map',fontsize=15)

在这里插入图片描述

特征工程

def get_features():
    
    df_data.loc[df_data['用户年龄']==0,'用户年龄']=df_data['用户年龄'].mode() #众数比平均值好
    
    #根据之前的特征重要性对其中几个强相关特征进行处理
    df_data['缴费金额是否能覆盖当月账单']=df_data['缴费用户最近一次缴费金额(元)']-df_data['用户账单当月总费用(元)']
    df_data['最近一次交费是否超过平均消费额']=df_data['缴费用户最近一次缴费金额(元)']-df_data['用户近6个月平均消费值(元)']
    df_data['当月账单是否超过平均消费额']=df_data['用户账单当月总费用(元)']-df_data['用户近6个月平均消费值(元)']
    
        
    #这些特征相关性比较小
    df_data['是否去过高档商场']=df_data['当月是否逛过福州仓山万达']+df_data['当月是否到过福州山姆会员店']
    df_data['是否去过高档商场']=df_data['是否去过高档商场'].map(lambda x:1 if x>=1 else 0)
    df_data['是否_商场_电影']=df_data['是否去过高档商场']*df_data['当月是否看电影'] #这里用的是乘法
    df_data['是否_商场_旅游']=df_data['是否去过高档商场']*df_data['当月是否景点游览']
    df_data['是否_商场_体育馆']=df_data['是否去过高档商场']*df_data['当月是否体育场馆消费']
    df_data['是否_电影_体育馆']=df_data['当月是否看电影']*df_data['当月是否体育场馆消费']
    df_data['是否_电影_旅游']=df_data['当月是否看电影']*df_data['当月是否景点游览']
    df_data['是否_旅游_体育馆']=df_data['当月是否景点游览']*df_data['当月是否体育场馆消费']
    
     
    df_data['是否_商场_旅游_体育馆']=df_data['是否去过高档商场']*df_data['当月是否景点游览']*df_data['当月是否体育场馆消费']
    df_data['是否_商场_电影_体育馆']=df_data['是否去过高档商场']*df_data['当月是否看电影']*df_data['当月是否体育场馆消费']
    df_data['是否_商场_电影_旅游']=df_data['是否去过高档商场']*df_data['当月是否看电影']*df_data['当月是否景点游览']
    df_data['是否_体育馆_电影_旅游']=df_data['当月是否体育场馆消费']*df_data['当月是否看电影']*df_data['当月是否景点游览']
    
    df_data['是否_商场_体育馆_电影_旅游']=df_data['是否去过高档商场']*df_data['当月是否体育场馆消费']*df_data['当月是否看电影']*df_data['当月是否景点游览']
       
    #将数据离散化,大多数情况下数据都是0或者1,所以采用离散化
    discretize_features=['交通类应用使用次数','当月物流快递类应用使用次数','当月飞机类应用使用次数','当月火车类应用使用次数','当月旅游资讯类应用使用次数']
    df_data['交通类应用使用次数']=df_data['当月飞机类应用使用次数']+df_data['当月火车类应用使用次数']
    
    def map_discreteze(x):
        if x==0:
            return 0
        elif x<=5:
            return 1
        elif x<=15:
            return 2
        elif x<=50:
            return 3
        elif x<=100:
            return 4
        else:
            return 5
        
    for col in discretize_features:
        
        df_data[col]=df_data[col].map(lambda x: map_discreteze(x))
            
    return df_data
           
all_data=get_features()
all_data

在这里插入图片描述

def base_process():
    transform_value_feature=['用户年龄','用户网龄(月)','当月通话交往圈人数','最近一次交费是否超过平均消费额',
                             '近三个月月均商场出现次数','当月网购类应用使用次数','当月物流快递类应用使用次数','当月账单是否超过平均消费额',
                     '当月金融理财类应用使用总次数','当月视频播放类应用使用次数', '当月飞机类应用使用次数','当月火车类应用使用次数',
                             '当月旅游资讯类应用使用次数']
    
    user_bill_features=['缴费用户最近一次缴费金额(元)','用户近6个月平均消费值(元)','用户账单当月总费用(元)','用户当月账户余额(元)']
    
    log_features=['当月网购类应用使用次数','当月金融理财类应用使用总次数','当月视频播放类应用使用次数']
    
    #处理离群点
    for col in transform_value_feature+user_bill_features+log_features:
        ulimit=np.percentile(all_data[col].values, 99.9) #计算一个多维数组的任意百分比分位数
        llimit=np.percentile(all_data[col].values, 0.1)
        all_data.loc[all_data[col]>ulimit,col]=ulimit  # 大于99.9%的直接赋值
        all_data.loc[all_data[col]<llimit,col]=llimit
        
      
    for col in user_bill_features+log_features:
        all_data[col]=all_data[col].map(lambda x: np.log1p(x)) #取对数变化
        
    
    train=all_data[:50000]
    test=all_data[50000:]
    return train,test   

train,test=base_process()
#最终的训练集和测试集
train.head()

在这里插入图片描述

test.head()

v

feature_name=[col for col in train.columns if col not in ['信用分'] if col not in ['用户编码'] ]
label_name=['信用分']
train_feature=train[feature_name]
train_label=train[label_name]
test_feature=test[feature_name]

模型和参数

from sklearn.model_selection import KFold,StratifiedKFold
from sklearn.metrics import accuracy_score
import lightgbm as lgb

def labcv_predict(train_feature,train_label):
   
    lgb_params1={
                'boosting_type':'gbdt','num_leaves':31,'reg_alpha':2.2,'reg_lambda':1.5,
                'max_depth':1,'n_estimators':2000,
                'subsample':0.8,'colsample_bytree':0.7,'subsample_freq':1,
                'learning_rate':0.03,'random_state':2019,'n_jobs':-1}
    
    clf2=lgb.LGBMRegressor(
                boosting_type='gbdt',num_leaves=31,reg_alpha=1.2,reg_lambda=1.8,
                max_depth=-1,n_estimators=2000,
                subsample=0.8,colsample_bytree=0.7,subsample_freq=1,
                learning_rate=0.03,random_state=2018,n_jobs=-1)

    
    kf=KFold(n_splits=10,random_state=2019,shuffle=True)  #十折交叉验证
    modell=[]
    model2=[]
    best_score=[]
    sub_list=[]
    
    t_feature=train_feature.values
    t_label=train['信用分'].values
   

    for i,(train_index,val_index) in enumerate(kf.split(t_feature)):
        X_train=t_feature[train_index,:]
        y_train=t_label[train_index]
        X_val=t_feature[val_index,:]
        y_val=t_label[val_index]
   
        
        #第一种参数预测
        clf=lgb.LGBMRegressor(**lgb_params1)
        clf.fit(X_train,y_train,eval_set=[(X_train,y_train),(X_val,y_val)],eval_metric='mae',early_stopping_rounds=100,verbose=200)
        pred_val1=clf.predict(X_val,num_iteration=clf.best_iteration_) #预测的划分出来的测试集的标签
        #vali_mae1=mean_absolute_error(y_val,np.round(pred_val1))
        vali_mae1=accuracy_score(y_val,np.round(pred_val1))
        #pred_test1=clf.predcit(test[feature_name],num_iteration=clf.best_iteration_) #预测的未带标签的测试集的标签
        modell.append(clf)
        
    
        
        # 第二种参数预测
        clf2.fit(X_train,y_train,eval_set=[(X_train,y_train),(X_val,y_val)],eval_metric='rmse',early_stopping_rounds=100,verbose=200)
        pred_val2=clf.predict(X_val,num_iteration=clf2.best_iteration_) #预测的划分出来的测试集的标签
        #vali_mae2=mean_absolute_error(y_val,np.round(pred_val2))
        vali_mae2=accuracy_score(y_val,np.round(pred_val2))
        #pred_test2=clf.predcit(test_featur,num_iteration=clf2.best_iteration_) #预测的未带标签的测试集的标签
        model2.append(clf2)
        
        
        pred_val=np.round(pred_val1*0.5+pred_val2*0.5)  #融合之后预测的划分出来的测试集的标签
        vali_mae=accuracy_score(y_val,pred_val)
        best_score.append(1/(1+vali_mae))
        
        #pred_test=np.round(pred_test1*0.5+pred_test2*0.5) #融合之后预测的未带标签的测试集的标签
        
        #显示特征重要程度
        predictors=[i for i in train_feature.columns]
        feat_imp=pd.Series(clf.feature_importances_,predictors).sort_values(ascending=False)

        #sub_list.append(pred_test)
        
        
    #pred_test=np.mean(np.array(sub_list),axis=0)
    print(best_score,'\n',np.mean(best_score),np.std(best_score))
    print('特征重要程度',feat_imp)
    return pred_val,modell,model2
    
pred_result,modell,model2=labcv_predict(train_feature,train_label)
#[0.9769441187964047, 0.9798157946306095, 0.9807767752059631, 0.9805844283192783, 0.9769441187964047, 0.9815469179426777, 0.9782821365681863, 0.9788566953797964, 0.9811616954474096, 0.9775171065493647] 
#0.9792429787636096 0.0016760169355545553
#特征重要程度 用户年龄               354
#当月通话交往圈人数          316
#用户近6个月平均消费值(元)     294
#用户网龄(月)            252
#当月账单是否超过平均消费额      146
#用户账单当月总费用(元)       111
#当月视频播放类应用使用次数       92
#用户话费敏感度             75
#当月金融理财类应用使用总次数      55
#缴费用户最近一次缴费金额(元)     54
#当月旅游资讯类应用使用次数       44
#近三个月月均商场出现次数        42
#缴费金额是否能覆盖当月账单       41
#用户当月账户余额(元)         37
#是否4G不健康客户           24
#缴费用户当前是否欠费缴费        19
#交通类应用使用次数           18
#当月是否景点游览            10
#当月网购类应用使用次数          8
#最近一次交费是否超过平均消费额      6
#用户最近一次缴费距今时长(月)      1
#当月物流快递类应用使用次数        1
#当月是否到过福州山姆会员店        0
#当月是否看电影              0
#当月飞机类应用使用次数          0
#当月火车类应用使用次数          0
#当月是否体育场馆消费           0
#是否_体育馆_电影_旅游         0
#是否_商场_电影_旅游          0
#是否_商场_电影_体育馆         0
#当月是否逛过福州仓山万达         0
#是否_商场_电影             0
#是否大学生客户              0
#是否经常逛商场的人            0
#是否去过高档商场             0
#用户实名制是否通过核实          0
#是否_商场_旅游_体育馆         0
#是否_商场_体育馆_电影_旅游      0
#是否_旅游_体育馆            0
#是否_电影_旅游             0
#是否_电影_体育馆            0
#是否_商场_体育馆            0
#是否_商场_旅游             0
#是否黑名单客户              0
#dtype: int32

预测真实测试集

# 模型一预测的结果
pred_test1=pd.DataFrame()
for i,model in enumerate(modell): 
    pred_mae= model.predict(test[feature_name])
pred_test1['pred_mae'] = pred_mae
pred_test1['ranks'] = list(range(50000))

# 模型二预测的结果
pred_test2=pd.DataFrame()
for i,model in enumerate(model2): 
    pred_mse= model.predict(test[feature_name])
pred_test2['pred_mse'] = pred_mse
pred_test2['ranks'] = list(range(50000))

# 模型参数进行融合之后的结果
pred_test=pd.DataFrame()
pred_test['ranks']=list(range(50000))
pred_test['result']=1
pred_test.loc[pred_test.ranks<10000,'result']  = pred_test1.loc[pred_test1.ranks< 10000,'pred_mae'].values *0.4 + pred_test2.loc[pred_test2.ranks< 10000,'pred_mse'].values * 0.6
pred_test.loc[pred_test.ranks>40000,'result']  = pred_test1.loc[pred_test1.ranks> 40000,'pred_mae'].values *0.4 + pred_test2.loc[pred_test2.ranks> 40000,'pred_mse'].values * 0.6 
pred_test

在这里插入图片描述

watch_feat='用户话费敏感度'
df_data[watch_feat].value_counts()
#4    29838
#5    21011
#2    20622
#3    20578
#1     7913
#0       38
#Name: 用户话费敏感度, dtype: int64
for v in df_data[watch_feat].unique():
    plt.subplots(figsize=(8,6))
    sns.distplot(df_data.loc[df_data[watch_feat]==v,'信用分'].values,bins=50,kde=False)
    plt.xlabel('用户花费敏感度{}'.format(v),fontsize=12)

在这里插入图片描述

import seaborn as sns
f, ax = plt.subplots(figsize=(20, 6))
sns.distplot(df_train['缴费用户最近一次缴费金额(元)'].values, color='r', bins=50, kde=False)
plt.show()

在这里插入图片描述

import seaborn as sns
name_list = ['当月旅游资讯类应用使用次数', '当月火车类应用使用次数', '当月物流快递类应用使用次数', '当月网购类应用使用次数',
             '当月视频播放类应用使用次数', '当月金融理财类应用使用总次数', '当月飞机类应用使用次数', '用户年龄',
             '用户当月账户余额(元)', '用户账单当月总费用(元)', '用户近6个月平均消费值(元)', '缴费用户最近一次缴费金额(元)']

f, ax = plt.subplots(3, 4, figsize=(20, 20))

for i,name in enumerate(name_list):     
    sns.scatterplot(data=df_data, x=name, y='信用分', color='b', ax=ax[i // 4][i % 4])
plt.show()

在这里插入图片描述

f, ax = plt.subplots(1, 3, figsize=(20, 6))

sns.kdeplot(data=df_data['当月飞机类应用使用次数'], color='r', shade=True, ax=ax[0])
sns.kdeplot(data=df_data['当月火车类应用使用次数'], color='c', shade=True, ax=ax[1])
sns.kdeplot(data=df_data['当月旅游资讯类应用使用次数'], color='b', shade=True, ax=ax[2])
plt.show()

在这里插入图片描述

""" 离散特征分析 """
f, ax = plt.subplots(1, 2, figsize=(20, 6))
sns.boxplot(data=df_data, x='用户最近一次缴费距今时长(月)', y='信用分', ax=ax[0])
sns.boxplot(data=df_data, x='缴费用户当前是否欠费缴费', y='信用分', ax=ax[1])
plt.show()

在这里插入图片描述

name_list = ['当月是否体育场馆消费', '当月是否到过福州山姆会员店', '当月是否景点游览', '当月是否看电影', '当月是否逛过福州仓山万达', 
             '是否4G不健康客户', '是否大学生客户', '是否经常逛商场的人', '是否黑名单客户', '用户实名制是否通过核实']

f, ax = plt.subplots(2, 5, figsize=(20, 12))

for i,name in enumerate(name_list):
    sns.boxplot(data=df_data, x=name, y='信用分', ax=ax[i // 5][i % 5])
plt.show()

在这里插入图片描述

f, ax = plt.subplots(figsize=(10, 6))
sns.boxplot(data=df_data, x='用户话费敏感度', y='信用分', ax=ax)
plt.show()

在这里插入图片描述
数据预处理涉及的内容很多,也包括特征工程,是任务量最大的一部分。为了让大家更清晰的阅读,以下先列出处理部分大致要用到的一些方法。

  • 数据清洗:缺失值,异常值,一致性;
  • 特征编码:one-hot 和 label coding;
  • 特征分箱:等频、等距,聚类等
  • 衍生变量:可解释性强,适合模型输入;
  • 特征选择:方差选择,卡方选择,正则化等;

最终确定的初级探索工程代码

df_data[df_data['当月通话交往圈人数'] > 1750].index
#Int64Index([], dtype='int64')
""" 
为什么只取消一个特征的拖尾,其它特征拖尾为什么保留,即使线下提高分数也要
保留,这是因为在线下中比如逛商场拖尾的数据真实场景下可能为保安,在
训练集中可能只有一个保安,所以去掉以后线下验证会提高,但是在测试集
中也存在一个保安,如果失去拖尾最终会导致测试集保安信用分精度下降 
"""
    
df_data.drop(df_data[df_data['当月通话交往圈人数'] > 1750].index, inplace=True)
df_data.reset_index(drop=True, inplace=True)


""" 0替换np.nan,通过线下验证发现数据实际情况缺失值数量大于0值数量,np.nan能更好的还原数据真实性 """
na_list = ['用户年龄', '缴费用户最近一次缴费金额(元)', '用户近6个月平均消费值(元)','用户账单当月总费用(元)']
for na_fea in na_list:
    df_data[na_fea].replace(0, np.nan, inplace=True)

### 交叉验证
    
""" 话费敏感度0替换,通过线下验证发现替换为中位数能比np.nan更好的还原数据真实性 """
df_data['用户话费敏感度'].replace(0, df_data['用户话费敏感度'].mode()[0], inplace=True)


### 交叉验证

结论:通过多次观察离散型特征,让我们对于数据的理解加深,比如用户话费敏感度特征并不是用户在现实世界中直接产生,而是由中国移动特定的关联模型通过计算产生,能够在很大程度上反应用户对于信用的关联程度。在箱型图中,用户敏感度呈现出高斯分布结果,符合我们对于业务场景的猜想。

中级特征探索(数据预工程)

通过初级的特征探索能够让我们加深对数据的理解并且实现初步的数据预处理, 接下来我们开始分析特征与特征之间对于信用分的影响、开展相关的中级特征探索。中级特征探索一般都是基于业务场景,但是作为新手在竞赛中可以简单的凭借感觉来关联特征进行分析。

f, ax = plt.subplots(figsize=(20, 6))
sns.boxenplot(data=df_data, x='当月是否逛过福州仓山万达', y='信用分', hue='当月是否到过福州山姆会员店', ax=ax)
plt.show()

在这里插入图片描述

""" 离散型探索 """
f, [ax0, ax1, ax2, ax3, ax4] = plt.subplots(1, 5, figsize=(20, 6))

sns.boxplot(data=df_data, x='当月是否逛过福州仓山万达', y='信用分', hue='是否经常逛商场的人', ax=ax0)
sns.boxplot(data=df_data, x='当月是否到过福州山姆会员店', y='信用分', hue='是否经常逛商场的人', ax=ax1)
sns.boxplot(data=df_data, x='当月是否看电影', y='信用分', hue='是否经常逛商场的人', ax=ax2)
sns.boxplot(data=df_data, x='当月是否景点游览', y='信用分', hue='是否经常逛商场的人', ax=ax3)
sns.boxplot(data=df_data, x='当月是否体育场馆消费', y='信用分', hue='是否经常逛商场的人', ax=ax4)
plt.show()

在这里插入图片描述

""" 连续型探索 """
f, ax = plt.subplots(1, 2, figsize=(20, 6))

sns.scatterplot(data=df_data, x='用户账单当月总费用(元)', y='信用分', color='b', ax=ax[0])
sns.scatterplot(data=df_data, x='用户当月账户余额(元)', y='信用分', color='r', ax=ax[1])
plt.show()

在这里插入图片描述

f, ax = plt.subplots(1, 2, figsize=(20, 6))

sns.scatterplot(data=df_data, x='用户账单当月总费用(元)', y='信用分', color='b', ax=ax[0])
sns.scatterplot(data=df_data, x='用户近6个月平均消费值(元)', y='信用分', color='r', ax=ax[1])
plt.show()

在这里插入图片描述

f, [ax0, ax1, ax2, ax3] = plt.subplots(1, 4, figsize=(20, 6))
sns.scatterplot(data=df_data, x='当月网购类应用使用次数', y='信用分', hue='是否经常逛商场的人', ax=ax0)
sns.scatterplot(data=df_data, x='当月物流快递类应用使用次数', y='信用分', hue='是否经常逛商场的人', ax=ax1)
sns.scatterplot(data=df_data, x='当月金融理财类应用使用总次数', y='信用分', hue='是否经常逛商场的人', ax=ax2)
sns.scatterplot(data=df_data, x='当月视频播放类应用使用次数', y='信用分', hue='是否经常逛商场的人', ax=ax3)
plt.show()

f, [ax0, ax1, ax2, ax3] = plt.subplots(1, 4, figsize=(20, 6))
sns.scatterplot(data=df_data, x='当月飞机类应用使用次数', y='信用分', hue='是否经常逛商场的人', ax=ax0)
sns.scatterplot(data=df_data, x='当月火车类应用使用次数', y='信用分', hue='是否经常逛商场的人', ax=ax1)
sns.scatterplot(data=df_data, x='当月旅游资讯类应用使用次数', y='信用分', hue='是否经常逛商场的人', ax=ax2)
sns.scatterplot(data=df_data, x='用户网龄(月)', y='信用分', hue='是否经常逛商场的人', ax=ax3)
plt.show()

在这里插入图片描述
在这里插入图片描述

最终确定的中级探索工程代码:

""" x / (y + 1) 避免无穷值Inf,采用高斯平滑 + 1 """
df_data['话费稳定'] = df_data['用户账单当月总费用(元)'] / (df_data['用户当月账户余额(元)'] + 1)
df_data['相比稳定'] = df_data['用户账单当月总费用(元)'] / (df_data['用户近6个月平均消费值(元)'] + 1)
df_data['缴费稳定'] = df_data['缴费用户最近一次缴费金额(元)'] / (df_data['用户近6个月平均消费值(元)'] + 1)

df_data['当月是否去过豪华商场'] = (df_data['当月是否逛过福州仓山万达'] + df_data['当月是否到过福州山姆会员店']).map(lambda x: 1 if x > 0 else 0)
df_data['应用总使用次数'] = df_data['当月网购类应用使用次数'] + df_data['当月物流快递类应用使用次数'] + df_data['当月金融理财类应用使用总次数'] + df_data['当月视频播放类应用使用次数'] + df_data['当月飞机类应用使用次数'] + df_data['当月火车类应用使用次数'] + df_data['当月旅游资讯类应用使用次数']

结论:通过大量的中级探索能够让我们加深对于数据之间的关联性更深刻,在进行中级探索的时候应该结合模型进行线下稳定的验证测试,在一些结构化竞赛中通过大量的中级探索就能够在竞赛中进入10%。

高级特征探索(数据真场景)

在数据竞赛中,想要获得高名次甚至拿下奖牌,除了需要扎实的特征工程基础,还需要对数据有着深刻的业务理解,能够将数据隐藏的信息进行挖掘提取,下面我将对该赛题进行高级特征探索。

1、对特征本身进行业务角度解读,提取出关键信息

通过对特征出处的挖掘,我们对缴费用户最近一次缴费金额(元)特征进行业务角度观察,发现该特征具有重要的隐藏含义,如有些用户没有缴费金额信息,也有些缴费金额存在个位数存在金额时缴费用户可能是通过互联网、自动缴费机等手段进行缴费,最终我们根据以上分析提取了用户缴费方式特征。

  • count:100000.000000
  • mean: 53.721932
  • std: 62.214807
  • min: 0.000000
  • 25%: 0.000000
  • 50%: 49.900000
  • 75%: 99.800000
  • max:1000.000000
  • name:缴费用户最近一次缴费金额(元),dtype:float64
df_data['缴费方式'] = 0
df_data.loc[(df_data['缴费用户最近一次缴费金额(元)'] != 0) & (df_data['缴费用户最近一次缴费金额(元)'] % 10 == 0), '缴费方式'] = 1
df_data.loc[(df_data['缴费用户最近一次缴费金额(元)'] != 0) & (df_data['缴费用户最近一次缴费金额(元)'] % 10 > 0), '缴费方式'] = 2

f, ax = plt.subplots(figsize=(20, 6))
sns.boxplot(data=df_data, x='缴费方式', y='信用分', ax=ax)
plt.show()

在这里插入图片描述
2、充分利用外部信息,让特征具有实际场景意义

通过对大量的中国移动星级信用分资料浏览,根据中国移动官网的套餐信息,我们对用户网龄特征进行了提取分类。

df_data['信用资格'] = df_data['用户网龄(月)'].apply(lambda x: 1 if x > 12 else 0)
f, ax = plt.subplots(figsize=(10, 6))
sns.boxenplot(data=df_data, x='信用资格', y='信用分', ax=ax)
plt.show()

在这里插入图片描述
3、充分利用官网信息,数据业务上的问题积极和主办方联系

在多次详细解读比赛官网题目,根据比赛官网提供的特征信息,我们对用户敏感度进行了用户敏感度占比提取。

用户话费敏感度一级表示敏感等级最大
根据极值计算法、叶指标权重后得出的结果,根据规则,生成敏感度用户的敏感级别:

先将敏感度用户按中间分值按降序进行排序:

  • 前5%的用户对应的敏感级别为1级
  • 接下来的15%的用户对应的敏感级别为二级;
  • 接下来的15%的用户对应的敏感级别为三级;
  • 接下来的25%的用户对应的敏感级别为四级;
  • 最后的40%的用户对应的敏感度级别为五级;
df_data['敏度占比'] = df_data['用户话费敏感度'].map({1:1, 2:3, 3:3, 4:4, 5:8})

f, ax = plt.subplots(1, 2, figsize=(20, 6))
sns.boxenplot(data=df_data, x='敏度占比', y='信用分', ax=ax[0])
sns.boxenplot(data=df_data, x='用户话费敏感度', y='信用分', ax=ax[1])
plt.show()

在这里插入图片描述
结论:在高级探索阶段,工程师总是会比学生更为敏感,当然天赋也是会在此展现,在数据竞赛中最重要的是要有希望的努力!

算法模型

在结构化竞赛中,机器学习常用的模型有LGB、XGB、CAT等模型,算法速度快并且能够容纳缺失值,由于之前我们已经提取出缺失值,并且明确了缺失值的业务意义,所以我们采用LGB来作为训练模型。

模型数据

lab = '信用分'
X = df_data.loc[df_data[lab].notnull(), (df_data.columns != lab) & (df_data.columns != '用户编码')]
y = df_data.loc[df_data[lab].notnull()][lab]
X_pred = df_data.loc[df_data[lab].isnull(), (df_data.columns != lab) & (df_data.columns != '用户编码')]
df_data.head()

模型参数

""" 模型参数为作者祖传参数 """
lgb_param_l1 = {
    'learning_rate': 0.01, #梯度下降的步长
    'boosting_type': 'gbdt',#梯度提升决策树
    'objective': 'regression_l1', #任务目标(L1 loss, alias=mean_absolute_error, mae)
    'metric': 'mae',
    'min_child_samples': 46,# 一个叶子上数据的最小数量
    'min_child_weight': 0.01,
    'feature_fraction': 0.6,#每次迭代中选择前60%的特征
    'bagging_fraction': 0.8,#不进行重采样的情况下随机选择部分数据
    'bagging_freq': 2, #每2次迭代执行bagging
    'num_leaves': 31,#一棵树上的叶子数
    'max_depth': 5,#树的最大深度
    'lambda_l2': 1, # 表示的是L2正则化
    'lambda_l1': 0,# 表示的是L1正则化
    'n_jobs': -1,
    'seed': 4590,
}

模型框架

在实际激烈竞赛中线上提交次数总是有限,所以选手必须构建一个合理的线下验证框架。在本赛题中,为了保证线下验证的准确性,我选择五折交叉验证,能够很好的避免过拟合情况。

from sklearn.model_selection import KFold
import lightgbm as lgb

y_counts = 0
y_scores = np.zeros(5)
y_pred_l1 = np.zeros([5, X_pred.shape[0]])#[5,50000]
y_pred_all_l1 = np.zeros(X_pred.shape[0])#[50000,]

for n in range(1): # 0
    kfold = KFold(n_splits=5, shuffle=True, random_state=2019 + n)
    kf = kfold.split(X, y)

    for i, (train_iloc, test_iloc) in enumerate(kf):
        #print(len(test_iloc))
        print("{}、".format(i + 1), end='')
        X_train, X_test, y_train, y_test = X.iloc[train_iloc, :], X.iloc[test_iloc, :], y[train_iloc], y[test_iloc]
        lgb_train = lgb.Dataset(X_train, y_train)
        lgb_valid = lgb.Dataset(X_test, y_test, reference=lgb_train)
        lgb_model = lgb.train(train_set=lgb_train, valid_sets=lgb_valid, 
                              params=lgb_param_l1, num_boost_round=6000, verbose_eval=-1, early_stopping_rounds=100)

        y_scores[y_counts] = lgb_model.best_score['valid_0']['l1']
        y_pred_l1[y_counts] = lgb_model.predict(X_pred, num_iteration=lgb_model.best_iteration)#预测信用分
        y_pred_all_l1 += y_pred_l1[y_counts] 
        y_counts += 1
        #print(y_pred_l1)
y_pred_all_l1 /= y_counts
print(y_scores, y_scores.mean())
#1、Training until validation scores don't improve for 100 rounds.
#Early stopping, best iteration is:
#[2555]	valid_0's l1: 14.6827
#2、Training until validation scores don't improve for 100 rounds.
#Early stopping, best iteration is:
#[3616]	valid_0's l1: 14.4936
#3、Training until validation scores don't improve for 100 rounds.
#Early stopping, best iteration is:
#[2196]	valid_0's l1: 14.8204
#4、Training until validation scores don't improve for 100 rounds.
#Early stopping, best iteration is:
#[3355]	valid_0's l1: 14.6649
#5、Training until validation scores don't improve for 100 rounds.
#Early stopping, best iteration is:
#[3195]	valid_0's l1: 14.7147
#[14.68266276 14.49360643 14.82035007 14.66492709 14.71471457] 14.675252185621542

y_scores

1、Training until validation scores don’t improve for 100 rounds.

Early stopping, best iteration is:

[4434] valid_0’s l1: 14.4686

[14.4686427 0. 0. 0. 0. ]

2、Training until validation scores don’t improve for 100 rounds.

Early stopping, best iteration is:

[3791] valid_0’s l1: 14.5579

[14.4686427 14.55785788 0. 0. 0. ]

3、Training until validation scores don’t improve for 100 rounds.

Early stopping, best iteration is:

[3255] valid_0’s l1: 14.7135

[14.4686427 14.55785788 14.71346416 0. 0. ]

4、Training until validation scores don’t improve for 100 rounds.

Early stopping, best iteration is:

[5165] valid_0’s l1: 14.8283

[14.4686427 14.55785788 14.71346416 14.82828992 0. ]

5、Training until validation scores don’t improve for 100 rounds.

Early stopping, best iteration is:

[2943] valid_0’s l1: 14.7555

[14.4686427 14.55785788 14.71346416 14.82828992 14.75547104]

在该线下验证函数下我们不能够很好的观察与榜单上的分数对比状况,在这里高手通常在比赛中都会写一个验证函数插入参数。

from sklearn.metrics import mean_absolute_error

def feval_lgb(y_pred, train_data):
    y_true = train_data.get_label()
    #y_pred = np.argmax(y_pred.reshape(7, -1), axis=0)
    
    score = 1 / (1 + mean_absolute_error(y_true, y_pred))
    return 'acc_score', score, True  

第一组模型

lgb_param_l1 = {
    'learning_rate': 0.01,
    'boosting_type': 'gbdt',
    'objective': 'regression_l1',
    'metric': 'None',
    'min_child_samples': 46,
    'min_child_weight': 0.01,
    'feature_fraction': 0.6,
    'bagging_fraction': 0.8,
    'bagging_freq': 2,
    'num_leaves': 31,
    'max_depth': 5,
    'lambda_l2': 1,
    'lambda_l1': 0,
    'n_jobs': -1,
    'seed': 4590,
}

n_fold = 5
y_counts = 0
y_scores = np.zeros(5)
y_pred_l1 = np.zeros([5, X_pred.shape[0]])
y_pred_all_l1 = np.zeros(X_pred.shape[0])

for n in range(1): 
    kfold = KFold(n_splits=n_fold, shuffle=True, random_state=2019 + n)
    kf = kfold.split(X, y)

    for i, (train_iloc, test_iloc) in enumerate(kf):
        print("{}、".format(i + 1), end='')
        X_train, X_test, y_train, y_test = X.iloc[train_iloc, :], X.iloc[test_iloc, :], y[train_iloc], y[test_iloc]

        lgb_train = lgb.Dataset(X_train, y_train)
        lgb_valid = lgb.Dataset(X_test, y_test, reference=lgb_train)
        lgb_model = lgb.train(train_set=lgb_train, valid_sets=lgb_valid, feval=feval_lgb, 
                              params=lgb_param_l1, num_boost_round=6000, verbose_eval=-1, early_stopping_rounds=100)
        
        y_scores[y_counts] = lgb_model.best_score['valid_0']['acc_score']
        y_pred_l1[y_counts] = lgb_model.predict(X_pred, num_iteration=lgb_model.best_iteration)
        y_pred_all_l1 += y_pred_l1[y_counts]
        y_counts += 1

y_pred_all_l1 /= y_counts
print(y_scores, y_scores.mean())
#1、Training until validation scores don't improve for 100 rounds.
#Early stopping, best iteration is:
#[2555]	valid_0's acc_score: 0.0637647
#2、Training until validation scores don't improve for 100 rounds.
#Early stopping, best iteration is:
#[3616]	valid_0's acc_score: 0.0645428
#3、Training until validation scores don't improve for 100 rounds.
#Early stopping, best iteration is:
#[2196]	valid_0's acc_score: 0.0632097
#4、Training until validation scores don't improve for 100 rounds.
#Early stopping, best iteration is:
#[3355]	valid_0's acc_score: 0.0638369
#5、Training until validation scores don't improve for 100 rounds.
#Early stopping, best iteration is:
#[3195]	valid_0's acc_score: 0.0636346
#[0.06376468 0.06454275 0.06320973 0.06383688 0.06363463] 0.06379773255950914

第二组模型

lgb_param_l2 = {
    'learning_rate': 0.01,
    'boosting_type': 'gbdt',
    'objective': 'regression_l2',
    'metric': 'None',
    'feature_fraction': 0.6,
    'bagging_fraction': 0.8,
    'bagging_freq': 2,
    'num_leaves': 40,
    'max_depth': 7,
    'lambda_l2': 1,
    'lambda_l1': 0,
    'n_jobs': -1,
}

n_fold = 5
y_counts = 0
y_scores = np.zeros(5)
y_pred_l2 = np.zeros([5, X_pred.shape[0]])
y_pred_all_l2 = np.zeros(X_pred.shape[0])

for n in range(1): 
    kfold = KFold(n_splits=n_fold, shuffle=True, random_state=2019 + n)
    kf = kfold.split(X, y)

    for i, (train_iloc, test_iloc) in enumerate(kf):
        print("{}、".format(i + 1), end='')
        X_train, X_test, y_train, y_test = X.iloc[train_iloc, :], X.iloc[test_iloc, :], y[train_iloc], y[test_iloc]

        lgb_train = lgb.Dataset(X_train, y_train)
        lgb_valid = lgb.Dataset(X_test, y_test, reference=lgb_train)
        lgb_model = lgb.train(train_set=lgb_train, valid_sets=lgb_valid, feval=feval_lgb, 
                              params=lgb_param_l1, num_boost_round=6000, verbose_eval=-1, early_stopping_rounds=100)
        
        y_scores[y_counts] = lgb_model.best_score['valid_0']['acc_score']
        y_pred_l2[y_counts] = lgb_model.predict(X_pred, num_iteration=lgb_model.best_iteration)
        y_pred_all_l2 += y_pred_l2[y_counts]
        y_counts += 1

y_pred_all_l2 /= y_counts
print(y_scores, y_scores.mean())
#1、Training until validation scores don't improve for 100 rounds.
#Early stopping, best iteration is:
#[2555]	valid_0's l1: 14.6827	valid_0's acc_score: 0.0637647
#2、Training until validation scores don't improve for 100 rounds.
#Early stopping, best iteration is:
#[3616]	valid_0's l1: 14.4936	valid_0's acc_score: 0.0645428
#3、Training until validation scores don't improve for 100 rounds.
#Early stopping, best iteration is:
#[2196]	valid_0's l1: 14.8204	valid_0's acc_score: 0.0632097
#4、Training until validation scores don't improve for 100 rounds.
#Early stopping, best iteration is:
#[3355]	valid_0's l1: 14.6649	valid_0's acc_score: 0.0638369
#5、Training until validation scores don't improve for 100 rounds.
#Early stopping, best iteration is:
#[3195]	valid_0's l1: 14.7147	valid_0's acc_score: 0.0636346
#[0.06376468 0.06454275 0.06320973 0.06383688 0.06363463] 0.06379773255950914

模型融合

在竞赛中采用了取整提交,能够获得线上前排的成绩。那么,如何能够达到TOP1~10呢?事实上,在任何竞赛中,模型融合都是冲顶必备,我们对模型采用了双损失分层加权的方案,经过几次线下验证,让队伍直接进入第一梯队。

使用不同的损失函数(MSE与MAE)得到多个模型。MSE损失函数能够加大对异常值的惩罚,在高分段(例如650分以上)和低分段(例如525以下)获得更好的表现。使用MAE误差的模型在中分段获得更好的表现,且更贴近指标。

lgb_param_l1 = {

    'learning_rate': 0.01,
    'boosting_type': 'gbdt',
    'objective': 'regression_l1',
    'metric': 'None',
    'min_child_samples': 46,
    'min_child_weight': 0.01,
    'feature_fraction': 0.6,
    'bagging_fraction': 0.8,
    'bagging_freq': 2,
    'num_leaves': 31,
    'max_depth': 5,
    'lambda_l2': 1,
    'lambda_l1': 0,
    'n_jobs': -1,
    'seed': 4590,
}

lgb_param_l2 = {
    'learning_rate': 0.01,
    'boosting_type': 'gbdt',
    'objective': 'regression_l2',
    'metric': 'None',
    'feature_fraction': 0.6,
    'bagging_fraction': 0.8,
    'bagging_freq': 2,
    'num_leaves': 40,
    'max_depth': 7,
    'lambda_l2': 1,
    'lambda_l1': 0,
    'n_jobs': -1,
}

将以上双损失参数对模型框架进行替换,对得到的结果进行融合。

submit = pd.DataFrame()
submit['id'] =df_data[df_data['信用分'].isnull()]['用户编码']

submit['score1'] = y_pred_all_l1
submit['score2'] = y_pred_all_l2

submit = submit.sort_values('score1')
submit['rank'] = np.arange(submit.shape[0])

min_rank = 100
max_rank = 50000 - min_rank

l1_ext_rate = 1
l2_ext_rate = 1 - l1_ext_rate

il_ext = (submit['rank'] <= min_rank) | (submit['rank'] >= max_rank)

l1_not_ext_rate = 0.5
l2_not_ext_rate = 1 - l1_not_ext_rate
il_not_ext = (submit['rank'] > min_rank) & (submit['rank'] < max_rank)

submit['score'] = 0
submit.loc[il_ext, 'score'] = (submit[il_ext]['score1'] * l1_ext_rate + submit[il_ext]['score2'] * l2_ext_rate + 1 + 0.25)
submit.loc[il_not_ext, 'score'] = submit[il_not_ext]['score1'] * l1_not_ext_rate + submit[il_not_ext]['score2'] * l2_not_ext_rate + 0.25
""" 输出文件 """
submit[['id', 'score']].to_csv('submit.csv')

结论:采用分段式融合后,提升效果显著,超越了自身的stakcing方案,在之后又组到一群优秀队友,取得了A榜Top1,B榜首次提交Top1的成绩。

实际在竞赛中,你花下的时间应该

通常是:特征工程 > 模型融合 > 算法模型 > 参数调整

或者是:模型融合 > 特征工程 > 算法模型 > 参数调整

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值