tf-ml-proj-merge-2

127 阅读1小时+

TensorFlow 机器学习项目(三)

原文:annas-archive.org/md5/83fecd7b232ff9aa6762c486005b5094

译者:飞龙

协议:CC BY-NC-SA 4.0

第十一章:使用 TensorFlow 进行高质量产品推荐

当你访问亚马逊、Netflix 或其他你喜欢的网站,或使用 Spotify、Pandora 等现代应用时,你会注意到它们会向你推荐不同的商品。这些推荐是通过推荐系统算法生成的。在基于机器学习的推荐系统之前,推荐是通过基于规则的系统生成的。然而,随着机器学习和神经网络的出现,推荐变得更加准确。

在本章中,我们将学习推荐系统。我们将使用 Retailrocket 数据集,通过两种不同的方式实现推荐系统,分别使用 TensorFlow 和 Keras。

本章将涉及以下主题:

  • 推荐系统

  • 基于内容的过滤

  • 协同过滤

  • 混合系统

  • 矩阵分解

  • 介绍 Retailrocket 数据集

  • 探索 Retailrocket 数据集

  • 数据预处理

  • Retailrocket 推荐系统的矩阵分解模型

  • Retailrocket 推荐系统的神经网络模型

推荐系统

机器学习系统的最常见应用之一是向用户推荐他们可能感兴趣的内容。你有没有注意到 Spotify 和 Pandora 如何推荐某种类型的音乐,或者特定的歌曲或电台?你可能也观察到 Netflix 向你推荐电影,如以下截图所示:

亚马逊如何根据你当前浏览的书籍推荐其他书籍,参考以下截图:

这样的系统被称为推荐系统。

推荐系统是学习用户可能感兴趣的项目,然后推荐这些项目供购买、租赁、收听、观看等使用的系统。推荐系统大致可以分为两类:基于内容的过滤和协同过滤。

基于内容的过滤

基于内容的过滤是通过创建内容的详细模型来进行推荐的,这些内容可能是书籍的文本、电影的属性或音乐的信息。内容模型通常表示为向量空间模型。一些常见的将内容转化为向量空间模型的方法包括 TFIDF、词袋模型、Word2Vec、GloVe 和 Item2Vec。

除了内容模型,还通过用户信息创建了用户档案。根据匹配用户档案和内容模型来推荐内容。

基于内容的过滤算法的优点

以下是基于内容的过滤算法的优点:

  • 消除了新物品的冷启动问题: 如果我们拥有足够的用户信息以及新内容的详细信息,那么协同过滤算法中的冷启动问题就不会影响基于内容的算法。推荐可以基于用户档案和内容信息进行。

  • 推荐结果是可解释和透明的: 使用内容表示模型,我们能够解释为什么某些物品被选择作为推荐项。

基于内容的过滤算法的缺点

以下是基于内容的过滤算法的缺点:

  • 基于内容的过滤算法需要详细的物品和内容信息,而这些信息有时并不可用

  • 基于内容的过滤算法容易导致过度专业化

协同过滤

协同过滤算法不需要关于用户或物品的详细信息。它们根据用户与物品的互动(如听过的歌曲、查看的物品、点击的链接、购买的物品或观看的视频)构建模型。通过用户与物品的互动生成的信息可以分为两类:隐式反馈和显式反馈:

  • 显式反馈信息是指用户明确地为物品打分,例如对物品进行 1 到 5 的评分。

  • 隐式反馈信息是通过用户与物品之间的不同互动收集的,例如在 Retailrocket 数据集中,我们将使用的查看、点击、购买等互动。

进一步的协同过滤算法可以分为基于用户和基于物品的两种类型。在基于用户的算法中,重点关注用户之间的互动,以识别相似的用户。然后,系统会推荐其他相似用户购买或查看的物品。在基于物品的算法中,首先根据物品-用户的互动识别相似的物品,然后推荐与当前物品相似的物品。

混合系统

混合系统通过结合两种方法,利用基于内容和协同过滤的优势。混合系统有许多实现方式,例如:

  • 创建基于内容和协同过滤算法的集成,并结合两种算法的推荐结果

  • 通过内容细节和用户信息增强协同过滤

  • 向基于内容的过滤算法添加用户-物品互动模型

鼓励读者进一步探索三种推荐系统。我们将在接下来的章节中,使用 Retailrocket 数据集的示例,探讨如何通过矩阵分解和神经网络构建推荐系统。

矩阵分解

矩阵分解是一种实现推荐系统的流行算法,属于协同过滤算法类别。在此算法中,用户-物品交互被分解为两个低维矩阵。例如,假设我们数据集中的所有访客-物品交互是一个 M x N 的矩阵,记作 A。矩阵分解将矩阵 A 分解成两个分别为 M x k 和 k x N 维度的矩阵,使得这两个矩阵的点积可以逼近矩阵 A。用于寻找低维矩阵的更流行的算法之一是基于奇异值分解SVD)。在下面的示例中,我们将使用 TensorFlow 和 Keras 库来实现矩阵分解。

介绍 Retailrocket 数据集

在本章中,我们将展示如何使用 Retailrocket 数据集实现推荐系统算法。

Retailrocket 数据集可以从 Kaggle 网站下载,网址为www.kaggle.com/retailrocket/ecommerce-dataset

我们使用以下命令下载数据集:

kaggle datasets download -d retailrocket/ecommerce-dataset

下载的文件已移动到~/datasets/kaggle-retailrocket文件夹。你可以根据自己的需要保存在任何文件夹中。

Retailrocket 数据集包含三个文件:

  • events.csv:此文件包含访客-物品交互数据

  • item_properties.csv:此文件包含物品属性

  • category_tree.csv:此文件包含类别树

数据包含来自电商网站的数值,但已被匿名化以确保用户隐私。交互数据代表了 4.5 个月内的交互情况。

访客可以参与三种类别的事件:viewaddtocarttransaction。该数据集包含总共 2,756,101 次交互,包括 2,664,312 次view事件、69,332 次addtocart事件和 22,457 次transaction事件。这些交互来自 1,407,580 个独特的访客。

由于数据包含的是用户-物品交互而不是用户对物品的明确排序,因此它属于隐式反馈信息类别。

探索 Retailrocket 数据集

让我们加载数据集并进行探索,了解更多关于数据的信息。

  1. 设置我们下载数据的文件夹路径:
dsroot = os.path.join(os.path.expanduser('~'),
                      'datasets',
                      'kaggle-retailrocket')
os.listdir(dsroot)
  1. events.csv加载到一个 pandas DataFrame 中:
events = pd.read_csv(os.path.join(dsroot,'events.csv'))
print('Event data\n',events.head())

事件数据包含timestampvisitorideventitemidtransactionid五列,如下所示:

Event data
        timestamp  visitorid event  itemid  transactionid
0  1433221332117     257597  view  355908            NaN
1  1433224214164     992329  view  248676            NaN
2  1433221999827     111016  view  318965            NaN
3  1433221955914     483717  view  253185            NaN
4  1433221337106     951259  view  367447            NaN
  1. 打印唯一的物品、用户和交易:
print('Unique counts:',events.nunique())

我们得到如下输出:

Unique counts: timestamp        2750455
visitorid        1407580
event                  3
itemid            235061
transactionid      17672
dtype: int64
  1. 验证我们之前提到的事件类型:
print('Kind of events:',events.event.unique())

我们看到了之前描述的三种事件:

Kind of events: ['view' 'addtocart' 'transaction']

数据预处理

visitoriditemid字段已为数值型,但我们仍需要将事件转换为数值。

  1. 我们通过以下代码将view事件转换为1addtocart事件转换为2transaction事件转换为3
events.event.replace(to_replace=dict(view=1, 
                                     addtocart=2, 
                                     transaction=3), 
                     inplace=True)
  1. 删除我们不需要的transactionidtimestamp列:
events.drop(['transactionid'],axis=1,inplace=True)
events.drop(['timestamp'],axis=1,inplace=True)
  1. 对数据集进行打乱,以获得用于训练和测试的数据:
events = events.reindex(np.random.permutation(events.index))

数据集也可以通过以下命令进行打乱:

events = events.sample(frac=1).reset_index(drop=True)
  1. 将数据分为 trainvalidtest 集,如下所示:
split_1 = int(0.8 * len(events))
split_2 = int(0.9 * len(events))
train = events[:split_1]
valid = events[split_1:split_2]
test = events[split_2:]
print(train.head())
print(valid.head())
print(test.head())

traintest 数据如下所示:

             timestamp  visitorid  event  itemid
1621867  1431388649092     896963      1  264947
1060311  1440610461477    1102098      1  431592
114317   1433628249991    1241997      1  283584
1658382  1431543289648     198153      1   97879
2173151  1436211020113    1278262      1  218178
             timestamp  visitorid  event  itemid
1903213  1432567070061      85425      1  344338
1722815  1431708672912    1085328      1   59691
1388040  1442124865777    1366284      1  248032
2669880  1438030300131     478634      1  388940
1893864  1432416049191    1052918      1  328647
             timestamp  visitorid  event  itemid
1004940  1440383070554     193171      1   11565
642906   1438664048047     704648      1  262522
902126   1439869996568      10212      1   46971
569976   1435624889084     753933      1   29489
1517206  1430856529370     261457      1  154821

用于 Retailrocket 推荐的矩阵分解模型

现在让我们在 Keras 中创建一个矩阵分解模型:

  1. 将访客和物品的数量存储在一个变量中,如下所示:
n_visitors = events.visitorid.nunique()
n_items = events.itemid.nunique()
  1. 将嵌入层的潜在因子数量设置为 5。你可能想尝试不同的值,以观察对模型训练的影响:
n_latent_factors = 5
  1. 从 Keras 库中导入 Input、Embedding 和 Flatten 层:
from tensorflow.keras.layers import Input, Embedding, Flatten
  1. 从物品开始—创建一个输入层,如下所示:
item_input = Input(shape=[1],name='Items')
  1. 创建一个嵌入表示层,然后将该嵌入层展平,以获得我们之前设置的潜在维度的输出:
item_embed = Embedding(n_items + 1,
                           n_latent_factors, 
                           name='ItemsEmbedding')(item_input)
item_vec = Flatten(name='ItemsFlatten')(item_embed)
  1. 类似地,创建访客的向量空间表示:
visitor_input = Input(shape=[1],name='Visitors')
visitor_embed = Embedding(n_visitors + 1,
                          n_latent_factors,
                          name='VisitorsEmbedding')(visitor_input)
visitor_vec = Flatten(name='VisitorsFlatten')(visitor_embed)
  1. 创建一个点积层,用于表示两个向量空间的点积:
dot_prod = keras.layers.dot([item_vec, visitor_vec],axes=[1,1],
                             name='DotProduct') 
  1. 从输入层构建 Keras 模型,并将点积层作为输出层,然后按如下方式编译:
model = keras.Model([item_input, visitor_input], dot_prod)
model.compile('adam', 'mse')
model.summary()

模型总结如下:

________________________
Layer (type)                    Output Shape         Param #     Connected to                     
================================================================================
Items (InputLayer)              (None, 1)            0                                            
________________________________________________________________________________
Visitors (InputLayer)           (None, 1)            0                                            
________________________________________________________________________________
ItemsEmbedding (Embedding)      (None, 1, 5)         1175310     Items[0][0]                      
________________________________________________________________________________
VisitorsEmbedding (Embedding)   (None, 1, 5)         7037905     Visitors[0][0]                   
________________________________________________________________________________
ItemsFlatten (Flatten)          (None, 5)            0           ItemsEmbedding[0][0]             
________________________________________________________________________________
VisitorsFlatten (Flatten)       (None, 5)            0           VisitorsEmbedding[0][0]          
________________________________________________________________________________
DotProduct (Dot)                (None, 1)            0           ItemsFlatten[0][0]               
                                                                 VisitorsFlatten[0][0]            
================================================================================
Total params: 8,213,215
Trainable params: 8,213,215
Non-trainable params: 0
________________________________________________________________________________

由于模型较为复杂,我们还可以使用以下命令将其图形化:

keras.utils.plot_model(model, 
                       to_file='model.png', 
                       show_shapes=True, 
                       show_layer_names=True)
from IPython import display
display.display(display.Image('model.png'))

你可以通过这个绘制的可视化图清晰地看到各层及输出的大小:

现在让我们训练和评估模型:

model.fit([train.visitorid, train.itemid], train.event, epochs=50)
score = model.evaluate([test.visitorid, test.itemid], test.event)
print('mean squared error:', score)

训练和评估的损失会非常高。我们可以通过使用矩阵分解的高级方法来改进这一点。

现在,让我们构建神经网络模型,以提供相同的推荐。

用于 Retailrocket 推荐的神经网络模型

在这个模型中,我们为用户和物品设置了两个不同的潜在因子变量,但都将它们设置为 5。读者可以尝试使用不同的潜在因子值进行实验:

n_lf_visitor = 5
n_lf_item = 5
  1. 按照我们之前的方法,构建物品和访客的嵌入表示和向量空间表示:
