2021-07-06

GAN(生成对抗网络)在合成时间序列数据中的应用(第一部分——利用GAN生成合成数据)

(本文基本是对Jasen 的《Machine Learning for Algorithmic Trading》第二版的第21章进行翻译、改写和复现,并用于我们的实际情况)

1. 准备阶段

配置介绍

在Anaconda下安装TensorFlow以及在Jupyter Notebook中引用的准备,可以参考以下文档

https://www.tensorflow.org/tutorials/quickstart/beginner

https://zhuanlan.zhihu.com/p/92370075(在pycharm中配置tensorflow)

https://blog.csdn.net/zxpcz/article/details/88864478

https://blog.csdn.net/weixin_41640583/article/details/86534358?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_baidulandingword-0&spm=1001.2101.3001.4242

https://www.jianshu.com/p/52c6c9afe33e

https://zhuanlan.zhihu.com/p/84568790

https://blog.csdn.net/hitzijiyingcai/article/details/83342905

GAN介绍

GANs由Goodfellow等人在2014年提出。被誉为“过去十年中AI领域最令人振奋的成果”。一个GAN模型需要训练两个神经网络丝——生成器和判别器。生成器的作用在于在给定样本的情况下生成新的样本,使判别器不能分辨这些新样本和给定样本。因此GAN总的来说是一个生成模型,它能够生成合成样本(synthetic samples),这些合成样本能够体现目标分布。由于这些样本是人工生成的,因此不需要从目标分布中抽取,相应的,抽样的成本问题也就得到了解决。

本文主要目的在于探究GAN在生成时间序列数据上的作用,特别是金融时间序列数据。

2. 利用GAN生成合成数据(synthetic data)

如前所述,GAN能够相当精确地模仿一系列输入数据背后的分布。由于在监督学习中,标记数据(labeled data)可能相当匮乏,或者获取标记数据的成本可能很高,因此如果能够利用GAN去生成类标记数据,那对于这些监督学习算法无疑是一个巨大的福音。

对生成模型和判别模型的考察

判别模型,就是在给定输入数据X的情况下,学习如何对输出\displaystyle\vec{y}中的元素进行分辨,换句话说,就是学习给定输入数据下,输出y的条件分布 p(y|X)

生成模型,就是学习输入数据和输出的联合分布p(y,X)。

生成模型通常用于解决预测问题

GANs由于能够在给定简单输入数据(例如随机数)的情况下,生成复杂的输出(如图片等),因此具有生成模型的性质。GANs具有这样的能力,是因为它能够对所有可能的输出的分布建模。这个概率分布的维数可能是很大的,比如对一张图片而言,一个像素就是一个维度,或者对一个文档而言,一个词语(token)就是一个维度。当然在这里,我们注重时间序列数据,对于时间序列数据而言,一个时间戳上的值,就是一个维度。这种对于输出的分布的建模,是AI迈向更加通用化的一大进步。

对抗训练——一种零和游戏

GANs的主要创新点,在于它的思想内核是新的一种学习输出数据分布的想法——利用两个神经网络(生成器和判别器)的对抗。

在这里,生成器的目标在于将随机噪声转换成一系列合成数据,这些合成数据是某一类真实数据(如真实的股票价格时间序列)的人工复制品。而判别器的作用则是竭力区别这些人工复制品和真实数据。这两个神经网络都必须竭力达到自己的性能极限,但我们搞GANs,最终是希望生成器能够战胜判别器。


1.png

上图是对GAN利用对抗训练生成图片的原理的形象阐述。

这里假定生成器采用反深度卷积神经网络(reversed deep CNN)结构(如同卷积自编码器的解码器部分),生成器的输入是一张图片,此图片每个像素上的值都是随机的。这个随机图片经过反卷积后输出一张合成图片(a fake and synthetic image),这张合成图片会作为判别器的输入。判别器采用的是和生成器网络互为镜像的CNN网络结构,它同时也接收真实的样本图片(图片就是目标分布的样本)作为输入。判别器的输出是一个概率的概念——这张图片来自真实数据源的概率。

两个网络都采用反向传播网络的损失函数梯度作为学习参数的方法。

GAN家族的蓬勃发展

GAN的想法引人注目,引发了大量的研究,这些研究又反过来改进了原始的GAN架构,使得新的GAN能够适应不同的领域和任务。

