GAN-生成手写数字-Keras

from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Reshape
from keras.layers.core import Activation
from keras.layers.normalization import BatchNormalization
from keras.layers.convolutional import UpSampling2D
from keras.layers.convolutional import Conv2D, MaxPooling2D
from keras.layers.core import Flatten
from keras.optimizers import SGD
from keras.datasets import mnist
import numpy as np
from PIL import Image
import argparse
import math

一、首先要定义一个生成器G,该生成器需要将输入的随机噪声变换为图像。

1. 该模型首先输入有100个元素的向量,该向量随机生成于某分布。

2. 随后利用两个全连接层接连将该输入向量扩展到1024维和128 * 7 * 7

3. 后面就开始将全连接层所产生的一维张量重新塑造成二维张量,即MNIST中的灰度图

4. 由全连接传递的数据会经过几个上采样层和卷积层,注意到最后一个卷积层所采用的卷积核为1,所以经过最后卷积层所生成的图像是一张二维灰度图

def generator_model():
    # 下面搭建生成器的架构,首先导入序贯模型(sequential),即多个网络层的线性堆叠
    model = Sequential()
    # 添加一个全连接层,输入为100维向量,输出1024维
    model.add(Dense(input_dim=100, output_dim=1024))
    # 添加一个激活函数tanh
    model.add(Activation('tanh'))
    # 添加一个全连接层,输出为 128 * 7 * 7维度
    model.add(Dense(128*7*7))
    # 添加一个批量归一化层,该层在每个batch上将前一层的激活值重新规范化,即使得其输出数据的均值接近0,其标准差接近1
    model.add(BatchNormalization())
    model.add(Activation('tanh'))
    
    # Reshape层用来将输入shape转换为特定的shape,将含有 128*7*7 个元素的向量转换为 7*7*128 张量
    model.add(Reshape((7, 7, 128), input_shape=(128*7*7,)))
    # 2维上采样层,即将数据的行和列分别重复2次
    model.add(UpSampling2D(size=(2, 2)))
    # 添加一个2维卷积层,卷积核大小为5X5,激活函数为tanh,共64个卷积核,并采用padding以保持图像尺寸不变
    model.add(Conv2D(64, (5, 5), padding='same'))
    model.add(Activation('tanh'))
    model.add(UpSampling2D(size=(2, 2)))
    
    # 卷积核设为1即输出图像的维度
    model.add(Conv2D(1, (5, 5), padding='same'))
    model.add(Activation('tanh'))
    return model

二、判别模型

判别模型就是比较传统的图像识别模型,可以按照经典的方法采用几个卷积层与最大池化层,而后再展开为一维张量并采用几个全连接层作为架构

