mastering-tf-1x-zh-merge-3

90 阅读58分钟

精通 TensorFlow 1.x(四)

原文:Mastering TensorFlow 1.x

协议:CC BY-NC-SA 4.0

十二、迁移学习和预训练模型

简单来说,迁移学习意味着你需要训练有素的预训练模型来预测一种类,然后直接使用它或仅再训练它的一小部分,以便预测另一种类。例如,您可以采用预训练的模型来识别猫的类型,然后仅对狗的类型再训练模型的小部分,然后使用它来预测狗的类型。

如果没有迁移学习,在大型数据集上训练一个巨大的模型需要几天甚至几个月。然而,通过迁移学习,通过采用预训练的模型,并且仅训练最后几层,我们可以节省大量时间从头开始训练模型。

当没有庞大的数据集时,迁移学习也很有用。在小型数据集上训练的模型可能无法检测在大型数据集上训练的模型可以进行的特征。因此,通过迁移学习,即使数据集较小,也可以获得更好的模型。

在本章中,我们将采用预训练的模型并对新物体进行训练。我们展示了带有图像的预训练模型的示例,并将它们应用于图像分类问题。 您应该尝试找到其他预训练的模型,并将它们应用于不同的问题,如对象检测,文本生成或机器翻译。本章将介绍以下主题:

  • ImageNet 数据集
  • 再训练或微调模型
  • COCO 动物数据集和预处理
  • 使用 TensorFlow 中预训练的 VGG16 进行图像分类
  • TensorFlow 中的图像预处理,用于预训练的 VGG16
  • 在 TensorFlow 中使用再训练的 VGG16 进行图像分类
  • 使用 Keras 中预训练的 VGG16 进行图像分类
  • 使用再训练的 VGG16 在 Keras 中进行图像分类
  • 使用 TensorFlow 中的 InceptionV3 进行图像分类
  • 在 TensorFlow 中使用再训练的 InceptionV3 进行图像分类

ImageNet 数据集

根据 ImageNet

ImageNet 是根据 WordNet 层次结构组织的图像数据集。WordNet 中的每个有意义的概念,可能由多个单词或单词短语描述,称为同义词集或 synset。

ImageNet 有大约 100K 个同义词集,平均每个同义词集约有 1,000 个人工标注图像。 ImageNet 仅存储对图像的引用,而图像存储在互联网上的原始位置。在深度学习论文中,ImageNet-1K 是指作为 ImageNet 的大规模视觉识别挑战ILSVRC)的一部分发布的数据集,用于将数据集分类为 1,000 个类别:

可以在以下 URL 找到 1,000 个挑战类别:

我们编写了一个自定义函数来从 Google 下载 ImageNet 标签:

def build_id2label(self):
    base_url = 'https://raw.githubusercontent.com/tensorflow/models/master/research/inception/inception/data/'
  synset_url = '{}/imagenet_lsvrc_2015_synsets.txt'.format(base_url)
    synset_to_human_url = '{}/imagenet_metadata.txt'.format(base_url)

    filename, _ = urllib.request.urlretrieve(synset_url)
    synset_list = [s.strip() for s in open(filename).readlines()]
    num_synsets_in_ilsvrc = len(synset_list)
    assert num_synsets_in_ilsvrc == 1000

  filename, _ = urllib.request.urlretrieve(synset_to_human_url)
    synset_to_human_list = open(filename).readlines()
    num_synsets_in_all_imagenet = len(synset_to_human_list)
    assert num_synsets_in_all_imagenet == 21842

  synset2name = {}
    for s in synset_to_human_list:
        parts = s.strip().split('\t')
        assert len(parts) == 2
  synset = parts[0]
        name = parts[1]
        synset2name[synset] = name

    if self.n_classes == 1001:
        id2label={0:'empty'}
        id=1
  else:
        id2label = {}
        id=0

  for synset in synset_list:
        label = synset2name[synset]
        id2label[id] = label
        id += 1

  return id2label

我们将这些标签加载到我们的 Jupyter 笔记本中,如下所示:

### Load ImageNet dataset for labels
from datasetslib.imagenet import imageNet
inet = imageNet()
inet.load_data(n_classes=1000)  
#n_classes is 1001 for Inception models and 1000 for VGG models

在 ImageNet-1K 数据集上训练过的热门预训练图像分类模型如下表所示:

模型名称Top-1 准确率Top-5 准确率Top-5 错误率原始文件的链接
AlexNet15.3%www.cs.toronto.edu/~fritz/absp…
Inception 也称为 InceptionV169.889.66.67%arxiv.org/abs/1409.48…
BN-Inception-V2 也称为 InceptionV273.991.84.9%arxiv.org/abs/1502.03…
InceptionV378.093.93.46%arxiv.org/abs/1512.00…
InceptionV480.295.2arxiv.org/abs/1602.07…
Inception-Resnet-V280.495.2arxiv.org/abs/1602.07…
VGG1671.589.87.4%arxiv.org/abs/1409.15…
VGG1971.189.87.3%arxiv.org/abs/1409.15…
ResNetV1 5075.292.27.24%arxiv.org/abs/1512.03…
ResNetV1 10176.492.9arxiv.org/abs/1512.03…
ResNetV1 15276.893.2arxiv.org/abs/1512.03…
ResNetV2 5075.692.8arxiv.org/abs/1603.05…
ResNetV2 10177.093.7arxiv.org/abs/1603.05…
ResNetV2 15277.894.1arxiv.org/abs/1603.05…
ResNetV2 20079.995.2arxiv.org/abs/1603.05…
Xception79.094.5arxiv.org/abs/1610.02…
MobileNet V141.3 至 70.766.2 至 89.5arxiv.org/pdf/1704.04…

在上表中,Top-1 和 Top-5 指标指的是模型在 ImageNet 验证数据集上的表现。

Google Research 最近发布了一种名为 MobileNets 的新模型。 MobileNets 采用移动优先策略开发,牺牲了低资源使用的准确率。 MobileNets 旨在消耗低功耗并提供低延迟,以便在移动和嵌入式设备上提供更好的体验。谷歌为 MobileNet 模型提供了 16 个预训练好的检查点文件,每个模型提供不同数量的参数和乘法累加MAC)。 MAC 和参数越高,资源使用和延迟就越高。因此,您可以在更高的准确率与更高的资源使用/延迟之间进行选择。

模型检查点百万 MAC百万参数Top-1 准确率Top-5 准确率
MobileNet_v1_1.0_2245694.2470.789.5
MobileNet_v1_1.0_1924184.2469.388.9
MobileNet_v1_1.0_1602914.2467.287.5
MobileNet_v1_1.0_1281864.2464.185.3
MobileNet_v1_0.75_2243172.5968.488.2
MobileNet_v1_0.75_1922332.5967.487.3
MobileNet_v1_0.75_1601622.5965.286.1
MobileNet_v1_0.75_1281042.5961.883.6
MobileNet_v1_0.50_2241501.3464.085.4
MobileNet_v1_0.50_1921101.3462.184.0
MobileNet_v1_0.50_160771.3459.982.5
MobileNet_v1_0.50_128491.3456.279.6
MobileNet_v1_0.25_224410.4750.675.0
MobileNet_v1_0.25_192340.4749.073.6
MobileNet_v1_0.25_160210.4746.070.7
MobileNet_v1_0.25_128140.4741.366.2

有关 MobileNets 的更多信息,请访问以下资源:

research.googleblog.com/2017/06/mob…

github.com/tensorflow/…

arxiv.org/pdf/1704.04…

再训练或微调模型

在像 ImageNet 这样的大型和多样化数据集上训练的模型能够检测和捕获一些通用特征,如曲线,边缘和形状。其中一些特征很容易适用于其他类型的数据集。因此,在迁移学习中,我们采用这样的通用模型,并使用以下一些技术来微调或再训练它们到我们的数据集:

  • 废除并替换最后一层: 通常的做法是删除最后一层并添加与我们的数据集匹配的新分类层。例如,ImageNet 模型使用 1,000 个类别进行训练,但我们的 COCO 动物数据集只有 8 个类别,因此我们删除了 softmax 层,该层使用 softmax 层生成 1,000 个类别的概率,该层生成 8 个类别的概率。通常,当新数据集几乎与训练模型的数据集类似时使用此技术,因此仅需要再训练最后一层。
  • 冻结前几层:另一种常见做法是冻结前几层,以便仅使用新数据集更新最后未冻结层的权重。我们将看到一个例子,我们冻结前 15 层,同时只再训练最后 10 层。通常,当新数据集与训练模型的数据集非常不相似时使用此技术,因此不仅需要训练最后的层。
  • 调整超参数:您还可以在再训练之前调整超参数,例如更改学习率或尝试不同的损失函数或不同的优化器。

TensorFlow 和 Keras 均提供预训练模型。

我们将在文件夹tensorflow/models/research/slim/nets中通过 TensorFlow Slim 演示我们的示例,TensorFlow Slim 在编写时有几个预训练的模型。我们将使用 TensorFlow Slim 来实例化预训练的模型,然后从下载的检查点文件加载权重。然后,加载的模型将用于使用新数据集进行预测。然后我们将再训练模型以微调预测。

我们还将通过keras.applications模块中提供的 Keras 预训练模型演示迁移学习。虽然 TensorFlow 有大约 20 多个预训练模型,但keras.appplications只有以下 7 种预训练模型:

COCO 动物数据集和预处理图像

对于我们的例子,我们将使用 COCO 动物数据集,这是 COCO 数据集的一小部分,由斯坦福大学的研究人员提供。 COCO 动物数据集有 800 个训练图像和 200 个动物类别的测试图像:熊,鸟,猫,狗,长颈鹿,马,绵羊和斑马。为 VGG16 和 Inception 模型下载和预处理图像。

对于 VGG 模型,图像大小为224 x 224,预处理步骤如下:

  1. 将图像调整为224×224,其函数类似于来自 TensorFlow 的tf.image.resize_image_with_crop_or_pad函数。我们实现了这个函数如下:
def resize_image(self,in_image:PIL.Image, new_width, 
    new_height, crop_or_pad=True):
  img = in_image
    if crop_or_pad:
        half_width = img.size[0] // 2
  half_height = img.size[1] // 2
  half_new_width = new_width // 2
  half_new_height = new_height // 2
  img = img.crop((half_width-half_new_width,
                        half_height-half_new_height,
                        half_width+half_new_width,
                        half_height+half_new_height
                        ))
  img = img.resize(size=(new_width, new_height))

    return img
  1. 调整大小后,将图像从PIL.Image转换为 NumPy 数组并检查图像是否有深度通道,因为数据集中的某些图像仅为灰度。
img = self.pil_to_nparray(img)
if len(img.shape)==2:   
    # greyscale or no channels then add three channels
  h=img.shape[0]
    w=img.shape[1]
    img = np.dstack([img]*3)
  1. 然后我们从图像中减去 VGG 数据集平均值以使数据居中。我们将新训练图像的数据居中的原因是这些特征具有与用于降雨模型的初始数据类似的范围。通过在相似范围内制作特征,我们确保再训练期间的梯度不会变得太高或太低。同样通过使数据居中,学习过程变得更快,因为对于以零均值为中心的每个通道,梯度变得均匀。
means = np.array([[[123.68, 116.78, 103.94]]]) #shape=[1, 1, 3]
img = img - means

完整的预处理函数如下:

def preprocess_for_vgg(self,incoming, height, width):
    if isinstance(incoming, six.string_types):
        img = self.load_image(incoming)
    else:
  img=incoming
    img_size = vgg.vgg_16.default_image_size
    height = img_size
    width = img_size
    img = self.resize_image(img,height,width)
    img = self.pil_to_nparray(img)
    if len(img.shape)==2:   
        # greyscale or no channels then add three channels
  h=img.shape[0]
        w=img.shape[1]
        img = np.dstack([img]*3)

    means = np.array([[[123.68, 116.78, 103.94]]]) #shape=[1, 1, 3]
  try:
        img = img - means
    except Exception as ex:
        print('Error preprocessing ',incoming)
        print(ex)

    return img

对于 Inception 模型,图像大小为299 x 299,预处理步骤如下:

  1. 图像大小调整为299 x 299,其函数类似于来自 TensorFlow 的tf.image.resize_image_with_crop_or_pad函数。我们实现了之前在 VGG 预处理步骤中定义的此函数。
  2. 然后使用以下代码将图像缩放到范围(-1, +1)
img = ((img/255.0) - 0.5) * 2.0

完整的预处理函数如下:

def preprocess_for_inception(self,incoming):
    img_size = inception.inception_v3.default_image_size
    height = img_size
    width = img_size
    if isinstance(incoming, six.string_types):
        img = self.load_image(incoming)
    else:
  img=incoming
    img = self.resize_image(img,height,width)
    img = self.pil_to_nparray(img)
    if len(img.shape)==2:   
        # greyscale or no channels then add three channels
  h=img.shape[0]
        w=img.shape[1]
        img = np.dstack([img]*3)
    img = ((img/255.0) - 0.5) * 2.0

  return img

让我们加载 COCO 动物数据集:

from datasetslib.coco import coco_animals
coco = coco_animals()
x_train_files, y_train, x_val_files, x_val = coco.load_data()