用于表示学习(representation learning)的深度卷积GAN

  • 深度卷积GAN(DCGANs)。卷积神经网路(CNN)在关于类网格数据(grid-like data)的监督学习的应用中大获成功,这促进了对深度卷积GAN的研究。深度卷积GAN架构引领了GANs在无监督学习中的应用,这种架构是基于对抗训练发展出一个特征提取器。这种架构易于训练并生成高质量的图片,在今天,它也是作为各种GAN的baseline。DCGANs采用均匀分布随机数作为输入,并输出分辨率为64×64的彩色图片。输入维度变化会导致输出图片的变化。这种架构由标准CNN组件组成,包括解卷积层(就是把卷积层反过来)或者是全连接层。

用于图片到图片转换的条件GANs

  • 条件GANs(cGANs)。这种结构将额外的标记信息(additional label information)引入了训练过程,这种做法让输入质量更高,更加可控。

用于图片和时间序列数据的GAN

伴随着大量对于原始GAN架构的拓展和修正,大量关于图片,还有序列数据(sequential data,like speech,music etc.)的应用也异军突起。关于图片的应用尤其多,从图片的混合和超分辨率到生成视频和人类动作识别,许多方面都能见到GAN。同时GANs也被用于提升监督学习算法的表现。

循环条件GANs(recurrent conditional GANs)和合成的时间序列

Recurrent GANs(RGANs)和Recurrent conditional GANs(RCGANs)这两种模型架构用于生成“真实的”、实值、多变量时间序列。这种方法的提出最初来自医疗领域,但它对于金融市场的价值不可谓不珍贵。

RGANs是基于循环神经网络(recurrent neural networks,RNNs)构建其生成器和判别器的。RCGANs则是利用cGANs的想法,在RGANs的基础上引入额外信息。

这种架构在生成相关数据上取得巨大成功,而且,架构的作者通过将生成的数据和真实数据分别对同一模型训练进行对比,发现合成数据的训练表现相较于真实数据的训练表现仅仅存在非常小的劣势(only minor degradation)。因此,在本文中我们就将利用这个方法生成金融市场数据。

3. 代码——深度卷积对抗生成网络

初始阶段

import warnings
warnings.filterwarnings('ignore')


from pathlib import Path #导入路径处理库

######https://blog.csdn.net/itanders/article/details/88754606
##### 关于path的解答

import imageio # 导入图片输入输出库

import matplotlib.pyplot as plt
## python 进度条
from tqdm import tqdm
from time import time

import tensorflow as tf
from tensorflow.keras.datasets import fashion_mnist
# 数据集

from tensorflow.keras.layers import (Dense, Reshape, Flatten, Dropout,
                                     BatchNormalization, LeakyReLU,
                                     Conv2D, Conv2DTranspose)
# 循序数据函数
from tensorflow.keras.models import Sequential
# 交叉熵损失函数
from tensorflow.keras.losses import BinaryCrossentropy

from tensorflow.keras.optimizers import Adam
# 用于可视化模型的函数
from tensorflow.keras.utils import plot_model

from IPython import display





gpu_devices = tf.config.experimental.list_physical_devices('GPU')
if gpu_devices:
    print('Using GPU')
    tf.config.experimental.set_memory_growth(gpu_devices[0], True)
else:
    print('Using CPU')

    
    
    
dcgan_path = Path('dcgan')
## dcgan(深度卷积对抗神经网络的路径)
img_path = dcgan_path / 'synthetic_images'
## 图片路径
if not img_path.exists():
    img_path.mkdir(parents=True)

样本图片保存器

def generate_and_save_images(model, epoch, test_input):
    # Training set to false so that every layer runs in inference mode
    predictions = model(test_input, training=False)

    fig = plt.figure(figsize=(5, 5.2))

    for i in range(predictions.shape[0]):
        plt.subplot(4, 4, i + 1)
        plt.imshow(predictions[i, :, :, 0] * 127.5 + 127.5, cmap='gray')
        plt.axis('off')

    fig.suptitle(f'Epoch {epoch:03d}', fontsize=14)
    fig.tight_layout()
    fig.subplots_adjust(top=.93)
    fig.savefig(img_path / f'epoch_{epoch:03d}.png', dpi=300)
    plt.show()

载入数据和准备数据

获取训练用的图片

# use only train images
#  Loads the Fashion-MNIST dataset.

