飞道的博客

现代黑科技版“指鹿为马":使用CycleGAN实现男女“无痛变性”

515人阅读  评论(0)

在秦朝末期,奸臣赵高一手遮天,为了显示自己的权势与力量,他在众人面前指着一头鹿说那是马,大家畏惧赵高的权势,明知那是鹿却不得不配合赵高说那是马,这就是经典成语”指鹿为马“的出处。

在光天化日之下,罔顾事实强行将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 = 3
        self.resnet_block_count = 9
        self.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 = x
        for layers in self.resnet_block_layers:
            for layer in layers:#实现残余网络层
                x = layer(x)
            x = tf.keras.layers.add([last_layer, x])
            last_layer = x
        for layers in self.upsample_layers:
            for layer in layers:
                x = layer(x)
        for layer in self.last_layers:
            x = layer(x)
        return x
    def  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.MSE
        with 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.5
        grads = 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.5
        grads = 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.MSE
        with 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->B
            reconstruct_B_loss = tf.reduce_mean(tf.abs(reconstructB - imgs_B))
            reconstructA = self.generator_BA(fake_B, training = True)#A->B->A
            reconstruct_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_loss
            total_cycle_loss = cycle_loss_BAB + cycle_loss_ABA#这里对应图17-16所示的运算流程
            img_B_id = self.generator_AB(imgs_B, training = True) #B->B
            img_B_identity_loss = tf.reduce_mean(tf.abs(img_B_id - imgs_B))
            img_A_id = self.generator_BA(imgs_A, training = True) #A->A
            img_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_loss
            gernator_BA_loss = self.validation_weight * fake_A_loss + total_cycle_loss + self.identification_weight * img_A_identity_loss
        grads_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 = epoch
            batch_count = 0
            for 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 += 1
                    self.sample_images(data_loader, batch_i, run_folder, test_A_file, test_B_file, info) #显示训练效果

由于篇幅原因,笔者没有将所有实现细节的代码都贴出来,感兴趣的读者请参看我的教学课程。

代码运行后会将大量的男女图片输入两个网络,于是能让网络具备将男人转换为女人,女人转换为男人的能力,我们先看网络将男人变成女人的能力:

其中左边是男性图片,右边是”变性“后的女性图片,比较发现女性特征是脸部表情更柔和,更具有女性的柔软,我们再看看将女性变成男性的结果:

上图效果就更加明显,可以看到的是右边男性面孔脸部轮廓曲线与左边女性基本相同,男性脸部特征就在于皮肤比较粗糙,同时线条比较粗狂和硬朗,从显示结果看,网络具备了将男变女,女变男的超能力。

更详细的讲解和完整代码调试演示过程,请点击链接

更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:


转载:https://blog.csdn.net/tyler_download/article/details/106116318
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场