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

时间:2024-09-29 14:17:19

在进阶阶段,将介绍一下GAN生成对抗网络在除图像领域的一些其他应用,本文将先介绍一下利用GAN进行曲线 y = x 2 y=x^2 y=x2拟合。

目录

    • 一、GAN结构
    • 二、函数代码
      • 2.1 生成器Generator
      • 2.2 判别器Discriminator
      • 2.3 生成真实数据
      • 2.4 train函数
      • 2.5 显示图像sample_images函数
    • 三、结果显示
    • 四、完整代码
    • 五、拓展

一、GAN结构

这里进行曲线拟合利用的是普通GAN的结构,即利用全连接层+激活函数,这里不过多介绍。

二、函数代码

2.1 生成器Generator

生成器的目标是输入一行正态分布随机数,生成曲线 y = x 2 y=x^2 y=x2上的点,因此它的输入是一个长度为5的一维的向量,输出一个2维的数据。
函数代码:

#定义生成器模型
#输入:5维向量;输出:2维数据
def build_generator(self):
    model=Sequential()
    noise=Input(shape=(self.latent_dim,))#输入
    # 第一层全连接层:5-->15
    model.add(Dense(15, activation='relu', kernel_initializer='he_uniform', input_dim=self.latent_dim))
    # 第二层全连接层:15-->2
    model.add(Dense(2, activation='linear'))
    img = model(noise)
    return Model(noise, img)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
'
运行

生成器模型如下:
在这里插入图片描述

2.2 判别器Discriminator

判别器的目的是根据输入的数据判断出真伪。因此它的输入一个长度为2的数据,输出是0到1之间的数,越接近1则代表数据越真实,越接近0则代表数据越虚假。
函数代码:

#定义判别器模型
#输入:2维数据;输出:0-1数字
def build_discriminator(self):
    model=Sequential()
    img=Input(shape=(self.img_shape,))
    # 第一层全连接层:5-->25
    model.add(Dense(25, activation='relu', kernel_initializer='he_uniform', input_dim=self.img_shape))  # 全连接层
    # 第二层全连接层:25-->1
    model.add(Dense(1, activation='sigmoid'))  # 输出
    validity = model(img)
    return Model(img, validity)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
'
运行

判别器模型如下:
在这里插入图片描述

2.3 生成真实数据

生成真实数据的函数目标是生成真实样本。该样本是二维数据,第一维度为x,第二维度为 x 2 x^2 x2,并且生成真伪判别矩阵y,返回X和y数据。

函数代码:

def generate_real_samples(n):
    # 生成输入[-0.5, 0.5]
    X1 = np.random.rand(n) - 0.5
    # 生成输出X1的平方
    X2 = X1 * X1
    X1 = X1.reshape(n, 1)  # (n,1)
    X2 = X2.reshape(n, 1)  # (n,1)
    X = np.hstack((X1, X2))  # 水平堆叠成(n,2)
    y = np.ones((n, 1))  # 生成类标签(n,1)
    return X, y
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
'
运行

2.4 train函数

训练的主要思路为:
细致解释:

  1. 首先对判别器进行训练:
    这时候输入值有2种:
    一是随机噪声输入生成器产生的假数据和标签0(第一次epoch时,生成器中可训练参数只是初始化的参数值);
    二是真正的数据和标签1。对这两种输入值分别经过二元交叉熵函数(binary crossentropy)计算损失值求出d_loss_real和d_loss_fake,最终判别器D的损失值为二者的平均值
    d_loss = 0.5 * d_loss_real + 0.5 * d_loss_fake

  2. 然后训练生成器:
    训练生成器的时候,停止对判别器的训练,设置=False
    为生成器输入随机噪声,将生成器产生的数据打上真实值标签1送入在这次epoch中已经训练好的判别器中,继续通过二元交叉熵函数(binary crossentropy)做loss进行生成器的训练,这样的话生成器就会向着真实值靠近。

  3. 再次迭代训练:
    训练完每次epoch后生成器性能就会增强,然后回到我们的第一步继续训练判别器,再训练生成器,在这样不断地相互博弈和促进下,最终生成器会达到以假乱真的效果。

