《机器学习算法竞赛实战》整理 | 八、实战案例:Elo Merchant Category Recommendation

 详情请参见原书

 ​​​​​《机器学习算法竞赛实战(图灵出品)》(王贺,刘鹏,钱乾)【摘要 书评 试读】- 京东图书

前言

比赛链接:

https://www.kaggle.com/competitions/elo-merchant-category-recommendation/overview

8.1 赛题理解

8.1.1 赛题背景

        想象一下,当你在一个不熟悉的地方饿着肚子想要找好吃的东西时,你是不是会得到基于你的个人喜好而被专属推荐的餐馆,且该推荐还附带着你的信用卡提供商为你提供的附近餐馆的折扣信息。
        目前,巴西最大的支付品牌之一Elo已经与商家建立了合作关系,以便向顾客提供促销或折扣活动。但这些促销活动对顾客和商家都有益吗?顾客喜欢他们的活动体验吗?商家能够看到重复交易吗?要回答这些问题,个性化是关键。
        Elo 建立了机器学习模型,以了解顾客生命周期中从食品到购物等最重要方面的偏好。但到目前为止,那些学习模型都不是专门为个人或个人资料量身定做的,这也就是这场竞赛举办的原因。
        在这场竞赛中,需要参赛者开发算法,通过发现顾客忠诚度的信号,识别并为个人提供最相关的机会。你的意见将改善顾客的生活,帮助Elo减少不必要的活动,为顾客创造精准正确的体验。

8.1.2 赛题数据

为了保证隐私与信息安全,本次竞赛的所有数据都是模拟与虚构数据或经过脱敏的数据,并非真实的顾客数据。具体包含下列数据文件。

  • train.csv:训练集
  • test.csv:测试集
  • sample_submission.csv:正确与规范的提交文件示例,含有需要参赛者预测的所有card_id
  • historical transactions.csv:信用卡(card_id)在给定商家的历史交易记录,对于每张信用卡,最多包含其三个月的交易记录
  • merchants.csv:数据集中所有商家(商家id)的附加信息
  • new_merchant transactions.csv:每张信用卡在新商家的购物数据,最多包含两个月的数据
  • Data_Dictionary.xlsx:数据字典的说明文件,提供了上述各表的字段含义,包括对train、historical_transactions、new_merchant_period 和 merchant的相应说明(这个new_merchant_period又是什么东西,且继续往下读。)

8.1.3 赛题任务

通过顾客的历史交易记录以及顾客和商家的信息数据进行模型训练,最终预测测试集里面所有信用卡的忠诚度分数

8.1.4 评价指标

\mathrm{RMSE}=\sqrt{\frac{1}{n} \sum_{i=1}^{n}\left(y_{i}-\hat{y}_{i}\right)^{2}}

其中\hat{y_{i}} \为是参赛者对每个信用卡预测的忠诚度分数,而y_{i}是对应信用卡的真实忠诚度分数。

8.1.5 赛题FAQ

Q:竞赛提供了这么多数据文件,至少需要哪些才能完成建模?
A:至少需要train.csv 和test.csv,这两个文件包含所有将会被用来进行训练与测试的信用卡card_id。另外 historical_transactions.csv 和 new_merchant_transactions.csv 包含每张信用卡的交易记录。

Q:参赛者如何能够将其余的数据利用上呢?
A:train.csv  test.csv 包含所有信用卡的 card_id 和信用卡本身的信息(比如卡激活的第一个月是何时等)。此外 train.csv 还包含部分顾客的目标值,即提供了这部分顾客确定的忠诚度分值。historical_transactions.csv 和 new_merchant_transactions.csv 设计为与 train.csv,test.csv 和merchants.csv 结合在一起,因为如上所述,这两个文件包含每张信用卡的交易记录所以将交易记录与商家结合在一起可以提供额外的商家级别等信息

8.2 数据探索

点击赛题主页中的data可以直接查看数据信息

《机器学习算法竞赛实战》整理 | 八、实战案例:Elo Merchant Category Recommendation_第1张图片

8.2.1 字段类别含义

