基于Keras的生成对抗网络进阶(2)——利用Keras和GAN进行正弦函数拟合

时间:2024-09-29 13:57:47

前面文章介绍了利用Kears拟合简单函数。在本文,从我们的实际采样数据中,我们将生成随机正弦曲线,并尝试使我们的GAN生成正确的正弦曲线(当然也可以通过改变样本数据函数和模型来将这个基础结构适应并解决一个更复杂的问题,例如图像)。

目录

    • 一、获取真实数据
    • 二、建立模型
      • 2.1 生成器
      • 2.2 判别器
      • 2.3 生成器与判别器结合——GAN
    • 三、训练
      • 3.1 生成真实数据和虚假数据
      • 3.2 预训练
      • 3.3 生成随机噪声
      • 3.4 正式训练
      • 3.5 绘制训练损失图像
    • 四、运行结果
    • 五、完整代码

一、获取真实数据

真实数据用于随机生成真实曲线。
代码示例:

# 生成真实数据(10000,50)
def sample_data(n_samples=10000, x_vals=np.arange(0, 5, .1), max_offset=100, mul_range=[1, 2]):
    vectors = []
    for i in range(n_samples):
        offset = np.random.random() * max_offset  # 偏移量(0,100)
        mul = mul_range[0] + np.random.random()* (mul_range[1] - mul_range[0])  # 
        vectors.append(
            np.sin(offset + x_vals * mul) / 2 +.5
        )  # 正弦结果
    return np.array(vectors)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

可以通过下列语句绘制生成的样本:

ax = pd.DataFrame(np.transpose(sample_data(5))).plot()
plt.show()
  • 1
  • 2

生成的真实正弦曲线如下:
在这里插入图片描述

二、建立模型

我们需要创建一个GAN模型,然后训练它从一些噪声中生成数据,并创建一个模型来从生成的数据中检测真实数据。

2.1 生成器

我们使用tanh激活的简单全连接层来创建生成模型。该模型将获取噪声并尝试从中生成正弦曲线。
函数代码如下:

# 生成器
def build_generation(G_in, dense_dim=200, lr=1e-3):
    x = Dense(dense_dim)(G_in)  # 全连接层
    x = Activation('tanh')(x)  # 激活函数
    G_out = Dense(50, activation='tanh')(x)  # 全连接层+激活函数

    G = Model(G_in, G_out)
    optimizer = SGD(lr=lr)
    G.compile(loss='binary_crossentropy', optimizer=optimizer)
    return G, G_out
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
'
运行

可以通过summary()函数查看该神经网络结构,结构如下:
在这里插入图片描述

2.2 判别器

类似地,我们创建了判别器来判断曲线是真实的还是由生成模型输出的。
函数代码:

# 判别器
def build_discrimination(D_in, lr=1e-3, drate=.25, n_channels=50, conv_sz=5):
    x = Reshape((-1, 1))(D_in)  # 将其尺寸调整为(X,1)
    x = Conv1D(n_channels, conv_sz, activation='relu')(x)  # 一维卷积层
    x = Dropout(drate)(x)  # Dropout层
    x = Flatten()(x)  # 压平
    x = Dense(n_channels)(x)  # 全连接层
    D_out = Dense(2, activation='sigmoid')(x)  # 输出层

    D = Model(D_in, D_out)
    Doptimizer = Adam(lr=lr)
    D.compile(loss='binary_crossentropy', optimizer=Doptimizer)
    return D, D_out
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
'
运行

同样也可以通过summary()函数查看该神经网络结构,其结构如下:
在这里插入图片描述

2.3 生成器与判别器结合——GAN

最后,我们需要将这两个模型连接成一个生成对抗网络GAN,在冻结判别器器的同时用来训练生成器。
为了冻结给定模型的权重,需要创建冻结函数,每次训练GAN时,我们都会将它应用到判别器上,以便训练生成器。
冻结判别器函数代码如下:

# 冻结判别器,只让生成器进行训练
def set_trainability(model, trainable=False):
    model.trainable = trainable
    for layer in model.layers:
        layer.trainable = trainable
  • 1
  • 2
  • 3
  • 4
  • 5
'
运行

生成对抗网络GAN代码如下:

# 判别器与生成器结合形成GAN
def build_GAN(GAN_in, G, D):
    set_trainability(D, False)  # 冻结判别器
    x = G(GAN_in)  # 训练G
    GAN_out = D(x)  # 训练D
    GAN = Model(GAN_in, GAN_out)
    GAN.compile(loss='binary_crossentropy', optimizer=G.optimizer)  # 优化器
    return GAN, GAN_out
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
'
运行

同样也可以通过summary()函数查看该神经网络结构,结构如下:
在这里插入图片描述

三、训练

训练模型。

3.1 生成真实数据和虚假数据

首先需要生成真实数据和假数据及相应标签(真实数据用标签1表示,虚假数据用标签0表示),函数代码如下:

# 生成真实数据和虚假数据
def sample_data_and_gen(G, noise_dim=10, n_samples=10000):
    XT = sample_data(n_samples=n_samples)  # 生成真实数据(10000,50)
    XN_noise = np.random.uniform(0, 1, size=[n_samples, noise_dim])  # 产生随机噪声(10000,10)
    XN = G.predict(XN_noise)  # 生成器生成虚假数据(10000,50)
    X = np.concatenate((XT, XN))  # 对XT和XN进行拼接(20000,50)
    y = np.zeros((2 * n_samples, 2))  # 全零标签(20000,2)
    y[:n_samples, 1] = 1  # 前10000行第2列为1
    y[n_samples:, 0] = 1  # 后10000行第1列为1
    return X, y
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
'
运行

3.2 预训练

启动GAN之前对判别器Discrimination进行预训练。这也让我们检查编译后的模型是否正确地运行在真实和有噪声的输入上。
预训练代码如下:

# 预训练
def pretrain(G, D, noise_dim=10, n_samples=10000, batch_size=32):
    X, y = sample_data_and_gen(G, n_samples=n_samples, noise_dim=noise_dim)  # 获取真实数据和虚假数据
    set_trainability(D, True)  # 设置只训练判别器
    D.fit(X, y, epochs=1, batch_size=batch_size)  # 训练D
  • 1
  • 2
  • 3
  • 4
  • 5
'
运行

运行结果:
在这里插入图片描述

3.3 生成随机噪声

随机噪声函数如下:

# 生成随机噪声
def sample_noise(G, noise_dim=10, n_samples=10000):
    X = np.random.uniform(0, 1, size=[n_samples, noise_dim])  # 产生(0,1)均匀分布随机噪声(10000,10)
    y = np.zeros((n_samples, 2))  # 全零标签(10000,2)
    y[:, 1] = 1  # 第二列置为1
    return X, y
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
'
运行

3.4 正式训练

我们现在可以通过交替训练判别器和生成器模型来训练GAN,当训练生成器时,判别器的权值被冻结。
训练代码如下:

# 正式训练
def train(GAN, G, D, epochs=500, n_samples=10000, noise_dim=10, batch_size=32, v_freq=50):
    d_loss = []
    g_loss = []
    for epoch in range(epochs):
        X, y = sample_data_and_gen(G, n_samples=n_samples, noise_dim=noise_dim)  # 生成真实数据和虚假数据
        set_trainability(D, True)  # 训练D
        d_loss.append(D.train_on_batch(X, y))  # D损失值

        X, y = sample_noise(G, n_samples=n_samples, noise_dim=noise_dim)  # 生成随机噪声及标签
        set_trainability(D, False)  # 冻结D
        g_loss.append(GAN.train_on_batch(X, y))  # G损失值
        # 每隔50次打印一次生成器损失和判别器损失
        if (epoch + 1) % v_freq == 0:
            print("Epoch #{}: Generative Loss: {}, Discriminative Loss: {}".format(epoch + 1, g_loss[-1], d_loss[-1]))
    return d_loss, g_loss
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
'
运行

训练过程如下:
在这里插入图片描述

3.5 绘制训练损失图像

绘制损失图像代码如下:

ax = pd.DataFrame({'Generative Loss': g_loss,'Discriminative Loss': d_loss,})\
    .plot(title='Training loss', logy=True)  # 训练loss
ax.set_xlabel("Epochs")
ax.set_ylabel("Loss")
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5