函数代码:

   #定义训练函数
    def train(self, epochs=10000, batch_size=128, sample_interval=50):
        half_batch = int(batch_size / 2)  # 一半真实样本,一半虚假样本
        # 创建标签(0或者1)--二分类问题
        # valid = ((half_batch, 1))#全1阵--真
        fake = np.zeros((half_batch, 1))#全0阵--假
        for i in range(epochs):
            imgs, y_real = generate_real_samples(half_batch)  # 生成真实样本,x_real(64,2);y_real(64,1)

            noise = np.random.normal(0, 1, (half_batch, self.latent_dim))  # 生成正态分布噪声
            gen_imgs = self.generator.predict(noise)  # 假图像

            #训练discriminator
            d_loss_real = self.discriminator.train_on_batch(imgs, y_real)#真数据训练
            d_loss_fake = self.discriminator.train_on_batch(gen_imgs, fake)#假数据训练
            d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)#数据求和

            #训练生成器
            x_gan = np.random.randn(self.latent_dim * batch_size)
            x_gan = x_gan.reshape(batch_size, self.latent_dim)  # reshape成网络输入
            y_gan = np.ones((batch_size, 1))  # 为假样本创建假标签
            g_loss=self.combined.train_on_batch(x_gan,y_gan)
            print("steps:%d [D loss: %f, acc.: %.2f%%] [G loss: %f]"% (i, d_loss[0], 100*d_loss[1],g_loss))

            #迭代50次打印一次
            if i % sample_interval == 0:
                self.sample_images(i)
  • 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

2.5 显示图像sample_images函数

分别绘制真实数据和生成数据的散点图,可视化查看拟合效果,其中红色点为真实样本,蓝色点为生成样本。
函数代码:

    def sample_images(self,epoch):
        n=100
        x_real, y_real = generate_real_samples(n)

        x_input = np.random.randn(self.latent_dim * n)  # 生成随机噪声
        noise = x_input.reshape(n, self.latent_dim)  # reshape成网络输入
        x_fake = self.generator.predict(noise)#生成器预测的数据


        # 绘制散点图
        plt.scatter(x_real[:, 0], x_real[:, 1], color='red')  # 真实样本
        plt.scatter(x_fake[:, 0], x_fake[:, 1], color='blue')  # 虚假样本
        # ()
        plt.savefig("images/%" % epoch)
        plt.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
'
运行

三、结果显示

迭代0次、5000次、10000次和20000次结果如下:
在这里插入图片描述
可以发现,生成的样本已经非常接近给定的样本了,说明曲线拟合效果还是比较好的。

四、完整代码

完整代码如下:

from keras.optimizers import Adam
from keras.models import Sequential,Model
from keras.layers import Input,Dense
import numpy as np
import matplotlib.pyplot as plt
import os

def generate_real_samples(n):
    # 生成输入[-0.5, 0.5]
    X1 = np.random.rand(n) - 0.5
    # 生成输出X1的平方
    X2 = X1 * X1
    X1 = X1.reshape(n, 1)  # (n,1)
    X2 = X2.reshape(n, 1)  # (n,1)
    X = np.hstack((X1, X2))  # 水平堆叠成(n,2)
    y = np.ones((n, 1))  # 生成类标签(n,1)
    return X, y