在进行数据探索前,参赛者首先应该明确对各数据文件的介绍以及文件中字段的含义,以便理解赛题和拾建分析逻辑。参考赛题主办方提供的字段信息表Data_Dictionary.xlsx可知,五个数据文件中的子段及含义如下。

train.csv 与 test.csv 中的字段及含义

字段 含义 举例
card_id 独一无二的信用卡标识,即信用卡id C_ID_92a2005557
first_active_month 首次使用信用卡购物的月份(注册时间),格式为YYYY-MM 2017-04
feature_1/2/3 匿名的信用卡离散特征1/2/3 3
target Loyalty numerical score calculated 2 months after historical and evaluation period. 忠诚度分数目标列 0.392913

        查看上述字段的含义可知,三个feature都是匿名的信用卡离散字段,还有一个首次购物的月份,而target是在历史和评估时期后的两个月进行量化计算得到的忠诚度分数。需要注意的是,这里的 evaluation period 应该是指 new_merchant_transactions.csv 中的信息,同时也是对应Data_Dictionary.xlsx 里面的 new_merchant_period 字段。同时校验一下数据的正确性就发现训练集与测试集的 card_id 均为唯一值,且训练集与测试集中的card_id不重复。

historical_transactions.csv 和 new_merchant_transaction.csv 中的字段及含义

字段 含义 举例 说明
authorized_flag 2
card_id 独一无二的信用卡标识,即信用卡id C_ID_415bb3a509 3
month_lag 距离参考日期的月份 [-12,-1]、[0,2] 2
purchase_date 购物日期(时间) 2018-03-11 14:57:36
category_3 匿名类别特征3 A/B/C/D/E 2
installments 购买商品的数量 1 1
category_1 匿名类别特征1 Y/N
merchant_category_id 商品种类id(经过了匿名处理) 307 2
subsector_id 商品种类群id(经过了匿名处理) 19
merchant_id 商品 id(经过了匿名处理) M_ID_bec793002c 3
purchase_amount 标准化的购物金额 -0.557574 1
city_id 城市id(经过了匿名处理) 300
state_id 州id(经过了匿名处理) 9
category_2 匿名类别特征2 1

merchants.csv 中的字段与含义

黄色背景:重复特征;加粗:离散型;加粗斜体:离散型非数值型;橙色字体:连续型

合并文件

8.2.2 字段取值情况

查看每个字段的含义及取值情况:离散与否、取值类型、大小关系(独立还是有顺序含义)

缺失值,字段的取值范围和分布:

        离散:数量分布;

        连续:异常值、离群点。可采用pandas.series的describe方法;若采用value_counts方法,可以发现极端值-33.2,占比约1%

以上可以通过kaggle直接查看(较为粗略)

《机器学习算法竞赛实战》整理 | 八、实战案例:Elo Merchant Category Recommendation_第2张图片

8.2.3 数据分布差异

数据集划分依据:训练集、测试集、验证集的数据分布要相似,尤其是特征和标签的联合分布要一致。

下面对train.csv和test.csv中的first_active_month、feature_1、feature_2、feature_3几个字段进行单变量分布对比展示。

kaggle网站可以直接展示数据分布情况。

《机器学习算法竞赛实战》整理 | 八、实战案例:Elo Merchant Category Recommendation_第3张图片

《机器学习算法竞赛实战》整理 | 八、实战案例:Elo Merchant Category Recommendation_第4张图片

绝对数量分布

《机器学习算法竞赛实战》整理 | 八、实战案例:Elo Merchant Category Recommendation_第5张图片

 《机器学习算法竞赛实战》整理 | 八、实战案例:Elo Merchant Category Recommendation_第6张图片

 《机器学习算法竞赛实战》整理 | 八、实战案例:Elo Merchant Category Recommendation_第7张图片

 《机器学习算法竞赛实战》整理 | 八、实战案例:Elo Merchant Category Recommendation_第8张图片

结论:训练集与测试集在所有单变量上的绝对数量分布形状极其相似,需要进一步查看相对占比分布

相对占比分布

《机器学习算法竞赛实战》整理 | 八、实战案例:Elo Merchant Category Recommendation_第9张图片

《机器学习算法竞赛实战》整理 | 八、实战案例:Elo Merchant Category Recommendation_第10张图片