def discrimiator_model():
    # 下面搭建判别器架构,同样采用序贯模型
    model = Sequential()
    
    # 添加一个2维卷积层,卷积核大小为5X5,激活函数为tanh,输入shape在 'channel_first' 模式下为 (samples, channels, rows, cols)
    # 在 ‘channel_last’模式下为 (samples, rows, cols, channels),输出为64维。 元素的顺序发生了一定的改变
    model.add(Conv2D(64, (5, 5),
                     padding='same',
                    input_shape=(28, 28, 1))
             )
    model.add(Activation('tanh'))
    
    # 为空域信号施加最大值池化,pool_size 取(2, 2)代表使图片在两个维度均变为原长的一半
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Conv2D(128, (5, 5)))
    model.add(Activation('tanh'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    
    # Flatten层把多维输入一维化,常用在卷积层到全连接层的过渡
    model.add(Flatten())
    model.add(Dense(1024))
    model.add(Activation('tanh'))
              
    # 一个结点进行二值分类,并采用sigmoid函数的输出作为概念
    model.add(Dense(1))
    model.add(Activation('sigmoid'))
    return model

三、模型拼接

我们在训练生成模型时,需要固定判别模型D以极小化价值函数而寻求更好的生成模型,这就意味着我们需要将生成模型与判别模型拼接在一起,并固定D的权重以训练G的权重。因此训练这个组合模型才能真正更新生成模型的参数。

def generator_containing_discriminator(g, d):
    # 将前面定义的生成器架构和判别器架构拼接成一个大的神经网络,用于判别生成的图片
    model = Sequential()
    # 先添加生成器架构,再令d不可训练,即固定d
    # 因此在给定d的情况下训练生成器,即通过将生成的结果投入到判别器进行辨别而优化生成器
    model.add(g)
    d.trainable = False
    model.add(d)
    return model

四、生成图片拼接

# 生成图片拼接
def combine_images(generated_images):
    num = generated_images.shape[0]
    width = int(math.sqrt(num))
    height = int(math.ceil(float(num)/width))
    shape = generated_images.shape[1:3]
    image = np.zeros((height*shape[0], width*shape[1]),
                    dtype=generated_images.dtype)
    
    for index, img in enumerate(generated_images):
        i = int(index / width)
        j = index % width
        image[i * shape[0] : (i + 1) * shape[0], j * shape[1] : (j + 1) * shape[1]] = img[:, :, 0]
    return image

五、训练

1. 加载MNIST数据

2. 将数据分割为训练与测试集,并赋值给变量

3. 设置训练模型的超参数

4. 编译模型的训练过程

5. 在每一次迭代内,抽取生成图像与真实图像,并打上标注

6. 随后将数据投入到判别模型中,并进行训练与计算损失

7. 固定判别模型,训练生成模型并计算损失,结束这一次迭代

def train(BATCH_SIZE):
    # 加载数据,将数据集下载到本地‘/.keras/datasers/’
    # 下载地址:https://s3.amazonaws.com/img-datasets/mnist.npz
    (X_train, y_train), (X_test, y_test) = mnist.load_data(r'C:/Users/Administrator/.keras/datasets/mnist.npz')
    # image_data_format选择‘channels_last’或‘channels_first’,该选项指定了Keras将要将要使用的维度顺序
    # ‘channels_first’假定2D数据的维度顺序为(channels, rows, cols), 3D数据的维度顺序为(channels, conv_dim1, conv_dim2, conv_dim3)
    
    # 转换字段类型,并将数据导入变量中
    X_train = (X_train.astype(np.float32) - 127.5) / 127.5
    X_train = X_train[:, :, :, None]
    X_test = X_test[:, :, :, None]
    
    # 将定义好的模型架构赋值给特定的变量
    d = discrimiator_model()
    g = generator_model()
    d_on_g = generator_containing_discriminator(g, d)
    
    # 定义生成器模型、判别器模型,更新所使用的优化算法及超参数
    d_optim = SGD(lr=0.001, momentum=0.9, nesterov=True)
    g_optim = SGD(lr=0.001, momentum=0.9, nesterov=True)
    
    # 编译三个神经网络并设置损失函数和优化算法,其中损失函数都是用二元分类交叉熵函数。编译是用来配置模型学习过程的
    g.compile(loss='binary_crossentropy', optimizer='SGD')
    d_on_g.compile(loss='binary_crossentropy', optimizer=g_optim)
    
    # 前一个架构在固定判别器的情况下训练了生成器,所以在训练判别器之前先要设定其为可训练
    d.trainable = True
    d.compile(loss='binary_crossentropy', optimizer=d_optim)
    
    # 下面在满足epoch条件下进行训练
    for epoch in range(10):
        print("Epoch is", epoch)
        
        # 计算一个epoch所需要的迭代数量,即训练样本数除批量大小数的值取整,其中shape[0]就是读取矩阵第一维度的长度
        print("Number of batches", int(X_train.shape[0] / BATCH_SIZE))
        
        # 在一个epoch内进行迭代训练
        for index in range(int(X_train.shape[0] / BATCH_SIZE)):
            # 随机生成的噪声服从均匀分布,且采样下届为-1,采样上届为1, 输出BATCH_SIZE * 100个样本,即抽取一个批量的随机样本
            noise = np.random.uniform(-1, 1, size=(BATCH_SIZE, 100))
            
            # 抽取一个批量的真实图片
            image_batch = X_train[index * BATCH_SIZE : (index + 1) * BATCH_SIZE]
            
            # 生成的图片使用生成器对随机噪声进行推断,verbose为日志显示
            # 0为不在标准输出流输出日志信息,1为输出进度条记录
            generated_images = g.predict(noise, verbose=0)
            
            # 每经过100次迭代输出一张生成的图片
            if index % 100 == 0:
                image = combine_images(generated_images)
                image = image * 127.5 + 127.5
                Image.fromarray(image.astype(np.uint8)).save("C:/Users/Administrator/GAN/" + str(epoch) + "_" + str(index) + ".png")
                
            # 将真实图片和生成图片以多维数组的形式拼接在一起,真实图片在上,生成图片在下
            X = np.concatenate((image_batch, generated_images))
            
            # 生成图片真假标签,即一个包含两倍批量大小的列表
            # 前一个批量大小都是1,代表真实图片,后一个批量大小都是0,代表伪造图片
            y = [1] * BATCH_SIZE + [0] * BATCH_SIZE
            
            # 判别器的损失,在一个batch的数据上进行一次参数更新
            d_loss = d.train_on_batch(X, y)
            print("batch %d d_loss : %f" % (index, d_loss))
            
            # 随机生成的噪声服从均匀分布
            noise = np.random.uniform(-1, 1, (BATCH_SIZE, 100))
            
            # 固定判别器
            d.trainable = False
            
            # 计算生成器损失,在一个batch的数据上进行一次参数更新
            g_loss = d_on_g.train_on_batch(noise, [1] * BATCH_SIZE)
            
            # 令判别器可训练
            d.trainable = True
            print("batch %d g_loss : %f" % (index, g_loss))
            
            # 每100次迭代保存一次生成器和判别器的权重
            if index % 100 == 0:
                g.save_weights('generator', True)
                d.save_weights('discrimiator', True)
train(32)

六、运行生成好的模型生成图片

# 训练完模型后,可以运行该函数生成图片
def generate(BATCH_SIZE, nice = False):
    g = generator_model()
    g.compile(loss='binary_crossentropy', optimizer='SGD')
    g.load_weights('generator')
    
    if nice:
        d = discrimiator_model()
        d.compile(loss='binary_crossentropy', optimizer='SGD')
        d.load_weights('discrimiator')
        noise = np.random.uniform(-1, 1, (BATCH_SIZE * 20, 100))
        generated_images = g.predict(noise, verbose=1)
        d_pret = d.predict(generated_images, verbose=1)
        index = np.arange(0, BATCH_SIZE * 20)
        index.resize((BATCH_SIZE * 20, 1))
        pre_with_index = list(np.append(d_pret, index, axis=1))
        pre_with_index.sort(key=lambda x : x[0], reverse=True)
        nice_images = np.zeros((BATCH_SIZE, ) + generated_images.shape[1:3], dtype=np.float32)
        nice_image = nice_images[:, :, :, None]
        
        for i in range(BATCH_SIZE):
            idx = int(pre_with_index[i][1])
            nice_images[i, :, :, 0] = generated_images[idx, :, :, 0]
        image = combine_images(nice_images)
    else:
        noise = np.random.uniform(-1, 1, (BATCH_SIZE, 100))
        generated_images = g.predict(noise, verbose=0)
        image = combine_images(generated_images)
    image = image * 127.5 + 127.5
    Image.fromarray(image.astype(np.uint8)).save("C:/Users/Administrator/GAN/generated_image.png")
generate(32)

由于只迭代了10个epoch,效果不是很好,不过已经能看出手写数字了。最后生成的图片如下:

转载于:https://www.cnblogs.com/gezhuangzhuang/p/10192590.html

好的,以下是对您的问题的回答: 首先,GAN指的是对抗生成网络,是一种由两个神经网络成的模型:一个生成器和一个判别器。生成器负责生成新的数据样本,而判别器则试区分出生成生成的样本和真实数据样本之间的差别。 使用Keras构建GAN网络用于手写数字生成的步骤大致如下: 1. 准备数据集:需要准备手写数字像的数据集,并将其分为训练集和测试集。 2. 定义生成器网络:生成器网络通常是一个多层的神经网络,其输入噪声向量,输出为生成像。可以使用Keras的Sequential模型定义生成器网络。 3. 定义判别器网络:判别器网络也是一个多层的神经网络,其输入像,输出为一个二元分类结果,代表该像是真实数据还是生成生成的数据。同样使用Keras的Sequential模型定义判别器网络。 4. 编译GAN网络:将生成器和判别器合起来形成一个GAN网络。编译GAN网络时需要注意的一点是,对于生成器而言,其误差评估的对象为GAN网络输出的结果,而非生成器的输出结果。 5. 训练GAN网络:将训练输入生成器网络中进行训练,通过GAN网络在生成像和判别真实像之间不断博弈,从而使得生成器不断优化生成效果。训练GAN网络的过程中需要注意的一点是,由于GAN网络的训练目标是非常复杂的,因此需要进行较长时间的训练。 以上是基于Keras构建GAN网络用于手写数字生成的大致步骤。希望对您有所帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值