item_input = Input(shape=[1],name='Items')
item_embed = Embedding(n_items + 1,
                           n_lf_visitor, 
                           name='ItemsEmbedding')(item_input)
item_vec = Flatten(name='ItemsFlatten')(item_embed)

visitor_input = Input(shape=[1],name='Visitors')
visitor_embed = Embedding(n_visitors + 1, 
                              n_lf_item,
                              name='VisitorsEmbedding')(visitor_input)
visitor_vec = Flatten(name='VisitorsFlatten')(visitor_embed)
  1. 不再创建点积层,而是将用户和访客的表示进行连接,然后应用全连接层以获得推荐输出:
concat = keras.layers.concatenate([item_vec, visitor_vec], name='Concat')
fc_1 = Dense(80,name='FC-1')(concat)
fc_2 = Dense(40,name='FC-2')(fc_1)
fc_3 = Dense(20,name='FC-3', activation='relu')(fc_2)

output = Dense(1, activation='relu',name='Output')(fc_3)
  1. 按如下方式定义并编译模型:
optimizer = keras.optimizers.Adam(lr=0.001)
model = keras.Model([item_input, visitor_input], output)
model.compile(optimizer=optimizer,loss= 'mse')

让我们看看这个模型的可视化效果:

  1. 训练和评估模型:
model.fit([train.visitorid, train.itemid], train.event, epochs=50)
score = model.evaluate([test.visitorid, test.itemid], test.event)
print('mean squared error:', score)

我们得到了一定准确度,并且误差率非常低:

275611/275611 [==============================] - 4s 14us/step
mean squared error: 0.05709125054560985

就是这样。我们鼓励读者了解更多不同的推荐系统算法,并尝试使用 Retailrocket 或其他公开可用的数据集来实现它们。

总结

在这一章中,我们学习了推荐系统。我们了解了不同种类的推荐系统,如协同过滤、基于内容的过滤和混合系统。我们使用 Retailrocket 数据集创建了两种推荐系统模型,一种是矩阵分解,另一种是使用神经网络。我们看到神经网络模型的准确度相当不错。

在下一章中,我们将学习如何使用分布式 TensorFlow 进行大规模目标检测。

问题

通过练习以下问题来增强理解:

  1. 实现基于文本内容的向量空间模型有哪些算法?

  2. 协同过滤的各种高级算法有哪些?

  3. 如何处理协同过滤模型中的过拟合问题?

  4. 尝试除了本章实现的算法之外的其他算法。

  5. 尝试不同的潜在因子值,分别用于访客和项目。

进一步阅读

你可以通过阅读以下材料来获得更多信息:

  • 以下链接中有推荐系统的教程和文章:recommendation-systems.org

  • 推荐系统手册 第二版 由 Francesco Ricci、Lior Rokach 和 Bracha Shapira 编著,2015 年。

  • 推荐系统:教科书 由 Charu C. Aggarwal 编著,2016 年。

第十二章:使用 TensorFlow 进行大规模目标检测

人工智能AI)领域的最新突破使深度学习成为焦点。如今,越来越多的组织正在采用深度学习技术分析其数据,而这些数据通常是庞大的。因此,将深度学习框架如 TensorFlow 与大数据平台和管道结合变得至关重要。

2017 年 Facebook 的论文讨论了如何使用 256 个 GPU 在 32 台服务器上训练 ImageNet,仅需一小时(research.fb.com/wp-content/uploads/2017/06/imagenet1kin1h5.pdf),以及香港浸会大学最近的论文,他们使用 2,048 个 GPU 在四分钟内训练 ImageNet(arxiv.org/pdf/1807.11205.pdf),这些研究证明了分布式 AI 是一个可行的解决方案。

分布式 AI 的主要思想是将任务划分到不同的处理集群中。已经提出了大量的框架用于分布式 AI。我们可以使用分布式 TensorFlow 或 TensorFlowOnSpark,这两种都是流行的分布式 AI 选择。我们将在本章中了解它们各自的优缺点。

在大规模应用计算密集型深度学习时,可能面临巨大的挑战。使用 TensorFlowOnSpark,我们可以在集群中分布这些计算密集型过程,使我们能够在更大规模上进行计算。

在这一章中,我们将探索 Yahoo 的 TensorFlowOnSpark 框架,用于在 Spark 集群上进行分布式深度学习。然后,我们将在一个大规模图像数据集上应用 TensorFlowOnSpark,并训练网络以检测物体。在这一章中,我们将涵盖以下主题:

  • 对分布式 AI 的需求

  • 对于大数据平台 Apache Spark 的介绍

  • TensorFlowOnSpark – 一种在 Spark 集群上运行 TensorFlow 的 Python 框架

  • 使用 TensorFlowOnSpark 和 Sparkdl API 执行目标检测

对于大数据,Spark 是事实上的首选,因此我们将从 Spark 的介绍开始。然后,我们将探索两种流行的选择:分布式 TensorFlow 和 TensorFlowOnSpark。

本章的代码可以在github.com/PacktPublishing/TensorFlow-Machine-Learning-Projects/tree/master/Chapter12找到。

介绍 Apache Spark

如果你曾经从事过大数据工作,可能已经知道 Apache Spark 是什么,可以跳过这一部分。但如果你不知道,别担心——我们会介绍基本概念。

Spark 是一个强大、快速、可扩展的实时数据分析引擎,用于大规模数据处理。它是一个开源框架,最初由加利福尼亚大学伯克利分校的 AMPLab 开发,约在 2009 年。到 2013 年,AMPLab 将 Spark 贡献给了 Apache 软件基金会,Apache Spark 社区在 2014 年发布了 Spark 1.0。

社区继续定期发布新版本并为项目带来新特性。在写本书时,我们有 Apache Spark 2.4.0 版本以及活跃的 GitHub 社区。它是一个实时数据分析引擎,允许你将程序分布式执行到多台机器上。

Spark 的美妙之处在于它是 可扩展的:它运行在集群管理器之上,允许你在最小修改的情况下使用用 Python(也可以是 Java 或 Scala)编写的脚本。Spark 由多个组件构成。核心部分是 Spark 核心,它负责分发数据处理以及大数据集的映射和归约。上面运行着一些库。以下是 Spark API 中的一些重要组件:

  • 弹性分布式数据集(RDD):RDD 是 Spark API 的基本元素。它是一个容错的元素集合,可以并行操作,这意味着 RDD 中的元素可以被集群中的工作节点同时访问和操作。

  • 转换和操作:在 Spark 的 RDD 上,我们可以执行两种类型的操作,转换和操作。转换以 RDD 作为参数,并返回另一个 RDD。操作以 RDD 作为参数,并返回本地结果。Spark 中的所有转换都是懒加载的,这意味着结果不会立即计算,而是只有当操作需要返回结果时,才会进行计算。

  • 数据框(DataFrames):这些与 pandas 的数据框非常相似。像 pandas 一样,我们可以从多种文件格式(如 JSON、Parquet、Hive 等)中读取数据,并使用单个命令对整个数据框执行操作。它们在集群中分布式运行。Spark 使用一个叫做 Catalyst 的引擎来优化它们的使用。

Spark 使用主/从架构。它有一个主节点/进程和多个工作节点/进程。驱动程序 SparkContext 是 Spark 应用程序的核心。它是 Spark 应用程序的主要入口点和主控,它设置内部服务并与 Spark 执行环境建立连接。下图展示了 Spark 的架构:

到目前为止,我们已经介绍了 Apache Spark。这是一个庞大且广泛的话题,我们建议读者参考 Apache 文档获取更多信息:spark.apache.org/documentation.html

理解分布式 TensorFlow

TensorFlow 还支持分布式计算,允许我们将图拆分并在不同进程上计算。分布式 TensorFlow 工作方式类似于客户端-服务器模型,或者更具体地说,是主节点-工作节点模型。在 TensorFlow 中,我们首先创建一个工作节点集群,其中一个节点是主节点。主节点负责协调任务分配到不同的工作节点。

当你需要在多台机器(或处理器)上工作时,首先要做的事情是定义它们的名称和工作类型,也就是构建一个机器(或处理器)集群。集群中的每台机器都会被分配一个唯一地址(例如,worker0.example.com:2222),并且它们会有一个特定的工作类型,比如type: master(参数服务器),或者是工作节点。稍后,TensorFlow 服务器会将特定的任务分配给每个工作节点。为了创建集群,我们首先需要定义集群规格。这是一个字典,用于映射工作进程和任务类型。以下代码创建了一个名为work的集群,并有两个工作进程:

import tensorflow as tf
cluster = tf.train.ClusterSpec({
   "worker":["worker0.example.com:2222",
           "worker1.example.com:2222"]
})

接下来,我们可以使用Server类并指定任务和任务索引来启动进程。以下代码将在worker1上启动worker任务:

server = tf.train.Server(cluster, job_name = "worker", task_index = 1)

我们需要为集群中的每个工作节点定义一个Server类。这将启动所有工作节点,使我们准备好进行分发。为了将 TensorFlow 操作分配到特定的任务上,我们将使用tf.device来指定哪些任务在哪个工作节点上运行。考虑以下代码,它将在两个工作节点之间分配任务:

import tensorflow as tf

# define Clusters with two workers
cluster = tf.train.ClusterSpec({
    "worker": [
        "localhost:2222",
        "localhost:2223"
         ]})

# define Servers
worker0 = tf.train.Server(cluster, job_name="worker", task_index=0)
worker1 = tf.train.Server(cluster, job_name="worker", task_index=1)

with tf.device("/job:worker/task:1"):
    a = tf.constant(3.0, dtype=tf.float32)
    b = tf.constant(4.0) 
    add_node = tf.add(a,b)

with tf.device("/job:worker/task:0"):
    mul_node = a * b

with tf.Session("grpc://localhost:2222") as sess:
    result = sess.run([add_node, mul_node])
    print(result)

上述代码在同一台机器上创建了两个工作节点。在这种情况下,工作被通过tf.device函数在两个工作节点之间分配。变量在各自的工作节点上创建;TensorFlow 在任务/工作节点之间插入适当的数据传输。

这是通过创建一个GrpcServer来完成的,它通过目标grpc://localhost:2222来创建。这个服务器知道如何通过GrpcChannels与同一任务中的其他任务进行通信。在下面的截图中,你可以看到前述代码的输出:

本章的代码位于仓库中的Chapter12/distributed.py目录下。

这看起来很简单,对吧?但是如果我们想将其扩展到我们的深度学习流水线中呢?

通过分布式 TensorFlow 进行深度学习

任何深度学习算法的核心是随机梯度下降优化器。这使得模型能够学习,同时也让学习过程计算开销大。将计算分发到集群中的不同节点应该能减少训练时间。TensorFlow 允许我们拆分计算图,将模型描述到集群中的不同节点,最后合并结果。

这一点通过主节点、工作节点和参数节点在 TensorFlow 中实现。实际的计算由工作节点执行;计算出的参数由参数节点保存,并与工作节点共享。主节点负责在不同工作节点之间协调工作负载。分布式计算中有两种常用的方法:

  • 同步方法:在这种方法中,工作节点之间分配了小批量数据。每个工作节点都有一个模型副本,并分别计算分配给它的小批量数据的梯度。稍后,梯度在主节点处合并,并同时应用于参数更新。

  • 异步方法:在这种方法中,模型参数的更新是异步应用的。

这两种方法在下图中展示:

现在,让我们看看如何在深度学习管道中集成分布式 TensorFlow。以下代码基于 Medium 上的文章,medium.com/@ntenenz/distributed-tensorflow-2bf94f0205c3

  1. 导入必要的模块。在这里,我们仅导入了必要的模块,以演示将现有深度学习代码转换为分布式 TensorFlow 代码所需的更改:
import sys
import tensorflow as tf
# Add other module libraries you may need
  1. 定义集群。我们将其创建为一个主节点,地址为 192.168.1.3,并且两个工作节点。我们希望将主节点分配到的机器有一个分配给它的 IP 地址,即 192.168.1.3,并且我们指定端口为 2222。你可以根据你机器的地址修改这些设置:
cluster = tf.train.ClusterSpec(
          {'ps':['192.168.1.3:2222'],
           'worker': ['192.168.1.4:2222',
                      '192.168.1.5:2222',
                      '192.168.1.6:2222',
                      '192.168.1.7:2222']
 })
  1. 相同的代码会在每台机器上执行,因此我们需要解析命令行参数:
job = sys.argv[1]
task_idx = sys.argv[2]
  1. 为每个工作节点和主节点创建 TensorFlow 服务器,以便集群中的节点能够进行通信:
server = tf.train.Server(cluster, job_name=job, task_index= int(task_idx))
  1. 确保变量分配在相同的工作设备上。TensorFlow 的 tf.train.replica_device_setter() 函数帮助我们在构造 Operation 对象时自动分配设备。同时,我们希望参数服务器在服务器关闭之前等待。这是通过在参数服务器上使用 server.join() 方法实现的:
if job == 'ps':  
    # Makes the parameter server wait 
    # until the Server shuts down
    server.join()