《机器学习算法竞赛实战》整理 | 八、实战案例:Elo Merchant Category Recommendation_第11张图片

《机器学习算法竞赛实战》整理 | 八、实战案例:Elo Merchant Category Recommendation_第12张图片

 结论:训练集与测试集在所有单变量上的相对占比分布形状基本一致,猜想训练集与测试集的生成方式一样,继续验证联合分布以加强猜想的事实依据

TODO:这里画图分析有一不严谨之处,即训练集与测试集的单变量取值范围可能不完全一样,由此两根线画在同一张图上有可能会出错,如发生偏移等,请参赛者自行验证二者的横坐标是否完全一样?如果不一样,运行这段代码会发生什么?在下面的联合分布验证中,我们将会填补这一遗漏之处

多变量联合分布

可以使用散点图。但是散点图适用于连续特征。因此可以将两个单变量拼接,再用上述方法。查看结果发现依然保持一致。(参见eda.ipynb)

8.2.4 表格关联分析

  • train.csv和test.csv帮助参赛者明确了训练集和测试集以及建模目标;
  • historical_transactions.csv 和 new_transactions.csv具有相同的字段,只是二者时间上有所区别,给参赛者提供了丰富的顾客交易信息;
  • merchants.csv则描述了商家的经营状况。

8.2.5 数据预处理

这里只给出详细步骤,具体代码请见本书附带资源中的eda.ipynb。

train.csv 和 test.csv

这两个表格只有test.csv中的first_active_month字段有一个缺失值,总体来说只有一个缺失值的影响不大,且这个字段是字符型,因此需要对其进行编码处理,考虑到其实质上具有先后顺序关系,采用字典排序进行编码即可。

merchants.csv

处理步骤如下:
(1)根据业务含义划分离散字段category_cols与连续字段numeric_cols;
(2)对字符型的离散字段进行字典排序编码;
(3)为了更方便统计,对缺失值进行处理,对离散字段统一用-1进行填充;
(4)探查离散字段发现有正无穷值,这是特征提取以及模型不能接受的,因此需要对无穷值进行处理,此处采用最大值进行替换;
(5)对离散字段的缺失值进行处理的方式有很多种,这里先使用平均值进行填充,后续有需要再进行优化处理;

(6)去除与交易记录表格重复的列以及对merchant_id的重复记录。
 

new_merchant_transactions.csv 和 historical_transactions.csv

处理步骤如下:
(1)为了统一处理,首先将这两张表格拼接起来,后续可以通过month_lag>=0这个条件进行区分
(2)划分离散字段、连续字段以及时间字段:

(3)可仿照merchants.csv的处理方式对字符型离散字段进行字典排序编码以及对缺失值进行填充
(4)对时间段进行处理,简单起见,提取月份、星期几(工作日与周末)以及时间段(上午、下午、晚上、凌晨)信息
(5)对新生成的购买月份离散字段进行字典排序编码;
(6)处理完商家信息和交易记录的表格后,为了方便特征的统一计算将这几个表格合并,然后重新划分相应的字段种类。

8.3 特征工程

本赛题的重点便是挖掘用户的各种交易行为与目标列的关系,进而达到良好的模型学习效果,使模型能够准确预测测试集用户的忠诚度分数。因此这是一个关注信用卡用户局部消费偏好画像的题目,通过找到相似的训练集用户来类推测试集用户的忠诚度分数,进而对高价值人群进行区分,给商家与信用卡银行提供决策支持,同时也能够提升消费者的购物体验,因此特征工程可集中于用户的交易行为画像,即用户在各个维度上购物行为的量化,比如最近一个月的消费金额与购买数量等。

在评估用户价值的画像领域,有个经典的RFM理论,即Recent,Frequency(频次)和Money(金钱)。结合前面的数据探索,能够明确这一理论的可行性。这里将用购买数量模拟Frequency,把消费金额作为Money。本赛题不仅在建模目标上具有广泛性,其数据结构也具有典型的特点,即主要利用用户的行为记录表格(historical_transactions.csv,merchants.csv.以及new_merchant transactions.csv)进行信息挖掘。

