本文首发于行者AI

引言

当咱们期望得到与现有有限数据相似的样本时,能够考虑运用一些数据增强的办法。本文从修建参数生成项目出发,介绍了两种数据生成办法:依据BiGRU以及GAN网络的数据生成。

BiGRU网络是由RNN开展而来,它在处理序列数据的使命中被广泛运用,1991年Elman[1]依据Jordan network[2]做了简化提出RNN,可是因为RNN中较远时刻步会发生梯度消失和梯度爆破导致RNN的使用受限,在1997年LSTM[3]网络和BiRNN[4]网络模型在RNN基础上进行改进使得RNN网络的适用规模扩大,之后Bengio团队优化了LSTM练习慢的问题提出了GRU网络。GRU网络与LSTM相差并不大,它将LSTM原有的三个门控单元削减到两个,得到了更快的收敛速度和与之不相上下的模型作用。本文运用GRU网络是因为,当面临(1:n)的样本组成的多维输入时,咱们期望能够运用到一切的输入样本,刚好GRU网络满足这种练习要求。

GAN[5]是近些年比较火的研究方向,在2014年由Goodfellow提出,GAN网络的初衷便是让模型有联想能力或许说“想象力”,它能够用来生成不存在于实在国际的数据,并且生成的数据符合规矩。

1. 使命描绘

简述使命需求:输入一组修建物的长和宽,期望得到满足要求的三个枚举值的组合以及随机数0~4的值,生成的枚举值和浮点数组合返回给恳求端处理后,会得到新的一对长宽组合,要求核算得到的长和宽与输入的长宽差别不超过50(mm)。依据神经网络的特征,需求分开枚举值生成使命与浮点数生成使命。

提出两种完成方案:

  1. GRU网络附近点数据拟合: 依据使命描绘可知,期望通过输入的长和宽来预测一系列数值,因为生成数据中包含了相关性不强的随机值,这给生成使命带来应战。因而考虑运用起现有的数据,按照有监督的办法进行练习。参考与输入的长和宽附近的n个样本数据进行拟合(附近样本运用KD树查找)。关于枚举类型组合,统计附近样本的枚举类型组合中每个类的数量,取数量最多的类作为输出。关于随机数组的生成,将选取的枚举类对应的随机数组组合成Data交给神经网络学习,因而交给网络的Data将是一个二维矩阵,矩阵的每一行都是一个与输入的长和宽接近样本的随机数组,为此考虑运用CNN或许RNN网络对这种多维数据提取信息,本文选用受约束的GRU网络作为数据提取办法,运用受约束的GRU在后文有详细介绍。

  2. GAN网络数据生成: 枚举类型的生成运用GAN网络,将长和宽输入给生成器,生成器生成一组one-hot类型数据交给判别器判别。随机数组合拆分红5个分别运用MLP网络生成。

2. 数据集描绘

数据集有数据条目约1060万个,每个样本包含枚举类型和浮点类型的数据,枚举类型包含:材分,开间数和架数。浮点型数据包含:随机数04、修建的长和宽。通过核算皮尔逊、肯德尔和斯皮尔曼相关系数(如下图)可知,随机数24根本上与任何特征都没有直接联系,枚举值相互之间的联系比较深刻。除此以外,在确认了修建的长和宽后,枚举值组合也能够根本确认。

基于BiGRU和GAN的数据生成方法

图1.数据特征皮尔逊系数混杂矩阵

基于BiGRU和GAN的数据生成方法

图2.数据特征肯德尔系数混杂矩阵

基于BiGRU和GAN的数据生成方法

图3.数据特征斯皮尔曼系数混杂矩阵

3. BiGRU生成数据

3.1 GRU数据安排办法

怎么找出与输入的长和宽相邻的样本也是一个需求处理的问题,本文挑选运用KD树来完成,KD树被用来完成KNN办法,它是一种平衡二叉树,KD树在构建中都会挑选一个维度进行划分,每个超平面都会把该空间划分为两个部分,每次挑选时都会按照中间值来划分。scipy库中有十分简洁的调用办法,运用如下:

from scipy import spatial
List_x_y = Data[:,-2:]                                              # 数据中的长宽在终究两位,取出他们
KDTree = spatial.KDTree(List_x_y)                                   # 构建KD树
position = List_x_y[i,:]                # 安排样本时从现有的数据取
# KDTree.query会返回两个内容,索引0的部分是一组array方式的间隔值,索引1是一组array方式的索引。
index = KDTree.query(position,(lib_n.search_size + 1))[1][1:]       # 这样就返回了在 List_x_y 中间隔(15,20)最近的 search_size + 1 个样本点[1:]表明不取最近的那个,也便是不取它本身

现在处理了数据查询的问题,下一步需求处理样本安排方式,咱们注意到确认了长和宽之后他们的枚举类型组合也根本随之确认了。一般的,关于一对长宽组合,最多有两到三个枚举类型组合,因而在样本安排过程中的查找阶段,咱们要求核算机查找sample_size * 2个接近点(通过尝试后发现是能够找到sample_size以上个附近样本的),然后拿取这一组附近样本中占比最大的sample_size个枚举类型组合的数据(带随机数组),将这组数据拆切片只保存随机数组作为data,本来的长和宽对应的随机数组做为label,他们的枚举类型就直接确定成近邻样本中枚举值类的众数类。在试验中咱们取10/20/30条数据为一个样本进行试验。以这种数据安排方式,数据的复用率很高。

3.2 Limited BiGRU网络

组成数据集后能够着手树立网络了,运用受约束的GRU是因为咱们以为一切输入样本数据都是有价值的,因而期望重置门R和更新门Z不要存在0的情况,也便是不让重置门和更新门将一切前史信息都遗忘。完成办法是压缩sigmiod这儿是压缩到70%,sigmiod函数乘上压缩系数后仍有为0的情况,所以咱们加上30%的前史数据作为确保,前史数据的汇入同样受更新门操控,答应至少40%的躲藏信息汇入,能够确保在每一个时刻步上都有至少12%的前史信息被保存。softsign函数有比较滑润的梯度改变,样本落入饱满区间的可能性会比tanh小很多。为完成了这个受约束的GRU作为数据提取网络,主要对GRUcell部分进行了如下改进:

基于BiGRU和GAN的数据生成方法

图4.传统GRU单元

 r = (sigma(W_{ir} x + b_{ir} + W_{hr} h + b_{hr})) * 0.7 + 0.3   # 约束sigmoid输出之后加上一个定值,能够确保这个门控信息是不会置于0的
	z = (sigma(W_{iz} x + b_{iz} + W_{hz} h + b_{hz})) * 0.6 + 0.4   # 并且仍给神经网络自适应的余地
	n = softsign(W_{in} x + b_{in} + r * (W_{hn} h + b_{hn}))        # softsign相关于tanh有着更滑润的梯度改变
	h' = (1 - z) * n + z * h 

下边给出整个网络的结构和参数

class GRU_attention(nn.Module):
    def __init__(self,lib):
        super(GRU_attention,self).__init__()
        self.gru = nn.GRU(input_size=lib.input_size,
                                  hidden_size=lib.hidden_size_01,
                                  num_layers=lib.num_layers,
                                  batch_first=lib.batch_first,
                                  bidirectional=lib.bidirectional)
        self.f1 = nn.Linear(lib.hidden_size_01 * 2,lib.hidden_size_02)
        self.bn1 = nn.BatchNorm1d(lib.hidden_size_02)
        self.drop1 = nn.Dropout(0.8)
        self.f2 = nn.Linear(lib.hidden_size_02,lib.output_size)
    def forward(self,input):
        out,_ = self.gru(input)     
		out = out[:, -1, :]
		out = F.elu(self.f1(out))
        out = self.bn1(out)
        out = self.drop1(out)
        out = self.f2(out)
        return out
class Lib_net:
    def __init__(self):
        self.input_size = 5
        self.hidden_size_01 = 128
        self.hidden_size_02 = 128
        self.output_size = 5
        self.num_layers = 4
        self.batch_first = True
        self.batch_size = 1024
        self.bidirectional = True
        self.dropout = 0.8
        self.learn_rate = 0.003
        self.directions = 2 if self.bidirectional else 1