我们从验证集中的每个类中取一个图像,制作列表,x_test并预处理图像以制作列表images_test

x_test = [x_val_files[25*x] for x in range(8)]
images_test=np.array([coco.preprocess_for_vgg(x) for x in x_test])

我们使用这个辅助函数来显示与图像相关的前五个类的图像和概率:

# helper function
def disp(images,id2label=None,probs=None,n_top=5,scale=False):
    if scale:
        imgs = np.abs(images + np.array([[[[123.68, 
                116.78, 103.94]]]]))/255.0
    else:
        imgs = images    
    ids={}
    for j in range(len(images)):
        if scale:
            plt.figure(figsize=(5,5))
            plt.imshow(imgs[j])
        else:
            plt.imshow(imgs[j].astype(np.uint8) )
        plt.show()
        if probs is not None:
            ids[j] = [i[0] for i in sorted(enumerate(-probs[j]), 
                        key=lambda x:x[1])]
            for k in range(n_top):
                id = ids[j][k]
                print('Probability {0:1.2f}% of[{1:}]'
                    .format(100*probs[j,id],id2label[id]))

上述函数中的以下代码恢复为预处理的效果,以便显示原始图像而不是预处理图像:

imgs = np.abs(images + np.array([[[[123.68, 116.78, 103.94]]]]))/255.0

在 Inception 模型的情况下,用于反转预处理的代码如下:

imgs = (images / 2.0) + 0.5

您可以使用以下代码查看测试图像:

images=np.array([mpimg.imread(x) for x in x_test])
disp(images)

按照 Jupyter 笔记本中的代码查看图像。它们看起来都有不同的尺寸,所以让我们打印它们的原始尺寸:

print([x.shape for x in images])

尺寸是:

[(640, 425, 3), (373, 500, 3), (367, 640, 3), (427, 640, 3), (428, 640, 3), (426, 640, 3), (480, 640, 3), (612, 612, 3)]

让我们预处理测试图像并查看尺寸:

images_test=np.array([coco.preprocess_for_vgg(x) for x in x_test])
print(images_test.shape)

维度为:

(8, 224, 224, 3)

在 Inception 的情况下,维度是:

(8, 299, 299, 3)

Inception 的预处理图像不可见,但让我们打印 VGG 的预处理图像,以了解它们的外观:

disp(images_test)

实际上图像被裁剪了,我们可以看到当我们在保持裁剪的同时反转预处理时它们的样子:

现在我们已经有来自 ImageNet 的标签以及来自 COCO 图像数据集的图像和标签,我们试试迁移学习示例。

TensorFlow 中的 VGG16

您可以按照 Jupyter 笔记本中的代码ch-12a_VGG16_TensorFlow

对于 TensorFlow 中 VGG16 的所有示例,我们首先从这里下载检查点文件并使用以下内容初始化变量码:

model_name='vgg_16'
model_url='http://download.tensorflow.org/models/'
model_files=['vgg_16_2016_08_28.tar.gz']
model_home=os.path.join(models_root,model_name) 

dsu.download_dataset(source_url=model_url,
    source_files=model_files,
    dest_dir = model_home,
    force=False,
    extract=True)

我们还定义了一些常见的导入和变量:

from tensorflow.contrib import slim
from tensorflow.contrib.slim.nets import vgg
image_height=vgg.vgg_16.default_image_size
image_width=vgg.vgg_16.default_image_size

TensorFlow 中的预训练 VGG16 的图像分类

现在让我们首先尝试预测测试图像的类别,而不进行再训练。首先,我们清除默认图并定义图像的占位符:

tf.reset_default_graph()
x_p = tf.placeholder(shape=(None,image_height, image_width,3),
                     dtype=tf.float32,name='x_p')

占位符x_p的形状是(?, 224, 224, 3)。接下来,加载vgg16模型:

with slim.arg_scope(vgg.vgg_arg_scope()):
     logits,_ = vgg.vgg_16(x_p,num_classes=inet.n_classes,
                            is_training=False)

添加 softmax 层以生成类的概率:

probabilities = tf.nn.softmax(logits)

定义初始化函数以恢复变量,例如检查点文件中的权重和偏差。

init = slim.assign_from_checkpoint_fn(
    os.path.join(model_home, '{}.ckpt'.format(model_name)),
    slim.get_variables_to_restore())

在 TensorFlow 会话中,初始化变量并运行概率张量以获取每个图像的概率:

with tf.Session() as tfs:
    init(tfs)
    probs = tfs.run([probabilities],feed_dict={x_p:images_test})
    probs=probs[0]

让我们看看我们得到的类:

disp(images_test,id2label=inet.id2label,probs=probs,scale=True)

Probability 99.15% of [zebra]
Probability 0.37% of [tiger cat]
Probability 0.33% of [tiger, Panthera tigris]
Probability 0.04% of [goose]
Probability 0.02% of [tabby, tabby cat]

Probability 99.50% of [horse cart, horse-cart]
Probability 0.37% of [plow, plough]
Probability 0.06% of [Arabian camel, dromedary, Camelus dromedarius]
Probability 0.05% of [sorrel]
Probability 0.01% of [barrel, cask]

Probability 19.32% of [Cardigan, Cardigan Welsh corgi] Probability 11.78% of [papillon] Probability 9.01% of [Shetland sheepdog, Shetland sheep dog, Shetland] Probability 7.09% of [Siamese cat, Siamese] Probability 6.27% of [Pembroke, Pembroke Welsh corgi]

Probability 97.09% of [chickadee]
Probability 2.52% of [water ouzel, dipper]
Probability 0.23% of [junco, snowbird]
Probability 0.09% of [hummingbird]
Probability 0.04% of [bulbul]

Probability 24.98% of [whippet]
Probability 16.48% of [lion, king of beasts, Panthera leo]
Probability 5.54% of [Saluki, gazelle hound]
Probability 4.99% of [brown bear, bruin, Ursus arctos]
Probability 4.11% of [wire-haired fox terrier]

Probability 98.56% of [brown bear, bruin, Ursus arctos]
Probability 1.40% of [American black bear, black bear, Ursus americanus, Euarctos americanus]
Probability 0.03% of [sloth bear, Melursus ursinus, Ursus ursinus]
Probability 0.00% of [wombat]
Probability 0.00% of [beaver]

Probability 20.84% of [leopard, Panthera pardus]
Probability 12.81% of [cheetah, chetah, Acinonyx jubatus]
Probability 12.26% of [banded gecko]
Probability 10.28% of [jaguar, panther, Panthera onca, Felis onca]
Probability 5.30% of [gazelle]

Probability 8.09% of [shower curtain]
Probability 3.59% of [binder, ring-binder]
Probability 3.32% of [accordion, piano accordion, squeeze box]
Probability 3.12% of [radiator]
Probability 1.81% of [abaya]

从未见过我们数据集中的图像,并且对数据集中的类没有任何了解的预训练模型已正确识别斑马,马车,鸟和熊。它没能认出长颈鹿,因为它以前从未见过长颈鹿。我们将在我们的数据集上再训练这个模型,只需要更少的工作量和 800 个图像的较小数据集大小。但在我们这样做之前,让我们看看在 TensorFlow 中进行相同的图像预处理。

为 TensorFlow 中的预训练 VGG16 预处理图像

我们为 TensorFlow 中的预处理步骤定义一个函数,如下所示:

def tf_preprocess(filelist):
    images=[]
    for filename in filelist:
        image_string = tf.read_file(filename)
        image_decoded = tf.image.decode_jpeg(image_string, channels=3)
        image_float = tf.cast(image_decoded, tf.float32)
        resize_fn = tf.image.resize_image_with_crop_or_pad
        image_resized = resize_fn(image_float, image_height, image_width)
        means = tf.reshape(tf.constant([123.68, 116.78, 103.94]), 
                                        [1, 1, 3])
        image = image_resized - means
        images.append(image)

    images = tf.stack(images)
    return images

在这里,我们创建images变量而不是占位符:

images=tf_preprocess([x for x in x_test])

我们按照与以前相同的过程来定义 VGG16 模型,恢复变量然后运行预测:

with slim.arg_scope(vgg.vgg_arg_scope()):
    logits,_ = vgg.vgg_16(images,
                          num_classes=inet.n_classes,
                          is_training=False
                         )
probabilities = tf.nn.softmax(logits)

init = slim.assign_from_checkpoint_fn(
        os.path.join(model_home, '{}.ckpt'.format(model_name)),
        slim.get_variables_to_restore())

我们获得与以前相同的类概率。我们只是想证明预处理也可以在 TensorFlow 中完成。但是,TensorFlow 中的预处理仅限于 TensorFlow 提供的功能,并将您与框架深深联系在一起。

我们建议您将预处理管道与 TensorFlow 模型训练和预测代码分开。 保持独立使其具有模块化并具有其他优势,例如您可以保存数据以便在多个模型中重复使用。

TensorFlow 中的再训练 VGG16 的图像分类

现在,我们将为 COCO 动物数据集再训练 VGG16 模型。让我们从定义三个占位符开始:

  • is_training占位符指定我们是否将模型用于训练或预测
  • x_p是输入占位符,形状为(None, image_height, image_width, 3)
  • y_p是输出占位符,形状为(None, 1)
is_training = tf.placeholder(tf.bool,name='is_training')
x_p = tf.placeholder(shape=(None,image_height, image_width,3),
                    dtype=tf.float32,name='x_p')
y_p = tf.placeholder(shape=(None,1),dtype=tf.int32,name='y_p')

正如我们在策略部分中所解释的那样,我们将从检查点文件中恢复除最后一层之外的层,这被称为vgg/fc8层:

with slim.arg_scope(vgg.vgg_arg_scope()):
    logits, _ = vgg.vgg_16(x_p,num_classes=coco.n_classes,
                            is_training=is_training)

probabilities = tf.nn.softmax(logits)
# restore except last last layer fc8
fc7_variables=tf.contrib.framework.get_variables_to_restore(exclude=['vgg_16/fc8'])
fc7_init = tf.contrib.framework.assign_from_checkpoint_fn(
    os.path.join(model_home, '{}.ckpt'.format(model_name)),
    fc7_variables)

接下来,定义要初始化但未恢复的最后一个层的变量:

# fc8 layer
fc8_variables = tf.contrib.framework.get_variables('vgg_16/fc8')
fc8_init = tf.variables_initializer(fc8_variables)

正如我们在前面章节中所学到的,用tf.losses. sparse_softmax_cross_entropy()定义损失函数。

tf.losses.sparse_softmax_cross_entropy(labels=y_p, logits=logits)
loss = tf.losses.get_total_loss()

训练最后一层几个周期,然后训练整个网络几层。因此,定义两个单独的优化器和训练操作。

learning_rate = 0.001
fc8_optimizer = tf.train.GradientDescentOptimizer(learning_rate)
fc8_train_op = fc8_optimizer.minimize(loss, var_list=fc8_variables)

full_optimizer = tf.train.GradientDescentOptimizer(learning_rate)
full_train_op = full_optimizer.minimize(loss)

我们决定对两个优化器函数使用相同的学习率,但如果您决定进一步调整超参数,则可以定义单独的学习率。

像往常一样定义精度函数:

y_pred = tf.to_int32(tf.argmax(logits, 1))
n_correct_pred = tf.equal(y_pred, y_p)
accuracy = tf.reduce_mean(tf.cast(n_correct_pred, tf.float32))

最后,我们运行最后一层 10 个周期的训练,然后使用批量大小为 32 的 10 个周期的完整网络。我们还使用相同的会话来预测类:

fc8_epochs = 10
full_epochs = 10
coco.y_onehot = False
coco.batch_size = 32
coco.batch_shuffle = True

total_images = len(x_train_files)
n_batches = total_images // coco.batch_size

with tf.Session() as tfs:
        fc7_init(tfs) 
        tfs.run(fc8_init) 

        for epoch in range(fc8_epochs):
            print('Starting fc8 epoch ',epoch)
            coco.reset_index()
            epoch_accuracy=0
            for batch in range(n_batches):
                x_batch, y_batch = coco.next_batch()
                images=np.array([coco.preprocess_for_vgg(x) \
                            for x in x_batch])
                feed_dict={x_p:images,y_p:y_batch,is_training:True}
                tfs.run(fc8_train_op, feed_dict = feed_dict)
                feed_dict={x_p:images,y_p:y_batch,is_training:False}
                batch_accuracy = tfs.run(accuracy,feed_dict=feed_dict)
                epoch_accuracy += batch_accuracy
                except Exception as ex:
            epoch_accuracy /= n_batches
            print('Train accuracy in epoch {}:{}'
                .format(epoch,epoch_accuracy))

        for epoch in range(full_epochs):
            print('Starting full epoch ',epoch)
            coco.reset_index()
            epoch_accuracy=0
            for batch in range(n_batches):
                x_batch, y_batch = coco.next_batch()
                images=np.array([coco.preprocess_for_vgg(x) \
                        for x in x_batch])                    
                feed_dict={x_p:images,y_p:y_batch,is_training:True}
                tfs.run(full_train_op, feed_dict = feed_dict )
                feed_dict={x_p:images,y_p:y_batch,is_training:False}
                batch_accuracy = tfs.run(accuracy,feed_dict=feed_dict)
                epoch_accuracy += batch_accuracy
            epoch_accuracy /= n_batches
            print('Train accuracy in epoch {}:{}'
                    .format(epoch,epoch_accuracy))

        # now run the predictions
        feed_dict={x_p:images_test,is_training: False}
        probs = tfs.run([probabilities],feed_dict=feed_dict)
        probs=probs[0]