接下来将分别介绍特征提取的两种办法,一种是借助python的原生字典结构进行通用特征的提取,另一种则借助pandas这一强大的数据处理工具的统计函数进行业务特征的提取。

8.3.1 通用特征

字典的键值结构很好地提供了便于使用的映射关系,这里的特征提取可以把用户作为第一层键值,把特征字段作为第二层键值,统计完成后再将字典转换成pandas.DataFrame格式;简单来说,就是想知道用户在每个类别字段的每个取值下的购买数量与消费金额。

首先,创建一个字典以存储生成的统计特征,并给每个card_id赋值:

features = {}
card_all = train['card_id'].append(test['card_id']).values.tolist()
for card in card_all:
    features[card] = {}

其次,记录好每个字段的索引以便按行处理的时候直接获取目标值:

columns = transaction.columns.tolist()
idx = columns.index('card_id')
category_cols_index = [columns.index(col) for col in category_cols]
numeric_cols_index = [columns.index(col) for col in numeric_cols]

然后,按行进行相应字段的特征提取和更新:

# 记录运行时间
s = time.time()
num = 0
for i in range(transaction.shape[0]):
    va = transaction.loc[i].values
    card = va[idx]
    for cate_ind in category_cols_index:
        for num_ind in numeric_cols_index:
            col_name = '&'.join([columns[cate_ind], va[cate_ind], columns[num_ind]])
            features[card][col_name] = features[card].get(col_name, 0) + va[num_ind]
    num += 1
    if num%1000000==0:
        print(time.time()-s, "s")
del transaction
gc.collect()

最后,将字典转换成特征DateFrame表格结构,并且重置表格的列名。

# 字典转dataframe
df = pd.DataFrame(features).T.reset_index()
del features
cols = df.columns.tolist()
df.columns = ['card_id'] + cols[1:]

在表格生成后就可以拼接训练集和测试集,进行后续的模型训练。为区别于 后续特征,将该特征集命名为dixt。(具体参见dict.ipynb)

# 生成训练集与测试集
train = pd.merge(train, df, how='left', on='card_id')
test =  pd.merge(test, df, how='left', on='card_id')
del df
train.to_csv("preprocess/train_dict.csv", index=False)
test.to_csv("preprocess/test_dict.csv", index=False)

8.3.2 通用特征

基于字典结构的通用特征提取,其优势在于可以按行读取及处理,无论速度还是内存都有一定的保障,还可以面面俱到地量化到每个子类下的用户行为。但其缺点也比较明显,即需要固定的数据结构,同时会产生较高维度的结果。另一种方案是使用pandas工具的groupby方法
进行统计,这种方式简单很多,但对内存性能要求较高,因为需要加载全部数据。需要注意的是,这里为了符合pandas的统计需要,不再对缺失值以及离散型字段进行转化

同时增加两个特征,这两个特征与用户两次购买行为之间的时间间隔有关,分别从日和月方面进行刻画,代码如下;

transaction['purchase_day_diff'] = transaction.groupby("card_id")['purchase_day'].diff()
transaction['purchase_month_diff'] = transaction.groupby("card_id")['purchase_month'].diff()

首先,根据字段的种类设置相应想获取的统计量,并给定相应的字段列表,为后续的计算做准备,这种方式逻辑清晰,特征构造更加全面:

aggs = {}
for col in numeric_cols:
    aggs[col] = ['nunique', 'mean', 'min', 'max','var','skew', 'sum']
for col in categorical_cols:
    aggs[col] = ['nunique']    
aggs['card_id'] = ['size', 'count']
cols = ['card_id']
for key in aggs.keys():
    cols.extend([key+'_'+stat for stat in aggs[key]])

然后,针对new_merchant_transactions.csv,historical_transactions.csv 以及全时间段分别进行计算和统计,获取多角度下的统计特征:

df = transaction[transaction['month_lag']<0].groupby('card_id').agg(aggs).reset_index()
df.columns = cols[:1] + [co+'_hist' for co in cols[1:]]

df2 = transaction[transaction['month_lag']>=0].groupby('card_id').agg(aggs).reset_index()
df2.columns = cols[:1] + [co+'_new' for co in cols[1:]]
df = pd.merge(df, df2, how='left',on='card_id')