else:
    # Executes only on worker machines    
    with tf.device(tf.train.replica_device_setter(cluster=cluster, worker_device='/job:worker/task:'+task_idx)):
        #build your model here like you are working on a single machine

    with tf.Session(server.target):
        # Train the model 

你可以通过 GitHub 或 Chapter12/tensorflow_distributed_dl.py 目录访问此脚本。请记住,相同的脚本需要在集群中的每台机器上执行,但命令行参数不同。

相同的脚本现在需要在参数服务器和四个工作节点上执行:

使用以下代码在参数服务器(192.168.1.3:2222)上执行脚本:

python tensorflow_distributed_dl.py ps 0
  1. 使用以下代码在 worker 0192.168.1.4:2222)上执行脚本:
python tensorflow_distributed_dl.py worker 0
  1. 使用以下代码在 worker 1192.168.1.5:2222)上执行脚本:
python tensorflow_distributed_dl.py worker 1
  1. 使用以下代码在 worker 2192.168.1.6:2222)上执行脚本:
python tensorflow_distributed_dl.py worker 2
  1. 使用以下代码在 worker 3192.168.1.6:2222)上执行脚本:
python tensorflow_distributed_dl.py worker 3

分布式 TensorFlow 的主要缺点是我们需要在启动时指定集群中所有节点的 IP 地址和端口。这限制了分布式 TensorFlow 的可扩展性。在下一部分中,您将了解由 Yahoo 构建的 TensorFlowOnSpark API。它提供了一个简化的 API,用于在分布式 Spark 平台上运行深度学习模型。

若要了解更多关于分布式 TensorFlow 的内容,建议您阅读 Google REsearch 团队的论文《TensorFlow: Large Scale Machine Learning on Heterogeneous Distributed Systems》(2012 年 NIPS)(download.tensorflow.org/paper/whitepaper2015.pdf)。

了解 TensorFlowOnSpark

2016 年,Yahoo 开源了 TensorFlowOnSpark,这是一个用于在 Spark 集群上执行基于 TensorFlow 的分布式深度学习的 Python 框架。从那时起,它经历了很多开发变化,是分布式深度学习框架中最活跃的开源项目之一。

TensorFlowOnSparkTFoS)框架允许您在 Spark 程序中运行分布式 TensorFlow 应用。它运行在现有的 Spark 和 Hadoop 集群上。它可以使用现有的 Spark 库,如 SparkSQL 或 MLlib(Spark 的机器学习库)。

TFoS 是自动化的,因此我们无需将节点定义为 PS 节点,也无需将相同的代码上传到集群中的所有节点。只需进行少量修改,我们就可以运行现有的 TensorFlow 代码。它使我们能够以最小的改动扩展现有的 TensorFlow 应用。它支持所有现有的 TensorFlow 功能,如同步/异步训练、数据并行和 TensorBoard。基本上,它是 TensorFlow 代码的 PySpark 封装。它通过 Spark 执行器启动分布式 TensorFlow 集群。为了支持 TensorFlow 的数据摄取,它添加了feed_dictqueue_runner,允许直接从 TensorFlow 访问 HDFS。

理解 TensorFlowOnSpark 的架构

以下图示展示了 TFoS 的架构。我们可以看到,TFoS 在张量通信中不涉及 Spark 驱动程序,提供与独立 TensorFlow 集群相同的可扩展性:

TFoS 提供了两种输入模式,用于训练和推理时获取数据:

  • Spark RDD:Spark RDD 数据被传递到每个 Spark 执行器。执行器将数据通过feed_dict传递给 TensorFlow 图。然而,在这种模式下,TensorFlow 工作节点的失败对 Spark 是隐藏的。

  • TensorFlow QueueRunners:在这里,TensorFlow 工作节点在前台运行。TFoS 利用 TensorFlow 的文件读取器和 QueueRunners,直接从 HDFS 文件读取数据。TensorFlow 工作节点的失败会被视为 Spark 任务,并通过检查点进行恢复。

深入探讨 TFoS API

使用 TFoS 可以分为三个基本步骤:

  1. 启动 TensorFlow 集群。我们可以使用TFCluster.run来启动集群:
cluster = TFCluster.run(sc, map_fn, args, num_executors, num_ps, tensorboard, input_mode)
  1. 将数据输入 TensorFlow 应用程序。数据用于训练和推理。为了训练,我们使用train方法:
cluster.train(dataRDD, num_epochs)

我们通过cluster.inference(dataRDD)来执行推理。

  1. 最后,通过cluster.shutdown()关闭 TensorFlow 集群。

我们可以修改任何 TensorFlow 程序以与 TFoS 一起使用。在接下来的部分中,我们将介绍如何使用 TFoS 训练一个模型来识别手写数字。

使用 TFoS 进行手写数字识别

在本节中,我们将介绍如何将 TensorFlow 代码转换为在 TFoS 上运行。为此,首先,我们需要在 Amazon AWS 上构建一个 EC2 集群。一个简单的方法是使用 Flintrock,这是一个从本地机器启动 Apache Spark 集群的 CLI 工具。

以下是完成本节所需的先决条件:

  • Hadoop

  • PySpark

  • Flintrock

  • Python

  • TensorFlow

  • TensorFlowOnSpark

现在,让我们看看如何实现这一点。我们使用的是 MNIST 数据集(yann.lecun.com/exdb/mnist/)。以下代码来自 TensorFlowOnSpark 的 GitHub 仓库。该仓库包含文档链接和更多示例(github.com/yahoo/TensorFlowOnSpark):

  1. main(argv, ctx)函数中定义模型架构和训练,其中argv参数包含命令行传递的参数,而ctx包含节点元数据,如jobtask_idxcnn_model_fn模型函数是定义的 CNN 模型:
def main(args, ctx):
    # Load training and eval data
    mnist = tf.contrib.learn.datasets.mnist.read_data_sets(args.data_dir)
    train_data = mnist.train.images # Returns np.array
    train_labels = np.asarray(mnist.train.labels, dtype=np.int32)
    eval_data = mnist.test.images # Returns np.array
    eval_labels = np.asarray(mnist.test.labels, dtype=np.int32)

    # Create the Estimator
    mnist_classifier = tf.estimator.Estimator(model_fn=cnn_model_fn, model_dir=args.model)

    # Set up logging for predictions
    # Log the values in the "Softmax" tensor with label "probabilities"

    tensors_to_log = {"probabilities": "softmax_tensor"}
    logging_hook = tf.train.LoggingTensorHook( tensors=tensors_to_log, every_n_iter=50)

    # Train the model
    train_input_fn = tf.estimator.inputs.numpy_input_fn(
         x={"x": train_data}, y=train_labels, 
         batch_size=args.batch_size, num_epochs=None, 
         shuffle=True)

      eval_input_fn = tf.estimator.inputs.numpy_input_fn(
         x={"x": eval_data},
         y=eval_labels,
         num_epochs=1,
         shuffle=False)

    #Using tf.estimator.train_and_evaluate
    train_spec = tf.estimator.TrainSpec(
        input_fn=train_input_fn, 
        max_steps=args.steps, 
        hooks=[logging_hook])
    eval_spec = tf.estimator.EvalSpec(
        input_fn=eval_input_fn)
    tf.estimator.train_and_evaluate(
        mnist_classifier, train_spec, eval_spec)

  1. if __name__=="__main__"块中,添加以下导入:
from pyspark.context import SparkContext
from pyspark.conf import SparkConf
from tensorflowonspark import TFCluster
import argparse
  1. 启动 Spark Driver 并初始化 TensorFlowOnSpark 集群:
sc = SparkContext(conf=SparkConf()
        .setAppName("mnist_spark"))
executors = sc._conf.get("spark.executor.instances")
num_executors = int(executors) if executors is not None else 1
  1. 解析参数:
parser = argparse.ArgumentParser()
parser.add_argument("--batch_size", 
            help="number of records per batch", 
            type=int, default=100)
parser.add_argument("--cluster_size", 
            help="number of nodes in the cluster", 
            type=int, default=num_executors)
parser.add_argument("--data_dir", 
            help="path to MNIST data", 
            default="MNIST-data")
parser.add_argument("--model", 
            help="path to save model/checkpoint", 
            default="mnist_model")
parser.add_argument("--num_ps", 
            help="number of PS nodes in cluster", 
            type=int, default=1)
parser.add_argument("--steps", 
            help="maximum number of steps", 
            type=int, default=1000)
parser.add_argument("--tensorboard", 
            help="launch tensorboard process", 
            action="store_true")

args = parser.parse_args()
  1. 使用TFCluster.run来管理集群:
cluster = TFCluster.run(sc, main, args, 
        args.cluster_size, args.num_ps, 
        tensorboard=args.tensorboard, 
        input_mode=TFCluster.InputMode.TENSORFLOW, 
        log_dir=args.model, master_node='master')
  1. 训练完成后,关闭集群:
cluster.shutdown()

完整代码可以在 GitHub 仓库的Chapter12/mnist_TFoS.py目录中找到。

要在 EC2 集群上执行代码,您需要使用spark-submit将其提交到 Spark 集群:

${SPARK_HOME}/bin/spark-submit \
--master ${MASTER} \
--conf spark.cores.max=${TOTAL_CORES} \
--conf spark.task.cpus=${CORES_PER_WORKER} \
--conf spark.task.maxFailures=1 \
--conf spark.executorEnv.JAVA_HOME="$JAVA_HOME" \
${TFoS_HOME}/examples/mnist/estimator/mnist_TFoS.py \
--cluster_size ${SPARK_WORKER_INSTANCES} \
--model ${TFoS_HOME}/mnist_model

该模型在 EC2 集群上用两台工作节点训练了 6.6 分钟:

我们可以使用 TensorBoard 来可视化模型架构。一旦代码成功运行,事件文件会被创建,并且可以在 TensorBoard 中查看。

当我们可视化损失时,可以看到随着网络学习,损失逐渐减少:

该模型在测试数据集上只用了 1,000 步就获得了 75%的准确率,且使用了一个非常基础的 CNN 模型。我们可以通过使用更好的模型架构和调整超参数进一步优化结果。

使用 TensorFlowOnSpark 和 Sparkdl 进行物体检测

Apache Spark 提供了一个高级 API Sparkdl,用于在 Python 中进行可扩展的深度学习。在本节中,我们将使用 Sparkdl API。在本节中,您将学习如何在预训练的 Inception v3 模型基础上构建一个模型,用于检测汽车和公交车。使用预训练模型的技术称为 迁移学习

迁移学习

人类的学习是一个持续的过程——我们今天学到的知识是建立在过去学习的基础上的。例如,如果你会骑自行车,你可以将相同的知识扩展到骑摩托车或开汽车。驾驶规则是一样的——唯一不同的是控制面板和执行器。然而,在深度学习中,我们通常是从头开始。是否可以利用模型在一个领域解决问题时获得的知识,来解决另一个相关领域中的问题呢?

是的,确实可以,这就是迁移学习。虽然该领域仍在进行大量研究,但在计算机视觉领域,迁移学习的应用已取得了巨大的成功。这是因为对于计算机视觉任务,卷积神经网络CNNs)被优先使用,因为它们擅长从图像中提取特征(例如,较低层次的特征如线条、圆圈和方块,以及较高层次的抽象特征如耳朵和鼻子)。因此,在学习一种类型的图像数据集时,卷积层提取的特征可以在其他相似领域的图像中重复使用。这有助于减少训练时间。

在本节中,我们将使用 Inception v3(arxiv.org/pdf/1512.00567v1.pdf),一个经过训练的最先进的 CNN,使用的是 ImageNet 数据集。ImageNet(image-net.org/)包含超过 1400 万张标注的高分辨率手工标注图像,这些图像被分类为 22,000 个类别。Inception v3 是在其子集上训练的,包含大约 130 万张图像和 1,000 个类别。

在迁移学习方法中,您保留特征提取的 CNN 层,但将分类器层替换为新的分类器。然后在新的图像上训练这个新的分类器。一般有两种方法:要么只训练新的分类器,要么微调整个网络。在第一种情况下,我们通过将新数据集输入 CNN 层来提取 瓶颈特征。然后将提取的瓶颈特征用于训练最终的分类器。在第二种情况下,我们在训练数据集上训练整个网络,原始的 CNN 以及新的分类器。

理解 Sparkdl 接口

要访问深度学习管道中的 Spark 功能,我们需要使用 Spark 驱动程序。自 Spark 2.0.0 起,我们有一个单一的入口点,使用 SparkSession。最简单的方法是使用 builder

SparkSession.builder().getOrCreate()

这样我们就可以获取现有的会话或创建一个新会话。在实例化时,我们可以使用 .config().master().appName() 方法来设置配置选项、设置 Spark 主节点和设置应用程序名称。

为了读取和处理图片,Sparkdl 提供了 ImageSchema 类。在其众多方法中,我们将使用 readImages 方法读取图片目录。它返回一个包含单个列 image 的 Spark DataFrame,其中存储了图片。

我们可以使用转换来添加或删除 Spark DataFrame 中的列/行。本节中的示例代码使用 withColumn 转换添加了一个名为 label 的列,并为我们的数据集分配了标签类。就像使用 pandas DataFrame 一样,我们可以通过 show() 方法查看 Spark DataFrame 的行。Spark DataFrame 还可以被拆分或组合在一起。

