不均衡学习

本文是《智能风控、算法和工程实践》第五章学习笔记。

幸存者偏差

  由于风控模型的拒绝属性,会导致幸存者偏差和样本不均衡两个问题。 幸存者偏差是指,每次模型迭代时,使用的样本都是被前一个模型筛选过的,只有高于前一版模型分数阈值的样本,才可以进入当前模型训练,于是样本空间就会不完备。进入模型的样本就是幸存者,它们不携带或者很少携带被拒绝的样本的信息,导致样本逐渐偏离真实分布。

  于是随着模型迭代,区分能力强的特征被弱化,甚至对模型起到完全相反的作用。想要修正有偏差的信息,就需要用无偏差的数据重新训练模型。因此核心问题就是如何获取无偏差的数据。 传统的解决方法就是使用拒绝推断为拒绝样本添加伪标记和权重。拒绝推断的方法见: 拒绝推断

  书中介绍的方法是一些其他的机器学习方法,具体有:

  1. 增量学习
  2. 生成对抗网络——GAN模型
  3. 高斯混合模型——GMM算法

  这里就不再深入学习以上的算法了,因为实际工作中用不用得到拒绝推断还是个问号。

样本不均衡

  在风控场景下,负样本的占比要远远小于正样本的占比。当不同类别的样本量有较大差异时,在梯度下降的过程中就会很难收敛到最优解。因为在每一次梯度下降的过程中,负样本所贡献的信息有限,导致模型无法很好地学习负样本。通俗说就是,负样本越多,模型的区分效果通常会越好,KS值会越大。

代价敏感加权方案

  下探是解决不均衡问题最直接的方法。就是在拒绝域中随机接收一些样本,以此来积累负样本。但这样风险较高,会牺牲一部分收益,而且较难量化每次下探的量。

  对少数类样本进行加权处理,使模型进行均衡训练,即代价敏感加权方案,又叫作展开法。在逻辑回归中,可以通过参数class_weight="balanced"调整正负样本的权重,大多数情况下样本加权可以增强模型的表现。

  类权重的计算方法如下:

$weight=frac{n_samples}{n_classes*np.bincount(y)}$

  其中,n_samples为样本数,n_classes为类别数量,np.bincount(y)会输出每个类别的样本数。

插值过采样方案

  插值过采样方案又叫作SMOTE算法,是机器学习中常用的采样方法。基本思想就是在现有少数类样本之间进行插值,人工合成新样本。SMOTE算法的原理如下图:

不均衡学习

  为了解决SMOTE算法的过拟合问题,Adaptive Synthetic Sampling方法被提出,主要包括:Borderline-SMOTE和Adaptive Synthetic Sampling(ADA-SYN)算法。

  1. Borderline-SMOTE
      对靠近边界的minority样本创造新数据。其与SMOTE的不同是:SMOTE是对每一个少数类样本产生综合新样本,而Borderline-SMOTE仅对靠近边界的少数样本创造新数据。

  Borderline-SMOTE算法只对近邻中多数类样本大于少数类样本的点进行合成新样本。但是如果一个样本的近邻都是多数类样本, 则会被认为是噪声,不合成新样本。

  1. ADA-SYN

  根据多数类和少数类的密度分布,动态改变权重,决定要生成多少少数类的新数据。

  1. 基于聚类的随机采样(CBO)

  可以用来解决类内不平衡问题,主要利用的聚类的方法。具体的过程如下:

随机选择K个样本作为K个簇,并且计算K类样本在特征空间的平均值,作为聚类中心;

对于剩下的每一个样本,计算它和K个聚类中心的欧氏距离,根据欧式聚类将其分配到最近的类簇中;

更新每个簇的聚类中心,直到所有的样本都用完;

不均衡学习

  CBO会使用过采样的方法填充多数类和少数类的类簇,所以每个类的样本数相同。少数类的样本个数为多数类样本除以少数类的类别数。即上图中60/2=30。

过采样算法实战

  由于SMOTE算法是基于样本空间进行插值,会放大数据集中的噪声和异常,因此要对训练样本进行清洗。这里使用lightgbm对数据进行拟合,将预测结果较差的样本权重降低,并且不参与SMOTE算法的插值过程。列出核心代码:

def  lgb_test(train_x,train_y,test_x,test_y):
    clf =lgb.LGBMClassifier(boosting_type = 'gbdt',
                           objective = 'binary',
                           metric = 'auc',
                           learning_rate = 0.1,
                           n_estimators = 24,
                           max_depth = 4,
                           num_leaves = 25,
                           max_bin = 40,
                           min_data_in_leaf = 5,
                           bagging_fraction = 0.6,
                           bagging_freq = 0,
                           feature_fraction = 0.8,
                           )
    clf.fit(train_x,train_y,eval_set = [(train_x,train_y),(test_x,test_y)],eval_metric = 'auc')
    return clf,clf.best_score_['valid_1']['auc'],
lgb_model , lgb_auc  = lgb_test(train_x,train_y,test_x,test_y)
feature_importance = pd.DataFrame({'name':lgb_model.booster_.feature_name(),
                                   'importance':lgb_model.feature_importances_}).sort_values(by=['importance'],ascending=False)

pred = lgb_model.predict_proba(train_x)[:,1]
fpr_lgb,tpr_lgb,_ = roc_curve(train_y,pred)
print(abs(fpr_lgb - tpr_lgb).max())
    
pred = lgb_model.predict_proba(test_x)[:,1]
fpr_lgb,tpr_lgb,_ = roc_curve(test_y,pred)
print(abs(fpr_lgb - tpr_lgb).max())

pred = lgb_model.predict_proba(evl_x)[:,1]
fpr_lgb,tpr_lgb,_ = roc_curve(evl_y,pred)
print(abs(fpr_lgb - tpr_lgb).max())
sample = x[feature_lst]
sample['bad_ind'] = y
sample['pred'] = lgb_model.predict_proba(x)[:,1]
sample = sample.sort_values(by=['pred'],ascending=False).reset_index()

sample['rank'] = np.array(sample.index)/75522

def weight(x,y):
    if x == 0 and y < 0.1:
        return 0.1
    elif x == 1 and y > 0.7:
        return 0.1
    else:
        return 1

sample['weight'] = sample.apply(lambda x: weight(x.bad_ind,x['rank']),axis = 1)

def lr_wt_predict(train_x,train_y,evl_x,evl_y,weight):
    lr_model = LogisticRegression(C=0.1,class_weight='balanced')
    lr_model.fit(train_x,train_y,sample_weight = weight )
    
    y_pred = lr_model.predict_proba(train_x)[:,1]
    fpr_lr,tpr_lr,_ = roc_curve(train_y,y_pred)
    train_ks = abs(fpr_lr - tpr_lr).max()
    print('train_ks : ',train_ks)
    
    y_pred = lr_model.predict_proba(evl_x)[:,1]
    fpr_lr,tpr_lr,_ = roc_curve(evl_y,y_pred)
    evl_ks = abs(fpr_lr - tpr_lr).max()
    print('evl_ks : ',evl_ks)
    
lr_wt_predict(sample[feature_lst],sample['bad_ind'],evl_x,evl_y,sample['weight'])

  这里的思想就是使用集成模型对数据做异常点检测,将难以辨别的样本视为噪音。可以理解为大神都做不对的题目,就不让普通学员做了。异常点检测的逻辑为:低分段错分比例和高分段错分比例。   下面做基于borderline1的smote算法做过采样。只对weight=1的样本进行过采样。

osvp_sample = sample[sample.weight == 1].drop(['pred','index','weight'],axis = 1)
osnu_sample = sample[sample.weight < 1].drop(['pred','index',],axis = 1)

train_x_osvp = osvp_sample[feature_lst]
train_y_osvp = osvp_sample['bad_ind']
def lr_predict(train_x,train_y,evl_x,evl_y):
    lr_model = LogisticRegression(C=0.1,class_weight='balanced')
    lr_model.fit(train_x,train_y)
    
    y_pred = lr_model.predict_proba(train_x)[:,1]
    fpr_lr,tpr_lr,_ = roc_curve(train_y,y_pred)
    train_ks = abs(fpr_lr - tpr_lr).max()
    print('train_ks : ',train_ks)
    
    y_pred = lr_model.predict_proba(evl_x)[:,1]
    fpr_lr,tpr_lr,_ = roc_curve(evl_y,y_pred)
    evl_ks = abs(fpr_lr - tpr_lr).max()
    print('evl_ks : ',evl_ks)
    return train_ks,evl_ks