让我们看看打印我们的预测结果:

disp(images_test,id2label=coco.id2label,probs=probs,scale=True)

Probability 100.00% of [zebra]

Probability 100.00% of [horse]

Probability 98.88% of [cat]

Probability 100.00% of [bird]

Probability 68.88% of [bear]
Probability 31.06% of [sheep]
Probability 0.02% of [dog]
Probability 0.02% of [bird]
Probability 0.01% of [horse]

Probability 100.00% of [bear]
Probability 0.00% of [dog]
Probability 0.00% of [bird]
Probability 0.00% of [sheep]
Probability 0.00% of [cat]

Probability 100.00% of [giraffe]

Probability 61.36% of [cat]
Probability 16.70% of [dog]
Probability 7.46% of [bird]
Probability 5.34% of [bear]
Probability 3.65% of [giraffe]

它正确识别了猫和长颈鹿,并将其他概率提高到 100%。它仍然犯了一些错误,因为最后一张照片被归类为猫,这实际上是裁剪后的噪音图片。我们会根据这些结果对您进行改进。

Keras 的 VGG16

您可以按照 Jupyter 笔记本ch-12a_VGG16_Keras中的代码进行操作。

现在让我们对 Keras 进行相同的分类和再训练。您将看到我们可以轻松地使用较少量的代码在 Keras 中使用 VGG16 预训练模型。

Keras 中的预训练 VGG16 的图像分类

加载模型是一个单行操作:

from keras.applications import VGG16
model=VGG16(weights='imagenet')

我们可以使用这个模型来预测类的概率:

probs = model.predict(images_test)

以下是此分类的结果:

Probability 99.41% of [zebra]
Probability 0.19% of [tiger cat]
Probability 0.13% of [goose]
Probability 0.09% of [tiger, Panthera tigris]
Probability 0.02% of [mushroom]

Probability 87.50% of [horse cart, horse-cart]
Probability 5.58% of [Arabian camel, dromedary, Camelus dromedarius]
Probability 4.72% of [plow, plough]
Probability 1.03% of [dogsled, dog sled, dog sleigh]
Probability 0.31% of [wreck]

Probability 34.96% of [Siamese cat, Siamese]
Probability 12.71% of [toy terrier]
Probability 10.15% of [Boston bull, Boston terrier]
Probability 6.53% of [Italian greyhound]
Probability 6.01% of [Cardigan, Cardigan Welsh corgi]

Probability 56.41% of [junco, snowbird]
Probability 38.08% of [chickadee]
Probability 1.93% of [bulbul]
Probability 1.35% of [hummingbird]
Probability 1.09% of [house finch, linnet, Carpodacus mexicanus]

Probability 54.19% of [brown bear, bruin, Ursus arctos]
Probability 28.07% of [lion, king of beasts, Panthera leo]
Probability 0.87% of [Norwich terrier]
Probability 0.82% of [Lakeland terrier]
Probability 0.73% of [wild boar, boar, Sus scrofa]

Probability 88.64% of [brown bear, bruin, Ursus arctos]
Probability 7.22% of [American black bear, black bear, Ursus americanus, Euarctos americanus]
Probability 4.13% of [sloth bear, Melursus ursinus, Ursus ursinus]
Probability 0.00% of [badger]
Probability 0.00% of [wombat]

Probability 38.70% of [jaguar, panther, Panthera onca, Felis onca]
Probability 33.78% of [leopard, Panthera pardus]
Probability 14.22% of [cheetah, chetah, Acinonyx jubatus]
Probability 6.15% of [banded gecko]
Probability 1.53% of [snow leopard, ounce, Panthera uncia]

Probability 12.54% of [shower curtain] 
Probability 2.82% of [binder, ring-binder] 
Probability 2.28% of [toilet tissue, 
    toilet paper, bathroom tissue] 
Probability 2.12% of [accordion, piano accordion, squeeze box] 
Probability 2.05% of [bath towel]

它无法识别绵羊,长颈鹿以及狗的图像被裁剪出来的最后一张噪音图像。现在,让我们用我们的数据集再训练 Keras 中的模型。

Keras 中的再训练 VGG16 的图像分类

让我们使用 COCO 图像数据集来再训练模型以微调分类任务。我们将删除 Keras 模型中的最后一层,并添加我们自己的完全连接层,其中softmax激活 8 个类。我们还将通过将前 15 层的trainable属性设置为False来演示冻结前几层。

  1. 首先导入 VGG16 模型而不使用顶层变量,方法是将include_top设置为False
# load the vgg model
from keras.applications import VGG16
base_model=VGG16(weights='imagenet',include_top=False, input_shape=(224,224,3))

我们还在上面的代码中指定了input_shape,否则 Keras 会在以后抛出异常。

  1. 现在我们构建分类器模型以置于导入的 VGG 模型之上:
top_model = Sequential()
top_model.add(Flatten(input_shape=base_model.output_shape[1:]))
top_model.add(Dense(256, activation='relu'))
top_model.add(Dropout(0.5))
top_model.add(Dense(coco.n_classes, activation='softmax'))
  1. 接下来,在 VGG 基础之上添加模型:
model=Model(inputs=base_model.input, outputs=top_model(base_model.output))
  1. 冻结前 15 层:
for layer in model.layers[:15]:
    layer.trainable = False
  1. 我们随机挑选了 15 层冻结,你可能想要玩这个数字。让我们编译模型并打印模型摘要:
model.compile(loss='categorical_crossentropy',
     optimizer=optimizers.SGD(lr=1e-4, momentum=0.9),
     metrics=['accuracy'])
model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 224, 224, 3)       0         
_________________________________________________________________
block1_conv1 (Conv2D)        (None, 224, 224, 64)      1792      
_________________________________________________________________
block1_conv2 (Conv2D)        (None, 224, 224, 64)      36928     
_________________________________________________________________
block1_pool (MaxPooling2D)   (None, 112, 112, 64)      0         
_________________________________________________________________
block2_conv1 (Conv2D)        (None, 112, 112, 128)     73856     
_________________________________________________________________
block2_conv2 (Conv2D)        (None, 112, 112, 128)     147584    
_________________________________________________________________
block2_pool (MaxPooling2D)   (None, 56, 56, 128)       0         
_________________________________________________________________
block3_conv1 (Conv2D)        (None, 56, 56, 256)       295168    
_________________________________________________________________
block3_conv2 (Conv2D)        (None, 56, 56, 256)       590080    
_________________________________________________________________
block3_conv3 (Conv2D)        (None, 56, 56, 256)       590080    
_________________________________________________________________
block3_pool (MaxPooling2D)   (None, 28, 28, 256)       0         
_________________________________________________________________
block4_conv1 (Conv2D)        (None, 28, 28, 512)       1180160   
_________________________________________________________________
block4_conv2 (Conv2D)        (None, 28, 28, 512)       2359808   
_________________________________________________________________
block4_conv3 (Conv2D)        (None, 28, 28, 512)       2359808   
_________________________________________________________________
block4_pool (MaxPooling2D)   (None, 14, 14, 512)       0         
_________________________________________________________________
block5_conv1 (Conv2D)        (None, 14, 14, 512)       2359808   
_________________________________________________________________
block5_conv2 (Conv2D)        (None, 14, 14, 512)       2359808   
_________________________________________________________________
block5_conv3 (Conv2D)        (None, 14, 14, 512)       2359808   
_________________________________________________________________
block5_pool (MaxPooling2D)   (None, 7, 7, 512)         0         
_________________________________________________________________
sequential_1 (Sequential)    (None, 8)                 6424840   
=================================================================
Total params: 21,139,528
Trainable params: 13,504,264
Non-trainable params: 7,635,264

我们看到近 40% 的参数是冻结的和不可训练的。

  1. 接下来,训练 Keras 模型 20 个周期,批量大小为 32:
from keras.utils import np_utils

batch_size=32
n_epochs=20

total_images = len(x_train_files)
n_batches = total_images // batch_size
for epoch in range(n_epochs):
 print('Starting epoch ',epoch)
 coco.reset_index_in_epoch()
 for batch in range(n_batches):
 try:
     x_batch, y_batch = coco.next_batch(batch_size=batch_size)
     images=np.array([coco.preprocess_image(x) for x in x_batch])
     y_onehot = np_utils.to_categorical(y_batch,
                    num_classes=coco.n_classes)
     model.fit(x=images,y=y_onehot,verbose=0)
 except Exception as ex:
     print('error in epoch {} batch {}'.format(epoch,batch))
     print(ex)
  1. 让我们使用再训练的新模型执行图像分类:
probs = model.predict(images_test)

以下是分类结果:

Probability 100.00% of [zebra]
Probability 0.00% of [dog]
Probability 0.00% of [horse]
Probability 0.00% of [giraffe]
Probability 0.00% of [bear]

Probability 96.11% of [horse]
Probability 1.85% of [cat]
Probability 0.77% of [bird]
Probability 0.43% of [giraffe]
Probability 0.40% of [sheep]

Probability 99.75% of [dog] Probability 0.22% of [cat] Probability 0.03% of [horse] Probability 0.00% of [bear] Probability 0.00% of [zebra]

Probability 99.88% of [bird]
Probability 0.11% of [horse]
Probability 0.00% of [giraffe]
Probability 0.00% of [bear]
Probability 0.00% of [cat]

Probability 65.28% of [bear]
Probability 27.09% of [sheep]
Probability 4.34% of [bird]
Probability 1.71% of [giraffe]
Probability 0.63% of [dog]

Probability 100.00% of [bear]
Probability 0.00% of [sheep]
Probability 0.00% of [dog]
Probability 0.00% of [cat]
Probability 0.00% of [giraffe]

Probability 100.00% of [giraffe]
Probability 0.00% of [bird]
Probability 0.00% of [bear]
Probability 0.00% of [sheep]
Probability 0.00% of [zebra]

Probability 81.05% of [cat] 
Probability 15.68% of [dog] 
Probability 1.64% of [bird] 
Probability 0.90% of [horse] 
Probability 0.43% of [bear]

除了最后的嘈杂图像外,所有类别都已正确识别。通过适当的超参数调整,也可以进行改进。

到目前为止,您已经看到了使用预训练模型进行分类并对预训练模型进行微调的示例。接下来,我们将使用 InceptionV3 模型显示分类示例。

TensorFlow 中的 InceptionV3

您可以按照 Jupyter 笔记本中的代码ch-12c_InceptionV3_TensorFlow

TensorFlow 的 InceptionV3 在 1,001 个标签上训练,而不是 1,000 个。此外,用于训练的图像被不同地预处理。我们在前面的部分中展示了预处理代码。让我们直接深入了解使用 TensorFlow 恢复 InceptionV3 模型。

让我们下载 InceptionV3 的检查点文件:

# load the InceptionV3 model
model_name='inception_v3'
model_url='http://download.tensorflow.org/models/'
model_files=['inception_v3_2016_08_28.tar.gz']
model_home=os.path.join(models_root,model_name) 

dsu.download_dataset(source_url=model_url,
    source_files=model_files,
    dest_dir = model_home,
    force=False,
    extract=True)

定义初始模块和变量的常见导入:

### define common imports and variables
from tensorflow.contrib.slim.nets import inception
image_height=inception.inception_v3.default_image_size
image_width=inception.inception_v3.default_image_size

TensorFlow 中的 InceptionV3 的图像分类

图像分类与使用 VGG 16 模型的上一节中说明的相同。 InceptionV3 模型的完整代码如下:

x_p = tf.placeholder(shape=(None,
                            image_height, 
                            image_width,
                            3
                           ),
                     dtype=tf.float32,
                     name='x_p')
with slim.arg_scope(inception.inception_v3_arg_scope()):
    logits,_ = inception.inception_v3(x_p,
                                      num_classes=inet.n_classes,
                                      is_training=False
                                     )
probabilities = tf.nn.softmax(logits)

init = slim.assign_from_checkpoint_fn(
        os.path.join(model_home, '{}.ckpt'.format(model_name)),
        slim.get_variables_to_restore())

with tf.Session() as tfs:
    init(tfs)
    probs = tfs.run([probabilities],feed_dict={x_p:images_test})
    probs=probs[0]

让我们看看我们的模型如何处理测试图像:

Probability 95.15% of [zebra]
Probability 0.07% of [ostrich, Struthio camelus]
Probability 0.07% of [hartebeest]
Probability 0.03% of [sock]
Probability 0.03% of [warthog]

Probability 93.09% of [horse cart, horse-cart]
Probability 0.47% of [plow, plough]
Probability 0.07% of [oxcart]
Probability 0.07% of [seashore, coast, seacoast, sea-coast]
Probability 0.06% of [military uniform]