Sparkdl API 提供了方法来实现快速的迁移学习。它提供了 DeepImageFeaturizer 类,自动剥离预训练模型中的分类器层,并使用预训练 CNN 层的特征(瓶颈特征)作为新分类器的输入。

使用 Sparkdl 的一个优势是我们可以通过相同的 SparkSession 实例访问所有 Spark API,甚至包括其机器学习 API MLlib。通过 MLlib,我们可以轻松地将多个算法结合成一个管道。Spark 机器学习 API MLlib 还提供对各种分类和回归方法的支持。

构建一个物体检测模型

现在我们将使用 TFoS 和 Sparkdl 编写一些代码。数据集由从 Google 图片搜索中整理出来的公交车和汽车图片组成。目标是训练一个模型,使其能够区分汽车和公交车。以下是您需要的先决条件,以确保此代码能够运行:

  • PySpark

  • Python

  • TensorFlow

  • TensorFlowOnSpark

  • Pillow

  • Keras

  • TensorFrames

  • Wrapt

  • pandas

  • FindSpark

  • py4j

首先,让我们查看一下我们的数据集。Inception v3 在包含 1,000 类别的 ImageNet 数据上进行了训练,其中也包含了各种车辆的图片。我们有 49 张公交车的图片和 41 张汽车的图片。这里,您可以看到数据集中的一些示例图片:

现在,让我们构建代码:

  1. 这次,我们不使用 spark-submit。相反,我们像运行任何标准的 Python 代码一样运行代码。因此,我们将在代码中定义 Spark 驱动程序的位置和 Spark 深度学习包,并使用 PySpark 的 SparkSession 构建器创建一个 Spark 会话。这里需要记住的一点是分配给堆的内存:Spark 执行器和 Spark 驱动程序。这个值应该基于您机器的规格:
import findspark
findspark.init('/home/ubuntu/spark-2.4.0-bin-hadoop2.7')

import os
SUBMIT_ARGS = "--packages databricks:spark-deep-learning:1.3.0-spark2.4-s_2.11 pyspark-shell"
os.environ["PYSPARK_SUBMIT_ARGS"] = SUBMIT_ARGS

from pyspark.sql import SparkSession
spark = SparkSession.builder \
    .appName("ImageClassification") \
    .config("spark.executor.memory", "70g") \
    .config("spark.driver.memory", "50g") \
    .config("spark.memory.offHeap.enabled",True) \
    .config("spark.memory.offHeap.size","16g") \
    .getOrCreate()
  1. 图片通过 PySpark 的 ImageSchema 类加载到 Spark DataFrame 中。公交车和汽车的图片分别加载到不同的 Spark DataFrame 中:
import pyspark.sql.functions as f
import sparkdl as dl
from pyspark.ml.image import ImageSchema

dfbuses = ImageSchema.readImages('buses/').withColumn('label', f.lit(0))
dfcars = ImageSchema.readImages('cars/').withColumn('label', f.lit(1))
  1. 你可以在这里看到 Spark DataFrame 的前五行:
dfbuses.show(5)
dfcars.show(5)

前面代码的输出结果如下:

  1. 我们将数据集划分为训练集和测试集,比例为 60%的训练集和 40%的测试集。请记住,这些值是随机的,你可以根据需要进行调整:
trainDFbuses, testDFbuses = dfbuses.randomSplit([0.60,0.40], seed = 123)
trainDFcars, testDFcars = dfcars.randomSplit([0.60,0.40], seed = 122)
  1. 公交车和汽车的训练数据集已合并。测试数据集也进行了相同的处理:
trainDF = trainDFbuses.unionAll(trainDFcars)
testDF = testDFbuses.unionAll(testDFcars)
  1. 我们使用 Sparkdl API 获取预训练的 Inception v3 模型,并在 Inception 的 CNN 层上添加了一个逻辑回归器。现在,我们将在我们的数据集上训练这个模型:
from pyspark.ml.classification import LogisticRegression
from pyspark.ml import Pipeline
vectorizer = dl.DeepImageFeaturizer(inputCol="image",
         outputCol="features", 
        modelName="InceptionV3")

logreg = LogisticRegression(maxIter=30, labelCol="label")
pipeline = Pipeline(stages=[vectorizer, logreg])
pipeline_model = pipeline.fit(trainDF)
  1. 让我们看看训练好的模型在测试数据集上的表现。我们使用完美的混淆矩阵来进行评估:
predictDF = pipeline_model.transform(testDF)
predictDF.select('prediction', 'label').show(n = testDF.toPandas().shape[0], truncate=False)
predictDF.crosstab('prediction', 'label').show()

前面代码的输出结果如下:

  1. 对于测试数据集,模型的准确率达到了 100%:
from pyspark.ml.evaluation import MulticlassClassificationEvaluator
scoring = predictDF.select("prediction", "label")
accuracy_score = MulticlassClassificationEvaluator(metricName="accuracy")
rate = accuracy_score.evaluate(scoring)*100
print("accuracy: {}%" .format(round(rate,2)))

我们的模型表现如此出色,是因为我们作为迁移学习基础模型使用的 Inception v3 模型已经在大量的车辆图像上进行了训练。然而,需要提醒的是,100%的准确率并不意味着这是最好的模型,只是说明它在当前测试图像上表现良好。

Sparkdl 是由 DataBricks 开发的,属于深度学习管道的一部分。它提供了用于 Python 中基于 Apache Spark 的可扩展深度学习的高级 API。你可以在这里了解更多关于它的功能及如何使用:github.com/databricks/spark-deep-learning

总结

深度学习模型在训练数据集较大时(大数据)能提供更好的性能。训练大数据模型在计算上开销很大。这个问题可以通过分治法来解决:我们将大量的计算任务分配给集群中的多台机器,换句话说,就是分布式人工智能。

实现这一目标的一种方法是使用 Google 的分布式 TensorFlow,这是一个帮助将模型训练分配到集群中不同工作机器的 API。你需要指定每台工作机器和参数服务器的地址。这使得模型扩展变得困难且繁琐。

这个问题可以通过使用 TensorFlowOnSpark API 来解决。通过对已有的 TensorFlow 代码进行最小的修改,我们可以使其在集群上运行。Spark 框架处理执行器机器和主节点之间的分配,避免了用户关注细节,同时提供了更好的可扩展性。

在本章中,使用了 TensorFlowOnSpark API 来训练一个模型,以识别手写数字。这解决了可扩展性的问题,但我们仍然需要处理数据,以确保它以正确的格式提供给训练。除非你对 Spark 基础设施,尤其是 Hadoop,十分熟悉,否则这可能是一个困难的任务。

为了降低难度,我们可以利用另一个 API,Sparkdl,它提供了基于 Spark 的完整深度学习训练管道,使用 Spark DataFrames 进行训练。最后,本章使用了 Sparkdl API 进行目标检测。一个模型在预训练的 Inception v3 模型基础上构建,用于分类公共汽车和汽车的图像。

在下一章中,你将学习如何使用 RNN 生成书籍脚本。谁知道呢——它也许会赢得布克奖!

第十三章:使用 LSTM 生成书籍脚本

自然语言生成NLG),作为人工智能的一个子领域,是从各种数据输入中生成可读的文本的自然语言处理任务。这是一个活跃的研究领域,近年来已获得广泛关注。

机器生成自然语言的能力可以有广泛的应用,包括手机中的文本自动补全功能、文档摘要生成,甚至是为喜剧创作新剧本。Google 的智能回复也使用了一种类似的技术来在你写电子邮件时提供回复建议。

在本章中,我们将研究一个 NLG 任务——从另一本名为《Mastering PostgreSQL 10》的 Packt 书籍中生成书籍脚本。我们从这本书中挑选了近 100 页,移除了所有图表、表格和 SQL 代码。数据量相当大,足以让神经网络学习数据集的细微差别。

我们将通过以下主题学习如何使用强化学习神经网络生成书籍脚本:

  • 循环神经网络和 LSTM 简介

  • 书籍脚本数据集的描述

  • 使用 LSTM 建模并生成新的书籍脚本

理解循环神经网络

循环神经网络RNNs)已成为任何涉及序列数据的任务中极为流行的选择。RNNs 的核心理念是利用数据中存在的序列信息。通常情况下,每个神经网络假设所有输入彼此独立。然而,如果我们要预测序列中的下一个词或时间序列中的下一个点,就必须使用基于之前使用的词或时间序列中的历史点的信息。

一种理解循环神经网络(RNNs)概念的方法是,它们具有一个内存,能够存储关于序列中历史数据的信息。理论上,RNNs 可以记住任意长序列的历史,但在实际应用中,它们在需要保留超过几个步骤的历史信息的任务中表现不佳。

RNN 的典型结构如下:

在前面的图示中,Xt 是不同时间步长的序列值。RNNs 被称为循环,因为它们对序列中的每个元素应用相同的操作,输出依赖于前一步的结果。可以清楚地观察到单元之间的连接,这些连接帮助将信息从前一步传递到下一步。

如前所述,RNNs 并不擅长捕捉长期依赖关系。RNNs 有不同的变种,其中一些如下:

  • 长短期记忆LSTMs

  • 门控循环单元GRU

  • 盯孔 LSTM(Peephole LSTMs)

与传统的 RNN 相比,LSTM 在捕捉长期依赖方面表现更好。LSTM 在诸如单词/句子预测、图像字幕生成,甚至是需要长期依赖的时间序列数据预测等任务中变得非常流行。以下是使用 LSTM 的一些优点:

  • 擅长建模涉及长期依赖的任务

  • 不同时间步之间的权重共享大大减少了模型中的参数数量

  • 比传统的 RNN 更少受到梯度消失和梯度爆炸问题的困扰

以下是使用 LSTM 的一些缺点:

  • LSTM 对数据有较高需求。通常需要大量的训练数据才能产生有意义的结果。

  • 训练速度比传统神经网络慢。

  • 存在计算效率更高的 RNN 变种,如 GRU,它们能实现与 LSTM 相似的性能。

本章讨论的内容不涉及其他类型的 RNN。如果您感兴趣,可以参考*《深度学习》*书中的序列建模章节(www.deeplearningbook.org/contents/rnn.html)。

数据预处理

如前所述,本项目使用的数据集来自一本流行的 Packt 书籍,书名为*《Mastering PostgreSQL 10》*,作者是 Hans-Jürgen Schönig (www.cybertec-postgresql.com)。我们考虑了书籍前 100 页的文本,排除了所有图形、表格和 SQL 代码。清理后的数据集与代码一起存储在一个文本文件中。数据集包含近 44,000 个单词,足以用来训练模型。以下是脚本中的几行:

"PostgreSQL 概述

PostgreSQL 是世界上最先进的开源数据库系统之一,具有许多功能,广泛受到开发者和系统管理员的使用。从 PostgreSQL 10 开始,许多新功能已被添加到 PostgreSQL 中,这些新功能极大地促进了这一卓越开源产品的成功。在本书中,将详细讲解和讨论许多这些酷炫的功能。

在本章中,您将了解 PostgreSQL 以及 PostgreSQL 10.0 及更高版本中的一些酷炫的新功能。所有相关的新功能将详细讲解。鉴于代码修改的数量以及 PostgreSQL 项目的庞大规模,这个功能列表显然远非完整,因此我尝试专注于最重要、最相关的大多数人都会用到的方面。

本章中概述的功能将分为以下几个类别:数据库管理

与 SQL 和开发者相关的备份、恢复和复制、性能相关主题

PostgreSQL 10.0 中的新功能。

PostgreSQL 10.0 于 2017 年底发布,是第一个采用 PostgreSQL 社区引入的新编号方案的版本。从现在开始,主要版本的发布方式将发生变化,因此,PostgreSQL 之后的下一个主要版本

10.0 不会是 10.1,而是 PostgreSQL 11。版本 10.1 和 10.2 只是服务版本,只会包含错误修复。