class GAN():
    def __init__(self):
        self.img_shape =2
        self.latent_dim=5  # 输入维度
        optimizer=Adam(0.0002,0.5)  # 优化器

        # 判别器
        self.discriminator = self.build_discriminator()
        self.discriminator.compile(loss='binary_crossentropy', optimizer=optimizer,
                                   metrics=['accuracy'])  # 定义loss函数和优化器
        # 生成器
        self.generator = self.build_generator()
        gan_input = Input(shape=(self.latent_dim,))
        img = self.generator(gan_input)
        # 训练生成器
        # 在训练generator的时候不训练discriminator
        self.discriminator.trainable = False  # 冻结discriminator
        validity = self.discriminator(img)  # 对生成的假数据进行预测
        self.combined = Model(gan_input, validity)  # 在Model中discriminator已经被冻结,仅剩下generator在运行了
        self.combined.compile(loss='binary_crossentropy', optimizer=optimizer)  # 定义loss函数和优化器

    #定义生成器模型
    #输入:5维向量;输出:2维数据
    def build_generator(self):
        model=Sequential()
        noise=Input(shape=(self.latent_dim,))#输入
        # 第一层全连接层:5-->15
        model.add(Dense(15, activation='relu', kernel_initializer='he_uniform', input_dim=self.latent_dim))
        # 第二层全连接层:15-->2
        model.add(Dense(2, activation='linear'))
        img = model(noise)
        return Model(noise, img)

    #定义判别器模型
    #输入:2维数据;输出:0-1数字
    def build_discriminator(self):
        model=Sequential()
        img=Input(shape=(self.img_shape,))
        # 第一层全连接层:5-->25
        model.add(Dense(25, activation='relu', kernel_initializer='he_uniform', input_dim=self.img_shape))  # 全连接层
        # 第二层全连接层:25-->1
        model.add(Dense(1, activation='sigmoid'))  # 输出
        validity = model(img)
        return Model(img, validity)

    # 定义生成真实样本

    #定义训练函数
    def train(self, epochs=10000, batch_size=128, sample_interval=50):
        half_batch = int(batch_size / 2)  # 一半真实样本,一半虚假样本
        # 创建标签(0或者1)--二分类问题
        # valid = ((half_batch, 1))#全1阵--真
        fake = np.zeros((half_batch, 1))#全0阵--假
        for i in range(epochs):
            imgs, y_real = generate_real_samples(half_batch)  # 生成真实样本,x_real(64,2);y_real(64,1)

            noise = np.random.normal(0, 1, (half_batch, self.latent_dim))  # 生成正态分布噪声
            gen_imgs = self.generator.predict(noise)  # 假图像

            #训练discriminator
            d_loss_real = self.discriminator.train_on_batch(imgs, y_real)#真数据训练
            d_loss_fake = self.discriminator.train_on_batch(gen_imgs, fake)#假数据训练
            d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)#数据求和

            #训练生成器
            x_gan = np.random.randn(self.latent_dim * batch_size)
            x_gan = x_gan.reshape(batch_size, self.latent_dim)  # reshape成网络输入
            y_gan = np.ones((batch_size, 1))  # 为假样本创建假标签
            g_loss=self.combined.train_on_batch(x_gan,y_gan)
            print("steps:%d [D loss: %f, acc.: %.2f%%] [G loss: %f]"% (i, d_loss[0], 100*d_loss[1],g_loss))

            #迭代50次打印一次
            if i % sample_interval == 0:
                self.sample_images(i)

    def sample_images(self,epoch):
        n=100
        x_real, y_real = generate_real_samples(n)

        x_input = np.random.randn(self.latent_dim * n)  # 生成随机噪声
        noise = x_input.reshape(n, self.latent_dim)  # reshape成网络输入
        x_fake = self.generator.predict(noise)#生成器预测的数据


        # 绘制散点图
        plt.scatter(x_real[:, 0], x_real[:, 1], color='red')  # 真实样本
        plt.scatter(x_fake[:, 0], x_fake[:, 1], color='blue')  # 虚假样本

        # ()
        plt.savefig("images/%" % epoch)
        plt.close()

if __name__=='__main__':
    if not os.path.exists("./images"):
        os.makedirs("./images")
    gan = GAN()
    gan.train(epochs=100000, batch_size=128, sample_interval=5000)
  • 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

五、拓展

为了更加直观的显示生成数据的拟合效果,可以对生成的数据点进行拟合,拟合代码如下:

        x_fake = sorted(x_fake, key=lambda x_fake: x_fake[0])
        x_fake = np.array(x_fake)
        z1 = np.polyfit(x_fake[:, 0], x_fake[:, 1], 2)  # 用7次多项式拟合,可改变多项式阶数;
        p1 = np.poly1d(z1)  # 得到多项式系数,按照阶数从高到低排列
        print(p1)  # 显示多项式
        yvals = p1(x_fake[:, 0])  # 可直接使用yvals=(z1,xx)
        plt.plot(x_fake[:, 0], yvals, 'b', label='polyfit values')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

第一行代码作用为对x_fake内的二维数据按照x坐标进行从小到大排序,这样才能进行拟合,将排序后的list类型形式转换为ndarray形式,然后利用poly1d函数进行拟合。
训练后拟合结果如下:
在这里插入图片描述
可以看到,经过训练,生成样本可以很好的拟合曲线。

如果对你有所帮助,记得点个赞哟~