图4:不同时间分布的交易热图。
图5:交易金额的分布。
图6:初始余额的分布。请注意,由于透支,某些帐户的初始余额为负。
图7:一个月内的交易数量-收入和支出。请注意,收入有非常明显的峰值。综合数据必须捕获这些峰
运行代码
我们使用以下参数仅运行了100个轮次的代码:
importsysimportossys.path.append("..") importmatplotlib.pyplotaspltfromganimportoutputsys.modules["output"] =outputimportnumpyasnpimportpickleimportpandasaspdfromgan.doppelgangerimportDoppelGANgerfromgan.load_dataimportload_datafromgan.networkimportDoppelGANgerGenerator, Discriminator, AttrDiscriminatorfromgan.outputimportOutput, OutputType, Normalizationimporttensorflowastffromgan.networkimportDoppelGANgerGenerator, Discriminator, \RNNInitialStateType, AttrDiscriminatorfromgan.utilimportadd_gen_flag, normalize_per_sample, \renormalize_per_samplesample_len=10epoch=100batch_size=20d_rounds=2g_rounds=1d_gp_coe=10.0attr_d_gp_coe=10.0g_attr_d_coe=1.0
请注意,生成器由具有softmax激活函数(用于分类输入)和线性激活(用于连续变量)的层列表组成。生成器和鉴别器均使用Adam算法以指定的学习速率和动量进行了优化。
现在,我们准备数据以供网络使用。real_attribute_mask是一个True / False列表,其长度与属性数相同。如果属性为(max-min)/ 2或(max + min)/ 2,则为False;否则为false。否则为True。首先我们实例化生成器和鉴别器:
#createthenecessaryinputarraysdata_all, data_attribut, data_gen_flag=format_data(data,cols) #normalisedata(data_feature, data_attribute, data_attribute_outputs, real_attribute_mask) =normalize_per_sample( data_all, data_attribut, data_feature_outputs, data_attribute_outputs) #addgenerationflagtofeaturesdata_feature, data_feature_outputs=add_gen_flag( data_feature, data_gen_flag, data_feature_outputs, sample_len)generator=DoppelGANgerGenerator( feed_back=False, noise=True, feature_outputs=data_feature_outputs, attribute_outputs=data_attribute_outputs, real_attribute_mask=real_attribute_mask, sample_len=sample_len, feature_num_units=100, feature_num_layers=2) discriminator=Discriminator() attr_discriminator=AttrDiscriminator()
我们使用了由两层100个神经元组成的神经网络,用于生成器和鉴别器。所有数据均经过标准化或1热编码。然后,我们使用以下参数训练模型:
checkpoint_dir="./results/checkpoint"sample_path="./results/time"epoch=100batch_size=50g_lr=0.0001d_lr=0.0001vis_freq=50vis_num_sample=5d_rounds=3g_rounds=1d_gp_coe=10.0attr_d_gp_coe=10.0g_attr_d_coe=1.0extra_checkpoint_freq=30num_packing=1
训练的注意事项
如果数据很大,则应使用更多的轮次-作者建议使用400个时期,但在我们的实验中,我们发现如果网络不退化和模式崩溃,则可以达到1000个轮次。另外,请考虑时期数与批次大小有关–较小的批次需要更多的时期和较低的学习率。
对于那些不熟悉神经网络的人来说,批处理,随机和Minibatch梯度下降是机器学习算法的三种主要形式。批量大小控制训练神经网络时误差梯度估计的准确性。用户应注意学习期间批次大小,速度和稳定性之间的权衡。较大的批次需要较高的学习率,并且网络将学习得更快,但是它也可能不稳定,这对于GAN来说尤其是因为模式崩溃问题。
根据经验,生成器和鉴别器的学习率应该很小(在10–3到10–5之间)并且彼此相似。在本例中,我们使用10–4,而不是默认的10–3。
另一个重要参数是生成器和鉴别器上的回合数。Wasserstein GAN(WGAN)需要两个组件才能正常工作:梯度裁剪和比生成器高的鉴别器(d_round)。通常,对于发生器的每一轮,鉴别器的轮数在3至5之间。在这里,我们使用d_round = 3和g_round = 1。
为了加快训练速度,我们对生成器使用了周期性的学习率,对鉴别器使用了固定的学习率。
目录sample_path存储在不同检查点收集的一组样本,这对于验证目的很有用。损失函数的可视化可以在您提供的检查点目录上使用TensorBoard完成。您可以使用参数extra_checkpoint_freq控制检查点的频率。
请注意,这可能会占用大量磁盘空间。
run_config=tf.ConfigProto() tf.reset_default_graph() #ifyouareusingspyderwithtf.Session(config=run_config) assess: gan=DoppelGANger( sess=sess, checkpoint_dir=checkpoint_dir, sample_dir=sample_dir, time_path=sample_path, epoch=epoch, batch_size=batch_size, data_feature=data_feature, data_attribute=data_attribute, real_attribute_mask=real_attribute_mask, data_gen_flag=data_gen_flag, sample_len=sample_len, data_feature_outputs=data_feature_outputs, data_attribute_outputs=data_attribute_outputs, vis_freq=vis_freq, vis_num_sample=vis_num_sample, generator=generator, discriminator=discriminator, attr_discriminator=attr_discriminator, d_gp_coe=d_gp_coe, attr_d_gp_coe=attr_d_gp_coe, g_attr_d_coe=g_attr_d_coe, d_rounds=d_rounds, g_rounds=g_rounds,g_lr=g_lr,d_lr=d_lr, num_packing=num_packing, extra_checkpoint_freq=extra_checkpoint_freq) gan.build() gan.train()
综合数据生成
训练模型后,您可以使用生成器根据噪声创建综合数据。有两种方法可以做到这一点:
- 纯噪声无条件生成
- 属性的条件生成
在第一种情况下,我们生成属性和特征。在第二种方法中,我们明确指定要使用哪些属性来条件化要素生成,以便仅生成要素。
以下是从中生成样本的代码:
run_config=tf.ConfigProto() total_generate_num_sample=1000withtf.Session(config=run_config) assess: gan=DoppelGANger( sess=sess, checkpoint_dir=checkpoint_dir, sample_dir=sample_dir, time_path=time_path, epoch=epoch, batch_size=batch_size, data_feature=data_feature, data_attribute=data_attribute, real_attribute_mask=real_attribute_mask, data_gen_flag=data_gen_flag, sample_len=sample_len, data_feature_outputs=data_feature_outputs, data_attribute_outputs=data_attribute_outputs, vis_freq=vis_freq, vis_num_sample=vis_num_sample, generator=generator, discriminator=discriminator, attr_discriminator=attr_discriminator, d_gp_coe=d_gp_coe, attr_d_gp_coe=attr_d_gp_coe, g_attr_d_coe=g_attr_d_coe, d_rounds=d_rounds, g_rounds=g_rounds, num_packing=num_packing, extra_checkpoint_freq=extra_checkpoint_freq) #buildthenetworkgan.build() length=int(data_feature.shape[1] /sample_len) real_attribute_input_noise=gan.gen_attribute_input_noise( total_generate_num_sample) addi_attribute_input_noise=gan.gen_attribute_input_noise( total_generate_num_sample) feature_input_noise=gan.gen_feature_input_noise( total_generate_num_sample, length) input_data=gan.gen_feature_input_data_free( total_generate_num_sample)#loadtheweights/changethepathaccordinglygan.load(checkpoint_dir+'/epoch_id-100') #generatefeatures, attributesandlengthsfeatures, attributes, gen_flags, lengths=gan.sample_from( real_attribute_input_noise, addi_attribute_input_noise, feature_input_noise, input_data, given_attribute=None, return_gen_flag_feature=False) #denormaliseaccordinglyfeatures, attributes=renormalize_per_sample( features, attributes, data_feature_outputs, data_attribute_outputs, gen_flags, num_real_attribute=1)
我们需要一些额外的步骤来将生成的样本处理为序列格式,并以1-hot编码格式返回向量。
nfloat=len(continuous) synth=np.zeros(features.shape[-1]) foriinrange(features.shape[0]): v=np.concatenate([np.zeros_like(attributes[i]), np.zeros_like(features[i])],axis=-1) v[attributes[i].shape] =attributes[i] V[attributes[i].shape[0]:attributes[i].shape[0]+1] =feature[i,:,0] forj, cinenumerate(categories_cum[:-1]): ac=features[:,nfloat+categories_cum[j]-1: nfloat+categories_cum[j+1]-1] a_hot=np.zeros((ac.shape[0], categories_n[j])) a_hot[np.arange(ac.shape[0]),ac.argmax(axis=1)] =1v[:,nfloat+categories_cum[j]:nfloat+categories_cum[j+1]]=a_hotv=np.concatenate([np.array([i]*len(ac))[np.newaxis].T,v],axis=1) synth=np.vstack([synth,v]) df=pd.DataFrame(synth[1:,1:],columns=processed_df.columns) formated_df=processor.format_df(df) formated_df['account_id']=synth[:,0] #addaccount_id
下面我们介绍了合成(生成)数据和真实数据之间的一些比较。我们可以观察到,总的来说,生成的数据分布与真实数据分布相对匹配-图8和图9。
图8:生成的数据与实际数据之间交易(中间)和标志(底部)之间的序列长度(顶部)时间间隔的直方图。
唯一的例外是变量Amount的分布,如图9所示。这是由于该变量具有不平滑的分布这一事实。为了解决这个问题,我们将其离散化为20个级别,从而实现了更好的匹配。
图9:使用连续编码(顶部)和二值一次热编码(底部)生成的实数与实数。
然后,我们使用模糊指标来计算相似度得分。该分数是三个分数的平均值:直方图和直方图2D相似度(实际数据和合成数据直方图重叠的程度)以及列之间的互信息。该分数确定了综合数据保留列之间相关性的程度。
将Amount视为连续变量时,我们获得的相似度得分为0.57,将其二进制化为20个仓位时,获得的相似度得分为0.63。相似度得分如下:
fromhazy_trainer.evaluation.similarityimportSimilaritysim=Similarity(metrics=['hist','hist2d','mi']) score=sim.score(real_df[cols], formated_df[cols]) print(score['similarity']['score'])
但是,我们注意到,这个数字并不能真正说明整个故事,因为它没有明确衡量合成数据序列的时间一致性,而是独立对待每一行。
图10:模型随时间产生的交易金额(入金和出金)。