图像如下:
在这里插入图片描述
我们可以看到,在某些阶段,无论是生成器还是判别器是降低其损失相对的损失增益在另一边。

四、运行结果

现在,我们可以将生成器生成的一些正弦曲线可视化:
可视化代码如下:

N_VIEWED_SAMPLES = 2  #要绘制的波形数目
data_and_gen, _ = sample_data_and_gen(G, n_samples=N_VIEWED_SAMPLES)  # 生成真实数据和虚假数据
pd.DataFrame(np.transpose(data_and_gen[N_VIEWED_SAMPLES:])).plot()  #绘制图形
plt.show()
  • 1
  • 2
  • 3
  • 4

运行结果:
在这里插入图片描述
这不是完美的,但是,我们看到,如果我们使用滚动平均值平滑一下曲线,会得到接近一个更精确的正弦曲线形状,代码如下:

N_VIEWED_SAMPLES = 2
data_and_gen, _ = sample_data_and_gen(G, n_samples=N_VIEWED_SAMPLES)
pd.DataFrame(np.transpose(data_and_gen[N_VIEWED_SAMPLES:])).rolling(5).mean()[5:].plot()
plt.show()
  • 1
  • 2
  • 3
  • 4

运行结果:
在这里插入图片描述
显然,通过更多的训练和调整,我们可以得到更好的结果。大量的epoch通常被证明在GAN上是有效的,但是在这样一个简单的教程中运行会很长时间。

五、完整代码

完整代码如下:

from keras.layers import Input, Reshape
from keras.layers.core import Dense, Activation, Dropout, Flatten
from keras.layers.convolutional import Conv1D
from keras.models import Model
from keras.optimizers import SGD, Adam
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 生成真实数据(10000,50)
def sample_data(n_samples=10000, x_vals=np.arange(0, 5, .1), max_offset=100, mul_range=[1, 2]):
    vectors = []
    for i in range(n_samples):
        offset = np.random.random() * max_offset  # 偏移量(0,100)
        mul = mul_range[0] + np.random.random() * (mul_range[1] - mul_range[0])  # 
        vectors.append(np.sin(offset + x_vals * mul) / 2 + .5)  # 正弦
    return np.array(vectors)

# ax = ((sample_data(5))).plot()
# ()

# 生成器
def build_generation(G_in, dense_dim=200, lr=1e-3):
    x = Dense(dense_dim)(G_in)  # 全连接层
    x = Activation('tanh')(x)  # 激活函数
    G_out = Dense(50, activation='tanh')(x)  # 全连接层+激活函数

    G = Model(G_in, G_out)
    Goptimizer = SGD(lr=lr)
    G.compile(loss='binary_crossentropy', optimizer=Goptimizer)
    return G, G_out

G_in = Input(shape=[10])  # 生成器输入
G, G_out = build_generation(G_in)  # 生成器训练
G.summary()  # 显示生成器结构

# 判别器
def build_discrimination(D_in, lr=1e-3, drate=.25, n_channels=50, conv_sz=5):
    x = Reshape((-1, 1))(D_in)  # 将其尺寸调整为(X,1)
    x = Conv1D(n_channels, conv_sz, activation='relu')(x)  # 一维卷积层
    x = Dropout(drate)(x)  # Dropout层
    x = Flatten()(x)  # 压平
    x = Dense(n_channels)(x)  # 全连接层
    D_out = Dense(2, activation='sigmoid')(x)  # 输出层

    D = Model(D_in, D_out)
    Doptimizer = Adam(lr=lr)
    D.compile(loss='binary_crossentropy', optimizer=Doptimizer)
    return D, D_out

D_in = Input(shape=[50])  # 判别器输入
D, D_out = build_discrimination(D_in)  # 判别器训练
D.summary()  # 显示判别器结构

# 冻结判别器,只让生成器进行训练
def set_trainability(model, trainable=False):
    model.trainable = trainable
    for layer in model.layers:
        layer.trainable = trainable

# 判别器与生成器结合形成GAN
def build_GAN(GAN_in, G, D):
    set_trainability(D, False)  # 冻结判别器
    x = G(GAN_in)  # 训练G
    GAN_out = D(x)  # 训练D
    GAN = Model(GAN_in, GAN_out)
    GAN.compile(loss='binary_crossentropy', optimizer=G.optimizer)  # 优化器
    return GAN, GAN_out


