Python 智能项目:1~5(4)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Python 智能项目:1~5(4)

Python 智能项目:1~5(3)https://developer.aliyun.com/article/1426931

可以通过如下运行脚本来调用模型的训练:

python MachineTranslation_word2vec.py --path '/home/santanu/ML_DS_Catalog-/Machine Translation/fra-eng/fra.txt' --epochs 20 --batch_size 32 --latent_dim 128 --num_samples 40000 --outdir '/home/santanu/ML_DS_Catalog-/Machine Translation/' --verbose 1 --mode train --embedding_dim 128

该模型在 GeForce GTX 1070 GPU 上进行了训练,大约需要 9.434 分钟才能训练 32,000 条记录并进行 8,000 条记录的推理。 强烈建议用户使用 GPU,因为 RNN 的计算量很大,可能需要数小时才能在 CPU 上训练相同的模型。

我们可以通过运行 python 脚本MachineTranslation.py来训练机器翻译模型并在保持数据集上执行验证,如下所示:

python MachineTranslation.py --path '/home/santanu/ML_DS_Catalog/Machine Translation/fra-eng/fra.txt' --epochs 20 --batch_size 32 -latent_dim 128 --num_samples 40000 --outdir '/home/santanu/ML_DS_Catalog/Machine Translation/' --verbose 1 --mode train

从嵌入向量方法获得的结果与单热编码词向量的结果相似。 这里提供了一些来自保持数据集推理的翻译:

Input sentence: Where is my book?
Decoded sentence:  Où est mon Tom ?
-
Input sentence: He's a southpaw.
Decoded sentence:  Il est en train de
-
Input sentence: He's a very nice boy.
Decoded sentence:  C'est un très bon
-
Input sentence: We'll be working.
Decoded sentence:  Nous pouvons faire
-
Input sentence: May I have a program?
Decoded sentence:  Puis-je une ? 
-
Input sentence: Can you make it safe?
Decoded sentence:  Peux-tu le faire
-
Input sentence: We walked to my room.
Decoded sentence:  Nous avons devons
-
Input sentence: Don't stand too close.
Decoded sentence:  Ne vous en prie.
-
Input sentence: Where's the dog?
Decoded sentence:  Où est le chien ?
-
Input sentence: He's a hopeless case.
Decoded sentence:  Il est un fait de
-
Input sentence: Where were we?
Decoded sentence:  Où fut ?

总结

读者现在应该对几种机器翻译方法以及神经翻译机器与传统机器有何不同有很好的理解。 现在,我们还应该深入了解如何从头开始构建神经机器翻译系统,以及如何以有趣的方式扩展该系统。 借助提供的信息和实现演示,建议读者探索其他并行语料库数据集。

在本章中,我们定义了嵌入层,但未使用预训练的嵌入(例如 GloVe,FastText 等)来加载它们。 建议读者使用预训练的词向量嵌入为嵌入层加载,并查看是否会产生更好的结果。 在第 4 章,“使用 GAN 进行时装行业中的样式迁移”中,我们将通过与生成性对抗网络(这是现代的革命)进行与时装业中样式迁移有关的项目。 人工智能领域。

四、使用 GAN 的时尚行业样式迁移

样式迁移的概念是指将产品样式渲染为另一种产品的过程。 想象一下,您的一位时尚狂朋友买了一个蓝色的手袋,想买一双类似印花的鞋子。 直到 2016 年,这还是不可能实现的,除非他们与一位时装设计师成为朋友,他们必须首先设计一款鞋子,然后才能批准生产。 然而,随着生成对抗网络的最新进展,这种设计过程可以很容易地进行。

生成对抗网络是通过在生成器网络和判别器网络之间进行零和游戏来学习的网络。 假设一位时装设计师想要设计一种特定结构的手袋,并且正在探索不同的印花。 设计人员可以绘制手提包的结构草图,然后将草图图像输入到生成的对抗网络中,以得出手提包的几种可能的最终印刷品。 这种样式迁移过程可以使客户自己绘制产品设计和图案,而无需征集大量设计师的意见,从而对时尚行业产生巨大影响。 通过推荐具有类似设计和风格的产品来补充客户已经拥有的产品,时装屋也可以从中受益。

在这个项目中,我们将构建一个智能人工智能系统,该系统将生成与给定手提袋样式相似的鞋子,反之亦然。 我们之前讨论的原始 GAN 不足以实现这个项目。 我们需要的是 GAN 的定制版本,例如 DiscoGAN 和 CycleGAN。

在本章中,我们将介绍以下主题:

  • 我们将讨论 DiscoGAN 背后的工作原理和数学基础
  • 我们将比较和对比 DiscoGAN 与 CycleGAN,后者在架构和工作原理上非常相似
  • 我们将训练一个 DiscoGAN,该系统学习从给定的袋子素描中生成袋子的图像
  • 最后,我们将讨论与训练 DiscoGAN 有关的复杂性

技术要求

读者应具有 Python 3 和人工智能的基础知识,才能完成本章中的项目。

本章的代码文件可以在 GitHub 上找到

观看以下视频,查看运行中的代码

DiscoGAN

DiscoGAN 是一个生成的对抗网络,它在给定域A中的图像的情况下生成域B中产品的图像。下图说明了 DisoGAN 网络的架构图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6iUsFnFh-1681653992516)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/ad2f3c06-0854-4f44-871f-d0916c1f0980.png)]

图 4.1:DiscoGAN 的架构图

B中生成的图像在样式和样式上都类似于域A中的图像。 无需在训练过程中显式配对来自两个域的图像就可以学习这种关系。 鉴于项目的配对是一项耗时的任务,因此这是一项非常强大的功能。 在较高的水平上,它尝试学习神经网络G[AB]G[BA]形式的两个生成器函数。 图像x[A],当通过生成器馈入时G[AB],产生图像x[AB],在域B中看起来很真实。此外,当此图像x[AB]通过其他生成器网络G[BA]馈送时,它应产生图像x[ABA],理想情况下应与原始图像x[A]相同。 关于生成器函数,以下关系应成立:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dxW7nznh-1681653992516)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/b12bb426-7c2a-4116-be5b-98a133536497.png)]

但是实际上,生成器函数G[AB]G[BA]不可能彼此相反,因此我们尝试通过选择 L1 或 L2 归一化的损失来尽量减少重建图像和原始图像之间的损失。 L1 规范损失基本上是每个数据点的绝对误差之和,而 L2 规范损失表示每个数据点的平方误差的和。 我们可以如下表示单个图像的 L2 范数损失:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JwY6c8YV-1681653992516)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/09ada4ef-4d12-45db-a6b8-19064db767e9.png)]

仅使前面的损失最小化是不够的。 我们必须确保在域B中创建的图像x[B]看起来逼真。例如,如果我们将域A中的衣服映射到域B中的鞋子,我们将确保x[B]类似于鞋子。 如果图像不够真实,则在域B侧的判别器D[B]将检测为x[B]为假。 鞋子,因此也要考虑与此有关的损失。 通常,在训练过程中,向判别器提供生成的域B图像x[AB] = G[AB](X[A]),我们选择在这里用y[B]表示,以便它学习从假图像中对真实图像进行分类。 您可能还记得,在 GAN 中,生成器和判别器相互进行零和最小最大值游戏,以便不断变得更好,直到达到平衡为止。 如果伪造的图像看起来不够逼真,则判别器将对其进行惩罚,这意味着生成器必须学习产生更好的图像x[AB],如果输入图像x[A]。 考虑到所有这些因素,我们可以将我们希望最小化的生成器损失公式化为重建损失,以及判别器将x[AB]识别为假冒的损失。 第二种损失将试图使生成器在域B中生成逼真的图像。将域A中的图像x[A]映射到域B中的图像的生成器损失可以表示如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JQZp1agh-1681653992516)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/f2cdd9c3-d4d4-4c89-a533-e5044ea37592.png)]