为了预处理数据并为 LSTM 模型做准备,请按照以下步骤进行:

  1. 标记化标点符号:在预处理过程中,我们将拆分标准设定为使用空格分隔的单词。然而,在这种情况下,神经网络将难以区分像“Hello”和“Hello!”这样的词。由于这个限制,需要在数据集中对标点符号进行标记化。例如,! 将被映射为 _Sym_Exclamation_。在代码中,我们实现了一个名为 define_tokens 的函数。它用于创建一个字典,在这个字典中,每个标点符号是键,对应的标记是值。这个示例中,我们将为以下符号创建标记:
    • 句号 ( . )

    • 逗号 ( , )

    • 引号 ( " )

    • 分号 ( ; )

    • 感叹号 ( ! )

    • 问号 ( ? )

    • 左括号 ( ( )

    • 右括号 ( ) )

    • 连字符 ( -- )

    • 返回 ( \n )

避免使用数据集中可能出现的词语。例如,? 被替换为 _Sym_Question_,这在数据集中不是一个单词。

  1. 转为小写并分割:我们必须将文本中的所有大写字母转换为小写字母,以便神经网络能够学习到“Hello”和“hello”实际上是相同的两个词。由于神经网络的基本输入单元是单词,下一步就是将文本中的句子拆分为单词。

  2. 映射创建:神经网络不能直接接受文本作为输入,因此我们需要将这些单词映射到索引/ID。为此,我们必须创建两个字典,如下所示:

    • Vocab_to_int:将文本中的每个单词映射到其唯一的 ID

    • Int_to_vocab:反向字典,将 ID 映射到对应的单词

定义模型

在使用预处理数据训练模型之前,我们先了解一下这个问题的模型定义。在代码中,我们在 model.py 文件中定义了一个模型类。该类包含四个主要部分,具体如下:

  • 输入:我们在模型中定义了 TensorFlow 的占位符,用于输入(X)和目标(Y)。

  • 网络定义:该模型的网络有四个组件,具体如下:

    • 初始化 LSTM 单元:为此,我们首先将两层 LSTM 堆叠在一起。然后,我们将 LSTM 的大小设置为代码中定义的 RNN_SIZE 参数。接着,RNN 被初始化为零状态。

    • 词嵌入:我们使用词嵌入来对文本中的词进行编码,而不是使用 one-hot 编码。这样做的主要目的是减少训练集的维度,从而帮助神经网络更快地学习。我们从均匀分布中为词汇表中的每个词生成嵌入,并使用 TensorFlow 的embedding_lookup函数来获取输入数据的嵌入序列。

    • 构建 LSTM:为了获得 LSTM 的最终状态,我们使用 TensorFlow 的tf.nn.dynamic_rnn函数,并传入初始单元和输入数据的嵌入。

    • 概率生成:在获得 LSTM 的最终状态和输出后,我们将其通过一个全连接层生成预测的 logits。我们使用softmax函数将这些 logits 转换为概率估计。代码如下:

  • 序列损失:我们必须定义损失函数,在这个情况下是序列损失。这实际上只是对一系列 logits 进行加权交叉熵损失计算。我们在批次和时间上对观测值进行等权重处理。

  • 优化器:我们将使用 Adam 优化器,并保持其默认参数。我们还将裁剪梯度,确保其在-1 到 1 的范围内。梯度裁剪是递归神经网络中的常见现象。当梯度在时间上反向传播时,如果它们不断地与小于 1 的数相乘,梯度可能会消失;或者如果与大于 1 的数相乘,梯度可能会爆炸。梯度裁剪通过将梯度限制在-1 到 1 之间,帮助解决这两个问题。

训练模型

在理解训练循环的实现之前,让我们仔细看看如何生成数据批次。

众所周知,神经网络使用批次来加速模型训练,并减少内存消耗。批次是原始数据集的样本,用于网络的正向传播和反向传播。正向传播指的是将输入与网络中不同层的权重相乘并获得最终输出的过程。反向传播则是基于正向传播输出的损失,更新神经网络中的权重。

在这个模型中,由于我们是在根据一组前置词来预测下一个词组以生成电视脚本,目标基本上是原始训练数据集中下一个词(根据序列长度)的几个词。我们来看一个例子,假设训练数据集包含如下内容:

The quick brown fox jumps over the lazy dog

如果使用的序列长度(处理的词数)是 4,那么以下内容成立:

  • X 是每四个词的序列,例如,[The quick brown fox, quick brown fox jumps …..]。

  • Y 是每四个词的序列,跳过第一个词,例如,[quick brown fox jumps, brown fox jumps over …]。

定义并训练一个文本生成模型

  1. 使用load_data函数加载保存的文本数据以进行预处理:
 def load_data():
 """
 Loading Data
 """
 input_file = os.path.join(TEXT_SAVE_DIR)
 with open(input_file, "r") as f:
 data = f.read()

return data
  1. 实现define_tokens,如本章数据预处理部分所定义。这将帮助我们创建一个关键字及其相应 tokens 的字典:
 def define_tokens():
 """
 Generate a dict to turn punctuation into a token. Note that Sym before each text denotes Symbol
 :return: Tokenize dictionary where the key is the punctuation and the value is the token
 """
 dict = {'.':'_Sym_Period_',
 ',':'_Sym_Comma_',
 '"':'_Sym_Quote_',
 ';':'_Sym_Semicolon_',
 '!':'_Sym_Exclamation_',
 '?':'_Sym_Question_',
 '(':'_Sym_Left_Parentheses_',
 ')':'_Sym_Right_Parentheses_',
 '--':'_Sym_Dash_',
 '\n':'_Sym_Return_',
 }
 return dict

我们创建的字典将用于用相应的 tokens 和分隔符(此处为空格)替换数据集中的标点符号。例如,Hello!将被替换为Hello _Sym_Exclamation_

注意,Hello和 token 之间有一个空格。这将帮助 LSTM 模型将每个标点符号当作独立的单词来处理。

  1. 使用Vocab_to_intint_to_vocab字典帮助将单词映射到索引/ID。我们这样做是因为神经网络不接受文本作为输入:
 def create_map(input_text):
 """
 Map words in vocab to int and vice versa for easy lookup
 :param input_text: TV Script data split into words
 :return: A tuple of dicts (vocab_to_int, int_to_vocab)
 """
 vocab = set(input_text)
 vocab_to_int = {c: i for i, c in enumerate(vocab)}
 int_to_vocab = dict(enumerate(vocab))
 return vocab_to_int, int_to_vocab
  1. 将前面所有步骤结合起来,创建一个函数来预处理我们可用的数据:
def preprocess_and_save_data():
 """
 Preprocessing the TV Scripts Dataset
 """
 generate_text_data_from_csv()
 text = load_data()
 text= text[14:] # Ignoring the STARTraw_text part of the dataset
 token_dict = define_tokens()
 for key, token in token_dict.items():
 text = text.replace(key, ' {} '.format(token))

text = text.lower()
 text = text.split()

vocab_to_int, int_to_vocab = create_map(text)
 int_text = [vocab_to_int[word] for word in text]
 pickle.dump((int_text, vocab_to_int, int_to_vocab, token_dict), open('processed_text.p', 'wb'))

然后我们将为映射字典生成整数文本,并将预处理后的数据和相关字典存储到pickle文件中。

  1. 为了定义我们的模型,我们将在model.py文件中创建一个模型类。我们将首先定义输入:
 with tf.variable_scope('Input'):
 self.X = tf.placeholder(tf.int32, [None, None], name='input')
 self.Y = tf.placeholder(tf.int32, [None, None], name='target')
 self.input_shape = tf.shape(self.X)

我们必须将变量类型定义为整数,因为数据集中的单词已被转换为整数。

  1. 通过定义 LSTM 单元、词嵌入、构建 LSTM 和概率生成来定义我们的模型网络。为了定义 LSTM 单元,堆叠两个 LSTM 层,并将 LSTM 的大小设置为RNN_SIZE参数。将 RNN 的值设置为 0:
 lstm = tf.contrib.rnn.BasicLSTMCell(RNN_SIZE)
 cell = tf.contrib.rnn.MultiRNNCell([lstm] * 2) # Defining two LSTM layers for this case
 self.initial_state = cell.zero_state(self.input_shape[0], tf.float32)
 self.initial_state = tf.identity(self.initial_state, name="initial_state")

为了减少训练集的维度并提高神经网络的速度,使用以下代码生成并查找嵌入:

embedding = tf.Variable(tf.random_uniform((self.vocab_size, RNN_SIZE), -1, 1))
embed = tf.nn.embedding_lookup(embedding, self.X)

运行tf.nn.dynamic_rnn函数以找到 LSTM 的最终状态:

outputs, self.final_state = tf.nn.dynamic_rnn(cell, embed, initial_state=None, dtype=tf.float32)
self.final_state = tf.identity(self.final_state, name='final_state')

使用softmax函数将 LSTM 最终状态获得的 logits 转换为概率估计:

self.final_state = tf.identity(self.final_state, name='final_state')
self.predictions = tf.contrib.layers.fully_connected(outputs, self.vocab_size, activation_fn=None)
# Probabilities for generating words
probs = tf.nn.softmax(self.predictions, name='probs')
  1. 为 logits 序列定义加权交叉熵或序列损失,这有助于进一步微调我们的网络:
 def define_loss(self):
 # Defining the sequence loss
 with tf.variable_scope('Sequence_Loss'):
 self.loss = seq2seq.sequence_loss(self.predictions, self.Y,
 tf.ones([self.input_shape[0], self.input_shape[1]]))
  1. 使用默认参数实现 Adam 优化器,并将梯度裁剪到-11的范围内,以避免在反向传播过程中梯度消失:
 def define_optimizer(self):
 with tf.variable_scope("Optimizer"):
 optimizer = tf.train.AdamOptimizer(LEARNING_RATE)
 # Gradient Clipping
 gradients = optimizer.compute_gradients(self.loss)
 capped_gradients = [(tf.clip_by_value(grad, -1., 1.), var) for grad, var in gradients]
 self.train_op = optimizer.apply_gradients(capped_gradients)
  1. 使用generate_batch_data函数定义序列长度。这有助于生成神经网络训练所需的批次:

    • 该函数的输入将是编码为整数的文本数据、批次大小和序列长度。

    • 输出将是一个形状为[#批次,2,批次大小,序列长度]的 numpy 数组。每个批次包含两个部分,定义如下:

      • X 的形状为[批次大小,序列长度]。

      • Y 的形状为[批次大小,序列长度]:

 def generate_batch_data(int_text):
 """
 Generate batch data of x (inputs) and y (targets)
 :param int_text: Text with the words replaced by their ids
 :return: Batches as a Numpy array
 """
 num_batches = len(int_text) // (BATCH_SIZE * SEQ_LENGTH)

x = np.array(int_text[:num_batches * (BATCH_SIZE * SEQ_LENGTH)])
y = np.array(int_text[1:num_batches * (BATCH_SIZE * SEQ_LENGTH) + 1])

x_batches = np.split(x.reshape(BATCH_SIZE, -1), num_batches, 1) y_batches = np.split(y.reshape(BATCH_SIZE, -1), num_batches, 1)
 batches = np.array(list(zip(x_batches, y_batches)))
 return batches
  1. 使用以下参数训练模型:
    • 训练轮次 = 500

    • 学习率 = 0.001

    • 批次大小 = 128

    • RNN 大小 = 128

    • 序列长度 = 32:

def train(model,int_text):
# Creating the checkpoint directory
 if not os.path.exists(CHECKPOINT_PATH_DIR):
 os.makedirs(CHECKPOINT_PATH_DIR)

batches = generate_batch_data(int_text)
with tf.Session() as sess:
 if RESTORE_TRAINING:
 saver = tf.train.Saver()
 ckpt = tf.train.get_checkpoint_state(CHECKPOINT_PATH_DIR)
 saver.restore(sess, ckpt.model_checkpoint_path)
 print('Model Loaded')
 start_epoch = int(str(ckpt.model_checkpoint_path).split('-')[-1])
 else:
 start_epoch = 0
 tf.global_variables_initializer().run()
 print('All variables initialized')

for epoch in range(start_epoch, NUM_EPOCHS):
 saver = tf.train.Saver()
 state = sess.run(model.initial_state, {model.X: batches[0][0]})

for batch, (x, y) in enumerate(batches):
 feed = {
 model.X: x,
 model.Y: y,
 model.initial_state: state}
 train_loss, state, _ = sess.run([model.loss, model.final_state, model.train_op], feed)

if (epoch * len(batches) + batch) % 200 == 0:
 print('Epoch {:>3} Batch {:>4}/{} train_loss = {:.3f}'.format(
 epoch,
 batch,
 len(batches),
 train_loss))
 # Save Checkpoint for restoring if required
 saver.save(sess, CHECKPOINT_PATH_DIR + '/model.tfmodel', global_step=epoch + 1)

# Save Model
 saver.save(sess, SAVE_DIR)
 print('Model Trained and Saved')
 save_params((SEQ_LENGTH, SAVE_DIR))

由于数据集不是很大,代码是在 CPU 上执行的。我们将保存输出图,因为它将对生成书籍脚本非常有用。

生成书籍脚本

现在模型已经训练好了,我们可以玩得更开心。在本节中,我们将看到如何使用模型生成书籍脚本。使用以下参数:

  • 脚本长度 = 200 个单词

  • 起始词 = postgresql

按照以下步骤生成模型:

  1. 加载训练模型的图。

  2. 提取四个张量,如下所示:

    • 输入/input:0

    • 网络/initial_state:0

    • 网络/final_state:0

    • 网络/probs:0

使用以下代码提取四个张量:

 def extract_tensors(tf_graph):
 """
 Get input, initial state, final state, and probabilities tensor from the graph
 :param loaded_graph: TensorFlow graph loaded from file
 :return: Tuple (tensor_input,tensor_initial_state,tensor_final_state, tensor_probs)
 """
 tensor_input = tf_graph.get_tensor_by_name("Input/input:0")
 tensor_initial_state = tf_graph.get_tensor_by_name("Network/initial_state:0")
 tensor_final_state = tf_graph.get_tensor_by_name("Network/final_state:0")
 tensor_probs = tf_graph.get_tensor_by_name("Network/probs:0")
 return tensor_input, tensor_initial_state, tensor_final_state, tensor_probs
  1. 定义起始词并从图中获得初始状态,稍后会使用这个初始状态:
# Sentences generation setup
sentences = [first_word]
previous_state = sess.run(initial_state, {input_text: np.array([[1]])})
  1. 给定一个起始词和初始状态,继续通过 for 循环迭代生成脚本中的下一个单词。在 for 循环的每次迭代中,使用之前生成的序列作为输入,从模型中生成概率,并使用select_next_word函数选择概率最大的单词:
 def select_next_word(probs, int_to_vocab):
 """
 Select the next work for the generated text
 :param probs: list of probabilities of all the words in vocab which can be selected as next word
 :param int_to_vocab: Dictionary of word ids as the keys and words as the values
 :return: predicted next word
 """
 index = np.argmax(probs)
 word = int_to_vocab[index]
 return word
  1. 创建一个循环来生成序列中的下一个单词:
 for i in range(script_length):

 # Dynamic Input
 dynamic_input = [[vocab_to_int[word] for word in sentences[-seq_length:]]]
 dynamic_seq_length = len(dynamic_input[0])

# Get Prediction
 probabilities, previous_state = sess.run([probs, final_state], {input_text: dynamic_input, initial_state: previous_state})
 probabilities= np.squeeze(probabilities)

pred_word = select_next_word(probabilities[dynamic_seq_length - 1], int_to_vocab)
 sentences.append(pred_word)
  1. 使用空格分隔符将句子中的所有单词连接起来,并将标点符号替换为实际符号。然后将获得的脚本保存在文本文件中,供以后参考:
# Scraping out tokens from the words
book_script = ' '.join(sentences)
for key, token in token_dict.items():
    book_script = book_script.replace(' ' + token.lower(), key)
book_script = book_script.replace('\n ', '\n')
book_script = book_script.replace('( ', '(')
  1. 以下是执行过程中生成的文本示例:
 postgresql comparatively).
one transaction is important, you can be used

create index is seen a transaction will be provided this index.
an index scan is a lot of a index
the index is time.
to be an index.
you can see is to make expensive,
the following variable is an index

the table will index have to a transaction isolation level
the transaction isolation level will use a transaction will use the table of the following index creation.
the index is marked.
the following number is one of the following one lock is not a good source of a transaction will use the following strategies
in this is not, it will be a table
in postgresql.
the postgresql cost errors is not possible to use a transaction.
postgresql 10\. 0\. 0\. you can see that the data is not free into more than a transaction ids, the same time. the first scan is an example
the same number.
one index is not that the same time is needed in the following strategies

in the same will copy block numbers.
the same data is a table if you can not be a certain way, you can see, you will be able to create statistics.
postgresql will

有趣的是,模型学会了在句子后使用句号,在段落之间留空行,并遵循基本语法。模型通过自我学习掌握了这一切,我们无需提供任何指导或规则。尽管生成的脚本远未完美,但看到机器能够生成类似书籍的真实句子,实在令人惊讶。我们可以进一步调整模型的超参数,生成更有意义的文本。

总结

本章中,我们学习了如何使用 LSTM 生成书籍脚本。

我们从 RNN 的基础知识以及其流行变体——通常称为 LSTM 的模型开始学习。我们了解到,RNN 在预测涉及时间序列、自然语言处理任务中的下一个单词预测等顺序数据集方面非常成功。我们还了解了使用 LSTM 的优缺点。

本章帮助我们理解了如何对文本数据进行预处理,并将其准备好,以便可以输入到 LSTM 模型中。我们还了解了用于训练的模型结构。接下来,我们学习了如何通过创建数据批次来训练神经网络。

最终,我们理解了如何使用我们训练的 TensorFlow 模型生成书籍脚本。尽管生成的脚本并不完全有意义,但看到神经网络生成书籍的句子仍然令人惊叹。然后我们将生成的书籍脚本保存到文本文件中,供以后参考。

在下一章中,我们将使用深度强化学习玩吃豆人游戏。

问题

以下是问题:

  1. 你能尝试使用另一本书来看看模型生成新文本的效果如何吗?

  2. 如果你将批处理大小加倍并减小学习率,生成的文本会发生什么变化?

  3. 你能在不使用梯度裁剪的情况下训练模型,看看结果是否有所改进吗?

第十四章:使用深度强化学习玩吃豆人

强化学习是指一种范式,代理通过从环境反馈中学习,依据其所采取的动作获得观察结果和奖励。以下图展示了强化学习的基于反馈的学习循环:

尽管强化学习主要应用于学习如何玩游戏,但它也已成功应用于数字广告、股票交易、自动驾驶汽车和工业机器人等领域。

在本章中,我们将使用强化学习创建一个吃豆人游戏,并在这个过程中学习强化学习。我们将涵盖以下主题:

  • 强化学习

  • 强化学习与监督学习和无监督学习的区别

  • 强化学习的组件

  • OpenAI Gym

  • OpenAI Gym 中的吃豆人游戏

  • 深度强化学习中的 DQN:

    • Q 学习

    • 深度 Q 网络

  • 将 DQN 应用于吃豆人游戏

让我们开始吧!

强化学习

强化学习是一种机器学习方法,代理通过与环境的交互进行学习。代理采取行动,并根据这些行动,环境返回观察结果和奖励。通过这些观察结果和奖励,代理学习策略并采取进一步的行动,从而不断延续行动、观察和奖励的循环。在长期来看,代理必须学习一种策略,使其在依据策略采取行动时,能够最大化长期奖励。

强化学习与监督学习和无监督学习的区别

机器学习解决方案可以分为三种主要类型:监督学习、无监督学习和强化学习。那么强化学习与其他两种类型有何不同呢?

  1. 监督学习:在监督学习中,代理从包含特征和标签的训练数据集中学习模型。监督学习的两种最常见问题是回归和分类。回归是指根据模型预测未来的值,分类是指预测输入值的类别。

  2. 无监督学习:在无监督学习中,代理从仅包含特征的训练数据集中学习模型。无监督学习的两种最常见问题是降维和聚类。降维是指在不改变数据集自然分布的情况下,减少数据集中的特征或维度数量。聚类是指将输入数据划分为多个组,从而产生聚类或段。

  3. 强化学习:在强化学习中,代理从初始模型开始,然后根据环境反馈不断学习该模型。强化学习代理通过对一系列动作、观察和奖励应用监督或无监督学习方法来更新模型。代理仅从奖励信号中学习,而不像其他机器学习方法那样从损失函数中学习。代理在采取动作后才会收到反馈,而在其他机器学习方法中,反馈在训练时就会通过损失或错误提供。数据不是独立同分布(i.i.d.),因为它依赖于先前采取的动作,而在其他机器学习方法中,数据是独立同分布的。

强化学习的组成部分

在任何强化学习的形式化中,我们使用状态空间动作空间来讨论。动作空间是代理可以采取的有限数量的动作集合,用A表示。状态空间是环境可能处于的有限状态集合,用S表示。

代理的目标是学习一个策略,表示为 策略可以是确定性的随机的。策略基本上代表了模型,代理使用该模型来选择最优动作。因此,策略将来自环境的奖励和观察映射到动作。

当代理遵循某个策略时,会产生一个状态、动作、奖励、状态等的序列。这个序列被称为轨迹回合

强化学习形式化中的一个重要组成部分是回报。回报是对总长期奖励的估计。通常,回报可以用以下公式表示:

这里  是一个折扣因子,值在(0,1)之间, 是时间步长t时的奖励。折扣因子表示在未来的时间步长中,奖励的重要性。若  为 0,则只考虑下一动作的奖励;若为 1,则未来的奖励与下一动作的奖励具有相同的权重。

然而,由于计算回报值非常困难,因此它是通过状态值动作值函数来估计的。我们将在本章的 Q 学习部分进一步讨论动作值函数。

为了模拟我们将要玩吃豆人游戏的代理,我们将使用 OpenAI Gym。现在让我们了解一下 OpenAI Gym。

你可以在本书的代码包中的 Jupyter Notebook 文件ch-14_Reinforcement_Learning中跟随代码进行学习。

OpenAI Gym

OpenAI Gym 是一个基于 Python 的工具包,用于开发强化学习算法。在撰写本书时,它提供了超过 700 个开源贡献的环境。还可以创建自定义的 OpenAI 环境。OpenAI Gym 提供了一个统一的接口,用于处理强化学习环境的工作,同时 OpenAI 的用户可以专注于设计和实现强化学习算法。

OpenAI Gym 的原始研究论文可以在以下链接找到:arxiv.org/abs/1606.01540

让我们看一看以下步骤,学习如何安装和探索 OpenAI Gym:

  1. 使用以下命令安装 OpenAI Gym:
pip3 install gym

如果前述命令无法运行,请参考以下链接获取安装的进一步帮助:github.com/openai/gym#installation

  1. 使用以下代码打印 OpenAI Gym 中可用的环境数量:
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.10.5 : 797
  1. 打印所有环境的列表,如下代码所示:
for e in list(all_env):
    print(e)

输出的部分列表如下:

EnvSpec(Copy-v0) EnvSpec(RepeatCopy-v0) EnvSpec(ReversedAddition-v0) EnvSpec(ReversedAddition3-v0) EnvSpec(DuplicatedInput-v0) EnvSpec(Reverse-v0) EnvSpec(CartPole-v0) EnvSpec(CartPole-v1) EnvSpec(MountainCar-v0) EnvSpec(MountainCarContinuous-v0) EnvSpec(Pendulum-v0)

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

  • 可以使用 env.make(<game-id-string>) 函数通过传递 id 字符串来创建 env 对象。

  • 每个 env 对象包含以下主要函数:

    • 函数 step() 接受一个动作对象作为参数,并返回四个对象:

      • observation: 环境实现的对象,表示环境的观察。

      • reward: 一个有符号浮点数,表示上一个动作的收益(或损失)。

      • done: 一个布尔值,表示场景是否结束。

      • info: 一个表示诊断信息的 Python 字典对象。

    • 函数 render() 创建环境的视觉表示。

    • 函数 reset() 将环境重置为原始状态。

  • 每个 env 对象都带有明确定义的动作和观察,分别由 action_spaceobservation_space 表示。

在 OpenAI Gym 中创建 Pacman 游戏

在本章中,我们将以 MsPacman-v0 作为示例,探索这个游戏更深入一些:

  1. 使用标准的 make 函数创建 env 对象,如下命令所示:
env=gym.make('MsPacman-v0')
  1. 让我们用以下代码打印游戏的动作空间:
print(env.action_space)

前面的代码生成了以下输出:

Discrete(9)

Discrete 9 指的是九种动作,如上、下、左、右。

  1. 现在我们可以看到观察空间,如下例所示:
print(env.observation_space)

前述代码生成了以下输出:

Box(210, 160, 3)

因此,观察空间有三个颜色通道,大小为 210 x 160。观察空间的渲染如下截图所示:

  1. 章节数是游戏次数的数量。我们现在将其设置为 1,表示我们只想玩一次游戏。由于每一轮游戏都是随机的,实际生产运行时,你会运行多轮游戏并计算奖励的平均值。让我们运行一次游戏,同时在游戏过程中随机选择一个动作,代码如下:
import time

frame_time = 1.0 / 15 # seconds
n_episodes = 1

for i_episode in range(n_episodes):
    t=0
    score=0
    then = 0
    done = False
    env.reset()
    while not done:
        now = time.time()
        if frame_time < now - then:
            action = env.action_space.sample()
            observation, reward, done, info = env.step(action)
            score += reward
            env.render()
            then = now
            t=t+1
    print('Episode {} finished at t {} with score {}'.format(i_episode,
                                                             t,score))

我们随后得到以下输出:

Episode 0 finished at t 551 with score 100.0
  1. 现在,让我们运行游戏 500 次,看看我们得到的最大分数、最小分数和平均分数。这在以下示例中得以演示:
import time
import numpy as np

frame_time = 1.0 / 15 # seconds
n_episodes = 500

scores = []
for i_episode in range(n_episodes):
    t=0
    score=0
    then = 0
    done = False
    env.reset()
    while not done:
        now = time.time()
        if frame_time < now - then:
            action = env.action_space.sample()
            observation, reward, done, info = env.step(action)
            score += reward
            env.render()
            then = now
            t=t+1
    scores.append(score)
    #print("Episode {} finished at t {} with score {}".format(i_episode,t,score))
print('Average score {}, max {}, min {}'.format(np.mean(scores),
                                          np.max(scores),
                                          np.min(scores)
                                         ))

上述代码生成了以下输出:

Average 219.46, max 1070.0, min 70.0

随机选择一个动作并应用它可能不是最佳策略。为了让智能体通过玩游戏学习并应用最佳动作,有许多算法可以用来找到解决方案。在本章中,我们将应用深度 Q 网络来从游戏中学习。我们鼓励读者探索其他算法。

DQN 用于深度强化学习

深度 Q 网络DQN)是基于 Q-learning 的。在本节中,我们将在实现 DQN 来玩 PacMan 游戏之前,先解释这两者。

  • Q-learning:在 Q-learning 中,智能体学习动作值函数,也称为 Q 函数。Q 函数表示为 q(s,a),用于估计当智能体处于状态 s 时采取动作 a 的长期价值。Q 函数将状态-动作对映射到长期价值的估计值,如下方程所示:

因此,在策略下,q 值函数可以写作如下:

q 函数可以递归地写作如下:

期望值可以展开如下:

一个最优的 q 函数是返回最大值的函数,而一个最优策略是应用最优 q 函数的策略。最优 q 函数可以写作如下:

这个方程表示贝尔曼最优方程。由于直接求解这个方程很困难,Q-learning 是一种用来逼近该函数值的方法。

因此,在 Q-learning 中,构建了一个模型,该模型能够预测给定状态和动作下的值。通常,这个模型以表格的形式存在,包含了所有可能的状态 s 和动作 a 的组合,以及该组合的期望值。然而,对于状态-动作组合数量较大的情况,这个表格就变得难以维护。DQN 有助于克服基于表格的 Q-learning 的这一缺点。

  • DQN:在 DQN 中,神经网络模型被用来从状态-动作-奖励-下一状态元组中学习,并根据提供的状态和动作预测近似的 q 值。由于状态-动作-奖励序列在时间上是相关的,深度学习面临挑战,因为深度学习中的输入样本需要是独立同分布(i.i.d.)。因此,在 DQN 算法中,经验回放被用来缓解这一问题。在经验回放中,之前的动作及其结果被随机采样,用于训练网络。

基本的深度 Q 学习算法如下:

  1. 从初始状态开始游戏

  2. 选择探索或利用

  3. 如果你选择了利用,则通过神经网络预测动作并执行预测的动作

  4. 如果你选择了探索,则随机选择一个动作

  5. 记录之前的状态、动作、奖励和下一状态到经验缓存中

  6. 使用 bellman 函数更新 q_values

  7. 使用 statesactionsq_values 训练神经网络

  8. 第 2 步开始重复