GAN_in = Input([10])
GAN, GAN_out = build_GAN(GAN_in, G, D)
GAN.summary()

# 生成真实数据和虚假数据
def sample_data_and_gen(G, noise_dim=10, n_samples=10000):
    XT = sample_data(n_samples=n_samples)  # 生成真实数据(10000,50)
    XN_noise = np.random.uniform(0, 1, size=[n_samples, noise_dim])  # 产生随机噪声(10000,10)
    XN = G.predict(XN_noise)  # 生成器生成虚假数据(10000,50)
    X = np.concatenate((XT, XN))  # 对XT和XN进行拼接(20000,50)
    y = np.zeros((2 * n_samples, 2))  # 全零标签(20000,2)
    y[:n_samples, 1] = 1  # 前10000行第2列为1
    y[n_samples:, 0] = 1  # 后10000行第1列为1
    return X, y

# 预训练
def pretrain(G, D, noise_dim=10, n_samples=10000, batch_size=32):
    X, y = sample_data_and_gen(G, n_samples=n_samples, noise_dim=noise_dim)  # 获取真实数据和虚假数据
    set_trainability(D, True)  # 设置只训练判别器
    D.fit(X, y, epochs=1, batch_size=batch_size)  # 训练D


pretrain(G, D)

# 生成随机噪声
def sample_noise(G, noise_dim=10, n_samples=10000):
    X = np.random.uniform(0, 1, size=[n_samples, noise_dim])  # 产生(0,1)均匀分布随机噪声(10000,10)
    y = np.zeros((n_samples, 2))  # 全零标签(10000,2)
    y[:, 1] = 1  # 第二列置为1
    return X, y

# 正式训练
def train(GAN, G, D, epochs=500, n_samples=10000, noise_dim=10, batch_size=32, v_freq=50):
    d_loss = []
    g_loss = []
    for epoch in range(epochs):
        X, y = sample_data_and_gen(G, n_samples=n_samples, noise_dim=noise_dim)  # 生成真实数据和虚假数据
        set_trainability(D, True)  # 训练D
        d_loss.append(D.train_on_batch(X, y))  # D损失值

        X, y = sample_noise(G, n_samples=n_samples, noise_dim=noise_dim)  # 生成随机噪声及标签
        set_trainability(D, False)  # 冻结D
        g_loss.append(GAN.train_on_batch(X, y))  # G损失值
        # 每隔50次打印一次生成器损失和判别器损失
        if (epoch + 1) % v_freq == 0:
            print("Epoch #{}: Generative Loss: {}, Discriminative Loss: {}".format(epoch + 1, g_loss[-1], d_loss[-1]))
    return d_loss, g_loss


d_loss, g_loss = train(GAN, G, D)

ax = pd.DataFrame({'Generative Loss': g_loss,'Discriminative Loss': d_loss,})\
    .plot(title='Training loss', logy=True)  # 训练loss
ax.set_xlabel("Epochs")
ax.set_ylabel("Loss")
plt.show()

# N_VIEWED_SAMPLES = 2  #要绘制的波形数目
# data_and_gen, _ = sample_data_and_gen(G, n_samples=N_VIEWED_SAMPLES)  # 生成真实数据和虚假数据
# ((data_and_gen[N_VIEWED_SAMPLES:])).plot()  #绘制图形
# ()

# N_VIEWED_SAMPLES = 2
# data_and_gen, _ = sample_data_and_gen(G, n_samples=N_VIEWED_SAMPLES)
# ((data_and_gen[N_VIEWED_SAMPLES:])).rolling(5).mean()[5:].plot()
# ()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136

最后,进行一下总结,GAN总体上很强大,但很难正确调整,特别是因为测量它们的performance十分困难。损失loss不一定是一个好的指标,目前,检查其输出是否合理的最可靠的方法是将输出摆在实际的人面前。

参考:Generative Adversarial Networks Part 2 - Implementation with Keras 2.0

ok,以上便是全部内容了,如果对你有所帮助,记得点个赞哟~