GAN的学习 - 训练过程(冻结判别器)

2024-06-15 16:32

本文主要是介绍GAN的学习 - 训练过程(冻结判别器),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

20200823 -

0. 引言

在前一篇文章《GAN的学习[1]》中简单介绍了构造GAN的过程,包括如何构造生成器和判别器,如何训练GAN等,但是其中存在一个问题,就是在训练过程中怎么保证判别器不更新权值。下面针对这部分进行具体的描述。
(20200910 - 增加)
经过了这段时间的学习,对这部分内容有了重新的认识。本篇文章记录的时候,我并不知道tensorflow是怎么实现这种冻结操作的, 但经过了这段时间的学习之后,对训练过程以及tensorflow和keras两种框架不同的处理方式加深了理解。

1. 整体的流程

在GAN进行工作的流程中,需要生成器和判别器的共同工作。判别器的模型使用生成器生成的数据示例和真实的样本进行二分类(可能也会是多分类,这种情况需要特殊考虑),在二分类时,一般是判定数据是真的还是假的;而生成器的训练需要利用判别器所反馈回来的信息(就是损失函数)来实现权值的更新。
(20200901 增加)
GAN的原始训练过程
上图是GAN的原始过程,当k=1的时候(按照图中所说),也就是每训练一次判别器,就训练一次生成器。所以GAN进行训练的过程中,就是生成器和判别器相互博弈,共同提升相应性能的过程。(个人感觉如果陷入了某个最优点是不是会产生震荡呢?)
整体的训练代码如下(同样使用文章[1]引用的文章[2]来进行说明)

# train the generator and discriminator
def train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=100, n_batch=256):bat_per_epo = int(dataset.shape[0] / n_batch)half_batch = int(n_batch / 2)# manually enumerate epochsfor i in range(n_epochs):# enumerate batches over the training setfor j in range(bat_per_epo):# get randomly selected 'real' samplesX_real, y_real = generate_real_samples(dataset, half_batch)# generate 'fake' examplesX_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)# create training set for the discriminatorX, y = vstack((X_real, X_fake)), vstack((y_real, y_fake))# update discriminator model weightsd_loss, _ = d_model.train_on_batch(X, y)# prepare points in latent space as input for the generatorX_gan = generate_latent_points(latent_dim, n_batch)# create inverted labels for the fake samplesy_gan = ones((n_batch, 1))# update the generator via the discriminator's errorg_loss = gan_model.train_on_batch(X_gan, y_gan)# summarize loss on this batchprint('>%d, %d/%d, d=%.3f, g=%.3f' % (i+1, j+1, bat_per_epo, d_loss, g_loss))

来具体描述一下上述代码的具体含义,关于batch,epochs就不再赘述,直接看最里层训练的东西。步骤如下:
1)生成真实和虚假图片,并放置于新的数据集
2)利用步骤1)中生成的完整数据集训练判别器,此时判别器肯定是能更新的
3)利用生成器来生成假图片,同时将其的标签全部定义为真实的
4)利用生成的假图片(同时标签也进行了反转)来训练GAN模型,注意此时判别器是不能更新权值的,那么这个步骤也就是完全是为了更新生成器来实现的。

注意几个细节:

  1. 先进行了判别器的权值更新~~,从实际上代码反馈出来的意思可以知道,这种单独的更新方式也会导致GAN中的判别器的权值被更新(不然肯定就没有意义了)~~
  2. 在训练GAN的过程中,只利用了假数据来进行训练。

同时这里需要注意的是,虽然代码前面显示训练了判别器,但是需要注意的是,这两个部分的判别器,一个会更新权值,另一个部分不会更新权值。
那么,好了, GAN大致的训练训练过程也就是知道了:就是同时训练判别器和生成器,让两者来促进整体的性能提升。下一小节来具体看各个模型之前的区别,有哪些特殊的设置,来理解为什么这样能工作。

2. 各个部分模型的构建