解说一些参数,其中num_layers代表包含几层GRU单元,batch_first操控输入信息的排布,如果置为True那么输入便是(batch_size, time_step, input_size),我也以为这样设置更符合片面判别,bidirectional代表是否运用双向网络,双向网络其实便是两个GRU结合,两个GRU的输入有所不同,一个是从第一个时刻步开始向后输入,另一个是从终究一个时刻步向前输入,也便是一个会积累前史信息,一个会积累未来信息,因而在一个确切的时刻步的使命中,未来和前史信息会共同作用。out = out[:, -1, :] 意思是只保存终究一个时刻步产出的躲藏信息。

运用到数据归一化,学习率调整,以下是代码。

# 这个是Z-score归一化,比较适用于最大最小值不确认或许未来使命中会有更改的情况
from sklearn.preprocessing import StandardScaler
Data_random = scaler_random.fit_transform(df_total[['random0','random1','random2','random3','random4']])
# 归一化参数保存
joblib.dump(scaler_random,'./Random')
# 归一化参数读取
sclar_test_random = joblib.load("./Random")
# 归一化使用
Data_random = sclar_test_random.transform(df_total[['random0','random1','random2','random3','random4']])
# 反归一化
pride_inver_random = sclar_test_random.inverse_transform(pride)
# 学习率调整 这是峰值下降法 具有自适应性 这儿再引荐一个余弦退火 余弦退火在前期试验中能够协助更好的找到更优的学习率初始值
lr = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer,'min',factor=0.8,patience=15,verbose=True,min_lr=0.00003)

练习500轮的loss在0.72左右,终究的作用返回去不是很抱负,这儿给出一个生成事例:

类型 random0 random1 random2 random3 random4
生成 8.6453 178.0748 0.3049 73.5137 2.4984
8.6655 178.0490 0.3101 73.8993 2.3135
8.6305 177.6793 0.2821 73.1978 2.4372
相同 8.5929 177.9480 0.2932 73.6774 2.5256
枚举 8.5876 177.7483 0.2807 73.2853 2.5585
组合 8.6229 177.9035 0.3066 73.5981 2.4429
长宽 8.6604 177.8795 0.3172 73.5704 2.3387
相近 8.6407 177.9358 0.3211 73.6720 2.3907
采样 8.6719 177.8728 0.3078 73.5397 2.3042
8.6019 178.0940 0.3091 73.9006 2.4908
8.5926 178.1059 0.3178 73.9185 2.5189

4.GAN网络生成数据

GAN网络主要由两个网络组成,一个是判别器,一个是生成器,这两个网络的构成没有定式,仍是要看确切的试验作用。GAN的思维是咱们输入一组实在存在的数据,或许说是期望网络去模仿的数据,这组数据会打上True的label,也便是label = 1。然后咱们将一些数据种子交给生成器,让生成器生成和实在数据相同维度的新数据。种子最好是和生成数据有逻辑上的延续可是需求确保二者不要直接影响。在生成器接纳种子并生成数据之后,将这段数据作为data交给判别器,与之对应的label = 0,判别器会分别核算实在数据和生成数据的loss,loss之和即为判别器需求反向传达的loss。至于生成器的loss,便是产生的数据在判别器的判别后与label = 1核算loss。这便是GAN网络的中心思维,判别器需求尽可能判出何为实在值何为虚伪值,生成器也要不断生成以假乱真的数据骗过判别器。

本文中判别器更新办法如下:

首先将data拆分红枚举值组与随机数组,枚举值组转换成onehot类型作为data交给判别器,这部分数据标签为TRUE,判别器核算后得到一组bool组合,核算第一部分loss_1。之后在符合要求的规模内随机出一组长和宽的组合交给生成器,生成器生成一组假的onehot类型数据交给判别器判别,这部分数据的标签为False,核算第二部分loss_2,与loss_1作和之后即为判别器的终究loss。

生成器更新办法如下:

在符合要求的规模内随机出一组长和宽的组合交给生成器,生成器生成一组假的onehot类型数据交给判别器判别,这部分数据的标签为TRUE,核算得到的loss即为生成器的终究丢失。流程图如下。

基于BiGRU和GAN的数据生成方法

图5.本文中GAN工作流

4.1 GAN完成

以下是GAN的网络结构部分:

'''
input : onehot类型的枚举数据
output: 一个值,0或1,负责判别。
'''
class Discriminator(nn.Module):
    def __init__(self, lib):
        super(Discriminator, self).__init__()
        self.fc1 = nn.Linear(lib.input_size_D, lib.hidden_size_1)
        self.fc2 = nn.Linear(lib.hidden_size_1, lib.hidden_size_2)
        self.fc3 = nn.Linear(lib.hidden_size_2, lib.hidden_size_3)
        self.fc4 = nn.Linear(lib.hidden_size_3, lib.output_size_D)
        # self.fc5 = nn.Linear(lib.hidden_size_4, lib.output_size_D)
    def forward(self, input):
        out = F.leaky_relu(self.fc1(input),0.2)
        out = F.dropout(out, 0.3)
        out = F.elu(self.fc2(out))
        out = F.dropout(out, 0.3)
        out = F.elu(self.fc3(out))
        out = F.dropout(out, 0.3)
        # out = F.leaky_relu(self.fc4(out),0.2)
        # out = F.dropout(out, 0.3)
        return torch.sigmoid(self.fc4(out))
'''
input : 随机的x,z数据
output: 假的one-hot的数据
'''
class Generator(nn.Module):
    def __init__(self, lib):
        super(Generator, self).__init__()
        self.f1 = nn.Linear(lib.input_size_G, lib.hidden_size_5)
        self.f2 = nn.Linear(lib.hidden_size_5, lib.hidden_size_6)
        self.f3 = nn.Linear(lib.hidden_size_6, lib.hidden_size_7)
        self.f4 = nn.Linear(lib.hidden_size_7, lib.output_size_G)
        # self.fc5 = nn.Linear(lib.hidden_size_8, lib.output_size_G)
    def forward(self, input):
        out = F.leaky_relu(self.f1(input),0.2)
        out = F.elu(self.f2(out))
        out = F.elu(self.f3(out))
        # out = F.leaky_relu(self.fc4(out),0.2)
        return self.f4(out)

以下为GAN的更新办法:

    for epoch in range(lib.epoch):
        for i, batch in enumerate(Loader):
            # ====+++++判别器练习+++++=====
            # 设置模型练习状态
            D_net.train()
            G_net.train()
            data,_ = batch
            # print("data:\n{}".format(data))
            data = data.to(lib.device)
            # 实在值的核算
            # 自拟label
            real_label = torch.ones(lib.batch_size, 1).type(torch.FloatTensor).to(lib.device)
            # 送入网络
            predict_real = D_net(data)
            real_score = predict_real
            # 核算 loss
            real_loss = criterion(predict_real,real_label)
            # 实在值部分核算完毕
            # 虚伪值核算
            # 随机生成(x, z) 按段生成,循环取样
            if (i + 1) % 3 == 1:
                x_column = np.random.uniform(1029,1085,size = (lib.batch_size,1))
                z_column = np.random.uniform(1093,2439,size = (lib.batch_size,1))
                gen_1 = np.hstack((x_column,z_column))
            elif (i + 1) % 3 == 2:
                x_column = np.random.uniform(1381, 1456, size=(lib.batch_size, 1))
                z_column = np.random.uniform(1630, 3210, size=(lib.batch_size, 1))
                gen_1 = np.hstack((x_column, z_column))
            elif (i + 1) % 3 == 0:
                x_column = np.random.uniform(1733, 1828, size=(lib.batch_size, 1))
                z_column = np.random.uniform(2103, 3210, size=(lib.batch_size, 1))
                gen_1 = np.hstack((x_column, z_column))
            sclar_xz = joblib.load('D:/pycharm_workstation/GAN_NN_budiling/Scalers/xz')
            gen_1_re = sclar_xz.transform(gen_1)
            # 生成假标签
            fake_label = torch.zeros(lib.batch_size, 1).type(torch.FloatTensor).to(lib.device)
            gen_1_re = torch.from_numpy(gen_1_re).float().to(lib.device)
            # 生成器产出假的枚举值序列
            fake_data = G_net(gen_1_re)
            # 把生成的序列交给判别器
            predict_fake = D_net(fake_data)
            # 核算loss
            fake_loss = criterion(predict_fake,fake_label)
            # 关于判别器,总的loss等于real_loss + fake_loss
            total_loss = real_loss + fake_loss
            # 记录loss在本轮epoch均值
            loss_once_d = total_loss.item()
            Loss_epoch_D.append(loss_once_d)
            # 判别器梯度更新
            optimizer_D.zero_grad()
            total_loss.backward()
            optimizer_D.step()
            # ====+++++生成器练习+++++=====
            # 生成一组假数据
            if (i + 1) % 3 == 1:
                x_column = np.random.uniform(1029,1085,size = (lib.batch_size,1))
                z_column = np.random.uniform(1093,2439,size = (lib.batch_size,1))
                gen_2 = np.hstack((x_column,z_column))
            elif (i + 1) % 3 == 2:
                x_column = np.random.uniform(1381, 1456, size=(lib.batch_size, 1))
                z_column = np.random.uniform(1630, 3210, size=(lib.batch_size, 1))
                gen_2 = np.hstack((x_column, z_column))
            elif (i + 1) % 3 == 0:
                x_column = np.random.uniform(1733, 1828, size=(lib.batch_size, 1))
                z_column = np.random.uniform(2103, 3210, size=(lib.batch_size, 1))
                gen_2 = np.hstack((x_column, z_column))
            # 归一化
            sclar_xz = joblib.load('D:/pycharm_workstation/GAN_NN_budiling/Scalers/xz')
            gen_2_re = sclar_xz.transform(gen_2)
            # save_data_generate = gen_2
            gen_2_re = torch.from_numpy(gen_2_re).float().to(lib.device)
            # 交给生成器生成
            fake_generate = G_net(gen_2_re)
            # 交给判别器判别
            teacher = D_net(fake_generate)
            fake_score = teacher
            teacher_say = criterion(teacher,real_label)
            # 记录loss在本轮epoch均值
            loss_once_g = teacher_say.item()
            Loss_epoch_G.append(loss_once_g)
            # 生成器梯度更新
            optimizer_G.zero_grad()
            teacher_say.backward()
            optimizer_G.step()

