30 分钟看懂 CatBoost(Python代码)

Posted Python数据科学

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了30 分钟看懂 CatBoost(Python代码)相关的知识,希望对你有一定的参考价值。


编码方法,也是CatBoost最重要的创新。

2、基于贪心策略的特征交叉方法

使用Ordered Target Statistics 方法将类别特征转化成为数值特征以后,会影响到特征交叉,因为数值特征无法有效地进行交叉。

依然以风控领域的预测信贷用户是否会违约为例,假设 city="北京市" 且 job="保安" 的用户信用特别好,但不是北京市所有的用户都信用好,也不是所有的保安都信用特别好。只有北京市的保安这个群体才信用好。

如果我们将 city转换为数值编码,也将保安转换为数值编码之后,我们得到两个数,这两个数相乘是没有意义的,我们无法表示 北京市的保安这个群体。

为了有效地利用特征交叉,CatBoost 在将类别特征转换为数值编码的同时,会自动生成 交叉特征。

如果让全部的类别特征之间都进行交叉,两两交叉,三三交叉,四四交叉,这个复杂度是指数级的,特征维度一定会爆炸。

CatBoost使用一种贪心的策略来进行特征交叉。生成tree的第一次分裂,CatBoost不使用任何交叉特征。在后面的分裂中,CatBoost会使用生成tree所用到的全部原始特征和交叉特征 跟 数据集中的全部 类别特征进行交叉。

在定义CatBoost模型时,我们可以用\'max_ctr_complexity\' 来控制允许的特征交叉的最大特征数量,如果设置为3,那么生成tree时所用到的交叉特征最多只会来自3个特征的交叉,也就是我们只能表示 city=\'北京市\' 且 job=\'保安\' 且 education=\'高中\'这样的三阶交叉特征,而无法表示  city=\'北京市\' 且 job=\'保安\' 且 education=\'高中\' 且 hobby=\'抽烟\' 这样的四阶交叉特征。

3、避免预测偏移的 Ordered Boosting 方法。

使用XGBoost或者LightGBM做模型时,我们可能经常会发现模型在训练集上拟合的很好,train_auc甚至达到了1.0, 但是在验证集上却差了很多, va_auc 可能只有0.7。这当然有可能是因为tree的数量太多了,或者是每棵tree的leaves太多了,总之模型太复杂了造成了过拟合。

但也有一些XGBoost和LightGBM自身算法的缺陷因素。我们知道LightGBM在训练下一棵tree的时候,需要计算前面这些tree构成的加法模型在所有样本上的一阶梯度和二阶梯度(Loss对模型预测结果的导数),然后用这些梯度来决定下一棵树的结构和叶子节点取值。

但是我们计算的这些一阶梯度和二阶梯度值是问题的。前面的这些tree都是在这些样本上训练的,现在我们又在这些样本上估计模型预测结果的一阶和二阶梯度。我们应该换一些新的样本才更合理。但是我们从哪里找这些新的样本呢?

CatBoost 的作者故伎重演。先将样本随机打乱,然后每个样本只使用排序在它前面的样本来训练模型。用这样的模型来估计这个样本预测结果的一阶和二阶梯度。然后用这些梯度构建一棵tree的结构,最终tree的每个叶子节点的取值,是使用全体样本进行计算的。

这就是Ordered Boosting的主要思想。可以有效地减少梯度估计的误差,缓解预测偏移。但是会增加较多的计算量,影响训练速度。

在定义CatBoost模型时,我们可以用\'boosting_type\'这个参数来设置是使用Ordered Boosting 还是 LightGBM那样的 Plain Boosting。如果不显式设置,CatBoost会根据样本和特征数量自己决定。

4、使用对称二叉树作为基模型,有正则作用且预测极快

XGBoost和LightGBM采用的基模型是普通的二叉树,但是CatBoost采用的是对称的二叉树。

这种对树结构上的约束有一定的正则作用。更为重要的是,它可以让CatBoost模型的推断过程极快。

对于CatBoost的tree的预测过程来说,每个特征的分裂都是独立的,不分先后顺序,多个样本可以一起预测。

三、使用范例

#!pip install catboost 
import catboost as cb 
print(cb.__version__)

1.0.4

from IPython.display import display 