关于生成器和判别器的模型内部内容,这里不进行关注,本质上使用什么样的模型(什么层这种内容)应该跟具体的任务相关,与GAN的整体训练是没有关系的。
判别器模型:

# define the standalone discriminator model
def define_discriminator(in_shape=(28,28,1)):model = Sequential()model.add(Conv2D(64, (3,3), strides=(2, 2), padding='same', input_shape=in_shape))model.add(LeakyReLU(alpha=0.2))model.add(Dropout(0.4))model.add(Conv2D(64, (3,3), strides=(2, 2), padding='same'))model.add(LeakyReLU(alpha=0.2))model.add(Dropout(0.4))model.add(Flatten())model.add(Dense(1, activation='sigmoid'))# compile modelopt = Adam(lr=0.0002, beta_1=0.5)model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])return model

生成器模型:

# define the standalone generator model
def define_generator(latent_dim):model = Sequential()# foundation for 7x7 imagen_nodes = 128 * 7 * 7model.add(Dense(n_nodes, input_dim=latent_dim))model.add(LeakyReLU(alpha=0.2))model.add(Reshape((7, 7, 128)))# upsample to 14x14model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))model.add(LeakyReLU(alpha=0.2))# upsample to 28x28model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))model.add(LeakyReLU(alpha=0.2))model.add(Conv2D(1, (7,7), activation='sigmoid', padding='same'))return model

GAN模型

# define the combined generator and discriminator model, for updating the generator
def define_gan(g_model, d_model):# make weights in the discriminator not trainabled_model.trainable = False# connect themmodel = Sequential()# add generatormodel.add(g_model)# add the discriminatormodel.add(d_model)# compile modelopt = Adam(lr=0.0002, beta_1=0.5)model.compile(loss='binary_crossentropy', optimizer=opt)return model

判别器模型中,进行了相应的编译过程,而生成器模型没有进行编译过程, 本身因为他只是为了生成向量,并不是进行分类过程,同时他后面还要连接判别器模型。

重点在这里,在GAN模型的构造过程中,将判别器模型的参数trainable=False,这个步骤能够让GAN模型在训练过程中,判别器的权值不用被更新,其所传达的思想很简单,就是要在GAN训练时只更新生成器的权值。

但是,为什么这样做就能成功呢?而且,需要注意的是,判别器的模型已经编译了最后的分类过程,同时GAN中的判别器模型就是前面的判别器模型,并没有进行deepcopy之类的操作。如果不是同一个模型的话,那么判别器的单独的训练成果必然是不能提现到GAN模型中的。
所以,这里要解决的问题就是为什么这样能够实现一个能够训练(权值更新),一个不能呢?

3. trainable与compile的关系

关于这部分内容,我一开始也很疑惑,特别是同一个模型不同的地方不同的表现;这里的关键点就在于trainablecompile的设置。关于这部分内容,github上也有两个issue[3][4]与其相关,其中[3]是直接来解决这个问题的。具体来说一下[3]的内容。
GAN的解答
把这段话直接引用过来:

By setting trainable=False after the discriminator has been compiled the discriminator is still trained during discriminator.train_on_batch but since it’s set to non-trainable before the combined model is compiled it’s not trained during combined.train_on_batch.

[3]的问题就是,判别器模型和GAN混合模型中的判别器是同一个模型,他们没有使用deepcopy之类的操作,为什么设置了trainable=False另外一个模型还能训练。
而他的解答,翻译过来就是说, 在编译了模型之后再调用这个模型的trainable=False,判别器依然是可以在他自己的训练过程中可以更新权值,而混合的GAN模型在编译之前就设置了这个参数,他的判别器就不会被更新权值。但是,他的解答只是给出了具体现象或者说结果,没有解释处具体原理。但从他的话里可以知道**关键点在于compile的部分。
在issue[4]中,提到了keras官网中给出的解释,具体内容在网址[5]的“How can I freeze layers and do fine-tuning?”部分。
compile的解释