L2 范数下的重建损失可以表示为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eh2XhhF5-1681653992517)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/db0f4732-8246-4e5f-ab9f-d680b444e478.png)]

由于我们正在处理图像,因此可以假设x[A]是所有像素的扁平向量,以符合 L2 规范项。 如果我们假设x[A]是矩阵,则最好将||·||_2^2称为 Frobenius 范数。 但是,这些只是数学术语,实质上,我们只是将原始图像和重建图像之间的像素值差的平方和求和。

让我们考虑一下生成器在使变换后的图像x[AB]追求时要尽量降低成本的做法。 判别器将尝试将图像标记为伪图像,因此生成器G[AB]应当在这种情况下产生x[AB]使其成为假图片的对数损失的方式尽可能小。 如果域B中的判别器D[B]将真实图像标记为1,将伪图像标记为0,则图像真实的概率由D[B](.),则生成器应使x[AB]在判别器网络下极有可能出现,从而使D[B](x[B]) = D[B](G[AB](x[A]))接近1)。 就对数损失而言,生成器应使先前概率的负对数最小化,这基本上使我们得到C[D(AB)],如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jnKt2b3I-1681653992517)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/549bd16c-1bd7-4f76-b4c1-6179369f92c7.png)]

结合(3)(4),我们可以获得将镜像从域A映射到域A的总生成器成本C_G[AB]B,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CAW2wBhF-1681653992517)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/581b9be4-661a-41e9-a9f3-597d1116c2c7.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K55D7diy-1681653992517)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/7d74f982-9649-4c22-a18e-d20e9b27d51c.png)]

最大的问题是,我们可以在这里停下来吗? 由于我们有来自两个域的图像,因此要获得更好的映射,我们也可以从域B拍摄图像,并通过生成器G[BA]将它们映射到域A。 如果我们在域B中拍摄x[B]图像,并通过生成器G[BA]将其转换为图像x[BA],而域A上的标识符由D[A]给出,则与这种转换相关的成本函数由以下给出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IpS8t7Pz-1681653992518)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/e8a983da-5a03-453d-8317-eff82e4164e6.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8BDMEB1B-1681653992518)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/0812b062-6e92-4e91-b725-747ac4b61f31.png)]

如果我们对两个域中的全部图像总数求和,则生成器损失将由(5)(6)之和给出,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wuNVbwrp-1681653992518)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/bd4d5890-a506-4180-b680-4c842cca1286.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3tO0ZBq1-1681653992518)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/bcb0c1db-84a7-44ac-864c-4e7545fb6016.png)]

现在,让我们构建成本函数,这些判别器将尝试最小化以建立零和最小/最大游戏。 每个域中的判别器都会尝试将真实图像与伪图像区分开,因此判别器G[B]会尝试将成本降到最低C_D[B],如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QM7u1fqR-1681653992518)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/c8b74f9b-c8cb-47d4-99e1-00c9b35eef05.png)]

同样,判别器D[A]会尝试将成本降到最低。C_D[A]如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OdriIbyu-1681653992519)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/78578465-7cc8-40c5-9662-21aa263baa81.png)]

结合(8)(9)的总判别器成本由C[D]给出,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RhtQHhIq-1681653992519)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/8907ca7c-ae87-4a4b-80e3-37c4bfb678cc.png)]

如果我们表示G[AB]的参数,则G[BA]D[A]D[B]设为θ[GAB]θ[GBA]θ[DA]θ[DB],则网络的优化参数可以表示为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QDcFadtV-1681653992519)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/3d609715-cecb-4823-8ce5-87fda4d8846f.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SurlmLi7-1681653992519)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/4937092d-eabc-4bc6-80ec-9e836b4f128e.png)]

对成本函数执行随机梯度下降(例如 Adam),以得出最优解。 请注意,如前所述,生成对抗网络的解决方案是优化成本函数的一个障碍。

CycleGAN

CycleGAN 从根本上类似于 DiscoGAN,但有一个小的修改。 在 CycleGAN 中,我们可以灵活地确定相对于 GAN 损失或归因于判别器的损失,为重建损失分配多少权重。 该参数有助于根据眼前的问题按正确比例平衡损失,以帮助网络在训练时更快地收敛。 CycleGAN 的其余实现与 DiscoGAN 相同。

学习从草绘的轮廓生成自然手袋

在本章中,我们将使用草绘的轮廓生成手袋,而无需使用 DiscoGAN 进行显式配对。 我们将草图图像表示为属于域A,而将自然手袋图像表示为属于域B。将有两种生成器:一种生成器,用于获取域A的图像并将其映射到在域B下看起来逼真的图像,以及另一个与此相反:将域B中的手袋图像映射到在域A下看起来很逼真的图像。判别器将尝试从每个域中真实图像的生成器中识别生成器生成的虚假图像。 生成器和判别器将相互进行 minimax 零和游戏。

要训练该网络,我们将需要两套图像,手袋的草图或轮廓以及手袋的自然图像。 可以从以下链接下载图像

在接下来的几节中,我们将完成在 TensorFlow 中定义 DiscoGAN 网络的过程,然后训练它使用充当图像边缘的手提包草图来生成逼真的手提包图像。 我们将从定义生成器网络的架构开始。

预处理图像

edges2handbags数据集文件夹中的每个图像在同一图像中包含bag的图片和bag edges的图片。 为了训练网络,我们需要将它们分离为属于我们在 DiscoGAN 架构中讨论过的两个域A和 B 的图像。 通过使用以下代码(image_split.py),可以将图像分为域A和域B图像:

# -*- coding: utf-8 -*-
"""
Created on Fri Apr 13 00:10:12 2018
@author: santanu
"""
import numpy as np
import os
from scipy.misc import imread
from scipy.misc import imsave
import fire
from elapsedtimer import ElapsedTimer
from pathlib import Path
import shutil 
'''
Process the images in Domain A and Domain and resize appropriately
Inputs contain the Domain A and Domain B image in the same image
This program will break them up and store them in their respecective folder
'''
def process_data(path,_dir_):
    os.chdir(path)
    try: 
        os.makedirs('trainA')
    except:
        print(f'Folder trainA already present, cleaning up and recreating empty folder trainA')
        try:
            os.rmdir('trainA')
        except:
            shutil.rmtree('trainA')
        os.makedirs('trainA')
    try: 
        os.makedirs('trainB')
    except:
        print(f'Folder trainA already present, cleaning up and recreating empty folder trainB')
        try:
            os.rmdir('trainB')
        except:
            shutil.rmtree('trainB')
        os.makedirs('trainB')
    path = Path(path) 
    files = os.listdir(path /_dir_)
    print('Images to process:', len(files))
    i = 0
    for f in files:
        i+=1 
        img = imread(path / _dir_ / str(f))
        w,h,d = img.shape
        h_ = int(h/2)
        img_A = img[:,:h_]
        img_B = img[:,h_:]
        imsave(f'{path}/trainA/{str(f)}_A.jpg',img_A)
        imsave(f'{path}/trainB/{str(f)}_B.jpg',img_A)
        if ((i % 10000) == 0 & (i >= 10000)):
            print(f'the number of input images processed : {i}')
    files_A = os.listdir(path / 'trainA')
    files_B = os.listdir(path / 'trainB')
    print(f'No of images written to {path}/trainA is {len(files_A)}')
    print(f'No of images written to {path}/trainA is {len(files_B)}')