import datetime,json
import numpy as np
import pandas as pd
import catboost as cb 
from catboost.datasets import titanic
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.model_selection import StratifiedKFold

from sklearn.metrics import f1_score,roc_auc_score,accuracy_score
import plotly.graph_objs as go 
import plotly.express as px 


def printlog(info):
    nowtime = datetime.datetime.now().strftime(\'%Y-%m-%d %H:%M:%S\')
    print("\\n"+"=========="*8 + "%s"%nowtime)
    print(info+\'...\\n\\n\')
     
#================================================================================
# 一,准备数据
#================================================================================
printlog("step1: preparing data...")

dfdata,dftest = titanic()

display(dfdata.head()) 

label_col = "Survived"

# 填充空值特征
dfnull = pd.DataFrame(dfdata.isnull().sum(axis=0),columns = ["null_cnt"]).query("null_cnt>0")
print("null_features:"
print(dfnull)

dfdata.fillna(-9999, inplace=True)
dftest.fillna(-9999, inplace=True)


# 刷选类别特征
cate_cols = [x for x in dfdata.columns 
             if dfdata[x].dtype not in [np.float32,np.float64] and x!=label_col]
for col in cate_cols:
    dfdata[col] = pd.Categorical(dfdata[col]) 
    dftest[col] = pd.Categorical(dftest[col]) 

# 分割数据集
dftrain,dfvalid = train_test_split(dfdata, train_size=0.75, random_state=42)
Xtrain,Ytrain = dftrain.drop(label_col,axis = 1),dftrain[label_col]
Xvalid,Yvalid = dfvalid.drop(label_col,axis = 1),dfvalid[label_col]
cate_cols_indexs = np.where(Xtrain.columns.isin(cate_cols))[0]


# 整理成Pool
pool_train = cb.Pool(data = Xtrain, label = Ytrain, cat_features=cate_cols)
pool_valid = cb.Pool(data = Xvalid, label = Yvalid, cat_features=cate_cols)


#================================================================================
# 二,设置参数
#================================================================================
printlog("step2: setting parameters...")
                               
iterations = 1000
early_stopping_rounds = 200

params = 
    \'learning_rate\'0.05,
    \'loss_function\'"Logloss",
    \'eval_metric\'"Accuracy",
    \'depth\'6,
    \'min_data_in_leaf\'20,
    \'random_seed\'42,
    \'logging_level\'\'Silent\',
    \'use_best_model\'True,
    \'one_hot_max_size\'5,   #类别数量多于此数将使用ordered target statistics编码方法,默认值为2。
    \'boosting_type\':"Ordered"#Ordered 或者Plain,数据量较少时建议使用Ordered,训练更慢但能够缓解梯度估计偏差。
    \'max_ctr_complexity\'2#特征组合的最大特征数量,设置为1取消特征组合,设置为2只做两个特征的组合,默认为4。
    \'nan_mode\'\'Min\' 



#================================================================================
# 三,训练模型
#================================================================================
printlog("step3: training model...")


model = cb.CatBoostClassifier(
    iterations = iterations,
    early_stopping_rounds = early_stopping_rounds,
    train_dir=\'catboost_info/\',
    **params
)


#直接训练
model.fit(
    pool_train,
    eval_set=pool_valid,
    plot=True
)

print("model.get_all_params():")
print(model.get_all_params() )


#5折交叉验证
cv_data= cb.cv(
    cb.Pool(dfdata.drop(label_col,axis = 1), dfdata[label_col], cat_features=cate_cols_indexs),
    params,
    fold_count = 3,
    plot=True
)

print(\'Best validation accuracy score: :.2f±:.2f on step \'.format(
    np.max(cv_data[\'test-Accuracy-mean\']),
    cv_data[\'test-Accuracy-std\'][np.argmax(cv_data[\'test-Accuracy-mean\'])],
    np.argmax(cv_data[\'test-Accuracy-mean\'])
))


#================================================================================
# 四,评估模型
#================================================================================
printlog("step4: evaluating model ...")


y_pred_train = model.predict(Xtrain)
y_pred_valid = model.predict(Xvalid)

train_score = f1_score(Ytrain,y_pred_train)
valid_score = f1_score(Yvalid,y_pred_valid)


print(\'train f1_score: :.5 \'.format(train_score))
print(\'valid f1_score: :.5 \\n\'.format(valid_score))   



#feature importance 
dfimportance = model.get_feature_importance(prettified=True
dfimportance = dfimportance.sort_values(by = "Importances").iloc[-20:]
fig_importance = px.bar(dfimportance,x="Importances",y="Feature Id",title="Feature Importance")

display(dfimportance)
display(fig_importance)


#score distribution
y_test_prob = model.predict_proba(dftest)[:,-1]
trace1 = go.Histogram(x = y_test_prob,histnorm = \'probability\',nbinsx=50)
layout = go.Layout(title = "Score Distribution",xaxis="title":"score",yaxis = "title":"frequecy")
fig_distribution = go.Figure(data = [trace1])
fig_distribution.update_layout(layout)
display(fig_distribution)


#================================================================================
# 五,使用模型
#================================================================================
printlog("step5: using model ...")

y_pred_test = model.predict(dftest)
y_pred_test_prob = model.predict_proba(dftest)

print("y_pred_test:\\n",y_pred_test[:10])
print("y_pred_test_prob:\\n",y_pred_test_prob[:10])

#================================================================================
# 六,保存模型
#================================================================================
printlog("step6: saving model ...")

model_dir = \'catboost_model\'
model.save_model(model_dir)
model_loaded = cb.CatBoostClassifier()
model.load_model(model_dir)


推荐阅读
1. pandas100个骚操作
2. 机器学习原创系列
3. 数据科学干货下载

catboost原理以及Python代码

原论文:

   http://learningsys.org/nips17/assets/papers/paper_11.pdf

catboost原理:

One-hot编码可以在预处理阶段或在训练期间完成。后者对于训练时间而言能更有效地执行,并在Catboost中执行。

类别特征:

为了减少过拟合以及使用整个数据集进行训练,Catboost使用更有效的策略。

1、对输入的观察值的集合进行随机排列,生成多个随机排列;

2、给定一个序列,对于每个例子,对于相同类别的例子我们计算平均样本值;

3、使用如下公式将所有的分类特征值转换为数值:

,那么可以代替为  

在这里,我们还增加了先验值P和参数a>0,即为先验的权重。添加先验是一种常见的做法,它有助于减少从低频类别获得的噪声。

特征组合:

在数据集中,组合的数量随类别特征个数成指数型增长,在算法中不太可能考虑所有。在当前树考虑新的拆分时,Catboost以贪婪的方式考虑组合。

1、 第一次分裂不考虑任何组合在树上;

2、 对于下一次分类,在有所有类别特征的数据集的当前树,Catboost包含了所有的组合和分类特征。组合值即被转换为数字;

3、 Catboost还以以下方式生成数值和类别特征的组合:在树中选择的所有分裂视为具有两个值的类别,并在组合中也类似使用。

python代码:

import catboost

model = CatBoostClassifier(iterations=17000,

#                              depth = 6,

                               learning_rate = 0.03,

                               custom_loss=\'AUC\',

                               eval_metric=\'AUC\',

                               bagging_temperature=0.83,

                               od_type=\'Iter\',

                               rsm = 0.78,

                               od_wait=150,

                               metric_period = 400,

                               l2_leaf_reg = 5,

                               thread_count = 20,

                               random_seed = 967

                              )

            model.fit(tr_x, tr_y, eval_set=(te_x, te_y),use_best_model=True)

            pre= model.predict_proba(te_x)[:,1].reshape((te_x.shape[0],1))

            train[test_index]=pre

            test_pre[i, :]= model.predict_proba(test_x)[:,1].reshape((test_x.shape[0],1))

            print (roc_auc_score(te_y, pre))

            cv_scores.append(roc_auc_score(te_y, pre))

 

以上是关于30 分钟看懂 CatBoost(Python代码)的主要内容,如果未能解决你的问题,请参考以下文章

catboost原理以及Python代码

30分钟全看懂127个常用的JS程序片段

Python实现GWO智能灰狼优化算法优化Catboost分类模型(CatBoostClassifier算法)项目实战

catboost算法中对称树背后的直觉是啥?

3分钟看懂Python后端必须知道的Django的信号机制!

10分钟看懂10大经典算法(Swift代码实现)