为了提高性能并实现经验回放,你可以在第 7 步中随机选择训练数据。

将 DQN 应用到游戏中

到目前为止,我们已经随机选择了一个动作并将其应用于游戏。现在,让我们应用 DQN 来选择动作,以便玩吃豆人游戏。

  1. 我们定义了 q_nn 策略函数,如下所示:
def policy_q_nn(obs, 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_nn.predict(np.array([obs])))
    return action
  1. 接下来,我们修改 episode 函数,将 q_values 的计算和神经网络训练加入到采样的经验缓存中。代码如下所示:
def episode(env, policy, r_max=0, t_max=0):

    # create the empty list to contain game memory
    #memory = deque(maxlen=1000)

    # observe initial state
    obs = env.reset()
    state_prev = obs
    #state_prev = np.ravel(obs) # replaced with keras reshape[-1]

    # initialize the variables
    episode_reward = 0
    done = False
    t = 0

    while not done:

        action = policy(state_prev, env)
        obs, reward, done, info = env.step(action)
        state_next = obs
        #state_next = np.ravel(obs) # replaced with keras reshape[-1]

        # add the state_prev, action, reward, state_new, done to memory
        memory.append([state_prev,action,reward,state_next,done])

        # Generate and update the q_values with 
        # maximum future rewards using bellman function:
        states = np.array([x[0] for x in memory])
        states_next = np.array([np.zeros(n_shape) 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

        # train the q_nn with states and q_values, same as updating the q_table
        q_nn.fit(states,q_values,epochs=1,batch_size=50,verbose=0)

        state_prev = state_next

        episode_reward += reward
        if r_max > 0 and episode_reward > r_max:
            break
        t+=1
        if t_max > 0 and t == t_max:
            break
    return episode_reward
  1. 定义一个 experiment 函数,该函数将在特定数量的回合中运行;每个回合运行直到游戏失败,即 doneTrue 时结束。我们使用 rewards_max 来表示何时退出循环,因为我们不希望实验永远运行,代码如下所示:
# 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)
        #print('episode:{}, reward {}'.format(i,val))
        rewards[i]=val

    print('Policy:{}, Min reward:{}, Max reward:{}, Average reward:{}'
        .format(policy.__name__,
              np.min(rewards),
              np.max(rewards),
              np.mean(rewards)))
  1. 使用以下代码创建一个简单的 MLP 网络:
from collections import deque 
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten

# build the Q-Network
model = Sequential()
model.add(Flatten(input_shape = n_shape))
model.add(Dense(512, activation='relu',name='hidden1'))
model.add(Dense(9, activation='softmax', name='output'))
model.compile(loss='categorical_crossentropy',optimizer='adam')
model.summary()
q_nn = model

上述代码生成了以下输出:

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
flatten_3 (Flatten)          (None, 100800)            0         
_________________________________________________________________
hidden1 (Dense)              (None, 8)                 806408    
_________________________________________________________________
output (Dense)               (None, 9)                 81        
=================================================================
Total params: 806,489
Trainable params: 806,489
Non-trainable params: 0
_________________________________________________________________
  1. 创建一个空列表来保存游戏记忆,并定义其他超参数,运行一个回合的实验,如下所示:
# Hyperparameters

discount_rate = 0.9
explore_rate = 0.2
n_episodes = 1

# create the empty list to contain game memory
memory = deque(maxlen=1000)

experiment(env, policy_q_nn, n_episodes)

我们得到的结果如下:

Policy:policy_q_nn, Min reward:490.0, Max reward:490.0, Average reward:490.0

在我们的案例中,这无疑是一个改进,但在你的案例中可能会有所不同。在这种情况下,我们的游戏只从有限的记忆中学习,并且仅仅通过一次回合中的游戏回放进行学习。

  1. 现在,运行 100 回合,如下所示:
# Hyperparameters

discount_rate = 0.9
explore_rate = 0.2
n_episodes = 100

# create the empty list to contain game memory
memory = deque(maxlen=1000)

experiment(env, policy_q_nn, n_episodes)

我们得到以下结果:

Policy:policy_q_nn, Min reward:70.0, Max reward:580.0, Average reward:270.5

因此,我们看到,尽管我们达到了高的最大奖励,平均结果并没有得到改善。调整网络架构、特征和超参数可能会产生更好的结果。我们鼓励你修改代码。例如,你可以用简单的单层卷积网络替代 MLP,如下所示:

from collections import deque 
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras.layers import Conv2D, MaxPooling2D

# build the CNN Q-Network
model = Sequential()
model.add(Conv2D(16, kernel_size=(5, 5), 
                 strides=(1, 1),
                 activation='relu',
                 input_shape=n_shape))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
model.add(Flatten())
model.add(Dense(512, activation='relu',name='hidden1'))
model.add(Dense(9, activation='softmax', name='output'))
model.compile(loss='categorical_crossentropy',optimizer='adam')
model.summary()
q_nn = model

上述代码显示了网络摘要,如下所示:

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_4 (Conv2D)            (None, 206, 156, 16)      1216      
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 103, 78, 16)       0         
_________________________________________________________________
flatten_8 (Flatten)          (None, 128544)            0         
_________________________________________________________________
hidden1 (Dense)              (None, 512)               65815040  
_________________________________________________________________
output (Dense)               (None, 9)                 4617      
=================================================================
Total params: 65,820,873
Trainable params: 65,820,873
Non-trainable params: 0

总结

在这一章中,我们学习了什么是强化学习。强化学习是一种高级技术,你会发现它常用于解决复杂问题。我们了解了 OpenAI Gym,这个框架提供了一个环境来模拟许多流行的游戏,以便实现和实践强化学习算法。我们简要介绍了深度强化学习的概念,我们鼓励你去阅读一些专门写关于强化学习的书籍(在深入阅读中提到),以便深入了解其理论和概念。

我们学习了如何在 OpenAI Gym 中玩 PacMan 游戏。我们实现了 DQN,并使用它来学习玩 PacMan 游戏。为了简化,我们只使用了一个 MLP 网络,但对于复杂的示例,你可能最终会使用复杂的 CNN、RNN 或 Sequence-to-Sequence 模型。

在下一章中,我们将学习机器学习和 TensorFlow 领域的未来机会。

深入阅读

  • Maxim Lapan 的《深度强化学习实战》,Packt 出版社

  • Richard S. Sutton 和 Andrew G. Barto 的《强化学习:导论》

  • Masashi Sugiyama 的《统计强化学习:现代机器学习方法》

  • Csaba Szepesvari 的《强化学习算法》

第十五章:接下来是什么?

恭喜你已经走到了这里。到目前为止,你已经学会了在 TensorFlow 中实现各种前沿的 AI 算法,并且在旁边做了一些很酷的项目。具体来说,我们已经构建了强化学习、贝叶斯神经网络、胶囊网络和生成对抗网络GANs)等项目。我们还学习了 TensorFlow 的几个模块,包括 TensorFlow.js、TensorFlow Lite 和 TensorFlow Probability 等等。你应该为此感到自豪,并享受片刻的休息。

在我们出去玩之前,还有一些我们应该考虑阅读的内容,确保在将这些前沿技术投入生产环境时,我们已做好充分准备。正如我们在这一章中将会意识到的那样,部署机器学习模型到生产环境中,不仅仅是实现最新的人工智能研究论文。为了理解我的意思,让我们浏览以下主题:

  • 部署模型到生产环境中的 TensorFlow 工具

  • 构建 AI 应用程序的一般规则

  • 深度学习的局限性

  • AI 在不同行业中的应用

  • 人工智能中的伦理考虑

在生产环境中实现 TensorFlow

在软件工程中,我们看到了一些最佳实践,比如通过 GitHub 进行版本控制、可重用的库、持续集成等,这些实践提高了开发人员的生产力。机器学习作为一个新兴领域,确实需要一些工具来简化模型部署,并提升数据科学家的生产力。在这方面,TensorFlow 最近发布了许多工具。

了解 TensorFlow Hub

软件仓库在软件工程领域具有实际的好处,因为它们增强了代码的可重用性。这不仅有助于提高开发人员的生产力,还帮助不同的开发人员之间共享专业知识。而且,由于开发人员现在希望共享他们的代码,他们会以更清晰和模块化的方式开发代码,从而使整个社区受益。

Google 引入了 TensorFlow Hub,旨在实现机器学习中的可重用性。它的设计目的是让你可以创建、分享和重用机器学习模型的组件。在机器学习中,可重用性比在软件工程中更为重要,因为我们不仅使用算法/架构和专业知识——我们还使用了大量用于训练模型的计算资源和数据。

TF Hub 包含了多个由 Google 专家使用最先进的算法和海量数据训练的机器学习模型。每一个经过训练的模型在 TF Hub 中被称为模块。一个模块可以在TensorFlow Hub上分享,任何人都可以将其导入到自己的代码中。下图展示了经过训练的 TensorFlow 模型如何被其他应用程序/模型使用的流程:

模块TensorFlow Hub中包含了模型的架构或TensorFlow图以及训练好的模型的权重。模块具有以下特点:

  • **可组合:**可组合意味着我们可以将模块作为构建块并在其上添加其他内容。

  • **可重用:**可重用意味着模块具有通用的签名,这样我们就可以交换不同的模块。这在我们迭代模型以获得最佳准确度时非常有用。

  • **可再训练:**模块带有预训练的权重,但它们足够灵活,可以在新数据集上进行再训练。这意味着我们可以通过反向传播调整模型,生成新的权重集。

让我们通过一个示例来理解这一点。假设我们有一个包含不同丰田汽车(如凯美瑞、卡罗拉等型号)的数据集。如果我们每个类别的图像不多,从头开始训练整个模型并不明智。

相反,我们可以做的是,利用一个已经在 TensorFlow Hub 上使用大量图像数据集训练的通用模型,提取该模型的可重用部分,如其架构和预训练权重。在预训练的模型基础上,我们可以添加一个分类器,将数据集中存在的图像进行适当分类。这个过程有时也被称为迁移学习。下图展示了这一过程:

