简介:很多情况下,由于业务开展的过程和条件所限,在做风控模型的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)))
领取专属 10元无门槛券
私享最新 技术干货