#This is a dataset of 60,000 28x28 grayscale images of 10 fashion categories,
#along with a test set of 10,000 images. This dataset can be used as
#a drop-in replacement for MNIST. The class labels are:

(train_images, train_labels), (_, _) = fashion_mnist.load_data()



##提取照片的维度
# get dimensionality
n_images = train_images.shape[0]
h = w = 28

#将输入转化为四维的格式
train_images = (train_images
                .reshape(n_images, h, w, 1)
                .astype('float32'))



# Normalize the images in between -1 and 1
train_images = (train_images - 127.5) / 127.5


##创建tf.data.dataset
BUFFER_SIZE = n_images
BATCH_SIZE = 256

train_set = (tf.data.Dataset
             .from_tensor_slices(train_images)
             .shuffle(BUFFER_SIZE)
             .batch(BATCH_SIZE))

创建DCGAN架构

##编写生成器的代码
##在这里生成器和判别器都是用CNN架构搭建,只是层数比较少。 生成器接受100个一维的随机数,并输出一个28×28分辨率的图片。
def build_generator():
    return Sequential([Dense(7 * 7 * 256, 
                             use_bias=False,
                             #输入为100个一维的随机数
                             input_shape=(100,), 
                             name='IN'),
                       BatchNormalization(name='BN1'),
                       LeakyReLU(name='RELU1'),
                       Reshape((7, 7, 256), name='SHAPE1'),
                       Conv2DTranspose(128, (5, 5), 
                                       strides=(1, 1),
                                       padding='same', 
                                       use_bias=False,
                                       name='CONV1'),
                       BatchNormalization(name='BN2'),
                       LeakyReLU(name='RELU2'),
                       Conv2DTranspose(64, (5, 5), 
                                       strides=(2, 2),
                                       padding='same', use_bias=False,
                                       name='CONV2'),
                       BatchNormalization(name='BN3'),
                       LeakyReLU(name='RELU3'),
                       Conv2DTranspose(1, (5, 5), 
                                       strides=(2, 2),
                                       padding='same', 
                                       use_bias=False,
                                       activation='tanh', 
                                       name='CONV3')],
                      name='Generator')

generator = build_generator()


#判别器
def build_discriminator():
    return Sequential([Conv2D(64, (5, 5), 
                              strides=(2, 2), 
                              padding='same',
                              input_shape=[28, 28, 1], 
                              name='CONV1'),
                       LeakyReLU(name='RELU1'),
                       Dropout(0.3, name='DO1'),
                       Conv2D(128, (5, 5), 
                              strides=(2, 2),
                              padding='same', 
                              name='CONV2'),
                       LeakyReLU(name='RELU2'),
                       Dropout(0.3, name='DO2'),
                       Flatten(name='FLAT'),
                       Dense(1, name='OUT')],
                      name='Discriminator')

discriminator = build_discriminator()

开始对抗训练

生成器判别器构建好,接下来就是设计和执行对抗训练过程。
设计对抗训练过程,需要注意以下

  • 必须引入能够反映两个网络交互作用的loss funcion
  • 要有一个执行反向传播算法的训练步骤
  • 要有一个训练循环(loop)去重复训练步骤以让GAN达到我们所需要的效果

定义生成器和判别器的loss function

生成器的loss反映的是判别器面对假图片输入时的决策能力,如果生成器的loss很小,说明判别器难以分辨真假,侧面说明生成器厉害

## 用交叉熵函数作为损失函数
cross_entropy = BinaryCrossentropy(from_logits=True)

#Creates a tensor of all ones that has the same shape as the input.
# tf.ones_like 函数接受一个tensor作为输入
#并且输出一个shape和输出相同的全一Tensor
def generator_loss(fake_output):
    return cross_entropy(tf.ones_like(fake_output), fake_output)

def discriminator_loss(true_output, fake_output):
    true_loss = cross_entropy(tf.ones_like(true_output), true_output)
    fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output)
    return true_loss + fake_loss


## 使用Adam算法的优化器
## 学习率
gen_optimizer = Adam(1e-1)
dis_optimizer = Adam(1e-1)

## 检查点

#tf.train.Checkpoint函数的作用
#Groups trackable objects, saving and restoring them.