with ElapsedTimer('process Domain A and Domain B Images'):
    fire.Fire(process_data)

image_split.py代码可以按以下方式调用:

python image_split.py --path /media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/edges2handbags/ --_dir_ train

输出日志如下:

Folder trainA already present, cleaning up and recreating empty folder trainA
Folder trainA already present, cleaning up and recreating empty folder trainB
Images to process: 138569 the number of input images processed : 10000
the number of input images processed : 20000
the number of input images processed : 30000
.....

DiscoGAN 的生成器

DiscoGAN 的生成器是前馈卷积神经网络,其中输入和输出是图像。 在网络的第一部分中,图像在空间维度上按比例缩小,而输出特征映射的数量随层的进展而增加。 在网络的第二部分中,图像沿空间维度按比例放大,而输出特征映射的数量则逐层减少。 在最终输出层中,将生成具有与输入相同的空间大小的图像。 如果生成器将图像x[A]转换为x[AB]从域A到域B表示为G[AB],则我们有x[AB] = G[AB](x[A])

此处显示的是build_generator函数,我们可以使用它来构建 DiscoGAN 网络的生成器:

def build_generator(self,image,reuse=False,name='generator'):
    with tf.variable_scope(name):
        if reuse:
            tf.get_variable_scope().reuse_variables()
        else:
            assert tf.get_variable_scope().reuse is False
            """U-Net generator"""
        def lrelu(x, alpha,name='lrelu'):
            with tf.variable_scope(name):
                return tf.nn.relu(x) - alpha * tf.nn.relu(-x)
    """Layers used during downsampling"""
        def common_conv2d(layer_input,filters,f_size=4,
                          stride=2,padding='SAME',norm=True,
                          name='common_conv2d'):
            with tf.variable_scope(name):
                if reuse:
                    tf.get_variable_scope().reuse_variables()
                else:
                    assert tf.get_variable_scope().reuse is False
                d = 
               tf.contrib.layers.conv2d(layer_input,filters,
                                        kernel_size=f_size,
                                        stride=stride,padding=padding)
                if norm:
                    d = tf.contrib.layers.batch_norm(d)
                d = lrelu(d,alpha=0.2)
                return d
         """Layers used during upsampling"""
       def common_deconv2d(layer_input,filters,f_size=4,
                           stride=2,padding='SAME',dropout_rate=0,
                           name='common_deconv2d'):
            with tf.variable_scope(name):
                if reuse:
                    tf.get_variable_scope().reuse_variables()
                else:
                    assert tf.get_variable_scope().reuse is False
                u = 
                tf.contrib.layers.conv2d_transpose(layer_input,
                                                   filters,f_size,
                                                   stride=stride,
                                                   padding=padding)
                if dropout_rate:
                    u = tf.contrib.layers.dropout(u,keep_prob=dropout_rate)
                u = tf.contrib.layers.batch_norm(u)
                u = tf.nn.relu(u)
                return u 
        # Downsampling
        #  64x64 -> 32x32
        dwn1 = common_conv2d(image,self.gf,stride=2,norm=False,name='dwn1') 
        #  32x32 -> 16x16
       dwn2 = common_conv2d(dwn1,self.gf*2,stride=2,name='dwn2')           
        #  16x16   -> 8x8
       dwn3 = common_conv2d(dwn2,self.gf*4,stride=2,name='dwn3')           
        #  8x8   -> 4x4 
       dwn4 = common_conv2d(dwn3,self.gf*8,stride=2,name='dwn4')            
        #  4x4   -> 1x1 
       dwn5 = common_conv2d(dwn4,100,stride=1,padding='valid',name='dwn5') 
        # Upsampling
        #  4x4    -> 4x4
        up1 = 
       common_deconv2d(dwn5,self.gf*8,stride=1,
                       padding='valid',name='up1')      
        #  4x4    -> 8x8
        up2 = common_deconv2d(up1,self.gf*4,name='up2')                  
        #  8x8    -> 16x16
        up3 = common_deconv2d(up2,self.gf*2,name='up3')                  
        #  16x16    -> 32x32 
        up4 = common_deconv2d(up3,self.gf,name='up4')                    
       out_img = tf.contrib.layers.conv2d_transpose(up4,self.channels,
                                                    kernel_size=4,stride=2,                                                                                                                                      
                                                    padding='SAME',
                                                    activation_fn=tf.nn.tanh) 
       # 32x32 -> 64x64
        return out_img

在生成器函数中,我们定义了 LReLU 激活函数,并使用0.2的泄漏因子。 我们还定义了卷积层生成函数common_conv2d(用于对图像进行下采样)和common_deconv2d(用于将经降采样的图像上采样至其原始空间大小)。

我们通过使用tf.get_variable_scope().reuse_variables()使用reuse选项定义生成器函数。 当多次调用同一个生成器函数时,重用选项可确保我们重用特定生成器使用的相同变量。 当我们删除重用选项时,我们为生成器创建了一组新的变量。

例如,我们可能使用生成器函数创建了两个生成器网络,因此在第一次创建这些网络时不会使用reuse选项。 如果再次引用该生成器函数,则使用reuse选项。 卷积(下采样)和解卷积(上采样)期间的激活函数是 LReLU,然后进行批量归一化,以实现稳定和快速的收敛。

网络不同层中的输出特征映射的数量可以是self.gf 或其倍数。 对于我们的 DiscoGAN 网络,我们选择了self.gf作为64

生成器中要注意的一件事是输出层的tanh激活函数。 这样可以确保生成器生成的图像的像素值在[-1, +1]的范围内。 这对于输入图像具有[-1, +1]范围内的像素强度非常重要,这可以通过对像素强度进行简单的逐元素变换来实现,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ltgSG6sX-1681653992520)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/7d1812c6-e792-454e-a693-76421c5d5d91.png)]

同样,要将图像转换为可显示的 0-255 像素强度格式,我们需要应用逆变换,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eBevT0Xs-1681653992520)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/118fba60-e8de-4a9c-8ffa-bcd85c456a21.png)]

DiscoGAN 的判别器

DiscoGAN 的判别器将学会在特定域中将真实图像与假图像区分开。 我们将有两个判别器:一个用于域A,一个用于域B。这些判别器也是可以执行二分类的卷积网络。 与传统的基于分类的卷积网络不同,判别器没有任何全连接层。 使用步长为 2 的卷积对输入图像进行下采样,直到最终层(输出为1 x 1)为止。同样,我们使用 LReLU 作为激活函数并使用批量归一化以实现稳定和快速的收敛。 以下代码显示了 TensorFlow 中判别器构建函数的实现:

def build_discriminator(self,image,reuse=False,name='discriminator'):
    with tf.variable_scope(name):
        if reuse:
            tf.get_variable_scope().reuse_variables()
        else:
            assert tf.get_variable_scope().reuse is False
        def lrelu(x, alpha,name='lrelu'):
            with tf.variable_scope(name):
                if reuse:
                    tf.get_variable_scope().reuse_variables()
                else:
                    assert tf.get_variable_scope().reuse is False
            return tf.nn.relu(x) - alpha * tf.nn.relu(-x)
                """Discriminator layer"""
        def d_layer(layer_input,filters,f_size=4,stride=2,norm=True,
                    name='d_layer'):
            with tf.variable_scope(name):
                if reuse:
                    tf.get_variable_scope().reuse_variables()
                else:
                    assert tf.get_variable_scope().reuse is False
                d = 
                tf.contrib.layers.conv2d(layer_input,
                                         filters,kernel_size=f_size,
                                         stride=2, padding='SAME')
                if norm:
                    d = tf.contrib.layers.batch_norm(d)
                d = lrelu(d,alpha=0.2)
                return d
        #64x64 -> 32x32        
        down1 = d_layer(image,self.df, norm=False,name='down1')  
        #32x32 -> 16x16
        down2 = d_layer(down1,self.df*2,name='down2')         
        #16x16 -> 8x8
        down3 = d_layer(down2,self.df*4,name='down3')         
        #8x8 -> 4x4
        down4 = d_layer(down3,self.df*8,name='down4')        
        #4x4 -> 1x1
        down5  = 
       tf.contrib.layers.conv2d(down4,1,kernel_size=4,stride=1,
                                padding='valid')
        return down5

判别器网络不同层中输出特征映射的数量为self.df或其倍数。 对于我们的网络,我们将self.df设为64

建立网络并定义成本函数

在本节中,我们将使用生成器和鉴别函数来构建整个网络,并定义在训练过程中要优化的成本函数。 TensorFlow 代码如下:

def build_network(self):
    def squared_loss(y_pred,labels):
        return tf.reduce_mean((y_pred - labels)**2)
   def abs_loss(y_pred,labels):
        return tf.reduce_mean(tf.abs(y_pred - labels))  
   def binary_cross_entropy_loss(logits,labels):
        return tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(
                                        labels=labels,logits=logits))
     self.images_real = tf.placeholder(tf.float32,[None,self.image_size,self.image_size,self.input_dim + self.output_dim])
    self.image_real_A = self.images_real[:,:,:,:self.input_dim]
    self.image_real_B = 
    self.images_real[:,:,:,self.input_dim:self.input_dim + self.output_dim]
    self.images_fake_B = 
    self.build_generator(self.image_real_A,
                         reuse=False,name='generator_AB')
    self.images_fake_A = 
    self.build_generator(self.images_fake_B,
                         reuse=False,name='generator_BA')
    self.images_fake_A_ = 
    self.build_generator(self.image_real_B,
                         reuse=True,name='generator_BA')
    self.images_fake_B_ = 
    self.build_generator(self.images_fake_A_,
                         reuse=True,name='generator_AB')
    self.D_B_fake = 
    self.build_discriminator(self.images_fake_B ,
                             reuse=False, name="discriminatorB")
    self.D_A_fake = 
    self.build_discriminator(self.images_fake_A_,
                             reuse=False, name="discriminatorA") 
    self.D_B_real = 
    self.build_discriminator(self.image_real_B,
                             reuse=True, name="discriminatorB")
    self.D_A_real = 
    self.build_discriminator(self.image_real_A,
                             reuse=True, name="discriminatorA")
    self.loss_GABA = 
    self.lambda_l2*squared_loss(self.images_fake_A,self.image_real_A) +
    binary_cross_entropy_loss(labels=tf.ones_like(self.D_B_fake),
    logits=self.D_B_fake)
    self.loss_GBAB = 
    self.lambda_l2*squared_loss(self.images_fake_B_,
    self.image_real_B) + 
    binary_cross_entropy_loss(labels=tf.ones_like(self.D_A_fake),
    logits=self.D_A_fake)
    self.generator_loss = self.loss_GABA + self.loss_GBAB
    self.D_B_loss_real =     
    binary_cross_entropy_loss(tf.ones_like(self.D_B_real),self.D_B_real)
    self.D_B_loss_fake = 
    binary_cross_entropy_loss(tf.zeros_like(self.D_B_fake),self.D_B_fake)
    self.D_B_loss = (self.D_B_loss_real + self.D_B_loss_fake) / 2.0
    self.D_A_loss_real = 
    binary_cross_entropy_loss(tf.ones_like(self.D_A_real),self.D_A_real)
    self.D_A_loss_fake = 
    binary_cross_entropy_loss(tf.zeros_like(self.D_A_fake),self.D_A_fake)
    self.D_A_loss = (self.D_A_loss_real + self.D_A_loss_fake) / 2.0
    self.discriminator_loss = self.D_B_loss + self.D_A_loss
    self.loss_GABA_sum = tf.summary.scalar("g_loss_a2b", self.loss_GABA)
    self.loss_GBAB_sum = tf.summary.scalar("g_loss_b2a", self.loss_GBAB)
    self.g_total_loss_sum = tf.summary.scalar("g_loss", self.generator_loss)
    self.g_sum = tf.summary.merge([self.loss_GABA_sum,
                                   self.loss_GBAB_sum,self.g_total_loss_sum])
    self.loss_db_sum = tf.summary.scalar("db_loss", self.D_B_loss)
    self.loss_da_sum = tf.summary.scalar("da_loss", self.D_A_loss)
    self.loss_d_sum = tf.summary.scalar("d_loss",self.discriminator_loss)
    self.db_loss_real_sum = tf.summary.scalar("db_loss_real", self.D_B_loss_real)
    self.db_loss_fake_sum = tf.summary.scalar("db_loss_fake", self.D_B_loss_fake)
    self.da_loss_real_sum = tf.summary.scalar("da_loss_real", self.D_A_loss_real)
    self.da_loss_fake_sum = tf.summary.scalar("da_loss_fake", self.D_A_loss_fake)
    self.d_sum = tf.summary.merge(
            [self.loss_da_sum, self.da_loss_real_sum, self.da_loss_fake_sum,
             self.loss_db_sum, self.db_loss_real_sum, self.db_loss_fake_sum,
             self.loss_d_sum]
        )
    trainable_variables = tf.trainable_variables()
    self.d_variables = 
    [var for var in trainable_variables if 'discriminator' in var.name]
    self.g_variables =
    [var for var in trainable_variables if 'generator' in var.name]
    print ('Variable printing start :'  )
    for var in self.d_variables: 
        print(var.name)
    self.test_image_A = 
    tf.placeholder(tf.float32,[None, self.image_size,
                   self.image_size,self.input_dim], name='test_A')
    self.test_image_B =
    tf.placeholder(tf.float32,[None, self.image_size,
                   self.image_size,self.output_c_dim], name='test_B')
    self.saver = tf.train.Saver()

在构建网络中,我们首先定义 L2 范数误差和二进制交叉熵误差的成本函数。 L2 范数误差将用作重建损失,而二元互熵将用作判别器损失。 然后,我们使用生成器函数为两个域中的图像定义占位符,并为每个域中的伪图像定义 TensorFlow 操作。 我们还通过传递特定于域的伪造和真实图像来定义判别器输出的操作。 除此之外,我们为两个域中的每个域中的重建图像定义 TensorFlow 操作。

一旦定义了操作,我们就可以使用它们来计算损失函数,同时考虑图像的重建损失和归因于判别器的损失。 请注意,我们使用了相同的生成器函数来定义域AB的生成器,也定义了从BA的生成器。唯一不同的是为这两个网络提供了不同的名称:generator_ABgenerator_BA。 由于变量作用域定义为name,所以这两个生成器都将具有不同的权重集,并以提供的名称为前缀。

下表显示了我们需要跟踪的不同损失变量。 就生成器或判别器的参数而言,所有这些损失都需要最小化:

不同损失的变量 说明
self.D_B_loss_real 在对域B中的真实图像进行分类时,判别器D[B]的二进制交叉熵损失。(相对于判别器D[B]的参数,该损失应最小化)
self.D_B_loss_fake 在对域B中的伪造图像进行分类时,判别器D[B]的二进制交叉熵损失。(相对于判别器D[B]的参数,该损失应最小化)
self.D_A_loss_real 在对域A中的真实图像进行分类时,判别器D[A]的二进制交叉熵损失。(相对于判别器D[A]的参数,该损失应最小化)
self.D_A_loss_fake 在对域A中的伪造图像进行分类时,判别器D[A]的二进制交叉熵损失。(相对于判别器D[A]的参数,该损失应最小化)
self.loss_GABA 通过两个生成器G[AB]G[BA]将域A中的图像映射到B,然后再映射回A的重建损失,加上假图片G[AB](x[A])的二进制交叉熵,由域B中的判别器标记为真实图像。(相对于生成器G[AB]G[BA]的参数,该损失应最小化)
self.loss_GBAB 通过两个生成器G[BA]G[AB]将域B中的图像映射到A,然后再映射回B的重建损失,加上伪图片G[BA](x[B])的二元交叉熵,由域A中的判别器标记为真实图像。(相对于生成器G[AB]G[BA]的参数,该损失应最小化)

前四个损失组成了判别器损失,需要根据判别器的参数将其最小化。 ]。 最后两个损失组成了生成器损失,需要根据生成器的参数将其最小化。 。

损失变量通过tf.summary.scaler 与 TensorBoard 绑定,以便可以在训练过程中监控这些损失,以确保以期望的方式减少损失。 稍后,我们将在训练进行时在 TensorBoard 中看到这些损失痕迹。

建立训练流程

train_network函数中,我们首先为生成器和判别器损失函数定义优化器。 我们将 Adam 优化器用于生成器和判别器,因为这是随机梯度下降优化器的高级版本,在训练 GAN 方面非常有效。 亚当使用梯度的衰减平均值(非常类似于稳定梯度的动量)和平方梯度的衰减平均值,以提供有关成本函数曲率的信息。 与tf.summary定义的不同损失有关的变量将写入日志文件,因此可以通过 TensorBoard 进行监视。 以下是train函数的详细代码:

def train_network(self):
        self.learning_rate = tf.placeholder(tf.float32)
        self.d_optimizer = tf.train.AdamOptimizer(self.learning_rate,beta1=self.beta1,beta2=self.beta2).minimize(self.discriminator_loss,var_list=self.d_variables)
        self.g_optimizer = tf.train.AdamOptimizer(self.learning_rate,beta1=self.beta1,beta2=self.beta2).minimize(self.generator_loss,var_list=self.g_variables) 
        self.init_op = tf.global_variables_initializer()
        self.sess = tf.Session()
        self.sess.run(self.init_op)
        #self.dataset_dir = '/home/santanu/Downloads/DiscoGAN/edges2handbags/train/'
        self.writer = tf.summary.FileWriter("./logs", self.sess.graph)
        count = 1
        start_time = time.time()
        for epoch in range(self.epoch):
            data_A = os.listdir(self.dataset_dir + 'trainA/')
            data_B = os.listdir(self.dataset_dir + 'trainB/')
            data_A = [ (self.dataset_dir + 'trainA/' + str(file_name)) for file_name in data_A ] 
            data_B = [ (self.dataset_dir + 'trainB/' + str(file_name)) for file_name in data_B ] 
            np.random.shuffle(data_A)
            np.random.shuffle(data_B)
            batch_ids = min(min(len(data_A), len(data_B)), self.train_size) // self.batch_size
            lr = self.l_r if epoch < self.epoch_step else self.l_r*(self.epoch-epoch)/(self.epoch-self.epoch_step)
            for id_ in range(0, batch_ids):
                batch_files = list(zip(data_A[id_ * self.batch_size:(id_ + 1) * self.batch_size],
                                      data_B[id_ * self.batch_size:(id_ + 1) * self.batch_size]))
                batch_images = [load_train_data(batch_file, self.load_size, self.fine_size) for batch_file in batch_files]
                batch_images = np.array(batch_images).astype(np.float32)
                    # Update G network and record fake outputs
                fake_A, fake_B, _, summary_str = self.sess.run(
                        [self.images_fake_A_,self.images_fake_B,self.g_optimizer,self.g_sum],
                        feed_dict={self.images_real: batch_images, self.learning_rate:lr})
                self.writer.add_summary(summary_str, count)
                [fake_A,fake_B] = self.pool([fake_A, fake_B])
                    # Update D network
                _, summary_str = self.sess.run(
                        [self.d_optimizer,self.d_sum],
                        feed_dict={self.images_real: batch_images,
                               # self.fake_A_sample: fake_A,
                               # self.fake_B_sample: fake_B,
                                   self.learning_rate: lr})
                self.writer.add_summary(summary_str, count)
                count += 1
                print(("Epoch: [%2d] [%4d/%4d] time: %4.4f" % (
                        epoch, id_, batch_ids, time.time() - start_time)))
                if count % self.print_freq == 1:
                    self.sample_model(self.sample_dir, epoch, id_)
                if count % self.save_freq == 2:
                    self.save_model(self.checkpoint_dir, count)

正如我们在代码末尾看到的那样,在训练期间会不时调用sample_model函数,以根据来自另一个域的输入图像来检查在一个域中生成的图像的质量。 还基于save_freq定期保存模型。

我们在前面的代码中引用了sample_model函数和save_model函数,以供参考:

def sample_model(self, sample_dir, epoch, id_):
    if not os.path.exists(sample_dir):
        os.makedirs(sample_dir)
    data_A = os.listdir(self.dataset_dir + 'trainA/')
    data_B = os.listdir(self.dataset_dir + 'trainB/') 
    data_A = [ (self.dataset_dir + 'trainA/' + str(file_name)) for 
              file_name in data_A ]
    data_B = [ (self.dataset_dir + 'trainB/' + str(file_name)) for 
              file_name in data_B ]
   np.random.shuffle(data_A)
    np.random.shuffle(data_B)
    batch_files = 
    list(zip(data_A[:self.batch_size], data_B[:self.batch_size]))
    sample_images = 
    [load_train_data(batch_file, is_testing=True) for 
     batch_file in batch_files]
    sample_images = np.array(sample_images).astype(np.float32)
    fake_A, fake_B = self.sess.run(
            [self.images_fake_A_,self.images_fake_B],
            feed_dict={self.images_real: sample_images}
        )
    save_images(fake_A, [self.batch_size, 1],
                    './{}/A_{:02d}_{:04d}.jpg'.format(sample_dir, epoch, id_))
    save_images(fake_B, [self.batch_size, 1],
                    './{}/B_{:02d}_{:04d}.jpg'.format(sample_dir, epoch, id_))

在此sample_model函数中,从域A随机选择的图像被拍摄并馈送到生成器G[AB],以在域B中生成图像。类似地,从域B随机选择的图像馈送到生成器G[BA]中,以在域A中生成图像。这些输出图像由两个生成器在不同周期生成,并将批量保存在样本文件夹中,来查看生成器在训练过程中是否随着时间的推移而不断改进,以产生更好的图像质量。

使用 TensorFlow 保存器功能保存模型的save_model函数如下所示:

def save_model(self,checkpoint_dir,step):
    model_name = "discogan.model"
    model_dir = "%s_%s" % (self.dataset_dir, self.image_size)
    checkpoint_dir = os.path.join(checkpoint_dir, model_dir)
    if not os.path.exists(checkpoint_dir):
        os.makedirs(checkpoint_dir)
    self.t(self.sess,
                    os.path.join(checkpoint_dir, model_name),
                    global_step=step)

GAN 训练的重要参数值

在本节中,我们将讨论用于训练 DiscoGAN 的不同参数值。 下表中列出了这些:

| 参数名称 | 变量名称和值集 | 原理 |

| Adam 优化器的学习率 | self.l_r = 2e-4 | 我们应该始终训练学习率较低的 GAN 网络以获得更好的稳定性,而 DiscoGAN 也不例外。 |

| Adam 优化器的衰减率 | self.beta1 = 0.5self.beta2 = 0.99 | 参数beta1定义梯度的衰减平均值,而参数beta2定义梯度平方的衰减平均值。 |

| 周期 | self.epoch = 200 | 在此实现中,200周期足以满足 DiscoGAN 网络的收敛要求。 |

| 批量大小 | self.batch_size = 64 | 64的批量大小非常适合此实现。 但是,由于资源限制,我们可能不得不选择较小的批量大小。 |

| 学习率线性下降的周期 | epoch_step = 10 | 在epoch_step指定的周期数之后,学习率呈线性下降,由以下方案确定:lr = self.l_r if epoch < self.epoch_step else self.l_r*(self.epoch-epoch)/(self.epoch-self.epoch_step) |

调用训练

我们前面说明的所有函数都是在DiscoGAN()类内创建的,并在__init__ 函数中声明了重要的参数值,如以下代码块所示。 训练网络时仅需要传递的两个参数是dataset_dir和需要对其进行训练的epochs的数量

def __init__(self,dataset_dir,epochs=200):
        # Input shape
        self.dataset_dir = dataset_dir
        self.lambda_l2 = 1.0
        self.image_size = 64
        self.input_dim = 3
        self.output_dim = 3
        self.batch_size = 64 
        self.df = 64
        self.gf = 64
        self.channels = 3
        self.output_c_dim = 3
        self.l_r = 2e-4
        self.beta1 = 0.5
        self.beta2 = 0.99
        self.weight_decay = 0.00001
        self.epoch = epochs
        self.train_size = 10000
        self.epoch_step = 10
        self.load_size = 64
        self.fine_size = 64 
        self.checkpoint_dir = 'checkpoint'
        self.sample_dir = 'sample'
        self.print_freq = 5
        self.save_freq = 10 
        self.pool = ImagePool()
        return None

现在我们已经定义了训练模型所需的所有内容,我们可以通过process_main 函数调用训练,如下所示:

def process_main(self):
        self.build_network()
        self.train_network()

我们之前为训练所展示的端到端代码在脚本cycledGAN_edges_to_bags.py中。 我们可以通过运行 python 脚本cycledGAN_edges_to_bags.py来训练模型,如下所示:

python cycledGAN_edges_to_bags.py process_main  --dataset_dir /media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/edges2handbags/ epochs 100

脚本cycledGAN_edges_to_bags.py执行的输出日志如下:

Epoch: [ 0] [ 0/ 156] time: 3.0835
Epoch: [ 0] [ 1/ 156] time: 3.9093
Epoch: [ 0] [ 2/ 156] time: 4.3661
Epoch: [ 0] [ 3/ 156] time: 4.8208
Epoch: [ 0] [ 4/ 156] time: 5.2821
Epoch: [ 0] [ 5/ 156] time: 6.2380
Epoch: [ 0] [ 6/ 156] time: 6.6960
Epoch: [ 0] [ 7/ 156] time: 7.1528
Epoch: [ 0] [ 8/ 156] time: 7.6138
Epoch: [ 0] [ 9/ 156] time: 8.0732
Epoch: [ 0] [ 10/ 156] time: 8.8163
Epoch: [ 0] [ 11/ 156] time: 9.6669
Epoch: [ 0] [ 12/ 156] time: 10.1256
Epoch: [ 0] [ 13/ 156] time: 10.5846
Epoch: [ 0] [ 14/ 156] time: 11.0427
Epoch: [ 0] [ 15/ 156] time: 11.9135
Epoch: [ 0] [ 16/ 156] time: 12.3712
Epoch: [ 0] [ 17/ 156] time: 12.8290
Epoch: [ 0] [ 18/ 156] time: 13.2899
Epoch: [ 0] [ 19/ 156] time: 13.7525
.......

监控生成器和判别器损失

可以在 TensorBoard 仪表板中监控损失。 TensorBoard 仪表板可以按以下方式调用:

  1. 在终端上,运行以下命令:
tensorboard --logdir=./logs

./logs是特定于该程序的 Tensorboard 日志的存储目标,应在程序中定义如下:

self.writer = tf.summary.FileWriter("./logs", self.sess.graph)
  1. 执行完步骤 1 中的命令后,导航到 TensorBoard 的localhost:6006站点:

以下屏幕快照中展示了在项目中实现的 DiscoGAN 训练期间在 TensorBoard 中查看的一些生成器和判别器损失的痕迹:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wMQRiasT-1681653992520)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/8c1362f9-bf37-4c56-ac05-30bb6d00a579.png)]

图 4.2:Tensorboard Scalars部分包含不同损失的迹线

以下屏幕截图显示了随着训练的进行,域A中判别器的损失成分:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OgZnUOvk-1681653992520)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/a02c2ba3-678a-4f01-8f16-0addf12d379e.png)]

图 4.3:域A中的判别器损失

从前面的屏幕截图中,我们可以看到不同批量中域A中判别器的损失。 da_lossda_loss_realda_loss_fake损失的总和。 da_loss_real稳步下降,这是因为判别器易于学会识别域A中的真实图像,而虚假图像的损失则稳定在 0.69 左右,这是二分类器输出时您可以期望的logloss 具有1/2概率的类。 发生这种情况是因为生成器也在同时学习以使伪图像看起来逼真,因此使鉴别人员很难轻松地将生成器图像分类为伪图像。 域B上的判别器的损失曲线看起来与域A的上一幅屏幕快照所示的相似。

现在让我们看一下生成器的损失曲线,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ObN24vkZ-1681653992520)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/70ea99da-3268-4b40-a8df-225aed87f8b3.png)]

图 4.4:DiscoGAN 生成器的损失曲线

g_loss_a2b 是从域A到域B以及从域B反向重构图像的组合生成器损失,也是与使变换后的图像在B域中看起来逼真相关的二进制交叉熵损失。 g_loss_b2a是从域B到域A以及从域A重建图像的组合生成器损失,也是与使变换后的图像在A域中看起来逼真相关的二进制交叉熵损失。这两种损失曲线及其总和随着批量的进行不断减少,正如我们从上一个屏幕快照中的 TensorBoard 视觉图中看到的那样。

由于训练生成对抗网络通常非常棘手,因此监视其损失概况的进度以了解训练是否按预期进行是有意义的。

DiscoGAN 生成的样本图像

在本章结束时,让我们看一下 DiscoGAN 在两个域中生成的一些图像:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZmGrFTWS-1681653992521)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/4feced13-0f3d-4f42-b954-41c050233276.png)]

图 4.5:根据草图生成的手提包图像

以下屏幕截图包含手提包草图的生成图像(域A

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uyYEEMlF-1681653992521)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/b4acc286-1586-405f-b2a9-52123d6b7c5a.png)]

