序列数据(具有时间依赖性的数据)在业务中非常常见,从信用卡交易到医疗保健记录再到股票市场价格。但是,隐私法规限制并极大地减慢了对研发至关重要的有用数据的访问。这就产生了对具有高度代表性但又完全私有的合成顺序数据的需求,这至少可以说是具有挑战性的。
生成合成时间序列和顺序数据要比表格数据更具挑战性,在表格数据中,通常将与一个人有关的所有信息存储在一行中。在顺序数据中,信息可以分布在许多行中,例如信用卡交易,并且保留行(事件)和列之间的相关性(变量是关键)。此外,序列的长度是可变的。有些案例可能只包含少量交易,而其他案例则可能包含数千笔交易。
序列数据和时间序列的生成模型已经得到了广泛的研究,但是,许多此类努力导致综合数据质量相对较差且灵活性较低。在许多情况下,模型被设计为特定于每个问题,因此需要详细的领域知识。
在本文中,我们描述并应用了一种最新的强大方法的扩展版本,以生成合成顺序数据DoppelGANger。它是基于生成对抗网络(GAN)的框架,具有一些创新,使生成复杂顺序数据集的合成版本成为可能。
我们通过引入两项创新来构建这项工作:
- 一种学习策略,可加快GAN的收敛速度并避免模式崩溃。
- 鉴别器中经过精心设计的噪声可使用差异会计处理策略的改进版本来提高模型的稳定性,从而使过程具有不同的私有性而不会降低数据质量。
时序数据生成的常用方法
用于时序数据生成的大多数模型都使用以下方法之一:
动态平稳过程通过将时间序列中的每个点表示为确定性过程的总和(加上一些噪声)而起作用。这是使用自举等技术对时间序列进行建模的一种广泛使用的方法。但是,必须纳入一些长期依赖性的先验知识,例如循环模式,以约束确定性过程。这使得很难为具有复杂,未知相关性的数据集建模。
马尔可夫模型是一种通过将系统动力学表示为条件概率分布来对分类时间序列建模的流行方法。诸如隐马尔可夫模型的变体也已用于对时间序列的分布进行建模。这种方法的问题在于它无法捕获长期的复杂依赖关系。
自回归(AR)模型是动态平稳过程,其中序列中的每个点都表示为前n个点的函数。非线性AR模型(如ARIMA)非常强大。像马尔可夫模型那样的AR模型存在保真度问题-它们产生的简单模型无法捕获复杂的时间相关性。
递归神经网络(RNN)最近已用于深度学习中的时间序列建模。像自回归模型和马尔可夫模型一样,RNN使用以前时间步长的滑动窗口来确定下一个时间点。RNN还存储一个内部状态变量,该变量捕获时间序列的整个历史记录。像长短期记忆网络(LTSM)一样,RNN在学习时间序列数据的判别模型方面也取得了巨大的成功,该模型可预测以样本为条件的标签。但是,RNN无法学习某些简单的时间序列分布。
基于GAN的方法或生成对抗网络模型已经成为一种流行的技术,用于生成或扩充数据集,尤其是图像和视频。但是,GAN在网络数据中保真度较差,网络数据既具有复杂的时间相关性,又具有混合的离散连续数据类型。尽管存在基于GAN的时间序列生成(例如,用于医疗时间序列),但此类技术无法处理更复杂的数据,这些数据在长序列上显示出较差的自相关评分,同时容易出现模式崩溃。这是由于以下事实:数据分布是重尾且长度可变的。这似乎在很大程度上影响GAN。
引入DoppelGANger以生成高质量的合成时间序列数据
在本节中,我将探索最近的模型以生成综合顺序数据DoppelGANger。我将使用基于GAN的此模型以及由循环单位组成的生成器,使用两个数据集生成交易数据的综合版本:银行交易和道路交通。我们使用了对DoppelGANger模型的修改,以解决顺序数据生成模型的局限性。
由于以下问题,传统的生成对抗网络或GAN难以对顺序数据进行建模:
- 它们没有捕获时间特征及其相关(不变)属性之间的复杂关联:例如,根据所有者的特征(年龄,收入等),交易中的信用卡模式非常不同。
- 时间序列内的长期相关性,例如昼夜模式:这些相关性与图像中的相关性在质量上有很大不同,图像具有固定的尺寸,不需要逐像素生成。
DoppelGANger结合了一些创新的想法,例如:
使用两个网络(一个多层感知机 MLP和一个递归网络)捕获时间依赖性
分离归因生成,以更好地捕获时间序列及其属性(例如用户的年龄,位置和性别)之间的相关性
批量生成-生成长序列的小批量堆叠
解耦归一化-将归一化因子添加到生成器以限制特征范围
DoppelGANger将属性的生成与时间序列解耦,同时在每个时间步将属性馈送到时间序列生成器。这与传统方法相反,在传统方法中,属性和特征是共同生成的。
DoppelGANger的条件生成体系结构还提供了更改属性分布和对属性进行条件调整的灵活性。这也有助于隐藏属性分布,从而增加隐私。
DoppelGANger模型还具有生成以数据属性为条件的数据特征的优势。
图1:原始DoppelGANger模型的示意图,两个生成器块和两个鉴别器。
该模型的另一个巧妙特征是它如何处理极端事件,这是一个非常具有挑战性的问题。顺序数据在样本中具有广泛的功能值并不少见-有些产品可能有成千上万笔交易,而另一些则只有几笔。对于GAN来说,这是有问题的,因为它肯定是模式崩溃的秘诀-样本将仅包含最常见的项目,而忽略罕见事件。对于图像-几乎所有工作都集中在GAN上-这不是问题,因为分布很平滑。这就是为什么DoppelGANger的作者提出了一种创新的方式来处理这些情况:自动归一化。它包括在训练之前对数据特征进行归一化,并将特征的最小和最大范围添加为每个样本的两个附加属性。
在生成的数据中,这两个属性通常会将要素缩放回现实范围。这分三个步骤完成:
- 使用多层感知器(MLP)生成器生成属性。
- 将生成的属性作为输入,使用另一个MLP生成两个“伪”(最大/最小)属性。
- 将生成的真实和假属性作为输入,生成要素。
将DoppelGANger模型放在银行交易数据上
首先,我们在银行交易数据集上评估了DoppelGANger。用于训练的数据是合成的,因此我们知道真实的分布,可以在这里访问。我们的目的是证明该模型能够学习数据中的时间依赖性。
如何准备数据?
图2:作为一组属性和长度不同的特征处理的数据的示意图。
我们假设顺序数据由一组最大长度为Lmax的序列组成-在本例中,我们认为Lmax =100。每个序列都包含一组属性A(固定数量)和特征F(交易)。在我们的例子中,唯一的属性是初始银行余额,其特征是:交易金额(正数或负数)以及两个描述交易的其他类别:标志和描述。
要运行模型,我们需要三个NumPy数组:
- data_feature:训练功能,采用NumPy float32数组格式。大小为[(训练样本数)x(最大长度)x(特征的总尺寸)]。分类特征通过一键编码存储。
- data_attribute:训练属性,为NumPy float32数组格式。大小为[(训练样本数)x(属性的总维数)]。
- data_gen_flag:一组标志,指示功能的激活。大小为[(训练样本数)x(最大长度)]。
另外,我们需要一个Output类的对象列表,其中包含每个变量,规范化和基数的数据类型。在这种情况下,它是:
data_feature_outputs= [ output.Output(type_=OutputType.CONTINUOUS,dim=1,normalization=Normalization.ZERO_ONE,is_gen_flag=False), #timeintervalsbetweentransactions (Dif) output.Output(type_=OutputType.DISCRETE,dim=20,is_gen_flag=False), #binarizedAmountoutput.Output(type_=OutputType.DISCRETE,dim=5,is_gen_flag=False), #Flagoutput.Output(type_=OutputType.DISCRETE,dim=44,is_gen_flag=False) #Description]
列表的第一个元素是事件Dif之间的时间间隔,其后是经过1个热编码的交易值(金额),然后是标志,第四个元素是交易描述。所有gen_flags都设置为False,因为它是一个内部标志,以后可以由模型本身进行修改。
该属性被编码为介于-1和1之间的归一化连续变量,以解决负余额问题:
data_attribute_outputs= [output.Output(type_=OutputType.CONTINUOUS,dim=1,normalization=Normalization.MINUSONE_ONE,is_gen_flag=False)]
此模拟中使用的唯一属性是初始余额。每个步骤的余额只需添加相应的交易金额即可更新。
我们使用Hazy处理器对每个序列进行预处理,并以正确的格式对其进行整形。
n_bins=20processor_dict= { "by_type": { "float": { "processor": "FloatToOneHot", #FloatToBin""kwargs": {"n_bins": n_bins}},"int": {"processor": "IntToFloat","kwargs": {"n_bins": n_bins}},"category": {"processor": "CatToOneHot",},"datetime": {"processor": "DtToFloat",}}}from hazy_trainer.processing import HazyProcessorprocessor = HazyProcessor(processor_dict)
现在,我们将读取数据并使用format_data函数对其进行处理。辅助变量category_n和category_cum分别存储变量的基数和基数的累积和。
data=pd.read_csv('data.csv',nrows=100000) #readthedatacategorical= ['Amount','Flag','Description'] continuous=['Balance','Dif'] cols=categorical+continuousprocessor=HazyProcessor(processor_dict) #callHazyprocessorprocessor.setup_df(data[cols]) #setuptheprocessorcategories_n= [] #Numberofcategoriesineachcategoricalvariableforcatincategorical: categories_n.append(len(processor.column_to_columns[cat]['process'])) categories_cum=list(np.cumsum(categories_n)) #Cumulativesumofnumberofcategoricalvariablescategories_cum= [xforxincategories_cum] #Wetakeoneoutbecausetheywillbeindexescategories_cum= [0] +categories_cumdefformat_data(data, cols, nsequences=1000, Lmax=100, cardinality=70): ''' cols is a list of columns to be processednsequences number of sequences to use for trainingLmax is the maximum sequence lengthCardinality shape of sequences'''idd=list(accenture.Account_id.unique()) #uniqueaccountidsdata.Date=pd.to_datetime(data.Date) #formatdate#dummytonormalizetheprocessorsdata['Dif']=np.random.randint(0,30*24*3600,size=accenture.shape[0]) data_all=np.zeros((nsequences,Lmax,Cardinality)) data_attribut=np.zeros((nsequences)) data_gen_flag=np.zeros((nsequences,Lmax)) real_df=pd.DataFrame() fori,idsinenumerate(idd[:nsequences]): user=data[data.Account_id==ids] user=user.sort_values(by='Date') user['Dif']=user.Date.diff(1).iloc[1:] user['Dif']=user['Dif'].dt.secondsuser=user[cols] real_df=pd.concat([real_df,user]) processed_df=processor.process_df(user) Data_attribut[i] =processed_df['Balance'].values[0] processed_array=np.asarray(processed_df.iloc[:,1:) data_gen_flag[i,:len(user)]=1data_all[i,:len(user),:]=processed_arrayreturndata_all, data_attribut, data_gen_flag
数据
数据包含大约1000万笔银行交易,我们将仅使用其中包含5,000个唯一帐户的100,000个样本,每个帐户平均20个交易。我们考虑以下领域:
- 交易日期
- 交易金额
- 平衡
- 交易标记(5级)
- 描述(44级)
以下是所用数据的标题:
表1:银行交易数据样本
如前所述,时间信息将被建模为两个连续事务之间的时间差(以秒为单位)。
图3:不同描述的交易直方图按收入和支出分开。
图4:不同时间分布的交易热图。