df2 = transaction.groupby('card_id').agg(aggs).reset_index()
df2.columns = cols
df = pd.merge(df, df2, how='left',on='card_id')

 可以看出,利用groupby方法统计出的特征数量会少很多,集中为用户各种行为的统计量,为区别于后续特征,将此处特征集命名为groupby。

8.3.3 文本特征

除去上述常规的特征之外,本赛题还可以对一类特征进行提取,就是基于CountVector和NLP 领域的TF-IDF向量特征,不同于前面的dict和groupby,这里只针对部分离散字段进行词频统计。CountVector与dict部分的特征比较像,而TF-IDF则是对多变量联合分布的补充

首先将相应字段处理成标准的输入格式,然后调用sklearn中的相关方法进行计算,需要注意这部分特征采用的是scipy的sparse稀疏矩阵结构,因此在处理上与dict和 groupby有所不同。

8.3.4 特征选择

常见的特征选择方法主要分两种,一种是过滤式选择,另一种是特征重要性选择。前者利用一些统计学上的相关性系数进行过滤,后者通过模型评估过程中的特征重要性进行选择。一般来讲,特征选择的功能主要出于提升模型训练速度与精度两个方面的考虑,在8.4节将会针对不同的特征选择方法进行模型训练,并对比最终的线下,线上结果。

8.4 模型训练

在准备好基础特征后,参赛者就可以开始尝试模型训练与预测的全流程,为尽可能多地给参赛者介绍一些处理技巧,本节将会介绍三种模型(随机森林、LightGBM和XGBoost)的全流程,同时组合不同的特征选择与参数调优方法。

8.4.1 随机森林

首先是sklearm库里的随机森林模型,本模型的全流程分为四个模块:读取数据、特征选取、参数调优以及训练预测。模型的要素组成为8.3.4节中的dictgroupby两部分,特征选取方面采用基于皮尔逊相关系数计算的Filter方法取前300个特征,参数调优方面使用skleam库的网格搜索(GridSearch)

首先,读取已经提前构造好的指定特征集和测试集并且进行数据集的拼接,具体代码如下:

def read_data(debug=True):
    """
    读取数据
    :param debug:是否调试版,可以极大节省debug时间
    :return:训练集,测试集
    """

    print("read_data...")
    NROWS = 10000 if debug else None
    train_dict = pd.read_csv("preprocess/train_dict.csv", nrows=NROWS)
    test_dict = pd.read_csv("preprocess/test_dict.csv", nrows=NROWS)
    train_groupby = pd.read_csv("preprocess/train_groupby.csv", nrows=NROWS)
    test_groupby = pd.read_csv("preprocess/test_groupby.csv", nrows=NROWS)

    # 去除重复列
    for co in train_dict.columns:
        if co in train_groupby.columns and co!='card_id':
            del train_groupby[co]
    for co in test_dict.columns:
        if co in test_groupby.columns and co!='card_id':
            del test_groupby[co]

    # 拼接特征
    train = pd.merge(train_dict, train_groupby, how='left', on='card_id').fillna(0)
    test = pd.merge(test_dict, test_groupby, how='left', on='card_id').fillna(0)
    print("done")
    return train, test

然后采用基于皮尔逊相关系数计算的Filter方法取前300个特征进行选取,这里的300是随意取的一个数字,参赛者可以多试几个数字以选出效果最佳的,具体代码如下:


def feature_select_pearson(train, test):
    """
    利用pearson系数进行相关性特征选择
    :param train:训练集
    :param test:测试集
    :return:经过特征选择后的训练集与测试集
    """
    print('feature_select...')
    features = train.columns.tolist()
    features.remove("card_id")
    features.remove("target")
    featureSelect = features[:]

    # 去掉缺失值比例超过0.99的
    for fea in features:
        if train[fea].isnull().sum() / train.shape[0] >= 0.99:
            featureSelect.remove(fea)

    # 进行pearson相关性计算
    corr = []
    for fea in featureSelect:
        corr.append(abs(train[[fea, 'target']].fillna(0).corr().values[0][1]))

    # 取top300的特征进行建模,具体数量可选
    se = pd.Series(corr, index=featureSelect).sort_values(ascending=False)
    feature_select = ['card_id'] + se[:300].index.tolist()
    print('done')
    return train[feature_select + ['target']], test[feature_select]

