消费者人群画像—信用智能评分

本文详述了中国移动人群画像赛中TOP1团队的实战经验,从数据探索到特征工程,再到模型融合,展示了如何利用大数据提升信用评分模型的精准度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言

比赛链接:https://www.datafountain.cn/competitions/337
Github链接:https://github.com/GakkiWl/-TOP1-

赛题信息

随着社会信用体系建设的深入推进, 社会信用标准建设飞速发展,相关的标准相继发布,包括信用服务标准、信用数据釆集和服务标准、信用修复标准、城市信用标准、行业信用标准等在内的多层次标准体系亟待出台,社会信用标准体系有望快速推进。社会各行业信用服务机构深度参与广告、政务、涉金融、共享单车、旅游、重大投资项目、教育、环保以及社会信用体系建设,社会信用体系建设是个系统工程,通讯运营商作为社会企业中不可缺少的部分同样需要打造企业信用评分体系,助推整个社会的信用体系升级。同时国家也鼓励推进第三方信用服务机构与政府数据交换,以增强政府公共信用信息中心的核心竞争力。

传统的信用评分主要以客户消费能力等少数的维度来衡量,难以全面、客观、及时的反映客户的信用。中国移动作为通信运营商拥有海量、广泛、高质量、高时效的数据,如何基于丰富的大数据对客户进行智能评分是中国移动和新大陆科技集团目前攻关的难题。运营商信用智能评分体系的建立不仅能完善社会信用体系,同时中国移动内部也提供了丰富的应用价值,包括全球通客户服务品质的提升、客户欠费额度的信用控制、根据信用等级享受各类业务优惠等,希望通过本次建模比赛,征集优秀的模型体系,准确评估用户信用分值。

数据清单

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 pred_{i} predi 为预测样本, y i y_{i} yi 为真实样本。MAE的值越小,说明预测数据与真实数据越接近。
最终结果为: S c o r e = 1 1 + M A E Score =\frac{1}{1+M A E} Score=1+MAE1
最终的结果越接近1分数越高

1 全面探索

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

""" 导入基本库 """
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore')

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)

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

train_data = pd.read_csv('./train_dataset.csv')
test_data = pd.read_csv('./test_dataset.csv')
df_data = pd.concat([train_data, test_data], ignore_index=True)
df_data.head()

在这里插入图片描述

""" 数据属性 """
df_data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100000 entries, 0 to 99999
Data columns (total 30 columns):
信用分                50000 non-null float64
当月旅游资讯类应用使用次数      100000 non-null int64
当月是否体育场馆消费         100000 non-null int64
当月是否到过福州山姆会员店      100000 non-null int64
当月是否景点游览           100000 non-null int64
当月是否看电影            100000 non-null int64
当月是否逛过福州仓山万达       100000 non-null int64
当月火车类应用使用次数        100000 non-null int64
当月物流快递类应用使用次数      100000 non-null int64
当月网购类应用使用次数        100000 non-null int64
当月视频播放类应用使用次数      100000 non-null int64
当月通话交往圈人数          100000 non-null int64
当月金融理财类应用使用总次数     100000 non-null int64
当月飞机类应用使用次数        100000 non-null int64
是否4G不健康客户          100000 non-null int64
是否大学生客户            100000 non-null int64
是否经常逛商场的人          100000 non-null int64
是否黑名单客户            100000 non-null int64
用户实名制是否通过核实        100000 non-null int64
用户年龄               100000 non-null int64
用户当月账户余额(元)        100000 non-null int64
用户最近一次缴费距今时长(月)    100000 non-null int64
用户编码               100000 non-null object
用户网龄(月)            100000 non-null int64
用户话费敏感度            100000 non-null int64
用户账单当月总费用(元)       100000 non-null float64
用户近6个月平均消费值(元)     100000 non-null float64
缴费用户当前是否欠费缴费       100000 non-null int64
缴费用户最近一次缴费金额(元)    100000 non-null float64
近三个月月均商场出现次数       100000 non-null int64
dtypes: float64(4), int64(25), object(1)
memory usage: 22.9+ 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("{}、{}      The number of types of features is:{}".format(i + 1, name, name_sum))
1、信用分      The number of types of features is278
2、当月旅游资讯类应用使用次数      The number of types of features is934
3、当月是否体育场馆消费      The number of types of features is2
4、当月是否到过福州山姆会员店      The number of types of features is2
5、当月是否景点游览      The number of types of features is2
6、当月是否看电影      The number of types of features is2
7、当月是否逛过福州仓山万达      The number of types of features is2
8、当月火车类应用使用次数      The number of types of features is180
9、当月物流快递类应用使用次数      The number of types of features is239
10、当月网购类应用使用次数      The number of types of features is8382
11、当月视频播放类应用使用次数      The number of types of features is16067
12、当月通话交往圈人数      The number of types of features is554
13、当月金融理财类应用使用总次数      The number of types of features is7232
14、当月飞机类应用使用次数      The number of types of features is209
15、是否4G不健康客户      The number of types of features is2
16、是否大学生客户      The number of types of features is2
17、是否经常逛商场的人      The number of types of features is2
18、是否黑名单客户      The number of types of features is2
19、用户实名制是否通过核实      The number of types of features is2
20、用户年龄      The number of types of features is88
21、用户当月账户余额(元)      The number of types of features is316
22、用户最近一次缴费距今时长(月)      The number of types of features is2
23、用户编码      The number of types of features is100000
24、用户网龄(月)      The number of types of features is283
25、用户话费敏感度      The number of types of features is6
26、用户账单当月总费用(元)      The number of types of features is16597
27、用户近6个月平均消费值(元)      The number of types of features is22520
28、缴费用户当前是否欠费缴费      The number of types of features is2
29、缴费用户最近一次缴费金额(元)      The number of types of features is532
30、近三个月月均商场出现次数      The number of types of features is93
""" 数据统计 """
df_data.describe()