图 4.6:根据手提包图像生成的草图

我们可以看到 DiscoGAN 在将任一域中的图像转换为另一域中的高质量逼真的图像方面做得非常出色。

总结

现在,我们到了本章的结尾。 您现在应该精通 DiscoGAN 的技术知识和实现复杂性。 我们在本章中探讨的概念可用于实现各种生成性对抗性网络,这些网络具有适合当前问题的细微变化。 DiscoGAN 网络的端到端实现位于 GitHub 存储库中,位于这里

在第 5 章,“视频字幕应用”中,我们将研究视频到文本翻译应用,它们属于人工智能领域的专家系统。

五、视频字幕应用

随着视频制作速度成倍增长,视频已成为一种重要的沟通媒介。 但是,由于缺乏适当的字幕,视频仍无法吸引更多的观众。

视频字幕(翻译视频以生成有意义的内容摘要的艺术)在计算机视觉和机器学习领域是一项具有挑战性的任务。 传统的视频字幕制作方法并没有成功。 但是,随着最近借助深度学习在人工智能方面的发展,视频字幕最近引起了极大的关注。 卷积神经网络以及循环神经网络的强大功能使得构建端到端企业级视频字幕系统成为可能。 卷积神经网络处理视频中的图像帧以提取重要特征,这些特征由循环神经网络依次处理以生成有意义的视频摘要。 视频字幕系统的一些重要应用如下:

  • 自动监控工厂安全措施
  • 根据通过视频字幕获得的内容对视频进行聚类
  • 银行,医院和其他公共场所的更好的安全系统
  • 网站中的视频搜索,以便提供更好的用户体验

通过深度学习构建智能视频字幕系统主要需要两种类型的数据:视频和文本字幕,它们是用于训练端到端系统的标签。

作为本章的一部分,我们将讨论以下内容:

  • 讨论 CNN 和 LSTM 在视频字幕中的作用
  • 探索序列到序列视频字幕系统的架构
  • 利用序列到序列的架构,构建视频到文本的视频字幕系统

在下一节中,我们将介绍如何使用卷积神经网络和循环神经网络的 LSTM 版本来构建端到端视频字幕系统。

技术要求

您将需要具备 Python 3,TensorFlow,Keras 和 OpenCV 的基础知识。

本章的代码文件可以在 GitHub 上找到

观看以下视频,查看运行中的代码

视频字幕中的 CNN 和 LSTM

视频减去音频后,可以认为是按顺序排列的图像集合。 可以使用针对特定图像分类问题训练的卷积神经网络(例如 ImageNet)从这些图像中提取重要特征。 预训练网络的最后一个全连接层的激活可用于从视频的顺序采样图像中得出特征。 从视频顺序采样图像的频率速率取决于视频中内容的类型,可以通过训练进行优化。

下图(“图 5.1”)说明了用于从视频中提取特征的预训练神经网络:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OeafbBJZ-1681653992521)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/079370cc-0b47-4578-bf5e-3fceaaba687f.png)]

图 5.1:使用预训练的神经网络提取视频图像特征

从上图可以看出,从视频中顺序采样的图像经过预训练的卷积神经网络,并且输出最后一个全连接层中的4,096单元的激活。 如果将t时的视频图像表示为x[t],并且最后一个全连接层的输出表示为f[t] ∈ R^4096,然后f[t] = f[w](x[t])。 此处,W表示直到最后一个全连接层的卷积神经网络的权重。

这些序列的输出函数f[1], f[2], ..., f[t], ..., f[n]可以作为循环神经网络的输入,该神经网络学习根据输入特征生成文本标题,如下图所示(“图 5.2”):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TxKKpQCH-1681653992522)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/c4301074-d0a6-499d-97fb-04b39f20d188.png)]

图 5.2:处理来自 CNN 的顺序输入特征时的 LSTM

从上图可以看出,来自预训练卷积神经的生成的特征f[1], f[2], ..., f[t], ..., f[n]由 LSTM 依次处理,产生文本输出o[1], o[2], ..., o[t], ..., o[n],它们是给定视频的文本标题。 例如,上图中的视频标题可能是“一名戴着黄色头盔的人正在工作”:

o1, o2, . . . . . ot . . . oN = { "A ","man" "in" "a" "yellow" "helmet" "is" "working"}

既然我们对视频字幕在深度学习框架中的工作方式有了一个很好的了解,让我们在下一部分中讨论一个更高级的视频字幕网络,称为逐序列视频字幕。 在本章中,我们将使用相同的网络架构来构建视频字幕系统

序列到序列的视频字幕系统

序列到序列的架构基于 Subhashini Venugopalan,Marcus Rohrbach,Jeff Donahue,Raymond Mooney,Trevor Darrell 和 Kate Saenko 撰写的名为《序列到序列-视频到文本》的论文。 该论文可以在这个页面中找到。

在下图(“图 5.3”)中,说明了基于先前论文的序列到字幕视频字幕神经网络架构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ogCZ47QG-1681653992522)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/6dac330d-1814-4543-8312-a3b863240d00.png)]

图 5.3:序列到序列视频字幕网络架构

序列到序列模型通过像以前一样通过预训练的卷积神经网络处理视频图像帧,最后一个全连接层的输出激活被视为要馈送到后续 LSTM 的特征。 如果我们在时间步t表示预训练卷积神经网络的最后一个全连接层的输出激活为f[t] ∈ R^4096,那么我们将为视频中的N个图像帧使用N个这样的特征向量。 这些N个特征向量f[1], f[2], ..., f[t], ..., f[n]依次输入 LSTM,以生成文本标题。

背靠背有两个 LSTM,LSTM 中的序列数是来自视频的图像帧数与字幕词汇表中文本字幕的最大长度之和。 如果在视频的N个图像帧上训练网络,并且词汇表中的最大文本标题长度为M,则 LSTM 在N + M时间步长上训练。 在N个时间步中,第一个 LSTM 依次处理特征向量f[1], f[2], ..., f[t], ..., f[n],并将其生成的隐藏状态馈送到第二 LSTM。 在这些N个时间步中,第二个 LSTM 不需要文本输出目标。 如果我们将第一个 LSTM 在时间步t的隐藏状态表示为h[t],则第二个 LSTM 在前N个时间步长的输入为h[t]。 请注意,从N + 1时间步长开始,第一个 LSTM 的输入是零填充的,因此该输入对t > Nh[t]没有隐藏状态的影响。。 请注意,这并不保证t > N的隐藏状态h[t]总是相同的。 实际上,我们可以选择在任何时间步长t > N中将h[t]作为h[T]送入第二个 LSTM。

N + 1时间步长开始,第二个 LSTM 需要文本输出目标。 在任何时间步长t > N的输入是h[t], w[t-1],其中h[t]是第一个 LSTM 在时间步t的隐藏状态,而w[t-1]是时间步t-1中的文本标题词。

N + 1时间步长处,馈送到第二 LSTM 的单词w[N]是由表示的句子的开头。 一旦生成句子符号的结尾,就训练网络停止生成标题词。 总而言之,两个 LSTM 的设置方式是,一旦它们处理完所有视频图像帧特征 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xa0PdQnh-1681653992522)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/8ad769e2-57c1-4581-98e1-7c7f70a12413.png)],它们便开始产生文本标题词。

