基于CGAN的网贷风控数据补充和调整

简介:很多情况下,由于业务开展的过程和条件所限,在做风控模型的A卡时,我们积累的客户数据条数很少,积累的坏客户更少,做出的模型和判断都会产生较大的统计偏差,模型稳定性较差。涉及到冷启动的问题,我相信外部合作、业务设计、风控规则和流程方面的改进是更重要的。但是如果仅借助统计分布、推断和技术努力,我相信CGAN是一个可以尝试的方向。因此我借助CGAN主要做了两方面的工作:第一,利用有监督的对抗神经网络,生成更多的建模数据,增加模型的泛化能力,不同于boostrap的抽样,生成的数据是对原有分布的模拟,会产生更强的泛化效果,理想状态是增加了数据的密度,但是分布接近; 第二,可以根据对未来一段时间,整体风险状况的预判,增加或减少坏客户的密度,如果预判风险是增大的趋势,在有监督的生成过程中,可以有监督的随机生成更多的坏样本,让我们的预测更有前瞻性,做一个不十分恰当的比喻,聚集的坏客户就像是一张白纸上,浸染的一些墨点,现在我们在边缘增大墨点的浸染。我相信有很多业务上的更好的改进方式,CGAN的尝试也很初级,效果一般,如果您觉得这个方法很扯淡,看到这里就可以结束了。接下来我将展示一些公式和小trick,最后在文末附上主要的代码,这些对于不敢兴趣的人,都没有任何裨益,只有无聊。

一、CGAN介绍

GAN应用集中在图像生成,Nlp等方面。通过构建一个生成网络G,用来生成数据,生成的数据通过区分网络D来辨别,是否和原来数据分布接近。G像是一个说谎的人,说出谎话由D来辨别,通过G和D的互相博弈,G说谎的能力,D分辨的能力都会慢慢增长,最后G的谎话以假乱真。

简单的说GAN就是以下三点:

(1)构建两个网络,一个G生成网络,一个D区分网络。两个网络的网络结构随意就好.

(2)训练方式。G网络的loss是log(1-D(G(z))),而D的loss是-(log(D(x))+log(1-D(G(z))))

(3)数据输入。G网络的输入是noise。而D的输入则混合G的输出数据及样本数据。

而CGAN是在GAN的基础上加入一个标签变量y,进行有监督的生成学习,流程如下:

整个CGAN的目标变成:

二、结合风控数据的CGAN

这套算法,应用在信用风险数据上的主要问题,是我们的数据不像图片那么立体,不同维度之间没有图片数据的联系,风控有用的数据都是围绕y变量的,不宜应用卷积,不宜加深深度。因此人工的进行一些处理和筛选,前提上,所有one-hot变量,所有应用机器学习(决策树,Gbdt衍生变量,ffm等)衍生的变量都不合用。我们结合业务经验进行变量衍生,再筛选IV值较高的变量,进行比较好的WOE分箱之后(也可以再加一层数据标准化,我在代码中亦有体现),分别用两层网络进行生成和判别,中间的一层用relu进行非线性化,输出采sigmoid函数。

三、生成数据效果:PSI检测

选取10个IV较高的变量,进行CGAN数据生成,我目前的效果就是,通过输入随机数据(噪声),不断学习两个网络,达到输出较低PSI的生成数据。

初步来讲,可以生成PSI在0.15左右的生成数据,进一步迭代,应该能够达到0.1左右。

PSI对比

为生成更好的数据主要是对生成和对抗网络进行调参,例如隐层网络节点数,生成网络训练次数和判别网络的比值数等。以及前期数据处理,woe分箱,标准化过程等等。

至于针对风险趋势预判风险走势,生成更有前瞻性的数据。我通过随机生成一些带坏标签的客户进行了尝试,由于原数据是在太少,还没有办法评估效果,因此不在代码里体现。

附件:代码

Created onSun Apr 8 05:33:30 2018

@author: wu

"""

# COPYRIGHT:

# AUTH: J.Wu

# RiskManagement Dept.

# 2018-04-12

#####

0.WOE

#####

#WOE代码略过,不宜展示

####

1.标准化

####

importnumpy as np

class Normalization:

# 因为对数据归一化的方法各式各样,所以尽可能地用多个模块分别写

def __init__(self,input_array):

# input_array :需要进行归一化处理的输入序列,类型:numpy array 对象。

self.input_array = input_array

self.array_normalized =input_array.astype(float) # 用于存储归一化之后的序列(浮点型)

self.ndim = input_array.ndim # 维度 (仅仅限于处理1 维或者二维的列表,再多就不行了)

self.normalized_statu = False # 数据是否归一化的判别

self.linear_norm_parameter = None # 离差标准化的参数(最大值 与 最小值 序列)

self.z_score_norm_parameter = None # z_score 标准化的参数(均值和标准差序列)

# 1 -1 归一化的方法1 :离差标准化

def linear_normalize(self,max_and_min_array= None):

""" 离差标准化