from imblearn.over_sampling import SMOTE,RandomOverSampler,ADASYN
smote = SMOTE(k_neighbors=15, kind='borderline1', m_neighbors=4, n_jobs=1,
              out_step='deprecated', random_state=0, ratio=None,
              svm_estimator='deprecated')
rex,rey = smote.fit_resample(train_x_osvp,train_y_osvp)
print('badpctn:',rey.sum()/len(rey))
df_rex = pd.DataFrame(rex)
df_rex.columns =feature_lst
df_rex['weight'] = 1
df_rex['bad_ind'] = rey
df_aff_ovsp = df_rex.append(osnu_sample)
lr_predict(df_aff_ovsp[feature_lst],df_aff_ovsp['bad_ind'],evl_x,evl_y)

  采样后的KS值会有一定的提升,且过拟合的风险不会很大。

半监督学习方案

  SMOTE算法所产生的新样本仍然是基于样本产生的,并没有为模型引入拒绝样本信息。通过半监督学习对拒绝样本进行预测,自动利用无标签样本提升学习性能。 书中介绍的两种半监督学习模型是半监督支持向量机和标签传播算法。

S3VM

  半监督支持向量机。基本思想是在不考虑无标签样本的情况下尝试寻找最大间隔划分超平面。

LP

  标签传播算法,是一种基于图的半监督学习方式。LP算法包括两个步骤:

  1. 构造相似矩阵
  2. 通过相似度进行传播
      下面通过一个例子介绍LP算法的应用。首先自动生成200个弧形分布的数据点,然后指定其中两个点的标签。使用LP算法根据有标签样本在样本空间的位置进行标签传递,最终使得每个样本都有标签。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.semi_supervised import label_propagation
from sklearn.datasets import make_moons

#生成弧形数据
n_samples=200
X,y=make_moons(n_samples,noise=0.04,random_state=0)
outer,inner=0,1
labels=np.full(n_samples,-1.)
labels[0]=outer
labels[-1]=inner

#使用LP算法实现标签传递
label_spread=label_propagation.LabelSpreading(kernel='rbf',alpha=0.8)
label_spread.fit(X,labels)

#输出标签
output_labels=label_spread.transduction_
plt.figure(figsize=(8.5,4))
plt.subplot(1,2,1)
plt.scatter(X[labels==outer,0].X[labels==outer,1],color='navy',marker='s',lw=0,label="outer labeled",s=10)
plt.scatter(X[labels==inner,0].X[labels==inner,1],color='c',marker='s',lw=0,label="inner labeled",s=10)
plt.scatter(X[labels==-1,0].X[labels==-1,1],color='darkorange',marker='.',label="unlabeled",)
plt.legend(scatterpoints=1,shadow=False,loc='upper right')
plt.title("Raw data (2 classes=outer and inner)")
不均衡学习

  左图为原始数据分布,橘黄色的点代表无标签样本,深蓝色的点为负样本,浅蓝色为正样本;右图为标签传播的结果。

小结

  1. 代价敏感加权实现较为简单,使用广泛;
  2. SMOTE算法需要进行样本和特征清洗,再进行过采样;
  3. 半监督学习需要一定的无标签样本,泛化能力强,但精度无法保证。

  半监督学习比过采样算法泛化能力更强,因为是从无标签样本中召回负样本。但准确率不好验证,模型不够稳定。

【作者】:Labryant

【原创公众号】:风控猎人

【简介】:某创业公司策略分析师,积极上进,努力提升。乾坤未定,你我都是黑马。

【转载说明】:转载请说明出处,谢谢合作!~

展开阅读全文

页面更新:2024-04-14

标签:梯度   向量   近邻   幸存者   权重   推断   偏差   样本   算法   模型   标签   方案   方法   数据   科技   空间

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top