处理时间步t > N的第二个 LSTM 输入的另一种方法是只喂w[t-1]而不是h[t], w[t-1],并在时间步T传递第一个 LSTM 的隐藏状态和单元状态h[T], c[T],到第二个 LSTM 的初始隐藏和单元状态。 这样的视频字幕网络的架构可以说明如下(请参见“图 5.4”):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1FT3Homg-1681653992522)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/f2805f4e-f512-4272-8988-4c3b2b73a400.png)]

图 5.4:序列到序列模型的替代架构

预训练的卷积神经网络通常具有通用架构,例如VGG16VGG19ResNet,并在 ImageNet 上进行了预训练。 但是,我们可以基于从我们要为其构建视频字幕系统的域中的视频中提取的图像来重新训练这些架构。 我们还可以选择一种全新的 CNN 架构,并在特定于该域的视频图像上对其进行训练。

到目前为止,我们已经介绍了使用本节中说明的序列到序列架构开发视频字幕系统的所有技术先决条件。 请注意,本节中建议的替代架构设计是为了鼓励读者尝试几种设计,并查看哪种设计最适合给定的问题和数据集。

从下一部分开始,我们致力于构建智能视频字幕系统。

视频字幕系统的数据

我们通过在MSVD dataset上训练模型来构建视频字幕系统,该模型是 Microsoft 的带预字幕的 YouTube 视频存储库。 可以从以下链接下载所需的数据可通过以下链接获得视频的文本标题

MSVD dataset中大约有1,938个视频。 我们将使用它们来训练逐序列视频字幕系统。 还要注意,我们将在“图 5.3”中所示的序列到序列模型上构建模型。 但是,建议读者尝试在“图 5.4”中介绍的架构上训练模型,并了解其表现。

处理视频图像来创建 CNN 特征

从指定位置下载数据后,下一个任务是处理视频图像帧,以从预训练的卷积神经网络的最后全连接层中提取特征。 我们使用在 ImageNet 上预训练的VGG16卷积神经网络。 我们将激活从VGG16的最后一个全连接层中取出。 由于VGG16的最后一个全连接层具有4096单元,因此每个时间步t的特征向量f[t]4096维向量,f[t] ∈ R^4096

在通过VGG16处理视频中的图像之前,需要从视频中对其进行采样。 我们从视频中采样图像,使每个视频具有80帧。 处理来自VGG1680图像帧后,每个视频将具有80特征向量f[1], f[2], ..., f[80]。 这些特征将被馈送到 LSTM 以生成文本序列。 我们在 Keras 中使用了预训练的VGG16模型。 我们创建一个VideoCaptioningPreProcessing类,该类首先通过函数video_to_frames从每个视频中提取80视频帧作为图像,然后通过函数extract_feats_pretrained_cnn中的预训练VGG16卷积神经网络处理这些视频帧。 。

extract_feats_pretrained_cnn的输出是每个视频帧大小为4096的 CNN 特征。 由于我们正在处理每个视频的80帧,因此每个视频将具有80这样的4096维向量。

video_to_frames函数可以编码如下:

def video_to_frames(self,video):
        with open(os.devnull, "w") as ffmpeg_log:
            if os.path.exists(self.temp_dest):
                print(" cleanup: " + self.temp_dest + "/")
                shutil.rmtree(self.temp_dest)
            os.makedirs(self.temp_dest)
            video_to_frames_cmd = ["ffmpeg",'-y','-i', video, 
                                       '-vf', "scale=400:300", 
                                       '-qscale:v', "2", 
                                       '{0}/%06d.jpg'.format(self.temp_dest)]
            subprocess.call(video_to_frames_cmd,
                            stdout=ffmpeg_log, stderr=ffmpeg_log)

从前面的代码中,我们可以看到在video_to_frames函数中,ffmpeg工具用于将 JPEG 格式的视频图像帧转换。 为图像帧指定为ffmpeg的大小为300 x 400。 有关ffmpeg工具的更多信息,请参考以下链接

extract_feats_pretrained_cnnfunction中已建立了从最后一个全连接层中提取特征的预训练 CNN 模型。 该函数的代码如下:

# Extract the features from the pre-trained CNN 
    def extract_feats_pretrained_cnn(self):
        model = self.model_cnn_load()
        print('Model loaded')
        if not os.path.isdir(self.feat_dir):
            os.mkdir(self.feat_dir)
        #print("save video feats to %s" % (self.dir_feat))
        video_list = glob.glob(os.path.join(self.video_dest, '*.avi'))
        #print video_list 
        for video in tqdm(video_list):
            video_id = video.split("/")[-1].split(".")[0]
            print(f'Processing video {video}')
            #self.dest = 'cnn_feat' + '_' + video_id
            self.video_to_frames(video)
            image_list = 
            sorted(glob.glob(os.path.join(self.temp_dest, '*.jpg')))
            samples = np.round(np.linspace(
                0, len(image_list) - 1,self.frames_step))
            image_list = [image_list[int(sample)] for sample in samples]
            images = 
            np.zeros((len(image_list),self.img_dim,self.img_dim,
                     self.channels))
            for i in range(len(image_list)):
                img = self.load_image(image_list[i])
                images[i] = img
            images = np.array(images)
            fc_feats = model.predict(images,batch_size=self.batch_cnn)
            img_feats = np.array(fc_feats)
            outfile = os.path.join(self.feat_dir, video_id + '.npy')
            np.save(outfile, img_feats)
            # cleanup
            shutil.rmtree(self.temp_dest)

Python 智能项目:1~5(5)https://developer.aliyun.com/article/1426934

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
12天前
|
机器学习/深度学习 数据采集 供应链
Python实现深度学习模型:智能库存管理系统
【10月更文挑战第5天】 Python实现深度学习模型:智能库存管理系统
62 9
|
8天前
|
机器学习/深度学习 数据采集 数据可视化
Python 数据分析:从零开始构建你的数据科学项目
【10月更文挑战第9天】Python 数据分析:从零开始构建你的数据科学项目
22 2
|
11天前
|
机器学习/深度学习 TensorFlow 算法框架/工具
使用Python实现深度学习模型:智能数据隐私保护
使用Python实现深度学习模型:智能数据隐私保护 【10月更文挑战第3天】
42 0
|
10天前
|
机器学习/深度学习 TensorFlow 算法框架/工具
使用Python实现深度学习模型:智能质量检测与控制
使用Python实现深度学习模型:智能质量检测与控制 【10月更文挑战第8天】
103 62
使用Python实现深度学习模型:智能质量检测与控制
|
3天前
|
机器学习/深度学习 传感器 存储
使用 Python 实现智能地震预警系统
使用 Python 实现智能地震预警系统
88 61
|
17天前
|
机器学习/深度学习 数据采集 TensorFlow
智能市场营销策略优化:使用Python实现深度学习模型
【10月更文挑战第1天】 智能市场营销策略优化:使用Python实现深度学习模型
143 63
|
7天前
|
机器学习/深度学习 TensorFlow 调度
使用Python实现深度学习模型:智能能源消耗预测与管理
使用Python实现深度学习模型:智能能源消耗预测与管理
78 30
|
1天前
|
机器学习/深度学习 数据采集 消息中间件
使用Python实现智能火山活动监测模型
使用Python实现智能火山活动监测模型
12 1
|
3天前
|
JSON 搜索推荐 API
Python的web框架有哪些?小项目比较推荐哪个?
【10月更文挑战第15天】Python的web框架有哪些?小项目比较推荐哪个?
11 1
|
5天前
|
机器学习/深度学习 数据可视化 TensorFlow
使用Python实现深度学习模型:智能天气预测与气候分析
使用Python实现深度学习模型:智能天气预测与气候分析
71 3