接着就是基于网格搜索的参数调优。网格搜索实际上是不同参数、不同取值的排列集合,有可能需要根据调优结果多次手动选代参数空间,当然每次选代都是在上一次最佳参数的基础上增加未搜索过的参数区域,具体代码如下:

def param_grid_search(train):
    """
    网格搜索参数寻优
    :param train:训练集
    :return:最优的分类器模型
    """
    print('param_grid_search')
    features = train.columns.tolist()
    features.remove("card_id")
    features.remove("target")
    parameter_space = {
        "n_estimators": [80],
        "min_samples_leaf": [30],
        "min_samples_split": [2],
        "max_depth": [9],
        "max_features": ["auto", 80]
    }

    print("Tuning hyper-parameters for mse")
    clf = RandomForestRegressor(
        criterion="mse",
        min_weight_fraction_leaf=0.,
        max_leaf_nodes=None,
        min_impurity_decrease=0.,
        min_impurity_split=None,
        bootstrap=True,
        oob_score=False,
        n_jobs=4,
        random_state=2020,
        verbose=0,
        warm_start=False)
    grid = GridSearchCV(clf, parameter_space, cv=2, scoring="neg_mean_squared_error")
    grid.fit(train[features].values, train['target'].values)

    print("best_params_:")
    print(grid.best_params_)
    means = grid.cv_results_["mean_test_score"]
    stds = grid.cv_results_["std_test_score"]
    for mean, std, params in zip(means, stds, grid.cv_results_["params"]):
        print("%0.3f (+/-%0.03f) for %r"
              % (mean, std * 2, params))
    return grid.best_estimator_

最后根据参数调优的最佳结果进行模型训练与预测,这里选择五折交叉验证,注意保存训练集的交叉预测结果以及测试集的预测结果,便于8.5节使用。

def train_predict(train, test, best_clf):
    """
    进行训练和预测输出结果
    :param train:训练集
    :param test:测试集
    :param best_clf:最优的分类器模型
    :return:
    """
    print('train_predict...')
    features = train.columns.tolist()
    features.remove("card_id")
    features.remove("target")

    prediction_test = 0
    cv_score = []
    prediction_train = pd.Series()
    kf = KFold(n_splits=5, random_state=2020, shuffle=True)
    for train_part_index, eval_index in kf.split(train[features], train['target']):
        best_clf.fit(train[features].loc[train_part_index].values, train['target'].loc[train_part_index].values)
        prediction_test += best_clf.predict(test[features].values)
        eval_pre = best_clf.predict(train[features].loc[eval_index].values)
        score = np.sqrt(mean_squared_error(train['target'].loc[eval_index].values, eval_pre))
        cv_score.append(score)
        print(score)
        prediction_train = prediction_train.append(pd.Series(best_clf.predict(train[features].loc[eval_index]),
                                                             index=eval_index))
    print(cv_score, sum(cv_score) / 5)
    pd.Series(prediction_train.sort_index().values).to_csv("preprocess/train_randomforest.csv", index=False)
    pd.Series(prediction_test / 5).to_csv("preprocess/test_randomforest.csv", index=False)
    test['target'] = prediction_test / 5
    test[['card_id', 'target']].to_csv("result/submission_randomforest.csv", index=False)
    return

这里最后一步采用的是五折交叉验证,一方面可以避免模型对训练集的过拟合,另一方面可使模型对测试集的预测结果更具健壮性,还有一个顺带的好处是可生成用于Stacking融合的特征,即训练集的交叉预测结果和测试集的模型预测结果,将这两者保留下来为后续模型融合做准备,总共需要保存三个文件:train_randomforest.csv,test_randomforest.csv和 submission randomforest.csv.
预测结果出来以后,提交测试,得到具体分数,交叉验证分数为3.68710936,其中提交得分为Public Score(公开榜,俗称A榜)是3.75283(2867/4127),Private Score(隐藏榜,俗称B榜)是3.65493(2814/4127).