Probability 18.94% of [Cardigan, Cardigan Welsh corgi]
Probability 8.19% of [Pembroke, Pembroke Welsh corgi]
Probability 7.86% of [studio couch, day bed]
Probability 5.36% of [English springer, English springer spaniel]
Probability 4.16% of [Border collie]

Probability 27.18% of [water ouzel, dipper]
Probability 24.38% of [junco, snowbird]
Probability 6.91% of [chickadee]
Probability 0.99% of [magpie]
Probability 0.73% of [brambling, Fringilla montifringilla]

Probability 93.00% of [hog, pig, grunter, squealer, Sus scrofa]
Probability 2.23% of [wild boar, boar, Sus scrofa]
Probability 0.65% of [ram, tup]
Probability 0.43% of [ox]
Probability 0.23% of [marmot]

Probability 84.27% of [brown bear, bruin, Ursus arctos]
Probability 1.57% of [American black bear, black bear, Ursus americanus, Euarctos americanus]
Probability 1.34% of [sloth bear, Melursus ursinus, Ursus ursinus]
Probability 0.13% of [lesser panda, red panda, panda, bear cat, cat bear, Ailurus fulgens]
Probability 0.12% of [ice bear, polar bear, Ursus Maritimus, Thalarctos maritimus]

Probability 20.20% of [honeycomb]
Probability 6.52% of [gazelle]
Probability 5.14% of [sorrel]
Probability 3.72% of [impala, Aepyceros melampus]
Probability 2.44% of [Saluki, gazelle hound]

Probability 41.17% of [harp]
Probability 13.64% of [accordion, piano accordion, squeeze box]
Probability 2.97% of [window shade]
Probability 1.59% of [chain]
Probability 1.55% of [pay-phone, pay-station]

虽然它在与 VGG 模型几乎相同的地方失败了,但并不算太糟糕。现在让我们用 COCO 动物图像和标签再训练这个模型。

TensorFlow 中的再训练 InceptionV3 的图像分类

InceptionV3 的再训练与 VGG16 不同,因为我们使用 softmax 激活层作为输出,tf.losses.softmax_cross_entropy()作为损耗函数。

  1. 首先定义占位符:
is_training = tf.placeholder(tf.bool,name='is_training')
x_p = tf.placeholder(shape=(None,
                            image_height, 
                            image_width,
                            3
                           ),
                     dtype=tf.float32,
                     name='x_p')
y_p = tf.placeholder(shape=(None,coco.n_classes),
                     dtype=tf.int32,
                     name='y_p')
  1. 接下来,加载模型:
with slim.arg_scope(inception.inception_v3_arg_scope()):
    logits,_ = inception.inception_v3(x_p,
                                      num_classes=coco.n_classes,
                                      is_training=True
                                     )
probabilities = tf.nn.softmax(logits)
  1. 接下来,定义函数以恢复除最后一层之外的变量:
with slim.arg_scope(inception.inception_v3_arg_scope()):
    logits,_ = inception.inception_v3(x_p,
                                      num_classes=coco.n_classes,
                                      is_training=True
                                     )
probabilities = tf.nn.softmax(logits)

# restore except last layer
checkpoint_exclude_scopes=["InceptionV3/Logits", 
                           "InceptionV3/AuxLogits"]
exclusions = [scope.strip() for scope in checkpoint_exclude_scopes]

variables_to_restore = []
for var in slim.get_model_variables():
    excluded = False
    for exclusion in exclusions:
        if var.op.name.startswith(exclusion):
            excluded = True
            break
    if not excluded:
        variables_to_restore.append(var)

init_fn = slim.assign_from_checkpoint_fn(
    os.path.join(model_home, '{}.ckpt'.format(model_name)),
    variables_to_restore)
  1. 定义损失,优化器和训练操作:
tf.losses.softmax_cross_entropy(onehot_labels=y_p, logits=logits)
loss = tf.losses.get_total_loss()
learning_rate = 0.001
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
train_op = optimizer.minimize(loss)
  1. 训练模型并在同一会话中完成训练后运行预测:
n_epochs=10
coco.y_onehot = True
coco.batch_size = 32
coco.batch_shuffle = True
total_images = len(x_train_files)
n_batches = total_images // coco.batch_size

with tf.Session() as tfs:
    tfs.run(tf.global_variables_initializer())
    init_fn(tfs) 

    for epoch in range(n_epochs):
        print('Starting epoch ',epoch)
        coco.reset_index()
        epoch_accuracy=0
        epoch_loss=0
        for batch in range(n_batches):
            x_batch, y_batch = coco.next_batch()
            images=np.array([coco.preprocess_for_inception(x) \
                    for x in x_batch])
            feed_dict={x_p:images,y_p:y_batch,is_training:True}
            batch_loss,_ = tfs.run([loss,train_op], 
                            feed_dict = feed_dict)
            epoch_loss += batch_loss 
       epoch_loss /= n_batches
       print('Train loss in epoch {}:{}'
            .format(epoch,epoch_loss))

   # now run the predictions
   feed_dict={x_p:images_test,is_training: False}
   probs = tfs.run([probabilities],feed_dict=feed_dict)
   probs=probs[0]

我们看到每个周期的损失都在减少:

INFO:tensorflow:Restoring parameters from /home/armando/models/inception_v3/inception_v3.ckpt
Starting epoch  0
Train loss in epoch 0:2.7896385192871094
Starting epoch  1
Train loss in epoch 1:1.6651896286010741
Starting epoch  2
Train loss in epoch 2:1.2332031989097596
Starting epoch  3
Train loss in epoch 3:0.9912329530715942
Starting epoch  4
Train loss in epoch 4:0.8110128355026245
Starting epoch  5
Train loss in epoch 5:0.7177265572547913
Starting epoch  6
Train loss in epoch 6:0.6175705575942994
Starting epoch  7
Train loss in epoch 7:0.5542363750934601
Starting epoch  8
Train loss in epoch 8:0.523461252450943
Starting epoch  9
Train loss in epoch 9:0.4923107647895813

这次结果正确识别了绵羊,但错误地将猫图片识别为狗:

Probability 98.84% of [zebra]
Probability 0.84% of [giraffe]
Probability 0.11% of [sheep]
Probability 0.07% of [cat]
Probability 0.06% of [dog]

Probability 95.77% of [horse]
Probability 1.34% of [dog]
Probability 0.89% of [zebra]
Probability 0.68% of [bird]
Probability 0.61% of [sheep]

Probability 94.83% of [dog] 
Probability 4.53% of [cat] 
Probability 0.56% of [sheep] 
Probability 0.04% of [bear] 
Probability 0.02% of [zebra]

Probability 42.80% of [bird]
Probability 25.64% of [cat]
Probability 15.56% of [bear]
Probability 8.77% of [giraffe]
Probability 3.39% of [sheep]

Probability 72.58% of [sheep] 
Probability 8.40% of [bear] 
Probability 7.64% of [giraffe] 
Probability 4.02% of [horse] 
Probability 3.65% of [bird]

Probability 98.03% of [bear] 
Probability 0.74% of [cat] 
Probability 0.54% of [sheep] 
Probability 0.28% of [bird] 
Probability 0.17% of [horse]

Probability 96.43% of [giraffe] 
Probability 1.78% of [bird] 
Probability 1.10% of [sheep] 
Probability 0.32% of [zebra] 
Probability 0.14% of [bear]

Probability 34.43% of [horse] 
Probability 23.53% of [dog] 
Probability 16.03% of [zebra] 
Probability 9.76% of [cat] 
Probability 9.02% of [giraffe]

总结

迁移学习是一项伟大的发现,它允许我们通过将在较大数据集中训练的模型应用于不同的数据集来节省时间。当数据集很小时,迁移学习也有助于热启动训练过程。在本章中,我们学习了如何使用预训练的模型,如 VGG16 和 InceptionV3,将不同数据集中的图像分类为他们所训练的数据集。我们还学习了如何使用 TensorFlow 和 Keras 中的示例再训练预训练模型,以及如何预处理图像以供给两个模型。

我们还了解到有几种模型在 ImageNet 数据集上进行了训练。尝试查找在不同数据集上训练的其他模型,例如视频数据集,语音数据集或文本/ NLP 数据集。尝试使用这些模型再训练并在您自己的数据集中使用您自己的深度学习问题。

十三、深度强化学习

强化学习是一种学习形式,其中软件智能体观察环境并采取行动以最大化其对环境的奖励,如下图所示:

这个比喻可以用来表示现实生活中的情况,如下所示:

  • 股票交易智能体观察交易信息,新闻,分析和其他形式信息,并采取行动买入或卖出交易,以便以短期利润或长期利润的形式最大化奖励。
  • 保险智能体观察有关客户的信息,然后采取行动确定保险费金额,以便最大化利润并最大限度地降低风险。
  • 类人机器人观察环境然后采取行动,例如步行,跑步或拾取物体,以便在实现目标方面最大化奖励。

强化学习已成功应用于许多应用,如广告优化,股票市场交易,自动驾驶汽车,机器人和游戏,仅举几例。

强化学习与监督学习不同,因为预先没有标签来调整模型的参数。该模型从运行中获得的奖励中学习。虽然短期奖励可以立即获得,但只有经过几个步骤才能获得长期奖励。这种现象也称为延迟反馈

强化学习也与无监督学习不同,因为在无监督学习中没有可用的标签,而在强化学习中,反馈可用于奖励。

在本章中,我们将通过涵盖以下主题来了解强化学习及其在 TensorFlow 和 Keras 中的实现:

  • OpenAI Gym 101
  • 将简单的策略应用于 Cartpole 游戏
  • 强化学习 101
    • Q 函数
    • 探索和利用
    • V 函数
    • RL 技术
  • RL 的简单神经网络策略
  • 实现 Q-Learning
    • Q-Learning 的初始化和离散化
    • 使用 Q-Table 进行 Q-Learning
    • 深度 Q 网络:使用 Q-Network 进行 Q-Learning

我们将在 OpenAI Gym 中演示我们的示例,让我们首先了解一下 OpenAI Gym。

OpenAI Gym 101

OpenAI Gym 是一个基于 Python 的工具包,用于研究和开发强化学习算法。 OpenAI Gym 在撰写本文时提供了 700 多个开源贡献环境。使用 OpenAI,您还可以创建自己的环境。最大的优势是 OpenAI 提供了一个统一的接口来处理这些环境,并在您专注于强化学习算法的同时负责运行模拟。

描述 OpenAI Gym 的研究论文可在此链接中找到

您可以使用以下命令安装 OpenAI Gym:

pip3 install gym

如果上述命令不起作用,您可以在此链接中找到有关安装的更多帮助。 

  1. 让我们在 OpenAI Gym 中打印可用环境的数量:

您可以按照本书代码包中的 Jupyter 笔记本ch-13a_Reinforcement_Learning_NN中的代码进行操作。

all_env = list(gym.envs.registry.all())
print('Total Environments in Gym version {} : {}'
    .format(gym.__version__,len(all_env)))

Total Environments in Gym version 0.9.4 : 777
  1. 让我们打印所有环境的列表:
for e in list(all_env):
    print(e)

输出的部分列表如下:

EnvSpec(Carnival-ramNoFrameskip-v0)
EnvSpec(EnduroDeterministic-v0)
EnvSpec(FrostbiteNoFrameskip-v4)
EnvSpec(Taxi-v2)
EnvSpec(Pooyan-ram-v0)
EnvSpec(Solaris-ram-v4)
EnvSpec(Breakout-ramDeterministic-v0)
EnvSpec(Kangaroo-ram-v4)
EnvSpec(StarGunner-ram-v4)
EnvSpec(Enduro-ramNoFrameskip-v4)
EnvSpec(DemonAttack-ramDeterministic-v0)
EnvSpec(TimePilot-ramNoFrameskip-v0)
EnvSpec(Amidar-v4)

env对象表示的每个环境都有一个标准化的接口,例如:

  • 通过传递 ID 字符串,可以使用env.make(<game-id-string>)函数创建env对象。
  • 每个env对象包含以下主要函数:
    • step()函数将操作对象作为参数并返回四个对象:
      • 观察:由环境实现的对象,代表对环境的观察。
      • 奖励:一个带符号的浮点值,表示前一个操作的增益(或损失)。
      • done:表示方案是否完成的布尔值。
      • info:表示诊断信息的 Python 字典对象。
    • render()函数可创建环境的直观表示。
    • reset()函数将环境重置为原始状态。
  • 每个env对象都有明确定义的动作和观察,由action_spaceobservation_space表示。

CartPole 是健身房里最受欢迎的学习强化学习游戏之一。在这个游戏中,连接到推车的杆必须平衡,以便它不会下降。如果杆子倾斜超过 15 度或者推车从中心移动超过 2.4 个单元,则游戏结束。 OpenAI.com 的主页用这些词强调游戏:

这种环境的小尺寸和简单性使得可以进行非常快速的实验,这在学习基础知识时是必不可少的。

游戏只有四个观察和两个动作。动作是通过施加 +1 或 -1 的力来移动购物车。观察结果是推车的位置,推车的速度,杆的角度以及杆的旋转速度。然而,学习观察语义的知识不是学习最大化游戏奖励所必需的。