Calling compile() on a model is meant to “freeze” the behavior of that model. This implies that the trainable attribute values at the time the model is compiled should be preserved throughout the lifetime of that model, until compile is called again. Hence, if you change trainable, make sure to call compile() again on your model for your changes to be taken into account.

这里的解释是从compile部分来说明:在一个模型上调用compile时,代表着将冻结这个模型的行为,那么trainable的行为,在这个模型的整个生命过程中,都将被保持,除非这个模型被二次编译。因此如果要改变行为,那么就需要重新调用compile。

从上面的解释中可以知道,compile之后,这个模型的具体内容就已经被保存了。在前面的GAN例子中就可以看到这个东西的流程,判别器模型被编译,但是在构建GAN模型时,又重新进行了设置,同时进行了编译。那么也就是说,他们时共享的权值(实际上他们使用的模型在引用上都是同一个位置),只不过在编译过程中,只有GAN设置了判别器的不可训练。
所以才会出现,两者之前使用的模型都是同一个,只不过一个是可训练,一个是不可训练;来看看issue[3]中给出的代码。

    # build the discriminatorprint('Discriminator model:')discriminator = build_discriminator()discriminator.compile(optimizer=Adam(lr=adam_lr, beta_1=adam_beta_1),loss=['binary_crossentropy', 'sparse_categorical_crossentropy'])discriminator.summary()# build the generatorgenerator = build_generator(latent_size)latent = Input(shape=(latent_size, ))image_class = Input(shape=(1,), dtype='int32')# get a fake imagefake = generator([latent, image_class])# we only want to be able to train generation for the combined modeldiscriminator.trainable = Falsefake, aux = discriminator(fake)combined = Model([latent, image_class], [fake, aux])

为了验证是否权值进行了更新,可以利用一下代码:

    for layer in self.discriminator.layers:print (layer.get_weights())

注:在新版的keras中,如果进行了这种设置,会导致keras产生报警,就会问你,在编译之后又设置了
trainable,但是没有调用compile,这里不一致。之前的时候看到过一篇文章说,这个警告是为了警告新用户对这部分不理解。但是也从侧面说明了前面的问题,在调用了compile之后,trainable的内容的确就伴随了编译之后的模型,所以你改了之后,系统发现了这个不一致。这部分可以在考虑考虑,但是这种方式应该是没有错误的。为了摆脱这种警告,文章[6]采用的方式是在训练之前,分别修改trainable参数,具体如下:

# Train discriminator
discriminator.trainable = True                            # get rid of warning messages (doesn't affect training)
dloss = discriminator.train_on_batch(x_all, y_all)        # this trains only discriminator, doesn't touch gen.noise = np.random.normal(size=[n_batch, 100])
y_fake = np.ones([n_batch, 1])
discriminator.trainable = False                           # get rid of warning messages 
gloss = gan_model.train_on_batch(noise, y_fake)

4. 总结

本篇文章中,讲解了前面的疑惑,就是为什么这样设置能够同时以两种属性对待同一个模型,其实本质上就是compile的问题。虽然这部分问题产生于GAN的学习过程中,但实际上在迁移学习,或者使用以后的一些模型例如vgg这种时,都会有这种需求,也就是保证某层或者整个模型的参数不更新。

参考文章

[1]GAN的学习
[2]How to Develop a GAN for Generating MNIST Handwritten Digits
[3]puzzled about “discriminator.trainable=False”
[4]Keras setting discriminator to non-trainable forever
[5]Keras FAQ
[6]3110_GAN_FC_MNIST.html

这篇关于GAN的学习 - 训练过程(冻结判别器)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1063980

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

作业提交过程之HDFSMapReduce

作业提交全过程详解 (1)作业提交 第1步:Client调用job.waitForCompletion方法,向整个集群提交MapReduce作业。 第2步:Client向RM申请一个作业id。 第3步:RM给Client返回该job资源的提交路径和作业id。 第4步:Client提交jar包、切片信息和配置文件到指定的资源提交路径。 第5步:Client提交完资源后,向RM申请运行MrAp

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识