本文主要是介绍现代黑科技版“指鹿为马:使用CycleGAN实现男女“无痛变性”,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
在秦朝末期,奸臣赵高一手遮天,为了显示自己的权势与力量,他在众人面前指着一头鹿说那是马,大家畏惧赵高的权势,明知那是鹿却不得不配合赵高说那是马,这就是经典成语”指鹿为马“的出处。
在光天化日之下,罔顾事实强行将A说成B,除非你有权有势,别人都依附于你,你才有可能做得到,要不然大家都会认为你傻逼。例如像我这样的平头百姓在大街上指着一个五大三粗,满脸胡渣子的大男人说那是个窈窕大美女,你会不会觉得我傻逼呢?在现代人工智能技术的加持下,我还真有指鹿为马,指男为女的”超能力“。
本节我们介绍一种功能强大的对抗性网络叫CycleGAN,它的特点是能将物体A平和的转变为物体B,例如下图就是CycleGAN的功能实现:
从上图可以看到,训练好的网络能将马变成斑马,将苹果与橘子互换,当然我们要实现更强大的功能,那就是男人与女人互换。CycleGAN的实现比前面介绍的对抗性网络在结构和算法上要复杂很多,首先它有两个生成者网络和鉴别者网络,因为我们想把物体A变成B,那么网络必须有识别和生成物体A和B的能力,因此CycleGAN要使用一组生成者和鉴别者网络来识别和生成物体A,使用第二组生成者和鉴别者网络识别和生成物体B,这点跟我们前面描述的对抗性网络一样,因此CycleGAN有如下结构特点:
不同之处在于两组网络要把自己掌握的信息与对方沟通,这样两组网络能共同掌握物体A和B的特性,这也是Cycle的由来。接下来是CycleGAN的算法关键所在,如下图所示:
从上图可见,两个生成者网络互相交互形成一个循环。由于第一个生成者网络Generator_AB用于接收图片A然后产出图片B,第二个生成者网络Generator_BA接收图片B然后生成图片A,如果第一个网络生成的图片B质量足够好,那么将它伪造的图片B输出给第二个生成者网络,后者生伪造的图片A就应该能获得好质量,因此判断第一个网络生成结果好坏的标准之一就是将它生成的结果用于第二个网络,看看后者能不能得到好结果,因此这就形成一个循环。
同理第二个生成者网络接收图片B后伪造图片A,如果它伪装出A的质量足够好,那么将它伪装的结果输入到第一个生成者网络,后者伪装出的图片B质量就应该足够好,于是这又形成一个循环。
这种循环训练的好处在于两个生成者网络能使用各自对相应图片的识别能力去训练另一个网络。例如一开始算法使用大量真实图片A来训练Generator_AB,于是它就掌握了物体A的内在特征,当Generator_BA将其伪装的图片A输入到Generator_AB,如此就形成了一条输入链,信息由Generator_BA—>Generator_AB,在训练时信息会反向传导变成Generator_AB->Generator_BA,于是前者就把自己对物体A特征的掌握和识别传导给后者,这样后者就能改进自己的构造能力,提升它伪造的图片质量,同理算法也可以形成Generator_AB—>Generator_BA的闭环,让后者伪装的图片A质量越来越好。
网络还有第二个循环,那就是对于接收图片B伪装图片A的生成者网络Generator_BA而言,算法要让它接收图片A,然后伪造的图片A质量也要足够好,其过程如下图所示:
该循环训练流程本质上是让网络Generator_AB和Generator_BA也学会识别图片A和B的特征,这样才有利于网络去提升他们伪造的图片质量。接下来我们看看算法代码的部分实现,首先是对训练数据的加载:
celeba_train = tfds.load(name="celeb_a", data_dir = '/content/drive/My Drive/tfds_celeba',split="train") #data_dir指向数据存储路径
celeba_test = tfds.load(name="celeb_a", data_dir = '/content/drive/My Drive/tfds_celeba',split="test")
assert isinstance(celeba_train, tf.data.Dataset)
import matplotlib.pyplot as plt
for data in celeba_train.take(1): #利用take接口获取数据print(data) #数据其实是Dict对象,它包含了数据的所有相关属性print(data["attributes"]["Male"])plt.imshow(data['image'])
首先我们使用Tensorflow提供的数据集接口加载Celeba人脸图像数据,然后将图片分为男女两个类别,上面代码运行后所得结果如下:
接下来我们看看Generator网络的代码实现:
class Generator(tf.keras.Model):def __init__(self):super(Generator, self).__init__()self.downsample_layers = []self.upsample_layers = []self.last_layers = []self.resnet_block_layers = []self.channels = 3self.resnet_block_count = 9self.build_self()def build_self(self):self.down_sample(filters = 64, kernel_size = 7, strides = 1) self.down_sample(filters = 128, kernel_size = 3)self.down_sample(filters = 256, kernel_size = 3)for i in range(self.resnet_block_count):self.resnet_block()self.up_sample(filters = 128, kernel_size = 3) #构造U网络右边网络层self.up_sample(filters = 64, kernel_size = 3)self.last_layers.append(tf.keras.layers.Conv2D(filters = self.channels, kernel_size = 7, strides = 1,padding = 'same', activation = 'tanh'))def down_sample(self, filters, kernel_size = 4, strides = 2): #构造U型网络的左边down_sample_layers = []down_sample_layers.append(tf.keras.layers.Conv2D(filters = filters, kernel_size = kernel_size,strides = strides, padding = 'same'))down_sample_layers.append(tfa.layers.InstanceNormalization(axis = -1, center = False, scale = False))down_sample_layers.append(tf.keras.layers.ReLU())self.downsample_layers.append(down_sample_layers)def up_sample(self, filters, kernel_size = 4, strides = 2):up_sample_layers = []up_sample_layers.append(tf.keras.layers.Conv2DTranspose(filters = filters, kernel_size = kernel_size,strides = strides, padding = 'same'))up_sample_layers.append(tfa.layers.InstanceNormalization(axis = -1,center = False,scale = False))up_sample_layers.append(tf.keras.layers.ReLU())self.upsample_layers.append(up_sample_layers)def resnet_block(self):renset_block_layers = []renset_block_layers.append(tf.keras.layers.Conv2D(filters = 256, kernel_size = 3,strides = 1, padding = 'same'))renset_block_layers.append(tfa.layers.InstanceNormalization(axis = -1,center = False,scale = False))renset_block_layers.append(tf.keras.layers.ReLU())renset_block_layers.append(tf.keras.layers.Conv2D(filters = 256, kernel_size = 3,strides = 1, padding = 'same'))renset_block_layers.append(tfa.layers.InstanceNormalization(axis = -1,center = False,scale = False))self.resnet_block_layers.append(renset_block_layers)def call(self, x):x = tf.convert_to_tensor(x, dtype = tf.float32)left_layer_results = []for layers in self.downsample_layers:for layer in layers:x = layer(x)last_layer = xfor layers in self.resnet_block_layers:for layer in layers:#实现残余网络层x = layer(x)x = tf.keras.layers.add([last_layer, x])last_layer = xfor layers in self.upsample_layers:for layer in layers:x = layer(x)for layer in self.last_layers:x = layer(x)return xdef create_variables(self, x): #实例化网络层参数x = np.expand_dims(x, axis = 0)self.call(x)
该网络使用了一种叫ResNet的结构,其具体原理请参看我的视频讲解,最后我们给出网络的训练流程代码实现:
def train_discriminators(self, imgs_A, imgs_B, valid, fake):'''训练discriminator_A识别来自数据集A的图片以及generator_BA伪造的图片,训练discriminoatr_B识别来自数据集B的图片以及generator_AB伪造的图片训练的方法是将图片分成64等分,真实数据每一等分赋值1,伪造数据每一等分赋值0,disriminator接收真实数据后输出每一等分的概率要尽可能接近1,接收伪造数据时输出每一等分的概率要接近0,valid和fake是规格为(64,64)的二维数组,元素分别为1和0'''fake_B = self.generator_AB(imgs_A, training = True)#将来自数据集A的图片伪造成数据集B的图片fake_A = self.generator_BA(imgs_B, training = True)#将来自数据集B的图片伪造成数据集A的图片loss_obj = tf.keras.losses.MSEwith tf.GradientTape(watch_accessed_variables=False) as tape: #训练discriminator_A识别真实图片tape.watch(self.discriminator_A.trainable_variables)d_A = self.discriminator_A(imgs_A, training = True)A_valid_loss = loss_obj(tf.ones_like(d_A), d_A)fake_d_A = self.discriminator_A(fake_A, training = True) A_fake_loss = loss_obj(tf.zeros_like(fake_d_A), fake_d_A)total_loss = (A_fake_loss + A_valid_loss) * 0.5grads = tape.gradient(total_loss, self.discriminator_A.trainable_variables)self.discriminator_A_optimizer.apply_gradients(zip(grads, self.discriminator_A.trainable_variables))with tf.GradientTape(watch_accessed_variables=False) as tape:#训练dicriminator_B识别真实图片tape.watch(self.discriminator_B.trainable_variables)d_B = self.discriminator_B(imgs_B, training = True)B_valid_loss = loss_obj(tf.ones_like(d_B), d_B)fake_d_B = self.discriminator_B(fake_B, training = True)B_fake_loss = loss_obj(tf.zeros_like(fake_d_B), fake_d_B)total_loss = (B_valid_loss + B_fake_loss) * 0.5grads = tape.gradient(total_loss, self.discriminator_B.trainable_variables)self.discriminator_B_optimizer.apply_gradients(zip(grads, self.discriminator_B.trainable_variables))def train_generators(self, imgs_A, imgs_B, valid):'''generator的训练要满足三个层次,1,generator生成的伪造图片要尽可能通过discrimator的识别;2,先由generator_A将来自数据集A的图片伪造成数据集B的图片,然后再将其输入generator_B,所还原的图片要与来自数据集A的图片尽可能相似;3,将来自数据集B的图片输入generator_AB后所得结果要与数据集B的图片尽可能相同,将来自数据集A的图片输入generator_BA后所得结果要尽可能与来自数据集A的数据相同'''loss_obj = tf.keras.losses.MSEwith tf.GradientTape(watch_accessed_variables=False) as tape_A,tf.GradientTape(watch_accessed_variables=False) as tape_B:tape_A.watch(self.generator_AB.trainable_variables)tape_B.watch(self.generator_BA.trainable_variables)fake_B = self.generator_AB(imgs_A, training = True) d_B = self.discriminator_B(fake_B, training = True)fake_B_loss = loss_obj(tf.ones_like(d_B), d_B)fake_A = self.generator_BA(imgs_B, training = True)d_A = self.discriminator_A(fake_A, training = True)fake_A_loss = loss_obj(tf.ones_like(d_A), d_A)#这段对应图17-15所示的运算流程reconstructB = self.generator_AB(fake_A, training = True)#B->A->Breconstruct_B_loss = tf.reduce_mean(tf.abs(reconstructB - imgs_B))reconstructA = self.generator_BA(fake_B, training = True)#A->B->Areconstruct_A_loss = tf.reduce_mean(tf.abs(reconstructA - imgs_A))cycle_loss_BAB = self.reconstruction_weight * reconstruct_B_loss cycle_loss_ABA = self.reconstruction_weight * reconstruct_A_losstotal_cycle_loss = cycle_loss_BAB + cycle_loss_ABA#这里对应图17-16所示的运算流程img_B_id = self.generator_AB(imgs_B, training = True) #B->Bimg_B_identity_loss = tf.reduce_mean(tf.abs(img_B_id - imgs_B))img_A_id = self.generator_BA(imgs_A, training = True) #A->Aimg_A_identity_loss = tf.reduce_mean(tf.abs(img_A_id - imgs_A))#这段对应图17-17所示运算流程generator_AB_loss = self.validation_weight * fake_B_loss + total_cycle_loss + self.identification_weight * img_B_identity_lossgernator_BA_loss = self.validation_weight * fake_A_loss + total_cycle_loss + self.identification_weight * img_A_identity_lossgrads_AB = tape_A.gradient(generator_AB_loss, self.generator_AB.trainable_variables)grads_BA = tape_B.gradient(gernator_BA_loss, self.generator_BA.trainable_variables)self.generator_AB_optimizer.apply_gradients(zip(grads_AB, self.generator_AB.trainable_variables))self.generator_BA_optimizer.apply_gradients(zip(grads_BA, self.generator_BA.trainable_variables))def train(self, data_loader, run_folder, epochs, test_A_file, test_B_file, batch_size = 1, sample_interval = 100):dummy = np.zeros((batch_size, self.patch, self.patch, 1))valid = tf.ones_like(dummy, dtype = tf.float32)fake = tf.zeros_like(dummy, dtype = tf.float32)for epoch in range(epochs):start = time.time()self.epoch = epochbatch_count = 0for batch_i, (imgs_A, imgs_B) in enumerate(data_loader.load_batch()):self.train_discriminators(imgs_A, imgs_B, valid, fake)self.train_generators(imgs_A, imgs_B, valid)if batch_i % sample_interval == 0:self.save_model(run_folder) #存储网络内部参数display.clear_output(wait=True)info = "[Epoch {}/{}/ batch {}]".format(self.epoch, epochs, batch_count)batch_count += 1self.sample_images(data_loader, batch_i, run_folder, test_A_file, test_B_file, info) #显示训练效果
由于篇幅原因,笔者没有将所有实现细节的代码都贴出来,感兴趣的读者请参看我的教学课程。
代码运行后会将大量的男女图片输入两个网络,于是能让网络具备将男人转换为女人,女人转换为男人的能力,我们先看网络将男人变成女人的能力:
其中左边是男性图片,右边是”变性“后的女性图片,比较发现女性特征是脸部表情更柔和,更具有女性的柔软,我们再看看将女性变成男性的结果:
上图效果就更加明显,可以看到的是右边男性面孔脸部轮廓曲线与左边女性基本相同,男性脸部特征就在于皮肤比较粗糙,同时线条比较粗狂和硬朗,从显示结果看,网络具备了将男变女,女变男的超能力。
更详细的讲解和完整代码调试演示过程,请点击链接
更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
这篇关于现代黑科技版“指鹿为马:使用CycleGAN实现男女“无痛变性”的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!