现在让我们加载一个流行的游戏环境 CartPole-v0,然后用随机控件播放:

  1. 使用标准make函数创建env对象:
env = gym.make('CartPole-v0')
  1. 剧集的数量是游戏的数量。我们现在将它设置为一个,表示我们只想玩一次游戏。由于每集都是随机的,因此在实际的制作过程中,您将运行多集并计算奖励的平均值。此外,我们可以初始化一个数组,以便在每个时间步都存储环境的可视化:
n_episodes = 1
env_vis = []
  1. 运行两个嵌套循环 - 一个用于剧集数量的外部循环和一个用于您要模拟的时间步数的内部循环。您可以继续运行内部循环,直到方案完成或将步数设置为更高的值。
    • 在每集开始时,使用env.reset()重置环境。
    • 在每个时间步的开始,使用env.render()捕获可视化。
for i_episode in range(n_episodes):
  observation = env.reset()
    for t in range(100):
  env_vis.append(env.render(mode = 'rgb_array'))
        print(observation)
        action = env.action_space.sample()
        observation, reward, done, info = env.step(action)
        if done:
  print("Episode finished at t{}".format(t+1))
            break
  1. 使用辅助函数渲染环境:
env_render(env_vis)
  1. 辅助函数的代码如下:
def env_render(env_vis):
  plt.figure()
    plot = plt.imshow(env_vis[0])
    plt.axis('off')
    def animate(i):
  plot.set_data(env_vis[i])

    anim = anm.FuncAnimation(plt.gcf(),
                             animate,
                             frames=len(env_vis),
                             interval=20,
                             repeat=True,
                             repeat_delay=20)
    display(display_animation(anim, default_mode='loop'))

运行此示例时,我们得到以下输出:

[-0.00666995 -0.03699492 -0.00972623  0.00287713]
[-0.00740985  0.15826516 -0.00966868 -0.29285861]
[-0.00424454 -0.03671761 -0.01552586 -0.00324067]
[-0.0049789  -0.2316135  -0.01559067  0.28450351]
[-0.00961117 -0.42650966 -0.0099006   0.57222875]
[-0.01814136 -0.23125029  0.00154398  0.27644332]
[-0.02276636 -0.0361504   0.00707284 -0.01575223]
[-0.02348937  0.1588694   0.0067578  -0.30619523]
[-0.02031198 -0.03634819  0.00063389 -0.01138875]
[-0.02103895  0.15876466  0.00040612 -0.3038716 ]
[-0.01786366  0.35388083 -0.00567131 -0.59642642]
[-0.01078604  0.54908168 -0.01759984 -0.89089036]
[  1.95594914e-04   7.44437934e-01  -3.54176495e-02  -1.18905344e+00]
[ 0.01508435  0.54979251 -0.05919872 -0.90767902]
[ 0.0260802   0.35551978 -0.0773523  -0.63417465]
[ 0.0331906   0.55163065 -0.09003579 -0.95018025]
[ 0.04422321  0.74784161 -0.1090394  -1.26973934]
[ 0.05918004  0.55426764 -0.13443418 -1.01309691]
[ 0.0702654   0.36117014 -0.15469612 -0.76546874]
[ 0.0774888   0.16847818 -0.1700055  -0.52518186]
[ 0.08085836  0.3655333  -0.18050913 -0.86624457]
[ 0.08816903  0.56259197 -0.19783403 -1.20981195]
Episode finished at t22

杆子需要 22 个时间步长才能变得不平衡。在每次运行中,我们得到不同的时间步长值,因为我们通过使用env.action_space.sample()在学术上选择了动作。

由于游戏如此迅速地导致失败,随机选择一个动作并应用它可能不是最好的策略。有许多算法可以找到解决方案,使杆子保持笔直,可以使用更长的时间步长,例如爬山,随机搜索和策略梯度。

解决 Cartpole 游戏的一些算法可通过此链接获得:

openai.com/requests-fo…

kvfrans.com/simple-algo…

github.com/kvfrans/ope…

将简单的策略应用于 Cartpole 游戏

到目前为止,我们已经随机选择了一个动作并应用它。现在让我们应用一些逻辑来挑选行动而不是随机机会。第三个观察指的是角度。如果角度大于零,则意味着杆向右倾斜,因此我们将推车向右移动(1)。否则,我们将购物车向左移动(0)。我们来看一个例子:

  1. 我们定义了两个策略函数如下:
def policy_logic(env,obs):
  return 1 if obs[2] > 0 else 0
def policy_random(env,obs):
  return env.action_space.sample()
  1. 接下来,我们定义一个将针对特定数量的剧集运行的实验函数;每一集一直持续到游戏损失,即doneTrue。我们使用rewards_max来指示何时突破循环,因为我们不希望永远运行实验:
def experiment(policy, n_episodes, rewards_max):
  rewards=np.empty(shape=(n_episodes))
    env = gym.make('CartPole-v0')

    for i in range(n_episodes):
  obs = env.reset()
        done = False
        episode_reward = 0
        while not done:
  action = policy(env,obs)
            obs, reward, done, info = env.step(action)
            episode_reward += reward
            if episode_reward > rewards_max:
  break
        rewards[i]=episode_reward
  print('Policy:{}, Min reward:{}, Max reward:{}'
          .format(policy.__name__,
                  min(rewards),
                  max(rewards)))
  1. 我们运行实验 100 次,或直到奖励小于或等于rewards_max,即设置为 10,000:
n_episodes = 100
rewards_max = 10000
experiment(policy_random, n_episodes, rewards_max)
experiment(policy_logic, n_episodes, rewards_max)

我们可以看到逻辑选择的动作比随机选择的动作更好,但不是更好:

Policy:policy_random, Min reward:9.0, Max reward:63.0, Average reward:20.26
Policy:policy_logic, Min reward:24.0, Max reward:66.0, Average reward:42.81

现在让我们进一步修改选择动作的过程 - 基于参数。参数将乘以观察值,并且将基于乘法结果是零还是一来选择动作。让我们修改随机搜索方法,我们随机初始化参数。代码如下:

def policy_logic(theta,obs):
  # just ignore theta
  return 1 if obs[2] > 0 else 0

def policy_random(theta,obs):
  return 0 if np.matmul(theta,obs) < 0 else 1

def episode(env, policy, rewards_max):
  obs = env.reset()
    done = False
    episode_reward = 0
    if policy.__name__ in ['policy_random']:
  theta = np.random.rand(4) * 2 - 1
    else:
  theta = None
    while not done:
  action = policy(theta,obs)
        obs, reward, done, info = env.step(action)
        episode_reward += reward
        if episode_reward > rewards_max:
  break
    return episode_reward

def experiment(policy, n_episodes, rewards_max):
  rewards=np.empty(shape=(n_episodes))
    env = gym.make('CartPole-v0')

    for i in range(n_episodes):
  rewards[i]=episode(env,policy,rewards_max)
        #print("Episode finished at t{}".format(reward))
  print('Policy:{}, Min reward:{}, Max reward:{}, Average reward:{}'
          .format(policy.__name__,
                  np.min(rewards),
                  np.max(rewards),
                  np.mean(rewards)))

n_episodes = 100
rewards_max = 10000
experiment(policy_random, n_episodes, rewards_max)
experiment(policy_logic, n_episodes, rewards_max)

我们可以看到随机搜索确实改善了结果:

Policy:policy_random, Min reward:8.0, Max reward:200.0, Average reward:40.04
Policy:policy_logic, Min reward:25.0, Max reward:62.0, Average reward:43.03

通过随机搜索,我们改进了结果以获得 200 的最大奖励。平均而言,随机搜索的奖励较低,因为随机搜索会尝试各种不良参数,从而降低整体结果。但是,我们可以从所有运行中选择最佳参数,然后在生产中使用最佳参数。让我们修改代码以首先训练参数:

def policy_logic(theta,obs):
  # just ignore theta
  return 1 if obs[2] > 0 else 0

def policy_random(theta,obs):
  return 0 if np.matmul(theta,obs) < 0 else 1

def episode(env,policy, rewards_max,theta):
  obs = env.reset()
    done = False
    episode_reward = 0

    while not done:
  action = policy(theta,obs)
        obs, reward, done, info = env.step(action)
        episode_reward += reward
        if episode_reward > rewards_max:
  break
    return episode_reward

def train(policy, n_episodes, rewards_max):

  env = gym.make('CartPole-v0')
    theta_best = np.empty(shape=[4])
    reward_best = 0

    for i in range(n_episodes):
 if policy.__name__ in ['policy_random']:  theta = np.random.rand(4) * 2 - 1
        else:
  theta = None

        reward_episode=episode(env,policy,rewards_max, theta)
        if reward_episode > reward_best:
  reward_best = reward_episode
            theta_best = theta.copy()
    return reward_best,theta_best

def experiment(policy, n_episodes, rewards_max, theta=None):
  rewards=np.empty(shape=[n_episodes])
    env = gym.make('CartPole-v0')

    for i in range(n_episodes):
  rewards[i]=episode(env,policy,rewards_max,theta)
        #print("Episode finished at t{}".format(reward))
  print('Policy:{}, Min reward:{}, Max reward:{}, Average reward:{}'
          .format(policy.__name__,
                  np.min(rewards),
                  np.max(rewards),
                  np.mean(rewards)))

n_episodes = 100
rewards_max = 10000

reward,theta = train(policy_random, n_episodes, rewards_max)
print('trained theta: {}, rewards: {}'.format(theta,reward))
experiment(policy_random, n_episodes, rewards_max, theta)
experiment(policy_logic, n_episodes, rewards_max)

我们训练了 100 集,然后使用最佳参数为随机搜索策略运行实验:

n_episodes = 100
rewards_max = 10000

reward,theta = train(policy_random, n_episodes, rewards_max)
print('trained theta: {}, rewards: {}'.format(theta,reward))
experiment(policy_random, n_episodes, rewards_max, theta)
experiment(policy_logic, n_episodes, rewards_max)

我们发现训练参数给出了 200 的最佳结果:

trained theta: [-0.14779543  0.93269603  0.70896423  0.84632461], rewards: 200.0
Policy:policy_random, Min reward:200.0, Max reward:200.0, Average reward:200.0
Policy:policy_logic, Min reward:24.0, Max reward:63.0, Average reward:41.94

我们可以优化训练代码以继续训练,直到我们获得最大奖励。笔记本ch-13a_Reinforcement_Learning_NN中提供了此优化的代码。

现在我们已经学习了 OpenAI Gym 的基础知识,让我们学习强化学习。

强化学习 101

强化学习由智能体从前一个时间步骤输入观察和奖励并以动作产生输出来描述,目标是最大化累积奖励。

智能体具有策略,值函数和模型:

  • 智能体用于选择下一个动作的算法称为策略。在上一节中,我们编写了一个策略,它将采用一组参数θ,并根据观察和参数之间的乘法返回下一个动作。该策略由以下等式表示:

    S是一组状态,A是一组动作。

    策略是确定性的或随机性的。

    • 确定性策略在每次运行中为相同状态返回相同的操作:

    • 随机策略为每次运行中的相同状态返回相同操作的不同概率:

  • 值函数根据当前状态中的所选动作预测长期奖励的数量。因此,值函数特定于智能体使用的策略。奖励表示行动的直接收益,而值函数表示行动的累积或长期未来收益。奖励由环境返回,值函数由智能体在每个时间步骤估计。

  • 模型表示智能体在内部保存的环境。该模型可能是环境的不完美表示。智能体使用该模型来估计所选动作的奖励和下一个状态。

智能体的目标还可以是为马尔可夫决策过程(MDP)找到最优策略。 MDP 是从一个州到另一个州的观察,行动,奖励和过渡的数学表示。为简洁起见,我们将省略对 MDP 的讨论,并建议好奇的读者在互联网上搜索更深入 MDP 的资源。

Q 函数(在模型不可用时学习优化)

如果模型不可用,则智能体通过反复试验来学习模型和最优策略。当模型不可用时,智能体使用 Q 函数,其定义如下:

如果状态s处的智能体选择动作a,则 Q 函数基本上将状态和动作对映射到表示预期总奖励的实数。

RL 算法的探索与利用

在没有模型的情况下,智能体在每一步都要探索或利用。 探索意味着智能体选择一个未知动作来找出奖励和模型。 利用意味着智能体选择最知名的行动来获得最大奖励。如果智能体总是决定利用它,那么它可能会陷入局部最优值。因此,有时智能体会绕过学到的策略来探索未知的行为。同样,如果智能体总是决定探索,那么它可能无法找到最优策略。因此,在探索和利用之间取得平衡非常重要。在我们的代码中,我们通过使用概率p来选择随机动作和概率1-p来选择最优动作来实现这一点。

V 函数(模型可用时学习优化)

如果事先知道模型,则智能体可以执行策略搜索以找到最大化值函数的最优策略。当模型可用时,智能体使用值函数,该函数可以朴素地定义为未来状态的奖励总和:

因此,使用策略p选择操作的时间步t的值将是:

V是值,R是奖励,值函数估计在未来最多n个时间步长。