咱们想通过GAN得到枚举值的组合,因而交给判别器的data只要one-hot之后的枚举数组,然而前文中咱们说过,修建物的长和宽是枚举数组的决定性要素,在交给网络时,它尽管以复合规矩规模的长和宽作为种子去生成枚举数组,可是并没有树立长和宽与枚举值的联系,也便是说判别器只知道那种枚举值组是实在存在的,并不清楚给定的长和宽对应哪几种枚举数组。因而这种数据是不合规的。

5. 处理办法

已然数据生成的办法行不通,那么就运用查询的办法,单看数据量咱们有900万随机数据和160万定点生成的数据,这些数据掩盖了一切合理的长宽组合,他们的分布如下,纵轴为长,横轴为宽,单位为mm:

基于BiGRU和GAN的数据生成方法

图6.160万条长宽数据掩盖规模

基于BiGRU和GAN的数据生成方法

图7.900万条长宽数据掩盖规模

1060万的数据根本上也满足随机的需求,因而将这1060万的数据建KD树进行查询,查询返回最接近样本点,这样也避免了生成数据误差较大的问题。

引证

[1] Elman, J. L., 1990. Finding structure in time. Cognitive science, volume 14,179–211. Doi: 10.1016/0364-0213(90)90002-E.

[2]Jordan, M. I., 1986. Serial order: A parallel distributed processing approach.Report Institute for Cognitive Science University of California. Doi:10.1016/S0166-4115(97)80111-2.

[3] Hochreiter, S., et al., 1997. Long short-term memory. Neural computation,volume 9, 1735–1780.

[4] Schuster, M., Paliwal, K. K., 1997. Bidirectional recurrent neural networks.IEEE transactions on Signal Processing, volume 45, 2673–2681. Doi:10.1109/78.650093.

[5] Goodfellow I , Pouget-Abadie J , Mirza M , et al. Generative Adversarial Nets[C]// Neural Information Processing Systems. MIT Press, 2014. arxiv.org/pdf/1406.26…

咱们是行者AI,咱们在“AI+游戏”中不断前行。

前往大众号 【行者AI】,和咱们一同探讨技术问题吧!