针对输入样本的每一个特征值,分别取出最大max_num与最小值min_num ,然后对每个特征值按照如下公式进行计算:

公式: after_num =(input_num - min_num ) / (max_num - min_num)

"""

if self.normalized_statu != False:

print("-*- 函数离差标准化:貌似已经进行了归一化处理,信息:".format(self.normalized_statu))

print("将不会进行再次归一化处理")

return

if max_and_min_array:

# 参数max_and_min_array允许指定每个特征值的最大最小值 。类型:numpy array

if self.ndim == 2:

if self.input_array.shape[1] !=len(max_and_min_array):

# 输入样本的特征数 与 自定义的特征数目不同的时候 :退出

print("错误:93040:离差标准化 - 长度不同")

exit(0)

try:

ifmax_and_min_array.shape[self.ndim -1 ] != 2:

print("错误:93050:离差标准化 - 指定最大最小序列格式错误")

exit(0)

except AttributeError as e:

print("错误:93060:离差标准化 - 指定最大最小序列不是numpy-array对象;\n")

print(" 详细信息:".format(e))

exit(0)

self.linear_norm_parameter =max_and_min_array

else:

# 未指定特征最大最小值,从 self.input_array中获取

# [ [x0_max,x0_min],[x1_max,x1_min],[x2_max,x2_min], ... ,[xn_max,xn_min] ]

if self.ndim == 2:

self.linear_norm_parameter =np.vstack((self.input_array.max(0), self.input_array.min(0))).T

elif self.ndim == 1:

self.linear_norm_parameter =np.array([self.input_array.max(0),self.input_array.min(0)])

# 下面才是归一化的核心

if self.ndim == 2:

# 2 维数据

for i inrange(len(self.linear_norm_parameter)):

# 前提得是 最大最小值不是相同的,否则分母为0,没有意义了

max_del_min =self.linear_norm_parameter[i][0] - self.linear_norm_parameter[i][1]

if max_del_min != 0:

self.array_normalized[:,i]= (self.input_array[:,i] -self.linear_norm_parameter[i][1]) / max_del_min

else:

self.array_normalized[:,i]= self.input_array[:,i] -self.linear_norm_parameter[i][1]

elif self.ndim == 1 :

# 1 维数据

max_del_min =self.linear_norm_parameter[0] - self.linear_norm_parameter[1]

if max_del_min != 0:

self.array_normalized =(self.input_array - self.linear_norm_parameter[1]) / max_del_min

else:

self.array_normalized =self.input_array - self.linear_norm_parameter[1]

# 改变是否已经归一化处理的状态

ifisinstance(self.linear_norm_parameter,np.ndarray) :

self.normalized_statu = "离差标准化" # 已经归一化了

# 1 -2 离差标准化的反归一化 输入样本(训练样本)根本就不需要反归一化,此函数只会用于预测结果的反

def rev_linear_normalize(self,predict_array):

# 输入待反归一化的数据 , 使用之前线性归一使用的参数,进行反归一化

rev_array_normalized =predict_array.copy()

if self.ndim == 1: # 一维数组

# 1 维数据

max_del_min =self.linear_norm_parameter[0] - self.linear_norm_parameter[1]

rev_array_normalized =predict_array * max_del_min + self.linear_norm_parameter[1]

elif self.ndim == 2:

# 2 维数据

for i inrange(len(self.linear_norm_parameter)):

max_del_min =self.linear_norm_parameter[i][0] - self.linear_norm_parameter[i][1]

rev_array_normalized[:, i] =predict_array[:, i] * max_del_min + self.linear_norm_parameter[i][1]

return rev_array_normalized

# 2 -1 归一化的第二种方法

def z_score_normalize(self):

# Z-score标准化

# 结果成正态分布

# 公式: after_num =(input_num - 平均值mean ) / 标准差std

# 首先验证数据

if self.normalized_statu != False:

print("-*- Z-score标准化:貌似已经进行了归一化处理,信息:".format(self.normalized_statu))

print("将不会进行再次归一化处理")

return

# 首先计算样本的均值和标准差

if self.ndim == 2:

self.z_score_norm_parameter =np.vstack((self.input_array.mean(0), self.input_array.std(0))).T # 均值 与 标准差

elif self.ndim == 1:

self.z_score_norm_parameter =np.array([self.input_array.mean(), self.input_array.std()]) # 均值与标准差

# 下面是归一化算法的核心

if self.ndim == 2:

# 2 维数据

for i inrange(len(self.z_score_norm_parameter)):

# 前提得是 最大最小值不是相同的,否则分母为0,没有意义了

std_num =self.z_score_norm_parameter[i][1] # 方差

if std_num != 0:

self.array_normalized[:,i]= (self.input_array[:,i] -self.z_score_norm_parameter[i][0]) / std_num

else:

self.array_normalized[:,i] =self.input_array[:,i] -self.z_score_norm_parameter[i][0]

elif self.ndim == 1 :

# 1 维数据

std_num =self.z_score_norm_parameter[1] # 方差

if std_num != 0:

self.array_normalized =(self.input_array - self.z_score_norm_parameter[0]) / std_num

else:

self.array_normalized =self.input_array - self.z_score_norm_parameter[0]

# z_score 反归一化的函数

def rev_z_score_normalize(self,predict_array):

# 输入待反归一化的数据 , 使用之前线性归一使用的参数,进行反归一化

rev_array_normalized =predict_array.copy()

if self.ndim == 1: # 一维数组

# 1 维数据

max_del_min = self.z_score_norm_parameter[1]

rev_array_normalized =predict_array * max_del_min + self.z_score_norm_parameter[0]

elif self.ndim == 2:

# 2 维数据

for i inrange(len(self.z_score_norm_parameter)):

max_del_min =self.z_score_norm_parameter[i][1]

rev_array_normalized[:, i] =predict_array[:, i] * max_del_min + self.z_score_norm_parameter[i][0]

return rev_array_normalized

####

2.CGAN

####

importtensorflow as tf

importmatplotlib.pyplot as plt

importnumpy as np

#kvv is setof train kvv=kv

#tf.set_random_seed(1)

BATCH_SIZE= 8

LR_G =0.0005

LR_D =0.0005

N_IDEAS =int(kvv.shape[1])

ART_COMPONENTS= kvv.shape[1]

PAINT_POINTS= kvv

art_labels= tf.placeholder(tf.float32,[None,1])

withtf.variable_scope('GGgg'):

G_in =tf.placeholder(tf.float32,[None,N_IDEAS])

G_art = tf.concat((G_in,art_labels),1)

withtf.variable_scope('DDdd'):

real_in =tf.placeholder(tf.float32,[None,ART_COMPONENTS],name='real_in')

real_art = tf.concat((real_in,art_labels),1)

#fake art

G_art = tf.concat((G_out,art_labels),1)

D_loss =-tf.reduce_mean(tf.log(prob_artist0)+tf.log(1-prob_artist1))

G_loss =tf.reduce_mean(tf.log(1-prob_artist1))

for step inrange(len(kvv)):

train_D =tf.train.AdamOptimizer(LR_D).minimize(

D_loss,var_list=tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES,scope='DDdd'))

train_G =tf.train.AdamOptimizer(LR_G).minimize(

G_loss,var_list=tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES,scope='GGgg'))

sess=tf.Session()

sess.run(tf.global_variables_initializer())

# artist_paintings=kvv[step:step+2]

# for i in range(3):

# artist_paintings=np.row_stack((artist_paintings,artist_paintings))

# labels =np.array([np.array(mm['y'].values)[step:step+2]]*BATCH_SIZE)

# labels.shape=(artist_paintings.shape[0],1)

artist_paintings=kvv[step]

for i in range(3):

artist_paintings=np.row_stack((artist_paintings,artist_paintings))

labels =np.array([np.array(mm['y'].values)[step]*BATCH_SIZE)

labels.shape=(artist_paintings.shape[0],1)

for k in range(2000):

G_paintings,pa0,D1,D2 = sess.run([G_out,prob_artist0,D_loss,G_loss,train_D,train_G],

)[:4]

if k%50==0:

print D1,D2

# if step==0:

# sx=np.mean(G_paintings,axis=0)

# sx.shape=(1,10)

# mmm=pd.DataFrame(sx,columns=ccol)

# mmm['y']=labels[0]

# else:

# ceshi=pd.DataFrame(G_paintings,columns=ccol)

# ceshi['y']=labels

# sx0=np.array(ceshi[ceshi['y']==0].mean())

# sx0.shape=(1,11)

# if np.isnan(sx0[0][0])==False:

# mmm=mmm.append(pd.DataFrame(sx0,columns=ccol+['y']))

#

# sx1=np.array(ceshi[ceshi['y']==1].mean())

# sx1.shape=(1,11)

# if np.isnan(sx1[0][0])==False:

# mmm=mmm.append(pd.DataFrame(sx1,columns=ccol+['y']))

if step==0:

kll=np.mean(G_paintings,axis=0)

else:

kll=np.row_stack((kll,np.mean(G_paintings,axis=0)))

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180416G0NQG100?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券