当智能体使用这种方法估计奖励时,它会平等地将所有行为视为奖励。在极点推车示例中,如果民意调查在步骤 50 处进行,则它将把直到第 50 步的所有步骤视为对跌倒的同等责任。因此,不是添加未来奖励,而是估计未来奖励的加权总和。通常,权重是提高到时间步长的折扣率。如果贴现率为零,则值函数变为上面讨论的幼稚函数,并且如果贴现率的值接近 1,例如 0.9 或 0.92,则与当前奖励相比,未来奖励的影响较小。

因此,现在行动a的时间步t的值将是:

V是值,R是奖励,r是折扣率。

V 函数和 Q 函数之间的关系

V*(s)是状态s下的最优值函数,其给出最大奖励,并且Q*(s,a)是状态s下的最佳 Q 函数,其通过选择动作a给出最大期望奖励。 因此,V*(s)是所有可能动作中所有最优 Q 函数Q*(s,a)的最大值:

强化学习技巧

可以根据模型的可用性对强化学习技术进行如下分类:

  • 模型可用:如果模型可用,则智能体可以通过迭代策略或值函数来离线计划,以找到提供最大奖励的最优策略。

    • 值迭代学习:在值迭代学习方法中,智能体通过将V(s)初始化为随机值开始,然后重复更新V(s)直到找到最大奖励。
    • 策略迭代学习 : 在策略迭代学习方法中,智能体通过初始化随机策略p开始,然后重复更新策略,直到找到最大奖励。
  • 模型不可用:如果模型不可用,则智能体只能通过观察其动作的结果来学习。因此,从观察,行动和奖励的历史来看,智能体会尝试估计模型或尝试直接推导出最优策略:

    • 基于模型的学习:在基于模型的学习中,智能体首先从历史中估计模型,然后使用策略或基于值的方法来找到最优策略。
    • 无模型学习:在无模型学习中,智能体不会估计模型,而是直接从历史中估计最优策略。 Q-Learning 是无模型学习的一个例子。

作为示例,值迭代学习的算法如下:

initialize V(s) to random values for all states
Repeat
    for s in states
        for a in actions
            compute Q[s,a]
        V(s) = max(Q[s])   # maximum of Q for all actions for that state
Until optimal value of V(s) is found for all states

策略迭代学习的算法如下:

initialize a policy P_new to random sequence of actions for all states
Repeat
    P = P_new
    for s in states
        compute V(s) with P[s]
        P_new[s] = policy of optimal V(s)
Until P == P_new

强化学习的朴素神经网络策略

我们按照以下策略进行:

  1. 让我们实现一个朴素的基于神经网络的策略。为定义一个新策略使用基于神经网络的预测来返回动作:
def policy_naive_nn(nn,obs):
  return np.argmax(nn.predict(np.array([obs])))
  1. nn定义为一个简单的单层 MLP 网络,它将具有四个维度的观测值作为输入,并产生两个动作的概率:
from keras.models import Sequential
from keras.layers import Dense
model = Sequential()
model.add(Dense(8,input_dim=4, activation='relu'))
model.add(Dense(2, activation='softmax'))
model.compile(loss='categorical_crossentropy',optimizer='adam')
model.summary()

这就是模型的样子:

Layer (type)                 Output Shape              Param #   
=================================================================
dense_16 (Dense)             (None, 8)                 40        
_________________________________________________________________
dense_17 (Dense)             (None, 2)                 18        
=================================================================
Total params: 58
Trainable params: 58
Non-trainable params: 0
  1. 这个模型需要训练。运行 100 集的模拟并仅收集分数大于 100 的那些剧集的训练数据。如果分数小于 100,那么这些状态和动作不值得记录,因为它们不是好戏的例子:
# create training data
env = gym.make('CartPole-v0')
n_obs = 4
n_actions = 2
theta = np.random.rand(4) * 2 - 1
n_episodes = 100
r_max = 0
t_max = 0

x_train, y_train = experiment(env, 
                              policy_random, 
                              n_episodes,
                              theta,r_max,t_max, 
                              return_hist_reward=100 )
y_train = np.eye(n_actions)[y_train]
print(x_train.shape,y_train.shape)

我们能够收集 5732 个样本进行训练:

(5732, 4) (5732, 2)
  1. 接下来,训练模型:
model.fit(x_train, y_train, epochs=50, batch_size=10)
  1. 训练的模型可用于玩游戏。但是,在我们合并更新训练数据的循环之前,模型不会从游戏的进一步游戏中学习:
n_episodes = 200
r_max = 0
t_max = 0

_ = experiment(env, 
              policy_naive_nn, 
              n_episodes,
              theta=model, 
              r_max=r_max, 
              t_max=t_max, 
              return_hist_reward=0 )

_ = experiment(env, 
              policy_random, 
              n_episodes,
              theta,r_max,t_max, 
              return_hist_reward=0 )

我们可以看到,这种朴素的策略几乎以同样的方式执行,虽然比随机策略好一点:

Policy:policy_naive_nn, Min reward:37.0, Max reward:200.0, Average reward:71.05
Policy:policy_random, Min reward:36.0, Max reward:200.0, Average reward:68.755

我们可以通过网络调整和超参数调整,或通过学习更多游戏玩法来进一步改进结果。 但是,有更好的算法,例如 Q-Learning。

在本章的其余部分,我们将重点关注 Q-Learning 算法,因为大多数现实生活中的问题涉及无模型学习。

实现 Q-Learning

Q-Learning 是一种无模型的方法,可以找到可以最大化智能体奖励的最优策略。在最初的游戏过程中,智能体会为每对(状态,动作)学习 Q 值,也称为探索策略,如前面部分所述。一旦学习了 Q 值,那么最优策略将是在每个状态中选择具有最大 Q 值的动作,也称为利用策略。学习算法可以以局部最优解决方案结束,因此我们通过设置exploration_rate参数来继续使用探索策略。

Q-Learning 算法如下:

initialize Q(shape=[#s,#a]) to random values or zeroes
Repeat (for each episode)
    observe current state s
    Repeat
        select an action a (apply explore or exploit strategy)
        observe state s_next as a result of action a
        update the Q-Table using bellman's equation
        set current state s = s_next       
    until the episode ends or a max reward / max steps condition is reached
Until a number of episodes or a condition is reached 
        (such as max consecutive wins)

上述算法中的Q(s, )表示我们在前面部分中描述的 Q 函数。此函数的值用于选择操作而不是奖励,因此此函数表示奖励或折扣奖励。使用未来状态中 Q 函数的值更新 Q 函数的值。众所周知的贝尔曼方程捕获了这一更新:

这基本上意味着在时间步骤t,在状态s中,对于动作a,最大未来奖励(Q)等于来自当前状态的奖励加上来自下一状态的最大未来奖励。

Q(s, a)可以实现为 Q 表或称为 Q 网络的神经网络。在这两种情况下,Q 表或 Q 网络的任务是基于给定输入的 Q 值提供最佳可能的动作。随着 Q 表变大,基于 Q 表的方法通常变得棘手,因此使神经网络成为通过 Q 网络逼近 Q 函数的最佳候选者。让我们看看这两种方法的实际应用。

您可以按照本书代码包中的 Jupyter 笔记本ch-13b_Reinforcement_Learning_DQN中的代码进行操作。

Q-Learning 的初始化和离散化

极地车环境返回的观测涉及环境状况。极点车的状态由我们需要离散的连续值表示。

如果我们将这些值离散化为小的状态空间,那么智能体会得到更快的训练,但需要注意的是会有收敛到最优策略的风险。

我们使用以下辅助函数来离散极推车环境的状态空间:

# discretize the value to a state space
def discretize(val,bounds,n_states):
  discrete_val = 0
    if val <= bounds[0]:
  discrete_val = 0
    elif val >= bounds[1]:
  discrete_val = n_states-1
    else:
  discrete_val = int(round( (n_states-1) * 
                                  ((val-bounds[0])/
                                   (bounds[1]-bounds[0])) 
                                ))
    return discrete_val

def discretize_state(vals,s_bounds,n_s):
  discrete_vals = []
    for i in range(len(n_s)):
  discrete_vals.append(discretize(vals[i],s_bounds[i],n_s[i]))
    return np.array(discrete_vals,dtype=np.int)

我们将每个观察尺寸的空间离散为 10 个单元。您可能想尝试不同的离散空间。在离散化之后,我们找到观察的上限和下限,并将速度和角速度的界限改变在 -1 和 +1 之间,而不是-Inf+Inf。代码如下:

env = gym.make('CartPole-v0')
n_a = env.action_space.n
# number of discrete states for each observation dimension
n_s = np.array([10,10,10,10])   # position, velocity, angle, angular velocity
s_bounds = np.array(list(zip(env.observation_space.low, env.observation_space.high)))
# the velocity and angular velocity bounds are 
# too high so we bound between -1, +1
s_bounds[1] = (-1.0,1.0) 
s_bounds[3] = (-1.0,1.0)   

使用 Q-Table 的 Q-Learning

您可以在ch-13b.ipynb中按照本节的代码进行操作。 由于我们的离散空间的尺寸为[10,10,10,10],因此我们的 Q 表的尺寸为[10,10,10,10,2]

# create a Q-Table of shape (10,10,10,10, 2) representing S X A -> R
q_table = np.zeros(shape = np.append(n_s,n_a)) 

我们根据exploration_rate定义了一个利用或探索的 Q-Table 策略:

def policy_q_table(state, env):
  # Exploration strategy - Select a random action
  if np.random.random() < explore_rate:
  action = env.action_space.sample()
    # Exploitation strategy - Select the action with the highest q
  else:
  action = np.argmax(q_table[tuple(state)])
    return action

定义运行单个剧集的episode()函数,如下所示:

  1. 首先初始化变量和第一个状态:
obs = env.reset()
state_prev = discretize_state(obs,s_bounds,n_s)

episode_reward = 0
done = False
t = 0
  1. 选择操作并观察下一个状态:
action = policy(state_prev, env)
obs, reward, done, info = env.step(action)
state_new = discretize_state(obs,s_bounds,n_s)
  1. 更新 Q 表:
best_q = np.amax(q_table[tuple(state_new)])
bellman_q = reward + discount_rate * best_q
indices = tuple(np.append(state_prev,action))
q_table[indices] += learning_rate*( bellman_q - q_table[indices])
  1. 将下一个状态设置为上一个状态,并将奖励添加到剧集的奖励中:
state_prev = state_new
episode_reward += reward

experiment()函数调用剧集函数并累积报告奖励。您可能希望修改该函数以检查连续获胜以及特定于您的游戏或游戏的其他逻辑:

# collect observations and rewards for each episode
def experiment(env, policy, n_episodes,r_max=0, t_max=0):
  rewards=np.empty(shape=[n_episodes])
    for i in range(n_episodes):
  val = episode(env, policy, r_max, t_max)
        rewards[i]=val
    print('Policy:{}, Min reward:{}, Max reward:{}, Average reward:{}'
      .format(policy.__name__,
              np.min(rewards),
              np.max(rewards),
              np.mean(rewards)))

现在,我们要做的就是定义参数,例如learning_ratediscount_rateexplore_rate,并运行experiment()函数,如下所示:

learning_rate = 0.8
discount_rate = 0.9
explore_rate = 0.2
n_episodes = 1000
experiment(env, policy_q_table, n_episodes)

对于 1000 集,基于我们的简单实现,基于 Q-Table 的策略的最大奖励为 180:

Policy:policy_q_table, Min reward:8.0, Max reward:180.0, Average reward:17.592

我们对算法的实现很容易解释。但是,您可以对代码进行修改以将探索率设置为最初,然后随着时间步长的过去而衰减。同样,您还可以实现学习和折扣率的衰减逻辑。让我们看看,由于我们的 Q 函数学得更快,我们是否可以用更少的剧集获得更高的奖励。

使用 Q-Network 或深度 Q 网络(DQN)的 Q-Learning

在 DQN 中,我们将 Q-Table 替换为神经网络(Q-Network),当我们使用探索状态及其 Q 值连续训练时,它将学会用最佳动作进行响应。因此,为了训练网络,我们需要一个存储游戏内存的地方:

  1. 使用大小为 1000 的双端队列实现游戏内存:
memory = deque(maxlen=1000)
  1. 接下来,构建一个简单的隐藏层神经网络模型,q_nn
from keras.models import Sequential
from keras.layers import Dense
model = Sequential()
model.add(Dense(8,input_dim=4, activation='relu'))
model.add(Dense(2, activation='linear'))
model.compile(loss='mse',optimizer='adam')
model.summary()
q_nn = model

Q-Network 看起来像这样:

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_1 (Dense)              (None, 8)                 40        
_________________________________________________________________
dense_2 (Dense)              (None, 2)                 18        
=================================================================
Total params: 58
Trainable params: 58
Non-trainable params: 0
_________________________________________________________________

执行游戏的一集的episode()函数包含基于 Q-Network 的算法的以下更改:

  1. 生成下一个状态后,将状态,操作和奖励添加到游戏内存中:
action = policy(state_prev, env)
obs, reward, done, info = env.step(action)
state_next = discretize_state(obs,s_bounds,n_s)

# add the state_prev, action, reward, state_new, done to memory
memory.append([state_prev,action,reward,state_next,done])
  1. 使用 bellman 函数生成并更新q_values以获得最大的未来奖励:
states = np.array([x[0] for x in memory])
states_next = np.array([np.zeros(4) if x[4] else x[3] for x in memory])
q_values = q_nn.predict(states)
q_values_next = q_nn.predict(states_next)

for i in range(len(memory)):
  state_prev,action,reward,state_next,done = memory[i]
    if done:
  q_values[i,action] = reward
    else:
  best_q = np.amax(q_values_next[i])
        bellman_q = reward + discount_rate * best_q
        q_values[i,action] = bellman_q
  1. 训练q_nn的状态和我们从记忆中收到的q_values
q_nn.fit(states,q_values,epochs=1,batch_size=50,verbose=0)

将游戏玩法保存在内存中并使用它来训练模型的过程在深度强化学习文献中也称为记忆重放。让我们按照以下方式运行基于 DQN 的游戏:

learning_rate = 0.8
discount_rate = 0.9
explore_rate = 0.2
n_episodes = 100
experiment(env, policy_q_nn, n_episodes)

我们获得 150 的最大奖励,您可以通过超参数调整,网络调整以及使用折扣率和探索率的速率衰减来改进:

Policy:policy_q_nn, Min reward:8.0, Max reward:150.0, Average reward:41.27

我们在每一步计算和训练模型;您可能希望在剧集之后探索将其更改为训练。此外,您可以更改代码以丢弃内存重放,并为返回较小奖励的剧集再训练模型。但是,请谨慎实现此选项,因为它可能会减慢您的学习速度,因为初始游戏会更频繁地产生较小的奖励。

总结

在本章中,我们学习了如何在 Keras 中实现强化学习算法。为了保持示例的简单,我们使用了 Keras;您也可以使用 TensorFlow 实现相同的网络和模型。我们只使用了单层 MLP,因为我们的示例游戏非常简单,但对于复杂的示例,您最终可能会使用复杂的 CNN,RNN 或序列到序列模型。

我们还了解了 OpenAI Gym,这是一个框架,提供了一个模拟许多流行游戏的环境,以实现和实践强化学习算法。我们谈到了深层强化学习概念,我们鼓励您探索专门写有关强化学习的书籍,以深入学习理论和概念。

强化学习是一种先进的技术,你会发现它常用于解决复杂的问题。在下一章中,我们将学习另一系列先进的深度学习技术:生成对抗网络。

十四、生成对抗网络

生成模型被训练以生成与他们训练的数据类似的更多数据,并且训练对抗模型以通过提供对抗性示例来区分真实数据和假数据。

生成对抗网络GAN)结合了两种模型的特征。 GAN 有两个组成部分:

  • 生成模型,用于学习如何生成类似数据的
  • 判别模型,用于学习如何区分真实数据和生成数据(来自生成模型)

GAN 已成功应用于各种复杂问题,例如:

  • 从低分辨率图像生成照片般逼真的高分辨率图像
  • 在文本中合成图像
  • 风格迁移
  • 补全不完整的图像和视频

在本章中,我们将学习以下主题,以学习如何在 TensorFlow 和 Keras 中实现 GAN:

  • 生成对抗网络
  • TensorFlow 中的简单 GAN
  • Keras 中的简单 GAN
  • TensorFlow 和 Keras 中的深度卷积 GAN

生成对抗网络 101

如下图所示,生成对抗网络(通常称为 GAN)有两个同步工作模型,用于学习和训练复杂数据,如图像,视频或音频文件:

直观地,生成器模型从随机噪声开始生成数据,但是慢慢地学习如何生成更真实的数据。生成器输出和实际数据被馈送到判别器,该判别器学习如何区分假数据和真实数据。

因此,生成器和判别器都发挥对抗性游戏,其中生成器试图通过生成尽可能真实的数据来欺骗判别器,并且判别器试图不通过从真实数据中识别伪数据而被欺骗,因此判别器试图最小化分类损失。两个模型都以锁步方式进行训练。

在数学上,生成模型G(z)学习概率分布p(z),使得判别器D(G(z), x)无法在概率分布p(z)p(x)之间进行识别。 GAN 的目标函数可以通过下面描述值函数V的等式来描述,(来自此链接):

可以在此链接中找到 IAN Goodfellow 在 NIPS 2016 上关于 GAN 的开创性教程

这个描述代表了一个简单的 GAN(在文献中也称为香草 GAN),由 Goodfellow 在此链接提供的开创性论文中首次介绍。从那时起,在基于 GAN 推导不同架构并将其应用于不同应用领域方面进行了大量研究。

例如,在条件 GAN 中,为生成器和判别器网络提供标签,使得条件 GAN 的目标函数可以通过以下描述值函数V的等式来描述:

描述条件 GAN 的原始论文位于此链接

应用中使用的其他几种衍生产品及其原始论文,如文本到图像,图像合成,图像标记,样式转移和图像转移等,如下表所示:

GAN 衍生物原始文件示例应用
StackGANarxiv.org/abs/1710.10…文字到图像
StackGAN++arxiv.org/abs/1612.03…逼真的图像合成
DCGANarxiv.org/abs/1511.06…图像合成
HR-DCGANarxiv.org/abs/1711.06…高分辨率图像合成
条件 GANarxiv.org/abs/1411.17…图像标记
InfoGANarxiv.org/abs/1606.03…风格识别
Wasserstein GANarxiv.org/abs/1701.07… arxiv.org/abs/1704.00…图像生成
耦合 GANarxiv.org/abs/1606.07…图像转换,域适应
BEGANarxiv.org/abs/1703.10…图像生成
DiscoGANarxiv.org/abs/1703.05…风格迁移
CycleGANarxiv.org/abs/1703.10…风格迁移

让我们练习使用 MNIST 数据集创建一个简单的 GAN。在本练习中,我们将使用以下函数将 MNIST 数据集标准化为介于[-1, +1]之间:

def norm(x):
  return (x-0.5)/0.5

我们还定义了 256 维的随机噪声,用于测试生成器模型:

n_z = 256
z_test = np.random.uniform(-1.0,1.0,size=[8,n_z])

显示将在本章所有示例中使用的生成图像的函数:

def display_images(images):
 for i in range(images.shape[0]):  plt.subplot(1, 8, i + 1)
        plt.imshow(images[i])
        plt.axis('off')
    plt.tight_layout()
    plt.show()

建立和训练 GAN 的最佳实践

对于我们为此演示选择的数据集,判别器在对真实和假图像进行分类方面变得非常擅长,因此没有为生成器提供梯度方面的大量反馈。因此,我们必须通过以下最佳实践使判别器变弱:

  • 判别器的学习率保持远高于生成器的学习率。
  • 判别器的优化器是GradientDescent,生成器的优化器是Adam
  • 判别器具有丢弃正则化,而生成器则没有。
  • 与生成器相比,判别器具有更少的层和更少的神经元。
  • 生成器的输出是tanh,而判别器的输出是 sigmoid。
  • 在 Keras 模型中,对于实际数据的标签,我们使用 0.9 而不是 1.0 的值,对于伪数据的标签,我们使用 0.1 而不是 0.0,以便在标签中引入一点噪声

欢迎您探索并尝试其他最佳实践。

TensorFlow 中的简单的 GAN

您可以按照 Jupyter 笔记本中的代码ch-14a_SimpleGAN

为了使用 TensorFlow 构建 GAN,我们使用以下步骤构建三个网络,两个判别器模型和一个生成器模型:

  1. 首先添加用于定义网络的超参数:
# graph hyperparameters
g_learning_rate = 0.00001
d_learning_rate = 0.01
n_x = 784  # number of pixels in the MNIST image 

# number of hidden layers for generator and discriminator
g_n_layers = 3
d_n_layers = 1
# neurons in each hidden layer
g_n_neurons = [256, 512, 1024]
d_n_neurons = [256]

# define parameter ditionary
d_params = {}
g_params = {}

activation = tf.nn.leaky_relu
w_initializer = tf.glorot_uniform_initializer
b_initializer = tf.zeros_initializer
  1. 接下来,定义生成器网络:
z_p = tf.placeholder(dtype=tf.float32, name='z_p', 
        shape=[None, n_z])
layer = z_p

# add generator network weights, biases and layers
with tf.variable_scope('g'):
 for i in range(0, g_n_layers):  w_name = 'w_{0:04d}'.format(i)
        g_params[w_name] = tf.get_variable(
            name=w_name,
            shape=[n_z if i == 0 else g_n_neurons[i - 1], 
                    g_n_neurons[i]],
            initializer=w_initializer())
        b_name = 'b_{0:04d}'.format(i)
        g_params[b_name] = tf.get_variable(
            name=b_name, shape=[g_n_neurons[i]], 
            initializer=b_initializer())
        layer = activation(
            tf.matmul(layer, g_params[w_name]) + g_params[b_name])
    # output (logit) layer
  i = g_n_layers
    w_name = 'w_{0:04d}'.format(i)
    g_params[w_name] = tf.get_variable(
        name=w_name,
        shape=[g_n_neurons[i - 1], n_x],
        initializer=w_initializer())
    b_name = 'b_{0:04d}'.format(i)
    g_params[b_name] = tf.get_variable(
        name=b_name, shape=[n_x], initializer=b_initializer())
    g_logit = tf.matmul(layer, g_params[w_name]) + g_params[b_name]
    g_model = tf.nn.tanh(g_logit)
  1. 接下来,定义我们将构建的两个判别器网络的权重和偏差:
with tf.variable_scope('d'):
 for i in range(0, d_n_layers):  w_name = 'w_{0:04d}'.format(i)
        d_params[w_name] = tf.get_variable(
            name=w_name,
            shape=[n_x if i == 0 else d_n_neurons[i - 1], 
                    d_n_neurons[i]],
            initializer=w_initializer())

        b_name = 'b_{0:04d}'.format(i)
        d_params[b_name] = tf.get_variable(
            name=b_name, shape=[d_n_neurons[i]], 
            initializer=b_initializer())

    #output (logit) layer
  i = d_n_layers
    w_name = 'w_{0:04d}'.format(i)
    d_params[w_name] = tf.get_variable(
        name=w_name, shape=[d_n_neurons[i - 1], 1], 
        initializer=w_initializer())

    b_name = 'b_{0:04d}'.format(i)
    d_params[b_name] = tf.get_variable(
        name=b_name, shape=[1], initializer=b_initializer())
  1. 现在使用这些参数,构建将真实图像作为输入并输出分类的判别器:
# define discriminator_real

# input real images
x_p = tf.placeholder(dtype=tf.float32, name='x_p', 
        shape=[None, n_x])

layer = x_p

with tf.variable_scope('d'):
 for i in range(0, d_n_layers):  w_name = 'w_{0:04d}'.format(i)
        b_name = 'b_{0:04d}'.format(i)

        layer = activation(
            tf.matmul(layer, d_params[w_name]) + d_params[b_name])
        layer = tf.nn.dropout(layer,0.7)
    #output (logit) layer
  i = d_n_layers
    w_name = 'w_{0:04d}'.format(i)
    b_name = 'b_{0:04d}'.format(i)
    d_logit_real = tf.matmul(layer, 
        d_params[w_name]) + d_params[b_name]
    d_model_real = tf.nn.sigmoid(d_logit_real)
  1. 接下来,使用相同的参数构建另一个判别器网络,但提供生成器的输出作为输入:
# define discriminator_fake

# input generated fake images
z = g_model
layer = z

with tf.variable_scope('d'):
 for i in range(0, d_n_layers):  w_name = 'w_{0:04d}'.format(i)
        b_name = 'b_{0:04d}'.format(i)
        layer = activation(
            tf.matmul(layer, d_params[w_name]) + d_params[b_name])
        layer = tf.nn.dropout(layer,0.7)
    #output (logit) layer
  i = d_n_layers
    w_name = 'w_{0:04d}'.format(i)
    b_name = 'b_{0:04d}'.format(i)
    d_logit_fake = tf.matmul(layer, 
        d_params[w_name]) + d_params[b_name]
    d_model_fake = tf.nn.sigmoid(d_logit_fake)
  1. 现在我们已经建立了三个网络,它们之间的连接是使用损失,优化器和训练函数完成的。在训练生成器时,我们只训练生成器的参数,在训练判别器时,我们只训练判别器的参数。我们使用var_list参数将此指定给优化器的minimize()函数。以下是为两种网络定义损失,优化器和训练函数的完整代码:
g_loss = -tf.reduce_mean(tf.log(d_model_fake))
d_loss = -tf.reduce_mean(tf.log(d_model_real) + tf.log(1 - d_model_fake))

g_optimizer = tf.train.AdamOptimizer(g_learning_rate)
d_optimizer = tf.train.GradientDescentOptimizer(d_learning_rate)

g_train_op = g_optimizer.minimize(g_loss, 
                var_list=list(g_params.values()))
d_train_op = d_optimizer.minimize(d_loss, 
                var_list=list(d_params.values()))
  1. 现在我们已经定义了模型,我们必须训练模型。训练按照以下算法完成:
For each epoch:
 For each batch:  get real images x_batch
    generate noise z_batch
    train discriminator using z_batch and x_batch
    generate noise z_batch
    train generator using z_batch

笔记本电脑的完整训练代码如下:

n_epochs = 400
batch_size = 100
n_batches = int(mnist.train.num_examples / batch_size)
n_epochs_print = 50

with tf.Session() as tfs:
  tfs.run(tf.global_variables_initializer())
    for epoch in range(n_epochs):
  epoch_d_loss = 0.0
        epoch_g_loss = 0.0
        for batch in range(n_batches):
  x_batch, _ = mnist.train.next_batch(batch_size)
            x_batch = norm(x_batch)
            z_batch = np.random.uniform(-1.0,1.0,size=[batch_size,n_z])
            feed_dict = {x_p: x_batch,z_p: z_batch}
            _,batch_d_loss = tfs.run([d_train_op,d_loss], 
                                    feed_dict=feed_dict)
            z_batch = np.random.uniform(-1.0,1.0,size=[batch_size,n_z])
            feed_dict={z_p: z_batch}
            _,batch_g_loss = tfs.run([g_train_op,g_loss], 
                                    feed_dict=feed_dict)
            epoch_d_loss += batch_d_loss 
            epoch_g_loss += batch_g_loss

        if epoch%n_epochs_print == 0:
  average_d_loss = epoch_d_loss / n_batches
            average_g_loss = epoch_g_loss / n_batches
            print('epoch: {0:04d}   d_loss = {1:0.6f}  g_loss = {2:0.6f}'
                  .format(epoch,average_d_loss,average_g_loss))
            # predict images using generator model trained            
 x_pred = tfs.run(g_model,feed_dict={z_p:z_test})
            display_images(x_pred.reshape(-1,pixel_size,pixel_size))   

我们每 50 个周期印刷生成的图像:

正如我们所看到的那样,生成器在周期 0 中只产生噪声,但是在周期 350 中,它经过训练可以产生更好的手写数字形状。您可以尝试使用周期,正则化,网络架构和其他超参数进行试验,看看是否可以产生更快更好的结果。

Keras 中的简单的 GAN

您可以按照 Jupyter 笔记本中的代码ch-14a_SimpleGAN

现在让我们在 Keras 实现相同的模型:

  1. 超参数定义与上一节保持一致:
# graph hyperparameters
g_learning_rate = 0.00001
d_learning_rate = 0.01
n_x = 784  # number of pixels in the MNIST image 
# number of hidden layers for generator and discriminator
g_n_layers = 3
d_n_layers = 1
# neurons in each hidden layer
g_n_neurons = [256, 512, 1024]
d_n_neurons = [256]
  1. 接下来,定义生成器网络:
# define generator

g_model = Sequential()
g_model.add(Dense(units=g_n_neurons[0], 
                  input_shape=(n_z,),
                  name='g_0'))
g_model.add(LeakyReLU())
for i in range(1,g_n_layers):
  g_model.add(Dense(units=g_n_neurons[i],
                      name='g_{}'.format(i)
                     ))
    g_model.add(LeakyReLU())
g_model.add(Dense(units=n_x, activation='tanh',name='g_out'))
print('Generator:')
g_model.summary()
g_model.compile(loss='binary_crossentropy',
              optimizer=keras.optimizers.Adam(lr=g_learning_rate)
             )

这就是生成器模型的样子:

Generator:
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
g_0 (Dense)                  (None, 256)               65792     
_________________________________________________________________
leaky_re_lu_1 (LeakyReLU)    (None, 256)               0         
_________________________________________________________________
g_1 (Dense)                  (None, 512)               131584    
_________________________________________________________________
leaky_re_lu_2 (LeakyReLU)    (None, 512)               0         
_________________________________________________________________
g_2 (Dense)                  (None, 1024)              525312    
_________________________________________________________________
leaky_re_lu_3 (LeakyReLU)    (None, 1024)              0         
_________________________________________________________________
g_out (Dense)                (None, 784)               803600    
=================================================================
Total params: 1,526,288
Trainable params: 1,526,288
Non-trainable params: 0
_________________________________________________________________
  1. 在 Keras 示例中,我们没有定义两个判别器网络,就像我们在 TensorFlow 示例中定义的那样。相反,我们定义一个判别器网络,然后将生成器和判别器网络缝合到 GAN 网络中。然后,GAN 网络仅用于训练生成器参数,判别器网络用于训练判别器参数:
# define discriminator

d_model = Sequential()
d_model.add(Dense(units=d_n_neurons[0],  
                  input_shape=(n_x,),
                  name='d_0'
                 ))
d_model.add(LeakyReLU())
d_model.add(Dropout(0.3))
for i in range(1,d_n_layers):
  d_model.add(Dense(units=d_n_neurons[i], 
                      name='d_{}'.format(i)
                     ))
    d_model.add(LeakyReLU())
    d_model.add(Dropout(0.3))
d_model.add(Dense(units=1, activation='sigmoid',name='d_out'))
print('Discriminator:')
d_model.summary()
d_model.compile(loss='binary_crossentropy',
              optimizer=keras.optimizers.SGD(lr=d_learning_rate)
             )

这是判别器模型的外观:

Discriminator:
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
d_0 (Dense)                  (None, 256)               200960    
_________________________________________________________________
leaky_re_lu_4 (LeakyReLU)    (None, 256)               0         
_________________________________________________________________
dropout_1 (Dropout)          (None, 256)               0         
_________________________________________________________________
d_out (Dense)                (None, 1)                 257       
=================================================================
Total params: 201,217
Trainable params: 201,217
Non-trainable params: 0
_________________________________________________________________
  1. 接下来,定义 GAN 网络,并将判别器模型的可训练属性转换为false,因为 GAN 仅用于训练生成器:
# define GAN network
d_model.trainable=False
z_in = Input(shape=(n_z,),name='z_in')
x_in = g_model(z_in)
gan_out = d_model(x_in)

gan_model = Model(inputs=z_in,outputs=gan_out,name='gan')
print('GAN:')
gan_model.summary()
gan_model.compile(loss='binary_crossentropy',
              optimizer=keras.optimizers.Adam(lr=g_learning_rate)
             )

这就是 GAN 模型的样子:

GAN:
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
z_in (InputLayer)            (None, 256)               0         
_________________________________________________________________
sequential_1 (Sequential)    (None, 784)               1526288   
_________________________________________________________________
sequential_2 (Sequential)    (None, 1)                 201217    
=================================================================
Total params: 1,727,505
Trainable params: 1,526,288
Non-trainable params: 201,217
_________________________________________________________________
  1. 太好了,现在我们已经定义了三个模型,我们必须训练模型。训练按照以下算法进行:
For each epoch:
 For each batch:  get real images x_batch
    generate noise z_batch
    generate images g_batch using generator model
    combine g_batch and x_batch into x_in and create labels y_out

    set discriminator model as trainable
    train discriminator using x_in and y_out

    generate noise z_batch
    set x_in = z_batch and labels y_out = 1

    set discriminator model as non-trainable
    train gan model using x_in and y_out, 
        (effectively training generator model)

为了设置标签,我们分别对真实和假图像应用标签 0.9 和 0.1。通常,建议您使用标签平滑,通过为假数据选择 0.0 到 0.3 的随机值,为实际数据选择 0.8 到 1.0。

以下是笔记本电脑训练的完整代码:

n_epochs = 400
batch_size = 100
n_batches = int(mnist.train.num_examples / batch_size)
n_epochs_print = 50

for epoch in range(n_epochs+1):
  epoch_d_loss = 0.0
    epoch_g_loss = 0.0
    for batch in range(n_batches):
  x_batch, _ = mnist.train.next_batch(batch_size)
        x_batch = norm(x_batch)
        z_batch = np.random.uniform(-1.0,1.0,size=[batch_size,n_z])
        g_batch = g_model.predict(z_batch)

        x_in = np.concatenate([x_batch,g_batch])

        y_out = np.ones(batch_size*2)
        y_out[:batch_size]=0.9
        y_out[batch_size:]=0.1

        d_model.trainable=True
        batch_d_loss = d_model.train_on_batch(x_in,y_out)

        z_batch = np.random.uniform(-1.0,1.0,size=[batch_size,n_z])
        x_in=z_batch

        y_out = np.ones(batch_size)

        d_model.trainable=False
        batch_g_loss = gan_model.train_on_batch(x_in,y_out)

        epoch_d_loss += batch_d_loss 
        epoch_g_loss += batch_g_loss 
    if epoch%n_epochs_print == 0:
  average_d_loss = epoch_d_loss / n_batches
        average_g_loss = epoch_g_loss / n_batches
        print('epoch: {0:04d}   d_loss = {1:0.6f}  g_loss = {2:0.6f}'
              .format(epoch,average_d_loss,average_g_loss))
        # predict images using generator model trained            
 x_pred = g_model.predict(z_test)
        display_images(x_pred.reshape(-1,pixel_size,pixel_size))   

我们每 50 个周期印刷结果,最多 350 个周期:

该模型慢慢地学习从随机噪声中生成高质量的手写数字图像。

GAN 有如此多的变化,它将需要另一本书来涵盖所有不同类型的 GAN。但是,实现技术几乎与我们在此处所示的相似。

TensorFlow 和 Keras 中的深度卷积 GAN

您可以按照 Jupyter 笔记本中的代码ch-14b_DCGAN

在 DCGAN 中,判别器和生成器都是使用深度卷积网络实现的:

  1. 在此示例中,我们决定将生成器实现为以下网络:
Generator:
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
g_in (Dense)                 (None, 3200)              822400    
_________________________________________________________________
g_in_act (Activation)        (None, 3200)              0         
_________________________________________________________________
g_in_reshape (Reshape)       (None, 5, 5, 128)         0         
_________________________________________________________________
g_0_up2d (UpSampling2D)      (None, 10, 10, 128)       0         
_________________________________________________________________
g_0_conv2d (Conv2D)          (None, 10, 10, 64)        204864    
_________________________________________________________________
g_0_act (Activation)         (None, 10, 10, 64)        0         
_________________________________________________________________
g_1_up2d (UpSampling2D)      (None, 20, 20, 64)        0         
_________________________________________________________________
g_1_conv2d (Conv2D)          (None, 20, 20, 32)        51232     
_________________________________________________________________
g_1_act (Activation)         (None, 20, 20, 32)        0         
_________________________________________________________________
g_2_up2d (UpSampling2D)      (None, 40, 40, 32)        0         
_________________________________________________________________
g_2_conv2d (Conv2D)          (None, 40, 40, 16)        12816     
_________________________________________________________________
g_2_act (Activation)         (None, 40, 40, 16)        0         
_________________________________________________________________
g_out_flatten (Flatten)      (None, 25600)             0         
_________________________________________________________________
g_out (Dense)                (None, 784)               20071184  
=================================================================
Total params: 21,162,496
Trainable params: 21,162,496
Non-trainable params: 0
  1. 生成器是一个更强大的网络,有三个卷积层,然后是 tanh 激活。我们将判别器网络定义如下:
Discriminator:
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
d_0_reshape (Reshape)        (None, 28, 28, 1)         0         
_________________________________________________________________
d_0_conv2d (Conv2D)          (None, 28, 28, 64)        1664      
_________________________________________________________________
d_0_act (Activation)         (None, 28, 28, 64)        0         
_________________________________________________________________
d_0_maxpool (MaxPooling2D)   (None, 14, 14, 64)        0         
_________________________________________________________________
d_out_flatten (Flatten)      (None, 12544)             0         
_________________________________________________________________
d_out (Dense)                (None, 1)                 12545     
=================================================================
Total params: 14,209
Trainable params: 14,209
Non-trainable params: 0
_________________________________________________________________
  1. GAN 网络由判别器和生成器组成,如前所述:
GAN:
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
z_in (InputLayer)            (None, 256)               0         
_________________________________________________________________
g (Sequential)               (None, 784)               21162496  
_________________________________________________________________
d (Sequential)               (None, 1)                 14209     
=================================================================
Total params: 21,176,705
Trainable params: 21,162,496
Non-trainable params: 14,209
_________________________________________________________________

当我们运行这个模型 400 个周期时,我们得到以下输出:

如您所见,DCGAN 能够从周期 100 本身开始生成高质量的数字。 DGCAN 已被用于样式转移,图像和标题的生成以及图像代数,即拍摄一个图像的一部分并将其添加到另一个图像的部分。 MNIST DCGAN 的完整代码在笔记本ch-14b_DCGAN中提供。

总结

在本章中,我们了解了生成对抗网络。我们在 TensorFlow 和 Keras 中构建了一个简单的 GAN,并将其应用于从 MNIST 数据集生成图像。我们还了解到,许多不同的 GAN 衍生产品正在不断推出,例如 DCGAN,SRGAN,StackGAN 和 CycleGAN 等等。我们还建立了一个 DCGAN,其中生成器和判别器由卷积网络组成。我们鼓励您阅读并尝试不同的衍生工具,以了解哪些模型适合他们试图解决的问题。

在下一章中,我们将学习如何使用 TensorFlow 集群和多个计算设备(如多个 GPU)在分布式集群中构建和部署模型。