checkpoints_dir = dcgan_path / 'training_chpk'
##检查点的前缀prefix
checkpoint_prefix = checkpoints_dir / 'ckpt'
checkpoint = tf.train.Checkpoint(gen_optimizer=gen_optimizer,
                                 dis_optimizer=dis_optimizer,
                                 generator=generator,
                                 discriminator=discriminator)

训练参数

我们的训练始于对生成器输入随机种子(random seed),然后生成器生成一个图片。判别器则竭力分辨真图片和假图片。两个模型的权重则伴随着loss的梯度下降而更新,随着训练过程的进行,生成器会变得更厉害,能够生成更高质量的假图片,同时判别器也会提高自己的判别能力。

##batch

##深度学习的优化算法,说白了就是梯度下降。每次的参数更新有两种方式。

##第一种,遍历全部数据集算一次损失函数,然后算函数对各个参数的梯度,更新###梯度。这种方法每更新一次参数都要把数据集里的所有样本都看一遍,计算量###开销大,计算速度慢,不支持在线学习,这称为Batch gradient ##descent,批梯度下降。

## 梯度下降的批次数
EPOCHS = 2
noise_dim = 100

# for gif generation
num_ex_to_gen = 16
seed = tf.random.normal([num_ex_to_gen, noise_dim])

核心-设计训练步骤(train step)

每个train-step都完成了一回合的随机梯度下降,一个train step 由五个部分组成

  1. 为每个模型提供最小批输入
  2. 在当前的权重中获得模型的输出
  3. 计算loss
  4. 计算梯度
  5. 将梯度应用于优化算法
@tf.function  # 将训练过程转化为图执行模式,加快训练过程
def train_step(images):
    # generate the random input for the generator
    # 制造生成器的输入
    noise = tf.random.normal([BATCH_SIZE, noise_dim])
    with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
        
        # get the generator output
        ## 获取生成器的输出
        generated_img = generator(noise, training=True)
        
        # collect discriminator decisions regarding real and fake input
        ## 对判别器输入真假图片,然后获取输出
        true_output = discriminator(images, training=True)
        fake_output = discriminator(generated_img, training=True)
        
        # 计算两个模型的loss
        # compute the loss for each model
        gen_loss = generator_loss(fake_output)
        disc_loss = discriminator_loss(true_output, fake_output)
        
    # compute the gradients for each loss with respect to the model variables
    # 计算各自的loss对各自模型的变量的梯度
    grad_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)
    grad_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)
    
    # 将计算好的梯度用于反向传播这一步骤
    # apply the gradient to complete the backpropagation step
    gen_optimizer.apply_gradients(zip(grad_generator, generator.trainable_variables))
    dis_optimizer.apply_gradients(zip(grad_discriminator, discriminator.trainable_variables))

    
    
## 训练循环
def train(dataset, epochs, save_every=10):
    for epoch in tqdm(range(epochs)):
        start = time()

        for img_batch in dataset:
            train_step(img_batch)

        # produce images for the GIF as we go
        display.clear_output(wait=True)
        generate_and_save_images(generator, epoch + 1, seed)

        # Save the model every 10 EPOCHS
        if (epoch + 1) % save_every == 0:
            checkpoint.save(file_prefix=checkpoint_prefix)

        # Generator after final epoch
    display.clear_output(wait=True)
    generate_and_save_images(generator, epochs, seed)
train(train_set, EPOCHS)
2.png

结果

在这里,由于时间关系,我将训练的批梯度下降定义为2,而且把学习率弄得很大(原文为10^{-4},我弄成了0.1),即使如此,由于两个网络的待训练参数数目极大,仍然需要花不少时间。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • (转)生成对抗网络(GANs)最新家谱:为你揭秘GANs的前世今生 生成对抗网络(GAN)一...
    Eric_py阅读 4,286评论 0 4
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,531评论 28 53
  • 信任包括信任自己和信任他人 很多时候,很多事情,失败、遗憾、错过,源于不自信,不信任他人 觉得自己做不成,别人做不...
    吴氵晃阅读 6,186评论 4 8
  • 步骤:发微博01-导航栏内容 -> 发微博02-自定义TextView -> 发微博03-完善TextView和...
    dibadalu阅读 3,131评论 1 3
  • 回这一趟老家,心里多了两个疙瘩。第一是堂姐现在谈了一个有妇之夫,在她的语言中感觉,她不打算跟他有太长远的计划,这让...
    安九阅读 3,501评论 2 4