如果你想了解更多关于迁移学习的内容,请参考斯坦福大学的课程笔记(cs231n.github.io/transfer-learning/

你可以访问 TensorFlow Hub(www.tensorflow.org/hub/)获取最先进的、以研究为导向的图像模型,并直接将其导入到你自定义的模型中。假设我们使用的是 NasNet(tfhub.dev/google/imagenet/nasnet_large/feature_vector/1),这是一个通过架构搜索训练的图像模块。在这里,我们将在代码中使用 NasNet 模块的 URL 来导入模块,如下所示:

module = hub.Module(“tfhub.dev/google/imag…

        或/1”)

features = module(toyota_images)

logits = tf.layers.dense(features, NUM_CLASSES)

probabilities = tf.nn.softmax(logits)

我们在模块上添加了一个带有 Softmax 非线性的全连接层。我们通过反向传播训练该层的权重,以便对丰田汽车图像进行分类。

请注意,我们不需要下载该模块,也不需要实例化它。

TensorFlow 处理所有这些底层细节,这使得该模块在真正意义上是可重用的。使用该模块的另一个好处是,您可以免费获得训练 NasNet 所需的数千小时计算资源。

假设我们确实有一个大数据集。在这种情况下,我们可以按以下方式训练模块的可重用部分:

module = hub.Module(“tfhub.dev/google/imag…, trainable=True, tags {“train”})features = module(toyota_images)

logits = tf.layers.dense(features, NUM_CLASSES)

probabilities = tf.nn.softmax(logits)

TensorFlow Hub 提供了用于图像分类、词嵌入、句子嵌入和其他应用的预训练模型。让我们考虑一下来自本书第三章的电影情感分析项目,使用 Tensorflow.js 在浏览器中进行情感分析。我们本可以使用 TensorFlow Hub 提供的每个数据集项的预训练嵌入。这些跨领域的预训练模块的可用性将帮助许多开发人员在不必担心模型背后的数学原理的情况下构建新应用。

您可以在 TensorFlow Hub 的官方网站上找到更多详细信息(www.tensorflow.org/hub/)。

TensorFlow Serving

TensorFlow Serving 是一个高度灵活的服务系统,用于在生产环境中部署机器学习模型。在详细介绍之前,我们先通过查看其架构来理解什么是服务:

我们有一些数据,并使用这些数据训练一个机器学习模型。一旦模型训练完成,它需要部署到网页或移动应用程序上,以服务最终用户。实现这一目标的一种方式是通过远程过程调用RPC)服务器(www.ibm.com/support/knowledgecenter/en/ssw_aix_72/com.ibm.aix.progcomc/ch8_rpc.htm)。TensorFlow Serving 可以作为RPC 服务器和一组库使用,可以在应用程序或嵌入式设备中使用。

TensorFlow Serving 有三个支柱:

  • C++ 库: 低级 C++库主要包含了 TensorFlow 服务所需的函数和方法。这些库是 Google 用来生成应用程序使用的二进制文件的库,它们也是开源的。

  • 二进制文件: 如果我们希望为我们的服务架构使用标准设置,可以使用预定义的二进制文件,这些文件包含了来自 Google 的所有最佳实践。Google 还提供了 Docker 容器(www.docker.com/)以便在 Kubernetes 上扩展这些二进制文件(kubernetes.io/)。

  • 托管服务: TensorFlow Serving 还在 Google Cloud ML 上提供托管服务,这使得使用和部署变得非常容易。

以下是 TensorFlow Serving 的一些优点:

  • 在线和低延迟: 用户不希望在应用程序中等待预测结果。使用 TF Serving,预测不仅快速,而且始终如一地保持快速。

  • 单一进程中的多个模型: TF Serving 允许在同一进程中加载多个模型。假设我们有一个模型,它为客户提供了很好的预测。但如果我们想进行实验,那么我们可能希望加载另一个模型,并与生产模型一起使用。

  • 自动加载和训练同一模型的版本: TF Serving 支持在不中断服务的情况下自动加载新训练的模型,并从生产中的旧版本切换到新版本。

  • 可扩展性: TF Serving 可以与 Cloud ML、Docker 和 Kubernetes 自动扩展。

如需了解如何使用 TF Serving 部署您的模型,请参考官方文档这里www.tensorflow.org/serving/)。

TensorFlow Extended

TensorFlow ExtendedTFX)是 Google 构建的通用机器学习平台。其部分组件是开源的,并且最近在 KDD 会议上有一篇论文(www.kdd.org/kdd2017/papers/view/tfx-a-tensorflow-based-production-scale-machine-learning-platform)展示了 TFX 的功能和愿景。

在本书中,我们主要理解了构建 TensorFlow 模型的语义。然而,当我们查看实际的生产中的机器学习应用时,还有许多其他组件。以下图示展示了这些组件:

如我们所见,机器学习代码是整个系统中非常小的组成部分。其他模块需要最大时间来构建,并且占用了最多的代码行数。TFX 提供了构建机器学习管道其他组件的库和工具。

让我们看一个机器学习过程的示例,以了解 TensorFlow Extended 的不同开源组件:

  • 数据分析: 探索性数据分析是构建任何机器学习模型的必要条件。TFX 有一个名为 Facets 的工具(github.com/PAIR-code/facets),它让我们可视化每个变量的分布,识别缺失数据或异常值,或者通知他人数据可能需要的转换。

  • 转换: TensorFlow 转换(www.tensorflow.org/tfx/transform/get_started)提供开箱即用的功能,能够对基础数据进行完整的转换,使其适合用于训练模型。它与 TF 图本身紧密相关,确保在训练和服务过程中应用相同的转换。

  • 训练 TF 估算器:在数据转换之后,我们可以使用 TF 估算器 (www.tensorflow.org/api_docs/python/tf/estimator/Estimator),它提供了一个高层次的 API,可以快速定义、训练和导出模型。TF 估算器还允许你以不同的格式导出模型进行推理和服务。

  • 分析模型:一旦模型构建完成,我们可以直接将其推送到生产环境,但这会是一个非常糟糕的主意。相反,我们应该分析模型的预测结果,并确保模型预测的是我们希望它预测的内容。TF 模型分析 (www.tensorflow.org/tfx/model_analysis/get_started) 使我们能够在大规模数据集上评估模型,并提供一个用户界面,可以根据不同的属性值切分预测结果。

  • 服务模型:在分析我们的模型并对其预测结果感到满意后,我们希望将模型部署到生产环境中。实现这一目标的一种方式是使用 TensorFlow Serving,这在上一节中已有描述。

TensorFlow Extended 在 Google 内部广泛用于构建产品。它显然比开源的版本有更多功能。对于那些在初创公司或没有自己内部机器学习平台的公司工作的人来说,强烈推荐使用 TFX 来构建端到端的机器学习应用。

构建 AI 应用的建议

现在我们了解了一些来自 TensorFlow 的工具,这些工具能帮助我们在大规模下开发和部署模型,让我们尝试理解构建 AI 应用时的一些通用经验法则。

  • 工程优先于机器学习:几乎所有问题的解决方案都始于工程。在构建任何机器学习模型之前,确保数据管道正确非常重要。

  • 保持简单:通常,数据科学家有一种自然倾向,倾向于为问题构建最复杂的模型。然而,从简单且可解释的模型开始是非常好的——比如说,使用逻辑回归模型进行分类。这有助于更好地发现和调试数据或工程管道中的问题。只有当你对基础模型的结果不满意时,才应该使用像深度学习这样的高级技术。

  • 分布式处理:在大数据时代,你几乎总会遇到无法将数据装入内存的问题。了解像 Spark 这样的分布式框架在处理和构建可扩展的机器学习应用时会大有帮助。

  • 自动化模型再训练:一旦模型部署,它的性能可能会随着时间的推移而下降。保持检查模型的准确性非常重要,以便可以使用新数据启动自动训练。这将有助于维持产品的预测准确性。

  • 训练和测试管道:对于独立的训练和测试管道,总是有可能导致训练和测试特征之间的偏差。尽量确保训练和测试管道之间有尽可能多的重叠。这可以帮助更轻松地调试模型预测。

  • 通过 A/B 测试推出新模型:A/B 测试是一种比较两个版本模型/网页等的方法。它是一个统计实验,其中随机向用户展示两个不同版本。你可以在普渡大学的讲义中阅读更多相关内容(www.cs.purdue.edu/homes/ribeirob/courses/Fall2016/lectures/hyp_tests.pdf)。

如果你构建的模型优于已经在生产环境中使用的模型,你会在测试数据集上看到准确度的提升。然而,由于各种问题(如相关性与因果关系、用户行为变化等),在生产环境中你可能不会看到与现有模型相同的提升。因此,在将新模型推出给所有用户之前,在生产环境中进行 A/B 测试是非常重要的。

  • 单一模型优于集成模型:集成模型(多个单一模型的组合)可能会比单一模型提供更好的准确性。然而,如果增益不显著,始终建议使用单一模型。这是因为集成模型在生产系统中难以维护、调试和扩展。

深度学习的局限性

在这个项目中,几乎所有的项目都涉及某种深度学习。深度学习在推动过去几年大部分进展中起到了关键作用。然而,深度学习存在一些显而易见的局限性,我们在将其应用于现实世界场景之前应当理解这些限制。以下是其中一些:

  • 数据饥渴:通常情况下,我们无法为每个需要使用机器学习解决的问题提供大规模的数据集。相反,深度学习算法只有在我们拥有庞大的数据集时才能发挥作用。

  • 计算密集型:深度学习训练通常需要 GPU 支持和大量内存。然而,这使得在像手机、平板电脑等边缘设备上训练深度神经网络变得不可能。

  • 没有预测不确定性:默认情况下,深度学习算法难以表现不确定性。深度神经网络可能会非常自信地错误地将一张猫的图片分类为狗的图片。

对于预测结果,没有置信区间或不确定性的概念。对于像自动驾驶汽车这样的应用,在做出任何决策之前考虑不确定性是非常重要的。在本书中,我们介绍了贝叶斯神经网络等概念,试图在深度神经网络中融入不确定性。

  • 不可解释的黑箱:深度学习模型很难解释和信任。例如,银行的贷款部门基于个人的过去购买记录或信用历史,通过深度神经网络来决定是否向个人发放贷款。

如果模型拒绝贷款,银行必须向个人解释为何贷款被拒。然而,使用深度神经网络时,几乎不可能提供明确的理由解释为何贷款被拒。不可解释性是这些模型未能广泛应用于各个行业的主要原因。

行业内的 AI 应用

AI 是每个公司都在努力转型的全新范式。根据麦肯锡报告(www.mckinsey.com/featured-insights/artificial-intelligence/notes-from-the-ai-frontier-modeling-the-impact-of-ai-on-the-world-economy),到 2030 年,预计 70% 的公司将至少采用一种 AI 技术。让我们看看不同行业中的 AI 应用:

  • 零售业

    • 供应链优化

    • 通过微定位定制购物体验

    • 产品定价及节假日折扣计算

    • 在零售店中定制产品摆放以增加销售

  • 社交网络(Facebook, LinkedIn, Twitter)

    • 朋友/关注者推荐

    • 基于过去历史的主页推荐定制,以提高参与度

    • 假新闻/欺诈检测

  • 医疗健康

    • 新药发现

    • 自动化医学影像处理

    • 通过 Apple Watch 或其他设备存储的数据推荐锻炼/食物

  • 金融业

    • 股票市场预测

    • 信用卡欺诈检测

    • 贷款资格

    • 客户支持聊天机器人

  • 制造业

    • 预测性维护

    • 需求预测

    • 库存管理

  • 物流

    • ETA 优化

    • 高峰定价

    • 共享出行/拼车

    • 定价

    • 自动驾驶汽车

AI 的伦理考量

我们正见证人工智能及其应用的非凡崛起。然而,AI 应用的日益复杂化引发了关于偏见、公平、安全、透明度和问责制等一系列问题。这主要是因为 AI 模型没有良知,无法独立区分好坏。它们的效果取决于它们训练所用的数据。因此,如果数据在某些方面存在偏见,那么预测结果也会偏见。还有关于因自动化导致的失业增加、AI 被用于恐怖主义以及 AI 模型的种族歧视预测等问题。

好消息是,许多大学正在投入时间和资源,寻求如何使 AI 更加公平并去除偏见的解决方案。同时,监管机构也在努力制定新的规则,以确保 AI 应用对人类来说是安全和可靠的。

作为一名 AI 从业者,我们必须在将 AI 应用于自己的产品之前理解这些问题。我敦促你关注你产品中的伦理问题,并相应地加以纠正。

总结

在这一章中,我们看到了 TensorFlow 的各种扩展,旨在提高数据科学家的生产力,并使前沿模型在大规模生产环境中更易部署。

我们了解了 TensorFlow Hub,它类似于 GitHub 上来自各个领域(如计算机视觉、自然语言处理等)的训练深度学习模型的仓库。之后,我们理解了 TensorFlow Serving 如何提供工具和库来大规模部署深度学习模型。最后,我们学习了TensorFlow ExtendedTFX)的开源组件,TFX 是谷歌的机器学习平台。TFX 帮助整个模型构建管道,从数据分析到模型部署。

接下来,我们了解了构建可扩展 AI 产品时的一些最佳实践。构建稳健的工程管道,在深度学习之前尝试简单的模型,并始终通过 A/B 测试发布新模型是其中的一些做法。

随后,我们通过了解深度神经网络的局限性,打破了关于深度学习的炒作。具体来说,我们了解到,它们需要大量的数据和计算能力来构建好的、准确的模型。此外,它们不可解释的特点使得它们在许多 AI 应用中无法使用。我们还了解了 AI 在各行各业中的应用,并学习了 AI 伦理的重要性。

最后,如果你已经看到这里并完成了项目,我感谢你,并祝贺你取得了非凡的成就。你已经掌握了使用强化学习、计算机视觉和自然语言处理等先进技术构建实用 AI 应用所需的技能。我希望你现在能将这些知识用于善事,让这个世界变得更美好。

我想以我最喜欢的名言结束本书:

未来不是我们要去的地方,而是我们在创造的地方。

  • 约翰·H·斯卡尔