if __name__ == "__main__":

    # 获取训练集与测试集
    train, test = read_data(debug=False)

    # 获取特征选择结果
    train, test = feature_select_pearson(train, test)

    # 获取最优分类器模型
    best_clf = param_grid_search(train)

    # 获取结果
    train_predict(train, test, best_clf)
# [3.6952175995861753, 3.653405245049519, 3.711542672510601, 3.78859477721067, 3.586786511640954] 3.687109361199584

8.4.2 LightGBM

  • 特征组合:Dict+GroupBy+nlp
  • 特征选择方式:Wrapper
  • 参数寻优办法:hyperopt
  • 模型:lightgbm

特征选取

重要性前300

参数调优

Hyperopt是一个 sklearn的Python库,它在搜索空间上进行串行和并行优化,搜索空间可以是实值、离散值和条件维度,提供了传递参数空间和评估函数的接口,目前支持的优化算法有随机搜索(random search)、模拟退火(simulated annealing)和TPE(Tree of Parzen Estimators)算法。相较于网格搜索,hyperopt往往能够在相对较短的时间内获取更优的参数结果。具体代码如下:

对于结果的输出,网格搜索输出含最佳参数的结果,hyperopt输出最佳参数字典

8.4.3 XGBoost

  • 特征组合:Dict+GroupBy+nlp
  • 特征选择方式:chi2
  • 参数寻优办法:beyesian
  • 模型:xgboost

读取数据时,需要把之前的特征集与nlp特征合并成sparse稀疏矩阵;参数调优阶段,最大化评估分数,即均方误差最小

8.5 模型融合

8.5.1 加权融合

将模型的结果按照分数和排名分配权重

  • 随机森林 0.2
  • lightgbm 0.3
  • XGBoost 0.5

8.5.2 Stacking融合​​​​​​​

使用最好的xgboost模型产生的stacking特征(训练集和测试集的预测结果)

8.6 高效提分

8.6.1 特征优化

(1)基础统计特征

以card_id为key进行聚合(groupby)统计

(2)全局card_id特征

分别对new_transactions.csv,historical_transactions.csv(authorized_flag=1)和historical transactions.csv(authorized_flag=0)的数据集提取此部分特征。

主要包含与用户行为时间相关的统计,比如

(3)最近一次交易与首次交易的时间差信用卡激活日与首次交易的时间差

  • 以card_id 为key聚合统计 authorized_flag 和month_diff 的统计量(mean/sum);
  • 以card_id为key聚合统计 state_i,city_id,installments,merchant_id,merchant_category_id等的nunique,并构造card_id频次与上述得到的nunique的比值特征,以此反映用户card_id的行为纯度(分散范围);
  • 以card_id为key聚合统计 purchase_amount相关变量的统计量(mean/sum/std/median);
  • 除此之外还构造了一些Pivot相关的特征

(4)最近两月的card_id

最近两月的card_id仅对historical_transactions.csv的数据集提取此部分特征。此部分与全局card_id特征有很多类似特征,主要差别在于时间范围不同,此处更加注重用户近期的行为变化情况。
 

(5)二阶特征

仅对historical_transactions.csv的数据集提取此部分特征,前提是要先构建一阶特征(nunique、count、sum等),具体提取结构如下;

for col_level1,col_level2 in tqdm_notebook(level12_nunique):  
    
    level1  = df.groupby(['card_id',col_level1])[col_level2].nunique().to_frame(col_level2 + '_nunique')
    level1.reset_index(inplace =True)  
        
    level2 = level1.groupby('card_id')[col_level2 + '_nunique'].agg(['mean', 'max', 'std'])
    level2 = pd.DataFrame(level2)
    level2.columns = [col_level1 + '_' + col_level2 + '_nunique_' + col for col in level2.columns.values]
    level2.reset_index(inplace = True)
    
    cardid_features = cardid_features.merge(level2, on='card_id', how='left') 

补充特征

8.6.2 融合技巧

  • 单模结果
  • 加权融合
  • stacking融合
  • Trick融合

8.7 赛题总结

你可能感兴趣的:(AI书籍阅读笔记,机器学习算法竞赛,机器学习,人工智能)