在这里插入图片描述

""" 观察训练/测试集数据同分布状况 """
df_data[df_data['信用分'].isnull()].describe()

在这里插入图片描述

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

在这里插入图片描述
结论一:数据集情况较好,特征全为数值型特征,我们直接建立模型进行提交验证线下分数。但是大部分的特征存在类拖尾情况,如当月旅游资讯类应用使用次数特征中max为87681次,明显偏离mean约为19的次数,后续我们将会对这些特征单独分析。

结论二:通常一个人的信用分的发展规律是获得基础分,随着时间推移,会有加分事件,和减分事件的发生。伴随着这些事件,信用分逐步上涨或降低。但是对于赛题本身,绝大部份特征属于正面特征。相应的违约,失信记录等负面特征较少。这会导致模型对高信用分结果预测较为准确,低信用分结果预测偏差较大。

结论三:训练集与测试集中都存在拖尾数据,所以拖尾数据不一定是异常数据,需要经过线下验证才能确定。

2 初级特征探索

接下来开始分析特征与信用分的关联性、开展相关的初级特征探索。很多新手经常在特征探索容易出现无从下手的情况,在这里作者新手推荐两种方案,

  • 1:按照特征顺序,
  • 2:按照连续、离散、非结构化特征顺序来进行特征探索.

总之要先上手为强。高手在经历一定的比赛以后就会开始总结出根据业务场景相关的特征探索思路。

""" 拖尾/顺序特征分析 """
f, ax = plt.subplots(figsize=(20, 6))

sns.scatterplot(data=df_data, x='当月通话交往圈人数', y='信用分', color='k', ax=ax)
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;
  • 特征分箱:等频、等距,聚类等
  • 衍生变量:可解释性强,适合模型输入;
  • 特征选择:方差选择,卡方选择,正则化等;

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

""" 
为什么只取消一个特征的拖尾,其它特征拖尾为什么保留,即使线下提高分数也要
保留,这是因为在线下中比如逛商场拖尾的数据真实场景下可能为保安,在
训练集中可能只有一个保安,所以去掉以后线下验证会提高,但是在测试集
中也存在一个保安,如果失去拖尾最终会导致测试集保安信用分精度下降 
"""
    
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)

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

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

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

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()

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

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

""" 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%。

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

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

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()

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

7 算法模型

在结构化竞赛中,机器学习常用的模型有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 != '用户编码')]

模型参数

""" 模型参数为作者祖传参数 """
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:
[4434]	valid_0's l1: 14.4686
2、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[3791]	valid_0's l1: 14.5579
3、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[3255]	valid_0's l1: 14.7135
4、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[5165]	valid_0's l1: 14.8283
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] 14.66474513833865

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

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]
        #print(len(y_test))
        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:
[4434]	valid_0's acc_score: 0.0646469
2、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[3791]	valid_0's acc_score: 0.0642762
3、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[3255]	valid_0's acc_score: 0.0636397
4、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[5165]	valid_0's acc_score: 0.063178
5、Training until validation scores don't improve for 100 rounds.
Early stopping, best iteration is:
[2943]	valid_0's acc_score: 0.06347
[0.06464691 0.0642762  0.06363969 0.06317802 0.06347002] 0.06384216796825781

8 模型融合

在竞赛中采用了取整提交,能够获得线上前排的成绩。那么,如何能够达到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_rateil_ext = (submit['rank'] <= min_rank) | (submit['rank'] >= max_rank)
l2_ext_rate  = (submit['rank'] <= min_rank) | (submit['rank'] >= max_rank)

l1_not_ext_rate = 0.5
l2_not_ext_rate = (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的成绩。

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

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

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

9 参考文献

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值