TensorFlow-1-x-深度学习秘籍-全-

TensorFlow 1.x 深度学习秘籍(全)

原文:TensorFlow 1.x Deep Learning Cookbook

协议:CC BY-NC-SA 4.0

零、前言

在本书中,您将学习如何有效地使用 TensorFlow,这是 Google 的深度学习开源框架。 您将实现不同的深度学习网络,例如卷积神经网络CNN),循环神经网络RNN),深度 Q 学习网络DQN)和生成对抗网络GAN),易于遵循独立的秘籍。 您将学习如何使用 TensorFlow 将 Keras 用作后端。
您将了解如何实现不同的深度神经架构以执行工作中的复杂任务。 您将在一些常用数据集(例如 MNIST,CIFAR-10,Youtube8m 等)上学习不同 DNN 的表现。 您不仅将了解 TensorFlow 支持的不同移动和嵌入式平台,还将了解如何为深度学习应用设置云平台。 简要了解 TPU 架构以及它们将如何影响 DNN 的未来。
到本书结尾,您将成为在不断增长的实际应用和研究领域中实现深度学习技术的专家,这些领域包括增强学习,GAN,自编码器等。

本书涵盖的内容

“TensorFlow 简介”讨论了 Google 的开源框架 TensorFlow,以及为什么它对深度学习很有用。 我们将讨论如何在 MAC,Windows 和 Ubuntu 上为 CPU 和 GPU 安装 TensorFlow。 我们还将讨论整本书中将使用的其他 python 包。 我们将解释 TensorFlow 代码的两个组成部分,图的定义及其执行。 我们将学习使用 TensorBoard 查看图结构。 我们将了解 TensorFlow 常量,变量和占位符之间的区别。 我们还将品尝 TensorFlow 估计器。

“回归”讨论了回归及其应用。 我们将讨论回归所涉及的概念,了解回归与聚类和分类的不同之处。 我们将学习可能的不同类型的损失函数以及如何在 Tensorflow 中实现它们。 我们学习如何实现 L1 和 L2 正则化。 我们将讨论梯度下降算法,学习如何优化它并在 Tensorflow 中实现它。 我们将简要了解交叉熵函数及其实现。

“神经网络感知机”,涵盖了人工神经网络,并解释了为什么它可以完成 DNN 最近声称的出色任务。 我们将学习激活函数的不同选择。 我们将使用所有这些来构建一个简单的感知机,并将其用于函数建模。 我们将在训练之前了解数据的正则化。 我们还将学习逐步构建多层感知机(MLP)。 我们将看到 TensorFlow 的自动区分器如何使我们的工作更轻松。

“TensorFlow 卷积神经网络”,讨论了卷积的过程及其如何提取特征。 我们将学习 CNN 的三个重要层:卷积层,池化层和完全连接层。 我们还将学习丢弃,如何提高表现以及不同的 CNN 架构(例如 LeNET 和 GoogleNET)。

“CNN 的运作”,涵盖了 CNN 的一些令人赞叹的成功案例,例如面部识别。 我们将编写一个使用 CNN 进行情感分析的方法。 我们将讨论 CNN 的预调整,并学习如何实现迁移学习。 我们将学习如何使用 VGG16 进行迁移学习。 我们将使用 VGGNet,ResNet,Inception 和 Xception 学习图像的分类。 我们将使用膨胀的卷积网络,Wavenet 和 Nsynth 生成音乐。 我们还将学习如何进行 Visual Q &A。我们将学习如何对视频进行分类。

“循环神经网络”讨论了循环神经网络。 我们将学习 RNN 的基本单元 RNN 单元。 我们将学习单词嵌入和时间排序。 我们将简要讨论 LSTM 网络。 我们将学习 seq2seq RNN。 我们将学习如何将 RNN 应用于机器翻译,生成文本和预测未来价值

“无监督学习”,讲授无监督学习范例。 我们将学习聚类和降维。 我们将学习诸如主成分分析(PCA)之类的技术,并了解如何将其用于降维。 我们将学习 K 均值聚类。 我们将了解地形图的概念,并学习如何训练自组织图。 我们将学习受限玻尔兹曼机(RBM)。 我们将讨论 RBM 的架构和训练。 我们将学习如何堆叠 RBM 来构成深度信念网络,并且将学习如何对其进行训练。 我们将使用预训练和微调进行情感检测的概念来训练 DBN。

“自编码器”,使自编码器神秘化。 我们将学习自编码器及其应用。 我们将讨论可以使用自编码器的各种实际示例。 我们将讨论编码和后续重构的过程。 我们将学习重建误差。 我们将学习稀疏自编码器,即 KL 散度的概念。 我们将学习去噪自编码器,并在给定嘈杂图像的情况下使用它们来重建清晰图像。 我们将学习如何构建卷积自编码器和堆叠式自编码器。

“强化学习”,涵盖了不同的强化学习算法。 我们将学习 Q 学习算法。 我们将讨论 Bellman-Ford 方程以及如何选择学习率,折扣因子。 我们将学习如何使用 OpenAI Gym 框架。 我们将学习经验重放和缓冲的概念,以实现值迭代 Q 网络。 我们将使用 Q 学习和策略梯度来构建游戏智能体。 最后,我们将学习如何建立自己的深度 Q 学习网络(DQN)。 简要描述 AlphaGo Zero 及其大获胜。

第 10 章,“TensorFlow 移动计算”,涵盖了 TensorFlow 移动。 我们将学习移动深度学习的不同应用。 我们将学习如何在 Windows 平台上的 Android Studio 中使用 Tensorflow。 我们将学习如何结合使用 Tensorflow 和 XCode 来制作基于 IOS 的应用。 我们将学习如何优化移动设备的 Tensorflow 图。 我们还将学习如何为移动设备转换 Tensorflow 图。

第 11 章,“TensorFlow 生成对抗网络(GAN),变分自编码器和胶囊网络”,从生成对抗网络及其相对于其他 DNN 的优势开始。 我们探索了不同的预测模型。 我们了解 GAN 的动机及其直观工作。 我们了解基本的 GAN 架构。 我们将探索 GAN 的一些非常酷的应用。 我们将学习另一种生成网络,即变分自编码器。 最后,我们将了解最近提出的胶囊网络

第 12 章,“分布式 TensorFlow 和云深度学习”,介绍了云环境,泊坞窗,容器以及如何使用它们。 我们将学习如何使用具有多个 GPU 和多个服务器的分布式 Tensorflow。 我们将学习如何设置 AWS 进行深度学习。 我们将学习如何为深度学习应用设置 Google 云。 我们将学习如何为深度学习应用设置 Microsoft Azure 云。 我们了解其他可用的云服务

附录 A,“通过 AutoML 学习(元学习)”,简要讨论了 AutoML 和连体网络。

附录 B,“TensorFlow 处理单元”,涵盖了 Tensor 处理单元,其基本架构以及它将如何影响 DNN 的未来。

这本书需要什么

对于这本书,您将需要 Python 版本 3.5TensorFlow。 建议使用以下硬件规格:

  • CPU 架构:x86_64
  • 系统内存:8-32 GB
  • CPU:4-8 核
  • GPU :(可选,最低 NVDIA®GTX 650)

这本书是给谁的

本书面向希望定期执行机器学习任务的数据科学家,机器学习从业人员和深度学习爱好者。 稍微熟悉深度神经网络并希望获得与 CNN 和 RNN 一起工作的专业知识的人会发现这本书很有用。

标题

在本书中,您会发现经常出现的几个标题(准备工作,如何做……,如何工作……,还有更多……以及另请参见)。 为了给出有关如何完成秘籍的明确说明,我们使用以下部分。

准备

本节将告诉您秘籍中的预期内容,并介绍如何设置秘籍所需的任何软件或任何初步设置。

操作步骤

本节包含遵循秘籍所需的步骤。

工作原理

本节通常包括对上一节中发生的情况的详细说明。

更多

本节包含有关秘籍的其他信息,以使读者对秘籍有更多的了解。

另见

本节提供了指向该秘籍其他有用信息的有用链接。

约定

在本书中,您将找到许多可以区分不同类型信息的文本风格。 以下是这些风格的一些示例,并解释了其含义。 文本,数据库表名称,文件夹名称,文件名,文件扩展名,路径名,虚拟 URL,用户输入和 Twitter 句柄中的代码字显示如下:“我们刚刚使用以下命令创建了jiradb数据库,在数据库中为 JIRA 创建新用户并授予该用户访问权限”。

代码块设置如下:

<Contextpath="/jira"docBase="${catalina.home}
/atlassian- jira" reloadable="false" useHttpOnly="true">

任何命令行输入或输出的编写方式如下:

 mysql -u root -p

新术语重要词以粗体显示。 您在屏幕上看到的单词,例如在菜单或对话框中,将以如下形式出现:从“管理”面板中选择“系统信息”。

警告或重要提示显示如下,提示和技巧显示如下。

一、TensorFlow 简介

曾经尝试仅使用 NumPy 用 Python 编写用于神经网络的代码的任何人都知道它很繁琐。 为一个简单的单层前馈网络编写代码需要 40 条线,这增加了编写代码和执行时间方面的难度。

TensorFlow 使得一切变得更容易,更快捷,从而减少了实现想法与部署之间的时间。 在这本书中,您将学习如何发挥 TensorFlow 的功能来实现深度神经网络。

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

  • 安装 TensorFlow
  • TensorFlow 中的 HelloWorld
  • 了解 TensorFlow 程序结构
  • 使用常量,变量和占位符
  • 使用 TensorFlow 执行矩阵操作
  • 使用数据流程图
  • 从 0.x 迁移到 1.x
  • 使用 XLA 增强计算性能
  • 调用 CPU/GPU 设备
  • 将 TensorFlow 用于深度学习
  • 基于 DNN 的问题所需的不同 Python 包

介绍

TensorFlow 是 Google Brain 团队针对深层神经网络DNN)开发的功能强大的开源软件库。 它于 2015 年 11 月首次在 Apache 2.x 许可下提供; 截止到今天,其 GitHub 存储库提交了超过 17,000 次提交,在短短两年内大约有 845 个贡献者。 这本身就是 TensorFlow 受欢迎程度和性能的衡量标准。 下图显示了流行的深度学习框架的比较,可以明显看出 TensorFlow 是其中的佼佼者:

该图是基于截至 2017 年 7 月 12 日的每个 Github 存储库中的数据。 每个气泡都有一个图例:(框架,贡献者)。

首先让我们了解 TensorFlow 到底是什么,以及为什么它在 DNN 研究人员和工程师中如此受欢迎。 TensorFlow 是开源深度学习库,它允许使用单个 TensorFlow API 在一个或多个 CPU,服务器,台式机或移动设备上的 GPU 上部署深度神经网络计算。 您可能会问,还有很多其他深度学习库,例如 Torch,Theano,Caffe 和 MxNet。 是什么让 TensorFlow 与众不同? TensorFlow 等大多数其他深度学习库具有自动微分功能,许多都是开源的,大多数都支持 CPU/GPU 选项,具有经过预训练的模型,并支持常用的 NN 架构,例如循环神经网络RNN),卷积神经网络CNN)和深度置信网络DBN)。 那么,TensorFlow 还有什么呢? 让我们为您列出它们:

  • 它适用于所有很酷的语言。 TensorFlow 适用于 Python,C++ ,Java,R 和 Go。
  • TensorFlow 可在多个平台上运行,甚至可以移动和分布式。
  • 所有云提供商(AWS,Google 和 Azure)都支持它。
  • Keras 是高级神经网络 API,已与 TensorFlow 集成。
  • 它具有更好的计算图可视化效果,因为它是本机的,而 Torch/Theano 中的等效视图看上去并不那么酷。
  • TensorFlow 允许模型部署并易于在生产中使用。
  • TensorFlow 具有很好的社区支持。
  • TensorFlow 不仅仅是一个软件库; 它是一套包含 TensorFlow,TensorBoard 和 TensorServing 的软件。

Google 研究博客列出了世界各地使用 TensorFlow 进行的一些引人入胜的项目:

  • Google 翻译正在使用 TensorFlow 和张量处理单元TPU
  • 可以使用基于强化学习的模型生成旋律的 Magenta 项目采用 TensorFlow
  • 澳大利亚海洋生物学家正在使用 TensorFlow 来发现和了解濒临灭绝的海牛
  • 一位日本农民使用 TensorFlow 开发了一个应用,该应用使用大小和形状等物理参数对黄瓜进行分类

列表很长,使用 TensorFlow 的可能性更大。 本书旨在向您提供对应用于深度学习模型的 TensorFlow 的理解,以便您可以轻松地将它们适应于数据集并开发有用的应用。 每章都包含一组秘籍,涉及技术问题,依赖项,实际代码及其理解。 我们已经将这些秘籍彼此构建在一起,以便在每一章的最后,您都拥有一个功能齐全的深度学习模型。

安装 TensorFlow

在本秘籍中,您将学习如何在不同的 OS(Linux,Mac 和 Windows)上全新安装 TensorFlow 1.3。 我们将找到安装 TensorFlow 的必要要求。 TensorFlow 可以在 Ubuntu 和 macOS 上使用本机 PIP,Anaconda,Virtualenv 和 Docker 安装。 对于 Windows 操作系统,可以使用本机 PIP 或 Anaconda。

由于 Anaconda 可以在所有三个 OS 上工作,并且提供了一种简便的方法,不仅可以在同一系统上进行安装,还可以在同一系统上维护不同的项目环境,因此在本书中,我们将集中精力使用 Anaconda 安装 TensorFlow。 可从这里阅读有关 Anaconda 及其管理环境的更多详细信息。

本书中的代码已在以下平台上经过测试:

  • Windows 10,Anaconda 3,Python 3.5,TensorFlow GPU,CUDA 工具包 8.0,cuDNN v5.1,NVDIA®GTX 1070
  • Windows 10 / Ubuntu 14.04 / Ubuntu 16.04 / macOS Sierra,Anaconda3,Python 3.5,TensorFlow(CPU)

准备

TensorFlow 安装的前提条件是系统已安装 Python 2.5 或更高版本。 本书中的秘籍是为 Python 3.5(Anaconda 3 发行版)设计的。 要准备安装 TensorFlow,请首先确保已安装 Anaconda。 您可以从这里下载并安装适用于 Windows/macOS 或 Linux 的 Anaconda。

安装后,您可以在终端窗口中使用以下命令来验证安装:

conda --version

安装 Anaconda 后,我们将继续下一步,确定是安装 TensorFlow CPU 还是 GPU。 尽管几乎所有计算机都支持 TensorFlow CPU,但只有当计算机具有具有 CUDA 计算能力 3.0 或更高版本的 NVDIA®GPU 卡(台式机最低为 NVDIA®GTX 650)时,才能安装 TensorFlow GPU。

CPU versus GPU: Central Processing Unit (CPU) consists of a few cores (4-8) optimized for sequential serial processing. A Graphical Processing Unit (GPU) on the other hand has a massively parallel architecture consisting of thousands of smaller, more efficient cores (roughly in 1,000s) designed to handle multiple tasks simultaneously.

对于 TensorFlow GPU,必须安装 CUDA 工具包 7.0 或更高版本,安装正确的 NVDIA®驱动程序,并安装 cuDNN v3 或更高版本。 在 Windows 上,此外,需要某些 DLL 文件。 您可以下载所需的 DLL 文件,也可以安装 Visual Studio C++ 。 要记住的另一件事是 cuDNN 文件安装在另一个目录中。 需要确保目录位于系统路径中。 也可以选择将相关文件复制到相应文件夹中的 CUDA 库中。

操作步骤

我们按以下步骤进行:

  1. 在命令行中使用以下命令创建 conda 环境(如果使用 Windows,最好在命令行中以管理员身份进行操作):
conda create -n tensorflow python=3.5
  1. 激活 conda 环境:
# Windows    
activate tensorflow
#Mac OS/ Ubuntu:    
source activate tensorflow

  1. 该命令应更改提示符:
# Windows
(tensorflow)C:>
# Mac OS/Ubuntu    
(tensorflow)$
  1. 接下来,根据要在 conda 环境中安装的 TensorFlow 版本,输入以下命令:
## Windows
# CPU Version only(tensorflow)C:>pip install --ignore-installed --upgrade https://storage.googleapis.com/tensorflow/windows/cpu/tensorflow-1.3.0cr2-cp35-cp35m-win_amd64.whl

# GPU Version  
(tensorflow)C:>pip install --ignore-installed --upgrade https://storage.googleapis.com/tensorflow/windows/gpu/tensorflow_gpu-1.3.0cr2-cp35-cp35m-win_amd64.whl
## Mac OS
# CPU only Version
(tensorflow)$ pip install --ignore-installed --upgrade https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.3.0cr2-py3-none-any.whl# GPU version(tensorflow)$ pip install --ignore-installed --upgrade https://storage.googleapis.com/tensorflow/mac/gpu/tensorflow_gpu-1.3.0cr2-py3-none-any.whl
## Ubuntu# CPU only Version(tensorflow)$ pip install --ignore-installed --upgrade https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.3.0cr2-cp35-cp35m-linux_x86_64.whl# GPU Version
(tensorflow)$ pip install --ignore-installed --upgrade https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.3.0cr2-cp35-cp35m-linux_x86_64.whl
  1. 在命令行上,输入python
  2. 编写以下代码:
import tensorflow as tf
message = tf.constant('Welcome to the exciting world of Deep Neural Networks!')
with tf.Session() as sess:
    print(sess.run(message).decode())
  1. 您将收到以下输出:

  1. 在 Windows 上使用命令deactivate在 MAC/Ubuntu 上使用source deactivate在命令行上禁用 conda 环境。

工作原理

Google 使用 Wheels 标准分发 TensorFlow。 它是具有.whl扩展名的 ZIP 格式存档。 Anaconda 3 中的默认 Python 解释器 Python 3.6 没有安装轮子。 在撰写本书时,仅对 Linux/Ubuntu 支持 Python 3.6。 因此,在创建 TensorFlow 环境时,我们指定了 Python 3.5。 这将在名为tensorflow的 conda 环境中安装 PIP,python 和 wheel 以及其他一些包。

创建 conda 环境后,可使用source activate/activate命令激活该环境。 在激活的环境中,将pip install命令与适当的 TensorFlow-API URL 配合使用以安装所需的 TensorFlow。 尽管存在使用 Conda forge 安装 TensorFlow CPU 的 Anaconda 命令,但 TensorFlow 文档建议使用pip install。 在 conda 环境中安装 TensorFlow 之后,我们可以将其停用。 现在您可以执行第一个 TensorFlow 程序了。

程序运行时,您可能会看到一些警告(W)消息,一些信息(I)消息以及最后的代码输出:

Welcome to the exciting world of Deep Neural Networks!

恭喜您成功安装并执行了第一个 TensorFlow 代码! 在下一个秘籍中,我们将更深入地研究代码。

更多

此外,您还可以安装 Jupyter 笔记本:

  1. 如下安装ipython
conda install -c anaconda ipython

  1. 安装nb_conda_kernels
conda install -channel=conda-forge nb_conda_kernels

  1. 启动Jupyter notebook
jupyter notebook 

This will result in the opening of a new browser window.

如果您的系统上已经安装了 TensorFlow,则可以使用pip install --upgrade tensorflow对其进行升级。

TensorFlow 中的 HelloWorld

您学习用任何计算机语言编写的第一个程序是 HelloWorld。 我们在本书中保持约定,并从 HelloWorld 程序开始。 我们在上一节中用于验证 TensorFlow 安装的代码如下:

import tensorflow as tf
message = tf.constant('Welcome to the exciting world of Deep Neural Networks!')
 with tf.Session() as sess:
     print(sess.run(message).decode()) 

让我们深入研究这个简单的代码。

操作步骤

  1. 导入tensorflow会导入 TensorFlow 库,并允许您使用其出色的功能。
 import tensorflow as tf 
  1. 由于我们要打印的消息是一个常量字符串,因此我们使用tf.constant
message = tf.constant('Welcome to the exciting world of Deep Neural Networks!')

  1. 要执行图元素,我们需要使用with定义Session并使用run运行会话:
with tf.Session() as sess:
     print(sess.run(message).decode()) 
  1. 根据您的计算机系统和操作系统,输出包含一系列警告消息(W),声称如果针对您的特定计算机进行编译,代码可以更快地运行:
The TensorFlow library wasn't compiled to use SSE instructions, but these are available on your machine and could speed up CPU computations. 
The TensorFlow library wasn't compiled to use SSE2 instructions, but these are available on your machine and could speed up CPU computations. 
The TensorFlow library wasn't compiled to use SSE3 instructions, but these are available on your machine and could speed up CPU computations. 
The TensorFlow library wasn't compiled to use SSE4.1 instructions, but these are available on your machine and could speed up CPU computations. 
The TensorFlow library wasn't compiled to use SSE4.2 instructions, but these are available on your machine and could speed up CPU computations. 
The TensorFlow library wasn't compiled to use AVX instructions, but these are available on your machine and could speed up CPU computations. 
The TensorFlow library wasn't compiled to use AVX2 instructions, but these are available on your machine and could speed up CPU computations. 
The TensorFlow library wasn't compiled to use FMA instructions, but these are available on your machine and could speed up CPU computations. 
  1. 如果您正在使用 TensorFlow GPU,则还会获得信息性消息列表(I),其中提供了所用设备的详细信息:
Found device 0 with properties:  
name: GeForce GTX 1070 
major: 6 minor: 1 memoryClockRate (GHz) 1.683 
pciBusID 0000:01:00.0 
Total memory: 8.00GiB 
Free memory: 6.66GiB 
DMA: 0  
0:   Y  
Creating TensorFlow device (/gpu:0) -> (device: 0, name: GeForce GTX 1070, pci bus id: 0000:01:00.0) 
  1. 最后是我们要求在会话中打印的消息:
Welcome to the exciting world of Deep Neural Networks

工作原理

前面的代码分为三个主要部分。 导入块包含我们的代码将使用的所有库; 在当前代码中,我们仅使用 TensorFlow。 import tensorflow as tf语句使 Python 可以访问所有 TensorFlow 的类,方法和符号。 第二块包含图定义部分; 在这里,我们建立了所需的计算图。 在当前情况下,我们的图仅由一个节点组成,张量常数消息由字节字符串"Welcome to the exciting world of Deep Neural Networks"组成。 我们代码的第三部分是作为会话运行计算图; 我们使用with关键字创建了一个会话。 最后,在会话中,我们运行上面创建的图。

现在让我们了解输出。 收到的警告消息告诉您,TensorFlow 代码可能会以更高的速度运行,这可以通过从源代码安装 TensorFlow 来实现(我们将在本章稍后的内容中进行此操作)。 收到的信息消息会通知您有关用于计算的设备。 对它们而言,这两种消息都相当无害,但是如果您不希望看到它们,则添加以下两行代码即可解决问题:

import os
os.environ['TF_CPP_MIN_LOG_LEVEL']='2'   

该代码将忽略直到级别 2 的所有消息。级别 1 用于提供信息,级别 2 用于警告,级别 3 用于错误消息。

程序将打印运行图的结果,该图是使用sess.run()语句运行的。 运行图的结果将馈送到print函数,可使用decode方法对其进行进一步修改。 sess.run求值消息中定义的张量。 print函数在stdout上打印求值结果:

b'Welcome to the exciting world of Deep Neural Networks' 

这表示结果是byte string。 要删除字符串引号和b(用于字节),我们使用方法decode()

了解 TensorFlow 程序结构

TensorFlow 与其他编程语言非常不同。 我们首先需要为要创建的任何神经网络构建一个蓝图。 这是通过将程序分为两个独立的部分来完成的,即计算图的定义及其执行。 首先,这对于常规程序员而言似乎很麻烦,但是执行图与图定义的这种分离赋予了 TensorFlow 强大的力量,即可以在多个平台上工作和并行执行的能力。

计算图:计算图是节点和边的网络。 在本节中,定义了所有要使用的数据,即张量对象(常量,变量和占位符)和所有要执行的计算,即操作对象(简称为ops)。 每个节点可以有零个或多个输入,但只有一个输出。 网络中的节点表示对象(张量和运算),边缘表示在运算之间流动的张量。 计算图定义了神经网络的蓝图,但其中的张量尚无与其关联的值。

为了构建计算图,我们定义了我们需要执行的所有常量,变量和操作。 常量,变量和占位符将在下一个秘籍中处理。 数学运算将在矩阵处理的秘籍中详细介绍。 在这里,我们使用一个简单的示例来描述结构,该示例定义并执行图以添加两个向量。

图的执行:使用会话对象执行图。 会话对象封装了求值张量和操作对象的环境。 这是实际计算和信息从一层传输到另一层的地方。 不同张量对象的值仅初始化,访问并保存在会话对象中。 到目前为止,张量对象仅仅是抽象的定义,在这里它们就变成了现实。

操作步骤

我们按以下步骤进行:

  1. 我们考虑一个简单的例子,将两个向量相加,我们有两个输入向量v_1v_2,它们将被作为Add操作的输入。 我们要构建的图如下:

  1. 定义计算图的相应代码如下:
v_1 = tf.constant([1,2,3,4]) 
v_2 = tf.constant([2,1,5,3]) 
v_add = tf.add(v_1,v_2)  # You can also write v_1 + v_2 instead 
  1. 接下来,我们在会话中执行图:
with tf.Session() as sess: 
    prin(sess.run(v_add)) 

上面的两个命令等效于以下代码。 使用with块的优点是不需要显式关闭会话。

sess = tf.Session() 
print(ses.run(tv_add)) 
sess.close() 
  1. 这导致打印两个向量的和:
[3 3 8 7] 

请记住,每个会话都需要使用close()方法显式关闭,而with块在结束时会隐式关闭会话。

工作原理

计算图的构建非常简单; 您将继续添加变量和运算,并按照您逐层构建神经网络的顺序将它们传递(使张量流动)。 TensorFlow 还允许您使用with tf.device()将特定设备(CPU/GPU)与计算图的不同对象一起使用。 在我们的示例中,计算图由三个节点组成,v_1v_2代表两个向量,Add是对其执行的操作。

现在,要使该图更生动,我们首先需要使用tf.Session()定义一个会话对象; 我们给会话对象起了名字sess。 接下来,我们使用 Session 类中定义的run方法运行它,如下所示:

run (fetches, feed_dict=None, options=None, run_metadata) 

这将求值fetches中的张量; 我们的示例在提取中具有张量v_addrun方法将执行导致v_add的图中的每个张量和每个操作。 如果您在提取中包含v_1而不是v_add,则结果将是向量v_1的值:

[1,2,3,4]  

访存可以是单个张量/运算对象,也可以是多个张量/操作对象,例如,如果访存为[v_1, v_2, v_add],则输出将为以下内容:

[array([1, 2, 3, 4]), array([2, 1, 5, 3]), array([3, 3, 8, 7])] 

在同一程序代码中,我们可以有许多会话对象。

更多

您一定想知道为什么我们必须编写这么多行代码才能进行简单的向量加法或打印一条小消息。 好吧,您可以很方便地以单线方式完成此工作:

print(tf.Session().run(tf.add(tf.constant([1,2,3,4]),tf.constant([2,1,5,3])))) 

编写这种类型的代码不仅会影响计算图,而且在for循环中重复执行相同的操作(OP)时可能会占用大量内存。 养成显式定义所有张量和操作对象的习惯,不仅使代码更具可读性,而且还有助于您以更简洁的方式可视化计算图。

使用 TensorBoard 可视化图形是 TensorFlow 最有用的功能之一,尤其是在构建复杂的神经网络时。 可以在图对象的帮助下查看我们构建的计算图。

如果您正在使用 Jupyter 笔记本或 Python Shell,则使用tf.InteractiveSession代替tf.Session更为方便。 InteractiveSession使其成为默认会话,因此您可以使用eval()直接调用运行张量对象,而无需显式调用该会话,如以下示例代码中所述:

sess = tf.InteractiveSession() 

v_1 = tf.constant([1,2,3,4]) 
v_2 = tf.constant([2,1,5,3]) 

v_add = tf.add(v_1,v_2) 

print(v_add.eval()) 

sess.close() 

使用常量,变量和占位符

用最简单的术语讲,TensorFlow 提供了一个库来定义和执行带有张量的不同数学运算。 张量基本上是 n 维矩阵。 所有类型的数据,即标量,向量和矩阵都是张量的特殊类型:

数据类型 张量 形状
标量 0 维张量 []
向量 一维张量 [D0]
矩阵 二维张量 [D0, D1]
张量 ND 张量 [D0, D1, D[n-1]]

TensorFlow 支持三种类型的张量:

  • 常量
  • 变量
  • 占位符

常量:常数是无法更改其值的张量。

变量:当值需要在会话中更新时,我们使用变量张量。 例如,在神经网络的情况下,需要在训练期间更新权重,这是通过将权重声明为变量来实现的。 在使用之前,需要对变量进行显式初始化。 另一个要注意的重要事项是常量存储在计算图定义中。 每次加载图时都会加载它们。 换句话说,它们是昂贵的内存。 另一方面,变量是分开存储的。 它们可以存在于参数服务器上。

占位符:这些占位符用于将值输入 TensorFlow 图。 它们与feed_dict一起用于输入数据。 它们通常用于在训练神经网络时提供新的训练示例。 在会话中运行图时,我们为占位符分配一个值。 它们使我们无需数据即可创建操作并构建计算图。 需要注意的重要一点是,占位符不包含任何数据,因此也无需初始化它们。

操作步骤

让我们从常量开始:

  1. 我们可以声明一个常量标量:
t_1 = tf.constant(4)   
  1. 形状为[1,3]的常数向量可以声明如下:
t_2 = tf.constant([4, 3, 2]) 
  1. 为了创建一个所有元素都为零的张量,我们使用tf.zeros()。 该语句创建一个形状为[M,N]dtype的零矩阵(int32float32等):
tf.zeros([M,N],tf.dtype)   

让我们举个例子:

zero_t = tf.zeros([2,3],tf.int32) 
# Results in an 2×3 array of zeros: [[0 0 0], [0 0 0]] 
  1. 我们还可以创建与现有 Numpy 数组形状相同的张量常数或张量常数,如下所示:
tf.zeros_like(t_2) 
# Create a zero matrix of same shape as t_2  
tf.ones_like(t_2) 
# Creates a ones matrix of same shape as t_2
  1. 我们可以将所有元素设置为一个来创建张量; 在这里,我们创建一个形状为[M,N]的 1 矩阵:
tf.ones([M,N],tf.dtype) 

让我们举个例子:

ones_t = tf.ones([2,3],tf.int32) 
# Results in an 2×3 array of ones:[[1 1 1], [1 1 1]] 

让我们继续序列:

  1. 我们可以在总的num值内生成从开始到结束的一系列均匀间隔的向量:
tf.linspace(start, stop, num) 
  1. 相应的值相差(stop-start)/(num-1)
  2. 让我们举个例子:
range_t = tf.linspace(2.0,5.0,5) 
# We get: [ 2\.    2.75  3.5   4.25  5\.  ]  
  1. 从头开始生成一系列数字(默认值为 0),以增量递增(默认值为 1),直到但不包括限制:
tf.range(start,limit,delta) 

这是一个例子:

range_t = tf.range(10) 
# Result: [0 1 2 3 4 5 6 7 8 9] 

TensorFlow 允许创建具有不同分布的随机张量

  1. 要根据形状为[M,N]的正态分布创建随机值,其中均值(默认值为 0.0),标准差(默认值为 1.0),种子,我们可以使用以下方法:
t_random = tf.random_normal([2,3], mean=2.0, stddev=4, seed=12) 

# Result: [[ 0.25347459  5.37990952  1.95276058], [-1.53760314  1.2588985   2.84780669]]
  1. 要从形状为[M,N]的截断正态分布(带有平均值(默认值为 0.0)和标准差(默认值为 1.0))创建随机值,我们可以使用以下方法:
t_random = tf.truncated_normal([1,5], stddev=2, seed=12) 
# Result: [[-0.8732627 1.68995488 -0.02361972 -1.76880157 -3.87749004]]
  1. 要根据给定的形状[M,N]的伽玛分布在[minval (default=0), maxval]范围内创建带有种子的随机值,请执行以下操作:
t_random = tf.random_uniform([2,3], maxval=4, seed=12) 

# Result: [[ 2.54461002  3.69636583  2.70510912], [ 2.00850058  3.84459829  3.54268885]] 
  1. 要将给定张量随机裁剪为指定大小,请执行以下操作:
tf.random_crop(t_random, [2,5],seed=12) 

在这里,t_random是已经定义的张量。 这将导致从张量t_random中随机裁剪出[2,5]张量。

很多时候,我们需要以随机顺序展示训练样本; 我们可以使用tf.random_shuffle()沿其第一维随机调整张量。 如果t_random是我们想要改组的张量,那么我们使用以下代码:

tf.random_shuffle(t_random) 
  1. 随机生成的张量受初始种子值的影响。 为了在多个运行或会话中获得相同的随机数,应将种子设置为恒定值。 当使用大量随机张量时,我们可以使用tf.set_random_seed()为所有随机生成的张量设置种子。 以下命令将所有会话的随机张量的种子设置为54
tf.set_random_seed(54)  

Seed can have only integer value.

现在转到变量:

  1. 它们是使用变量类创建的。 变量的定义还包括应从中初始化变量的常数/随机值。 在下面的代码中,我们创建两个不同的张量变量t_at_b。 都将初始化为形状为[50, 50]minval=0maxval=10的随机均匀分布:
rand_t = tf.random_uniform([50,50], 0, 10, seed=0) 
t_a = tf.Variable(rand_t) 
t_b = tf.Variable(rand_t) 

变量通常用于表示神经网络中的权重和偏置。

  1. 在下面的代码中,我们定义了两个变量权重和偏差。 权重变量使用正态分布随机初始化,均值为零,标准差为 2,权重的大小为100×100。 偏差由 100 个元素组成,每个元素都初始化为零。 在这里,我们还使用了可选的参数名称来为计算图中定义的变量命名。
weights = tf.Variable(tf.random_normal([100,100],stddev=2)) 
bias = tf.Variable(tf.zeros[100], name = 'biases') 
  1. 在所有前面的示例中,变量的初始化源都是某个常量。 我们还可以指定一个要从另一个变量初始化的变量。 以下语句将从先前定义的权重中初始化weight2
weight2=tf.Variable(weights.initialized_value(), name='w2') 
  1. 变量的定义指定如何初始化变量,但是我们必须显式初始化所有声明的变量。 在计算图的定义中,我们通过声明一个初始化操作对象来实现:
intial_op = tf.global_variables_initializer(). 
  1. 在运行图中,还可以使用tf.Variable.initializer分别初始化每个变量:
bias = tf.Variable(tf.zeros([100,100]))
 with tf.Session() as sess:
     sess.run(bias.initializer)
  1. 保存变量:我们可以使用Saver类保存变量。 为此,我们定义一个saver操作对象:
saver = tf.train.Saver()  
  1. 在常量和变量之后,我们来到最重要的元素占位符,它们用于将数据馈入图。 我们可以使用以下内容定义占位符:
tf.placeholder(dtype, shape=None, name=None) 
  1. dtype指定占位符的数据类型,并且在声明占位符时必须指定。 在这里,我们为x定义一个占位符,并使用feed_dict为随机4×5矩阵计算y = 2 * x
x = tf.placeholder("float")
y = 2 * x
data = tf.random_uniform([4,5],10)
with tf.Session() as sess:
     x_data = sess.run(data)
     print(sess.run(y, feed_dict = {x:x_data})) 

工作原理

所有常量,变量和占位符都将在代码的计算图部分中定义。 如果在定义部分中使用print语句,我们将仅获得有关张量类型的信息,而不是张量的值。

为了找出该值,我们需要创建会话图,并显式使用run命令,并将所需的张量值设为fetches

print(sess.run(t_1))  
# Will print the value of t_1 defined in step 1 

更多

很多时候,我们将需要恒定的大尺寸张量对象。 在这种情况下,为了优化内存,最好将它们声明为具有可训练标志设置为False的变量:

t_large = tf.Variable(large_array, trainable = False) 

TensorFlow 的设计可完美地与 Numpy 配合使用,因此所有 TensorFlow 数据类型均基于 Numpy 的数据类型。 使用tf.convert_to_tensor(),我们可以将给定值转换为张量类型,并将其与 TensorFlow 函数和运算符一起使用。 该函数接受 Numpy 数组,Python 列表和 Python 标量,并允许与张量对象互操作。

下表列出了一些常见的 TensorFlow 支持的数据类型(摘自 TensorFlow.org ):

数据类型 TensorFlow 类型
DT_FLOAT tf.float32
DT_DOUBLE tf.float64
DT_INT8 tf.int8
DT_UINT8 tf.uint8
DT_STRING tf.string
DT_BOOL tf.bool
DT_COMPLEX64 tf.complex64
DT_QINT32 tf.qint32

请注意,与 Python/Numpy 序列不同,TensorFlow 序列不可​​迭代。 尝试以下代码:

for i in tf.range(10)  

您会得到一个错误:

#TypeError("'Tensor' object is not iterable.")

使用 TensorFlow 执行矩阵操作

矩阵运算(例如执行乘法,加法和减法)是任何神经网络中信号传播中的重要运算。 通常在计算中,我们需要随机,零,一或恒等矩阵。

本秘籍将向您展示如何获取不同类型的矩阵以及如何对它们执行不同的矩阵操作。

操作步骤

我们按以下步骤进行:

  1. 我们开始一个交互式会话,以便可以轻松求值结果:
import tensorflow as tf

#Start an Interactive Session
sess = tf.InteractiveSession()

#Define a 5x5 Identity matrix
I_matrix = tf.eye(5)
print(I_matrix.eval()) 
# This will print a 5x5 Identity matrix

#Define a Variable initialized to a 10x10 identity matrix
X = tf.Variable(tf.eye(10))
X.initializer.run()  # Initialize the Variable
print(X.eval()) 
# Evaluate the Variable and print the result

#Create a random 5x10 matrix
A = tf.Variable(tf.random_normal([5,10]))
A.initializer.run()

#Multiply two matrices
product = tf.matmul(A, X)
print(product.eval())

#create a random matrix of 1s and 0s, size 5x10
b = tf.Variable(tf.random_uniform([5,10], 0, 2, dtype= tf.int32))
b.initializer.run()
print(b.eval())
b_new = tf.cast(b, dtype=tf.float32)
#Cast to float32 data type

# Add the two matrices
t_sum = tf.add(product, b_new)
t_sub = product - b_new
print("A*X _b\n", t_sum.eval())
print("A*X - b\n", t_sub.eval()) 

  1. 可以按以下方式执行其他一些有用的矩阵操作,例如按元素进行乘法,与标量相乘,按元素进行除法,按元素进行除法的余数:
import tensorflow as tf

# Create two random matrices
a = tf.Variable(tf.random_normal([4,5], stddev=2))
b = tf.Variable(tf.random_normal([4,5], stddev=2))

#Element Wise Multiplication
A = a * b

#Multiplication with a scalar 2
B = tf.scalar_mul(2, A)

# Elementwise division, its result is
C = tf.div(a,b)

#Element Wise remainder of division
D = tf.mod(a,b)

init_op = tf.global_variables_initializer()
with tf.Session() as sess:
     sess.run(init_op)
     writer = tf.summary.FileWriter('graphs', sess.graph)
     a,b,A_R, B_R, C_R, D_R = sess.run([a , b, A, B, C, D])
     print("a\n",a,"\nb\n",b, "a*b\n", A_R, "\n2`a`b\n", B_R, "\na/b\n", C_R, "\na%b\n", D_R)

writer.close() 

tf.div returns a tensor of the same type as the first argument.

工作原理

矩阵的所有算术运算(例如加,乘,除,乘(元素乘),模和叉)都要求两个张量矩阵的数据类型相同。 如果不是这样,它们将产生错误。 我们可以使用tf.cast()将张量从一种数据类型转换为另一种数据类型。

更多

如果我们要在整数张量之间进行除法,最好使用tf.truediv(a,b),因为它首先将整数张量强制转换为浮点,然后执行逐元素除法。

使用数据流程图

TensorFlow 使用 TensorBoard 提供计算图的图形图像。 这使得理解,调试和优化复杂的神经网络程序变得很方便。 TensorBoard 还可以提供有关网络执行情况的定量指标。 它读取 TensorFlow 事件文件,其中包含您在运行 TensorFlow 会话时生成的摘要数据。

操作步骤

  1. 使用 TensorBoard 的第一步是确定您想要的 OP 摘要。 对于 DNN,习惯上要知道损耗项(目标函数)如何随时间变化。 在自适应学习率的情况下,学习率本身随时间变化。 我们可以在tf.summary.scalar OP 的帮助下获得所需项的摘要。 假设变量损失定义了误差项,并且我们想知道它是如何随时间变化的,那么我们可以这样做,如下所示:
loss = tf... 
tf.summary.scalar('loss', loss) 
  1. 您还可以使用tf.summary.histogram可视化特定层的梯度,权重甚至输出的分布:
output_tensor  = tf.matmul(input_tensor, weights) + biases 
tf.summary.histogram('output', output_tensor)
  1. 摘要将在会话期间生成。 您可以在计算图中定义tf.merge_all_summaries OP,而不用单独执行每个摘要操作,以便一次运行即可获得所有摘要。
  2. 然后需要使用tf.summary.Filewriter将生成的摘要写入事件文件:
writer = tf.summary.Filewriter('summary_dir', sess.graph) 
  1. 这会将所有摘要和图写入'summary_dir'目录。
  2. 现在,要可视化摘要,您需要从命令行调用 TensorBoard:
tensorboard --logdir=summary_dir 
  1. 接下来,打开浏览器并输入地址http://localhost:6006/(或运行 TensorBoard 命令后收到的链接)。
  2. 您将看到类似以下的内容,顶部带有许多选项卡。 图表标签会显示图表:

从 0.x 迁移到 1.x

TensorFlow 1.x 不提供向后兼容性。 这意味着适用于 TensorFlow 0.x 的代码可能不适用于 TensorFlow 1.0。 因此,如果您有适用于 TensorFlow 0.x 的代码,则需要对其进行升级(旧的 GitHub 存储库或您自己的代码)。 本秘籍将指出 TensorFlow 0.x 和 TensorFlow 1.0 之间的主要区别,并向您展示如何使用脚本tf_upgrade.py自动升级 TensorFlow 1.0 的代码。

操作步骤

这是我们进行秘籍的方法:

  1. 首先,从这里下载tf_upgrade.py
  2. 如果要将一个文件从 TensorFlow 0.x 转换为 TensorFlow 1.0,请在命令行中使用以下命令:
python tf_upgrade.py --infile old_file.py --outfile upgraded_file.py

  1. 例如,如果您有一个名为test.py的 TensorFlow 程序文件,则将使用以下命令,如下所示:
python tf_upgrade.py --infile test.py --outfile test_1.0.py
  1. 这将导致创建一个名为test_1.0.py的新文件。
  2. 如果要迁移目录的所有文件,请在命令行中使用以下命令:
python tf_upgrade.py --intree InputDIr --outtree OutputDir
 # For example, if you have a directory located at /home/user/my_dir you can migrate all the python files in the directory located at /home/user/my-dir_1p0 using the above command as:
python tf_upgrade.py --intree /home/user/my_dir --outtree /home/user/my_dir_1p0
  1. 在大多数情况下,该目录还包含数据集文件。 您可以使用以下方法确保将非 Python 文件也复制到新目录(上例中为my-dir_1p0):
python tf_upgrade.py --intree /home/user/my_dir --outtree /home/user/my_dir_1p0 -copyotherfiles True

  1. 在所有这些情况下,都会生成一个report.txt文件。 该文件包含转换的详细信息以及过程中的任何错误。
  2. 读取report.txt文件,然后手动升级脚本无法更新的部分代码。

更多

tf_upgrade.py具有某些限制:

  • 它不能更改tf.reverse()的参数:您将必须手动修复它
  • 对于参数列表重新排序的方法,例如tf.split()tf.reverse_split(),它将尝试引入关键字参数,但实际上无法对其重新排序
  • 您将必须手动将tf.get.variable_scope().reuse_variables()之类的结构替换为以下内容:
with tf.variable_scope(tf.get_variable_scope(), resuse=True):

使用 XLA 增强计算性能

加速线性代数XLA)是线性代数的特定领域编译器。 根据这个页面的说法,它仍处于实验阶段,可用于优化 TensorFlow 计算。 它可以提高服务器和移动平台上的执行速度,内存使用率和可移植性。 它提供双向 JIT即时)编译或 AoT预先)编译。 使用 XLA,您可以生成平台相关的二进制文件(适用于 x64,ARM 等大量平台),可以针对内存和速度进行优化。

准备

目前,XLA 不包含在 TensorFlow 的二进制发行版中。 需要从源代码构建它。 要从源代码构建 TensorFlow,需要具备 LLVM 和 Bazel 以及 TensorFlow 的知识。 TensorFlow.org 仅在 MacOS 和 Ubuntu 中支持从源代码构建。 从源代码构建 TensorFlow 所需的步骤如下

  1. 确定要安装的 TensorFlow-仅具有 CPU 支持的 TensorFlow 或具有 GPU 支持的 TensorFlow。
  2. 克隆 TensorFlow 存储库:
git clone https://github.com/tensorflow/tensorflow 
cd tensorflow 
git checkout Branch #where Branch is the desired branch  
  1. 安装以下依赖项:

  2. 配置安装。 在此步骤中,您需要选择不同的选项,例如 XLA,Cuda 支持,动词等等:

./configure 
  1. 接下来,使用bazel-build
  2. 对于仅 CPU 版本,请使用:
bazel build --config=opt //tensorflow/tools/pip_package:build_pip_package

  1. 如果您有兼容的 GPU 设备,并且需要 GPU 支持,请使用:
bazel build --config=opt --config=cuda //tensorflow/tools/pip_package:build_pip_package

  1. 成功运行后,您将获得一个脚本build_pip_package
  2. 如下运行此脚本以构建whl文件:
bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg
  1. 安装pip包:
sudo pip install /tmp/tensorflow_pkg/tensorflow-1.1.0-py2-none-any.whl

现在您可以开始了。

操作步骤

TensorFlow 生成 TensorFlow 图。 借助 XLA,可以在任何新型设备上运行 TensorFlow 图。

  1. JIT 编译:这将在会话级别打开 JIT 编译:
# Config to turn on JIT compilation
 config = tf.ConfigProto()
 config.graph_options.optimizer_options.global_jit_level = tf.OptimizerOptions.ON_1

 sess = tf.Session(config=config) 
  1. 这是为了手动打开 JIT 编译:
jit_scope = tf.contrib.compiler.jit.experimental_jit_scope

x = tf.placeholder(np.float32)
with jit_scope():
   y = tf.add(x, x)  # The "add" will be compiled with XLA. 
  1. 我们还可以通过将运算符放在特定的 XLA 设备XLA_CPUXLA_GPU上,通过 XLA 运行计算:
with tf.device \ ("/job:localhost/replica:0/task:0/device:XLA_GPU:0"):
   output = tf.add(input1, input2) 

AoT 编译:在这里,我们将 tfcompile 作为独立版本将 TensorFlow 图转换为适用于不同设备(移动设备)的可执行代码。

TensorFlow.org 讲述了 tfcompile:

tfcompile 接受一个由 TensorFlow 概念的提要和获取标识的子图,并生成实现该子图的函数。 提要是函数的输入参数,而提取是函数的输出参数。 提要必须完全指定所有输入; 结果修剪后的子图不能包含占位符或变量节点。 通常将所有占位符和变量指定为提要,以确保结果子图不再包含这些节点。 生成的函数打包为 cc_library,带有导出函数签名的头文件和包含实现的目标文件。 用户编写代码以适当地调用生成的函数。

有关执行此操作的高级步骤,可以参考这里

调用 CPU/GPU 设备

TensorFlow 支持 CPU 和 GPU。 它还支持分布式计算。 我们可以在一台或多台计算机系统中的多个设备上使用 TensorFlow。 TensorFlow 将支持的设备命名为 CPU 设备的"/device:CPU:0"(或"/cpu:0"),将第i个 GPU 的设备命名为"/device:GPU:I"(或"/gpu:I")。

如前所述,GPU 比 CPU 快得多,因为它们具有许多小型内核。 但是,就计算速度而言,将 GPU 用于所有类型的计算并不总是一个优势。 与 GPU 相关的开销有时可能比 GPU 提供的并行计算的优势在计算上更为昂贵。 为了解决这个问题,TensorFlow 规定将计算放在特定的设备上。 默认情况下,如果同时存在 CPU 和 GPU,则 TensorFlow 会优先考虑 GPU。

操作步骤

TensorFlow 将设备表示为字符串。 在这里,我们将向您展示如何在 TensorFlow 中手动分配用于矩阵乘法的设备。 为了验证 TensorFlow 确实在使用指定的设备(CPU 或 GPU),我们使用log_device_placement标志设置为True,即config=tf.ConfigProto(log_device_placement=True)创建会话:

  1. 如果您不确定设备并希望 TensorFlow 选择现有和受支持的设备,则可以将allow_soft_placement标志设置为True
config=tf.ConfigProto(allow_soft_placement=True, log_device_placement=True) 
  1. 手动选择 CPU 进行操作:
with tf.device('/cpu:0'): 
    rand_t = tf.random_uniform([50,50], 0, 10, dtype=tf.float32, seed=0) 
    a = tf.Variable(rand_t) 
    b = tf.Variable(rand_t) 
    c = tf.matmul(a,b) 
    init = tf.global_variables_initializer() 

sess = tf.Session(config) 
sess.run(init) 
print(sess.run(c))    
  1. 我们得到以下输出:

我们可以看到,在这种情况下,所有设备都是'/cpu:0'

  1. 手动选择单个 GPU 进行操作:
with tf.device('/gpu:0'): 
    rand_t = tf.random_uniform([50,50], 0, 10, dtype=tf.float32, seed=0) 
    a = tf.Variable(rand_t) 
    b = tf.Variable(rand_t) 
    c = tf.matmul(a,b) 
    init = tf.global_variables_initializer() 

sess = tf.Session(config=tf.ConfigProto(log_device_placement=True)) 
sess.run(init) 
print(sess.run(c)) 
  1. 现在,输出更改为以下内容:

  1. 每次操作后的'/cpu:0'现在由'/gpu:0'代替。
  2. 手动选择多个 GPU:
c=[]
for d in ['/gpu:1','/gpu:2']:
     with tf.device(d):
         rand_t = tf.random_uniform([50, 50], 0, 10, dtype=tf.float32, seed=0)
         a = tf.Variable(rand_t)
         b = tf.Variable(rand_t)
         c.append(tf.matmul(a,b))
         init = tf.global_variables_initializer()

sess = tf.Session(config=tf.ConfigProto(allow_soft_placement=True,log_device_placement=True))
sess.run(init)
print(sess.run(c))
sess.close() 
  1. 在这种情况下,如果系统具有三个 GPU 设备,则第一组乘法将由'/gpu:1'进行,第二组乘法将由'/gpu:2'进行。

工作原理

tf.device()参数选择设备(CPU 或 GPU)。 with块确保选择设备的操作。 with块中定义的所有变量,常量和操作都将使用tf.device()中选择的设备。 会话配置使用tf.ConfigProto控制。 通过设置allow_soft_placementlog_device_placement标志,我们告诉 TensorFlow 在指定设备不可用的情况下自动选择可用设备,并在执行会话时提供日志消息作为输出,描述设备的分配。

将 TensorFlow 用于深度学习

今天的 DNN 是 AI 社区的流行语。 使用 DNN 的候选人最近赢得了许多数据科学/凝视竞赛。 自 1962 年 Rosenblat 提出感知机以来,就一直使用 DNN 的概念,而 1986 年 Rumelhart,Hinton 和 Williams 发明了梯度下降算法后,DNN 就变得可行了。 直到最近,DNN 才成为 AI/ML 爱好者和全世界工程师的最爱。

造成这种情况的主要原因是现代计算功能的可用性,例如 GPU 和 TensorFlow 之类的工具,这些功能使只需几行代码即可更轻松地访问 GPU 并构建复杂的神经网络。

作为机器学习爱好者,您必须已经熟悉神经网络和深度学习的概念,但是为了完整起见,我们将在此处介绍基础知识并探索 TensorFlow 的哪些功能使其成为深度学习的热门选择。

神经网络是一种受生物学启发的模型,用于计算和学习。 像生物神经元一样,它们从其他单元(神经元或环境)中获取加权输入。 该加权输入经过一个处理元素,并产生可以是二进制(触发或不触发)或连续(概率,预测)的输出。 人工神经网络ANN)是这些神经元的网络,可以随机分布或以分层结构排列。 这些神经元通过一组权重和与之相关的偏置来学习。

下图很好地说明了生物学中的神经网络与人工神经网络的相似性:

由 Hinton 等人定义的深度学习,由包含多个处理层(隐藏层)的计算模型组成。 层数的增加导致学习时间的增加。 由于数据集很大,因此学习时间进一步增加,正如当今的 CNN 或生成对抗网络GAN)的标准一样。 因此,要实际实现 DNN,我们需要很高的计算能力。 NVDIA®的 GPU 的出现使其变得可行,然后 Google 的 TensorFlow 使得无需复杂的数学细节即可实现复杂的 DNN 结构成为可能,并且大型数据集的可用性为 DNN 提供了必要的条件。 TensorFlow 是最受欢迎的深度学习库,其原因如下:

  • TensorFlow 是一个强大的库,用于执行大规模的数值计算,例如矩阵乘法或自动微分。 这两个计算对于实现和训练 DNN 是必需的。
  • TensorFlow 在后端使用 C/C++,这使其计算速度更快。
  • TensorFlow 具有高级的机器学习 API(tf.contrib.learn),使配置,训练和评估大量机器学习模型变得更加容易。
  • 在 TensorFlow 之上,可以使用高级深度学习库 Keras。 Keras 非常易于使用,可以轻松快速地制作原型。 它支持各种 DNN,例如 RNN,CNN,甚至是两者的组合。

操作步骤

任何深度学习网络都包含四个重要组成部分:数据集,定义模型(网络结构),训练/学习和预测/评估。 我们可以在 TensorFlow 中完成所有这些操作; 让我们看看如何:

  • 数据集:DNN 依赖于大量数据。 可以收集或生成数据,或者也可以使用可用的标准数据集。 TensorFlow 支持三种主要方法来读取数据。 有不同的数据集。 我们将用来训练本书中构建的模型的一些数据集如下:

  • MNIST:这是最大的手写数字数据库(0-9)。 它由 60,000 个示例的训练集和 10,000 个示例的测试集组成。 数据集保存在 Yann LeCun 的主页中。 数据集包含在tensorflow.examples.tutorials.mnist中的 TensorFlow 库中。

  • CIFAR10:此数据集包含 10 类 60,000 张32 x 32彩色图像,每类 6,000 张图像。 训练集包含 50,000 张图像和测试数据集 10,000 张图像。 数据集的十类是:飞机,汽车,鸟,猫,鹿,狗,青蛙,马,船和卡车。 数据由多伦多大学计算机科学系维护。

  • WORDNET:这是英语的词汇数据库。 它包含名词,动词,副词和形容词,它们被分组为认知同义词(同义词集),也就是说,代表相同概念的单词(例如,关闭和关闭或汽车和汽车)被分组为无序集合。 它包含 155,287 个单词,按 117,659 个同义词集进行组织,总计 206,941 个单词感对。 数据由普林斯顿大学维护。

  • ImageNET:这是根据 WORDNET 层次结构组织的图像数据集(目前仅名词)。 每个有意义的概念(同义词集)由多个单词或单词短语描述。 每个同义词集平均由 1,000 张图像表示。 目前,它具有 21,841 个同义词集和总共 14,197,122 张图像。 自 2010 年以来,每年组织一次 ImageNet 大规模视觉识别挑战赛ILSVRC),以将图像分类为 1,000 个对象类别之一。 这项工作由普林斯顿大学,斯坦福大学,A9 和 Google 赞助。

  • YouTube-8M:这是一个大规模的标记视频数据集,包含数百万个 YouTube 视频。 它有大约 700 万个 YouTube 视频 URL,分为 4716 个类别,分为 24 个顶级类别。 它还提供了预处理支持和帧级功能。 该数据集由 Google Research维护。

读取数据:在 TensorFlow 中可以通过三种方式读取数据:通过feed_dict馈送,从文件读取以及使用预加载的数据。 我们将在整本书中使用本秘籍中描述的组件来阅读和提供数据。 在接下来的步骤中,您将学习每个步骤。

  1. 馈送:在这种情况下,使用run()eval()函数调用中的feed_dict参数在运行每个步骤时提供数据。 这是在占位符的帮助下完成的,该方法使我们可以传递 Numpy 数据数组。 考虑使用 TensorFlow 的以下代码部分:
... 
y = tf.placeholder(tf.float32) 
x = tf.placeholder(tf.float32). 
... 
with tf.Session as sess: 
   X_Array = some Numpy Array 
   Y_Array = other Numpy Array 
   loss= ... 
sess.run(loss,feed_dict = {x: X_Array, y: Y_Array}). 
... 

这里,xy是占位符; 使用它们,我们在feed_dict的帮助下传递包含X值的数组和包含Y值的数组。

  1. 从文件中读取:当数据集非常大时,可以使用此方法来确保并非所有数据都一次占用内存(想象 60 GB YouTube-8m 数据集)。 从文件读取的过程可以按照以下步骤完成:
filename_queue = tf.train.string_input_producer(files) 
# where files is the list of filenames created above 

此函数还提供了随机播放和设置最大周期数的选项。 文件名的整个列表将添加到每个周期的队列中。 如果选择了改组选项(shuffle=True),则文件名将在每个周期被改组。

reader = tf.TextLineReader() 
key, value = reader.read(filename_queue)
record_defaults = [[1], [1], [1]]
col1, col2, col3 = tf.decode_csv(value, record_defaults=record_defaults) 

  1. 预加载数据:当数据集较小且可以完全加载到内存中时使用。 为此,我们可以将数据存储为常量或变量。 在使用变量时,我们需要将可训练标记设置为False,以便在训练期间数据不会更改。 作为 TensorFlow 常量:
# Preloaded data as constant
training_data = ... 
training_labels = ... 
with tf.Session as sess: 
   x_data = tf.Constant(training_data) 
   y_data = tf.Constant(training_labels) 
...
# Preloaded data as Variables
training_data = ... 
training_labels = ... 
with tf.Session as sess: 
   data_x = tf.placeholder(dtype=training_data.dtype, shape=training_data.shape) 
   data_y = tf.placeholder(dtype=training_label.dtype, shape=training_label.shape) 
   x_data = tf.Variable(data_x, trainable=False, collections[]) 
   y_data = tf.Variable(data_y, trainable=False, collections[]) 
... 

按照惯例,数据分为三部分-训练数据,验证数据和测试数据。

  1. 定义模型:建立一个描述网络结构的计算图。 它涉及指定超参数,变量和占位符序列,其中信息从一组神经元流向另一组神经元,并传递损失/误差函数。 您将在本章的后续部分中了解有关计算图的更多信息。

  2. 训练/学习:DNN 中的学习通常基于梯度下降算法,(将在第 2 章,“回归”中详细介绍) 目的是找到训练变量(权重/偏差),以使误差或损失(由用户在步骤 2 中定义)最小。 这是通过初始化变量并使用run()实现的:

with tf.Session as sess: 
   .... 
   sess.run(...) 
   ... 
  1. 评估模型:训练完网络后,我们将使用predict()对验证数据和测试数据进行评估。 通过评估,我们可以估计出模型对数据集的拟合程度。 因此,我们可以避免过拟合或拟合不足的常见错误。 对模型满意后,便可以将其部署到生产中。

还有更多

在 TensorFlow 1.3 中,添加了一个称为 TensorFlow 估计器的新功能。 TensorFlow 估计器使创建神经网络模型的任务变得更加容易,它是一个高级 API,封装了训练,评估,预测和服务的过程。 它提供了使用预建估计器的选项,也可以编写自己的自定义估计器。 有了预建的估计器,就不再需要担心构建计算或创建会话,它就可以处理所有这些。

目前,TensorFlow 估计器有六个预建的估计器。 使用 TensorFlow 预建的估计器的另一个优势是,它本身也可以创建可在 TensorBoard 上可视化的摘要。 有关估计器的更多详细信息,请访问这里

基于 DNN 的问题所需的不同 Python 包

TensorFlow 负责大多数神经网络的实现。 但是,这还不够。 对于预处理任务,序列化甚至是绘图,我们需要更多的 Python 包。

操作步骤

以下列出了一些常用的 Python 包:

  1. Numpy:这是使用 Python 进行科学计算的基本包。 它支持 n 维数组和矩阵。 它还具有大量的高级数学函数。 它是 TensorFlow 所需的必需包,因此,如果尚未安装pip install tensorflow,则将其安装。
  2. Matplolib:这是 Python 2D 绘图库。 只需几行代码,您就可以使用它来创建图表,直方图,条形图,误差图,散点图和功率谱。 可以使用pip:安装
pip install matplotlib 
# or using Anaconda    
conda install -c conda-forge matplotlib
  1. OS:这是基本 Python 安装中包含的内容。 它提供了一种使用与操作系统相关的函数(如读取,写入和更改文件和目录)的简便方法。

  2. Pandas:这提供了各种数据结构和数据分析工具。 使用 Pandas,您可以在内存数据结构和不同格式之间读取和写入数据。 我们可以读取.csv和文本文件。 可以使用pip installconda install进行安装。

  3. Seaborn:这是基于 Matplotlib 构建的专门统计数据可视化工具。

  4. H5fs:H5fs​​是适用于 Linux 的文件系统(也是具有 FUSE 实现的其他操作系统,例如 MacOSX),可以在 HDFS分层数据格式文件系统)上运行 。

  5. PythonMagick:它是ImageMagick库的 Python 绑定。 它是显示,转换和编辑光栅图像和向量图像文件的库。 它支持 200 多种图像文件格式。 可以使用ImageMagick.提供的源代码版本进行安装。某些.whl格式也可用于方便的pip install

  6. TFlearn:TFlearn 是建立在 TensorFlow 之上的模块化透明的深度学习库。 它为 TensorFlow 提供了更高级别的 API,以促进并加速实验。 它目前支持大多数最新的深度学习模型,例如卷积,LSTM,BatchNorm,BiRNN,PReLU,残差网络和生成网络。 它仅适用于 TensorFlow 1.0 或更高版本。 要安装,请使用pip install tflearn

  7. Keras:Keras 也是神经网络的高级 API,它使用 TensorFlow 作为其后端。 它也可以在 Theano 和 CNTK 之上运行。 这是非常用户友好的,添加层只是一项工作。 可以使用pip install keras安装。

另见

您可以在下面找到一些 Web 链接以获取有关 TensorFlow 安装的更多信息

二、回归

本章说明如何使用 TensorFlow 进行回归。 在本章中,我们将介绍以下主题:

  • 选择损失函数
  • TensorFlow 中的优化器
  • 从 CSV 文件读取和预处理数据
  • 房价估计 -- 简单线性回归
  • 房价估计 -- 多元线性回归
  • MNIST 数据集上的逻辑回归

介绍

回归是用于数学建模,分类和预测的最古老但功能非常强大的工具之一。 回归在工程,物理科学,生物学,金融市场到社会科学等各个领域都有应用。 它是数据科学家手中的基本工具。

回归通常是机器学习中人们使用的第一个算法。 它使我们能够通过学习因变量和自变量之间的关系来根据数据进行预测。 例如,在房价估计的情况下,我们确定房屋面积(自变量)与其价格(因变量)之间的关系; 然后,可以使用这种关系来预测给定房屋面积的任何房屋的价格。 我们可以有多个影响因变量的自变量。 因此,回归有两个重要的组成部分:自变量和因变量之间的关系,以及不同自变量对因变量的影响强度

有多种可用的回归方法:

  • 线性回归:这是最广泛使用的建模技术之一。 它已有 200 多年的历史,几乎从所有可能的角度进行了探索。 线性回归假设输入变量(X)和单个输出变量(Y)之间存在线性关系。 它涉及寻找以下形式的预测值Y的线性方程:

在这里,X = (x[1], x[2], ..., x[n])n个输入变量和W = (w[1], w[2], ... w[n])是线性系数,以b为偏差项。 目标是找到系数W的最佳估计,以使预测Y的误差最小。 线性系数W使用最小二乘法估计,即最小化预测值(Y_hat)和值(Y)之间的平方差之和。因此,我们尝试最小化损失函数:

在这里,总和是所有训练样本的总和。 根据输入变量X的数量和类型,可以使用不同类型的线性回归:简单线性回归(一个输入变量,一个输出变量),多个线性回归(许多独立输入变量,一个输出变量) )或多元线性回归(许多独立的输入变量和多个输出变量)。 有关线性回归的更多信息,请参考这个页面

  • Logistic 回归:用于确定事件的概率。 按照惯例,事件表示为分类因变量。 使用logit函数(sigmoid函数)表示事件的可能性:

现在的目标是估计权重W = (w1, w2, ... wn)和偏差项b。 在逻辑回归中,使用最大似然估计器或随机梯度下降法估计系数。 通常将损耗定义为互熵项,如下所示:

Logistic 回归用于分类问题,例如,给定医学数据,我们可以使用 Logistic 回归对一个人是否患有癌症进行分类。 如果输出分类变量具有两个或多个级别,则可以使用多项逻辑回归。 用于两个或多个输出变量的另一种常用技术是“一对多”。 对于多类逻辑回归,对交叉熵损失函数的修改如下:

在此, K是类别的总数。 有关逻辑回归的更多信息,请参见这个页面

这是两种常用的回归技术。

  • 正则化:当存在大量输入特征时,需要进行正则化以确保预测的模型不复杂。 正则化有助于防止数据过拟合。 它还可以用于获得凸的损失函数。 有两种类型的正则化,L1 和 L2 正则化,在以下几点中进行了描述:

在希腊字母上方,lambda(λ)是正则化参数。

选择损失函数

如前所述,在回归中,我们定义loss函数或目标函数,目的是找到使损失最小的系数。 在本秘籍中,您将学习如何在 TensorFlow 中定义loss函数,并根据眼前的问题选择合适的loss函数。

准备

声明loss函数需要将系数定义为变量,将数据集定义为占位符。 一个人可以具有恒定的学习率或变化的学习率和正则化常数。 在以下代码中,令m为样本数,n为特征数,P为类数。 我们应该在代码之前定义以下全局参数:

m = 1000
n = 15
P = 2

操作步骤

现在让我们看一下如何进行秘籍:

  1. 在标准线性回归的情况下,我们只有一个输入变量和一个输出变量:
# Placeholder for the Training Data
X = tf.placeholder(tf.float32, name='X')
Y = tf.placeholder(tf.float32, name='Y')

# Variables for coefficients initialized to 0
w0 = tf.Variable(0.0)
w1 = tf.Variable(0.0)

# The Linear Regression Model
Y_hat = X*w1 + w0

# Loss function
loss = tf.square(Y - Y_hat, name='loss')
  1. 在多元线性回归的情况下,输入变量大于 1,而输出变量保持为 1。 现在,您可以定义形状为[m, n]X占位符,其中m是样本数,n是特征数,然后代码如下:
# Placeholder for the Training DataX = tf.placeholder(tf.float32, name='X', shape=[m,n])
Y = tf.placeholder(tf.float32, name='Y')

# Variables for coefficients initialized to 0w0 = tf.Variable(0.0)
w1 = tf.Variable(tf.random_normal([n,1]))

# The Linear Regression ModelY_hat = tf.matmul(X, w1) + w0

# Multiple linear regression loss functionloss = tf.reduce_mean(tf.square(Y - Y_hat, name='loss')
  1. 在逻辑回归的情况下,loss函数由交叉熵定义。 现在,输出Y的尺寸将等于训练数据集中的类数。 通过P个类,我们将具有以下内容:
# Placeholder for the Training DataX = tf.placeholder(tf.float32, name='X', shape=[m,n])
Y = tf.placeholder(tf.float32, name='Y', shape=[m,P])

# Variables for coefficients initialized to 0w0 = tf.Variable(tf.zeros([1,P]), name=’bias’)
w1 = tf.Variable(tf.random_normal([n,1]), name=’weights’)
# The Linear Regression ModelY_hat = tf.matmul(X, w1) + w0

# Loss functionentropy = tf.nn.softmax_cross_entropy_with_logits(Y_hat,Y)
loss = tf.reduce_mean(entropy)
  1. 如果我们要对损失添加 L1 正则化,则代码如下:
lamda = tf.constant(0.8)  # regularization parameter
regularization_param = lamda*tf.reduce_sum(tf.abs(W1))

# New loss
loss += regularization_param
  1. 对于 L2 正则化,我们可以使用以下代码:
lamda = tf.constant(0.8)  # regularization parameter
regularization_param = lamda*tf.nn.l2_loss(W1)

# New loss
loss += regularization_param

工作原理

您学习了如何实现不同类型的loss函数。 根据手头的回归任务,您可以选择相应的loss函数或自行设计。 也可以在损耗项中组合 L1 和 L2 正则化。

更多

loss函数应为凸形以确保收敛。 平滑,可微凸的loss函数可提供更好的收敛性。 随着学习的进行,loss函数的值应减小并最终变得稳定。

TensorFlow 中的优化器

从中学数学开始,您必须知道函数的一阶导数的最大值和最小值为零。 梯度下降算法基于相同的原理-调整系数(权重和偏差),以使loss函数的梯度减小。 在回归中,我们使用梯度下降来优化loss函数并获得系数。 在本秘籍中,您将学习如何使用 TensorFlow 的梯度下降优化器及其某些变体。

准备

系数(Wb)的更新与loss函数的梯度的负值成比例地完成。 根据训练样本的大小,梯度下降有三种变化:

  • 普通梯度下降:在普通梯度下降(有时也称为全量梯度下降)中,为每个周期的整个训练集计算loss函数的梯度。 对于非常大的数据集,此过程可能很慢且难以处理。 对于凸loss函数,可以保证收敛到全局最小值,但是对于非凸loss函数,可以收敛到局部最小值。
  • 随机梯度下降:在随机梯度下降中,一次显示一个训练样本,权重和偏差得到更新,以使loss函数的梯度减小,然后我们移至下一个训练样本 。 重复整个过程许多周期。 由于它一次执行一次更新,因此它比普通更新要快,但是同时,由于频繁更新,loss函数可能会有很大差异。
  • 小批量梯度下降:结合了前两种产品的最佳质量; 在这里,为一批训练样本更新了参数。

操作步骤

我们按以下步骤进行:

  1. 我们决定的第一件事是我们想要的优化器。 TensorFlow 为您提供了各种各样的优化器。 我们从最流行,最简单的梯度下降优化器开始:
tf.train.GradientDescentOptimizer(learning_rate)
  1. GradientDescentOptimizerlearning_rate参数可以是常数或张量。 其值可以在 0 到 1 之间。
  2. 必须告知优化器要优化的函数。 这是使用其方法来完成的,最小化。 该方法计算梯度并将梯度应用于学习系数。 TensorFlow 文档中定义的函数如下:
minimize(
     loss,
     global_step=None,
     var_list=None,
     gate_gradients=GATE_OP,
     aggregation_method=None,
     colocate_gradients_with_ops=False,
     name=None,
     grad_loss=None
 )
  1. 结合所有这些,我们定义计算图:
 ...
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
train_step = optimizer.minimize(loss)
...

#Execution Graph
with tf.Session() as sess:
    ...
    sess.run(train_step, feed_dict = {X:X_data, Y:Y_data})
    ...
  1. 馈送到feed_dictXY数据可以是单个XY点(随机梯度),整个训练集(普通)或批次。

  2. 梯度下降的另一个变化是增加了动量项(我们将在第 3 章“神经网络感知机”中找到更多相关信息)。 为此,我们使用优化器tf.train.MomentumOptimizer()。 它同时将learning_ratemomentum作为init参数:

optimizer = tf.train.MomentumOtimizer(learning_rate=0.01, momentum=0.5).minimize(loss)
  1. 如果使用tf.train.AdadeltaOptimizer(),则可以自适应地单调降低学习率,它使用两个init自变量learning_rate和衰减因子rho
optimizer = tf.train.AdadeltaOptimizer(learning_rate=0.8, rho=0.95).minimize(loss)
  1. TensorFlow 还支持 Hinton 的 RMSprop,其工作方式类似于 Adadelta -- tf.train.RMSpropOptimizer()
optimizer = tf.train.RMSpropOptimizer(learning_rate=0.01, decay=0.8, momentum=0.1).minimize(loss)

Adadelta 和 RMSprop 之间有一些细微的差异。 要了解有关它们的更多信息,可以参考这里这里

  1. TensorFlow 支持的另一种流行的优化器是 Adam 优化器。 该方法使用第一个和第二个梯度矩的估计来计算不同系数的个体自适应学习率:
 optimizer = tf.train.AdamOptimizer().minimize(loss)
  1. 除了这些,TensorFlow 还提供以下优化器:
tf.train.AdagradOptimizer  #Adagrad Optimizer
tf.train.AdagradDAOptimizer #Adagrad Dual Averaging optimizer 
tf.train.FtrlOptimizer #Follow the regularized leader optimizer
tf.train.ProximalGradientDescentOptimizer #Proximal GD optimizer
tf.train.ProximalAdagradOptimizer # Proximal Adagrad optimizer

更多

通常建议您从较高的学习率值入手,并随着学习的进行逐渐降低。 这有助于对训练进行微调。 我们可以使用 TensorFlow tf.train.exponential_decay方法来实现。 根据 TensorFlow 文档:

训练模型时,通常建议随着训练的进行降低学习率。 此函数将指数衰减函数应用于提供的初始学习率。 它需要一个global_step值来计算衰减的学习率。 您可以只传递一个 TensorFlow 变量,该变量在每个训练步骤中都会递增。该函数返回递减的学习率。

参数:

  • learning_rate: float32float64标量张量或 Python 数字。 初始学习率。
  • global_step: float32float64标量张量或 Python 数字。 用于衰减计算的全局步长。 不能为负。
  • decay_steps: float32float64标量张量或 Python 数字。 必须是正的。 请参阅前面介绍的衰减计算。
  • decay_rate: float32float64标量张量或 Python 数字。 衰减率。
  • staircase: 布尔值。 如果True,在离散时间间隔衰减学习率。
  • name: 字符串。 操作的可选名称。 默认为'ExponentialDecay'

返回值:

learning_rate类型相同的标量张量。 学习率衰减。

要实现指数衰减的学习率,请考虑以下代码示例:

global_step = tf.Variable(0, trainable = false)
initial_learning_rate = 0.2
learning_rate = tf.train.exponential_decay(initial_learning_rate, global_step, decay_steps=100000, decay_rate=0.95, staircase=True)
# Pass this learning rate to optimizer as before.

另见

以下是一些针对不同优化器的良好链接:

从 CSV 文件读取和预处理数据

你们大多数人已经熟悉 Pandas 及其在处理大型数据集文件中的实用性。 TensorFlow 还提供了读取文件的方法。 在第一章中,我们介绍了从 TensorFlow 中读取文件的方法。 在本秘籍中,我们将重点介绍如何在训练之前从 CSV 文件读取和预处理数据。

准备

我们将考虑 Harrison 和 Rubinfield 在 1978 年收集的波士顿住房价格数据集。该数据集包含 506 个样本案例。 每个房屋都有 14 个属性:

  • CRIM:按城镇划分的人均犯罪率
  • ZN:已划定 25,000 平方英尺以上土地的居住用地比例
  • INDIA:每个城镇的非零售营业面积比例
  • CHAS:查尔斯河虚拟变量(如果束缚河,则为 1;否则为 0)
  • NOX:一氧化氮浓度(百万分之几)
  • RM:每个住宅的平均房间数
  • AGE:1940 年之前建造的自有住房的比例
  • DIS:到五个波士顿就业中心的加权距离
  • RAD:径向公路的可达性指数
  • TAX:每 10,000 美元的全值财产税率
  • PTRATIO:按城镇划分的师生比率
  • B1000(Bk-0.63)^2,其中Bk是按城镇划分的黑人比例
  • LSTAT:人口状况降低的百分比
  • MEDV:自有住房的中位数价值,单位为 1,000 美元

操作步骤

我们按以下步骤进行:

  1. 导入所需的模块并声明全局变量:
import tensorflow as tf

# Global parameters 
DATA_FILE = 'boston_housing.csv' BATCH_SIZE = 10
NUM_FEATURES = 14
  1. 接下来,我们定义一个函数,该函数将文件名作为参数,并以等于BATCH_SIZE的大小批量返回张量:
defdata_generator(filename):
    """
    Generates Tensors in batches of size Batch_SIZE.
    Args: String Tensor
    Filename from which data is to be read
    Returns: Tensors
    feature_batch and label_batch
    """
  1. 定义文件名f_queuereader
f_queue = tf.train.string_input_producer(filename)
reader = tf.TextLineReader(skip_header_lines=1) 
# Skips the first line
_, value = reader.read(f_queue)
  1. 我们指定了数据缺失时要使用的数据。 解码.csv并选择我们需要的特征。 对于示例,我们选择RMPTRATIOLSTAT
record_defaults = [ [0.0] for _ in range(NUM_FEATURES)]
data = tf.decode_csv(value, record_defaults=record_defaults)
features = tf.stack(tf.gather_nd(data,[[5],[10],[12]]))
label = data[-1]
  1. 定义参数以生成批量,并使用tf.train.shuffle_batch()随机调整张量。 函数返回张量-feature_batchlabel_batch
# minimum number elements in the queue after a dequeuemin_after_dequeue = 10 * BATCH_SIZE

# the maximum number of elements in the queue capacity = 20 * BATCH_SIZE

# shuffle the data to generate BATCH_SIZE sample pairs feature_batch, label_batch = tf.train.shuffle_batch([features, label], batch_size=BATCH_SIZE,
                                                 capacity=capacity, min_after_dequeue=min_after_dequeue)

return feature_batch, label_batch
  1. 我们定义了另一个函数来在会话中生成批量:
def generate_data(feature_batch, label_batch):
    with tf.Session() as sess:
        # intialize the queue threads
        coord = tf.train.Coordinator()
        threads = tf.train.start_queue_runners(coord=coord)
        for _ in range(5): 
            # Generate 5 batches
            features, labels = sess.run([feature_batch, label_batch])
            print (features, "HI")
        coord.request_stop()
        coord.join(threads)
  1. 现在,我们可以使用这两个函数来批量获取数据。 在这里,我们只是打印数据。 学习时,我们将在此时执行优化步骤:
if __name__ =='__main__':
    feature_batch, label_batch = data_generator([DATA_FILE])
    generate_data(feature_batch, label_batch)

更多

我们可以使用第一章中介绍的 TensorFlow 控件操作和张量操作来预处理数据。 例如,在波士顿房价的情况下,大约有 16 个数据行,其中MEDV50.0。 这些数据点最有可能包含缺失或审查的值,建议不要在训练中考虑它们。 我们可以使用以下代码将它们从训练数据集中删除:

condition = tf.equal(data[13], tf.constant(50.0))
data = tf.where(condition, tf.zeros(NUM_FEATURES), data[:])

在这里,我们首先定义一个张量布尔条件,如果MEDV等于50.0,则为真。 然后,如果条件为真,则使用 TensorFlow tf.where()操作分配全零。

房价估计 -- 简单线性回归

在此秘籍中,我们将基于波士顿房价数据集上的房间数(RM)执行简单的线性回归。

准备

我们的目标是预测最后一栏(MEDV)中给出的房价。 在此秘籍中,我们直接从 TensorFlow Contrib 数据集中加载数据集。 我们使用随机梯度下降优化器优化单个训练样本的系数。

操作步骤

我们按以下步骤进行:

  1. 第一步是导入我们需要的所有包:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
  1. 在神经网络中,所有输入都线性相加以产生活动。 为了进行有效的训练,应该对输入进行标准化,因此我们定义了对输入数据进行标准化的函数:
def normalize(X):
    """ Normalizes the array X"""
    mean = np.mean(X)
    std = np.std(X)
    X = (X - mean)/std
    return X
  1. 现在,我们使用 TensorFlow contrib数据集加载波士顿房价数据集,并将其分为X_trainY_train。 我们可以选择在此处标准化数据:
# Data
boston = tf.contrib.learn.datasets.load_dataset('boston')
X_train, Y_train = boston.data[:,5], boston.target
#X_train = normalize(X_train)  # This step is optional here
n_samples = len(X_train)
  1. 我们为训练数据声明 TensorFlow 占位符:
# Placeholder for the Training Data
X = tf.placeholder(tf.float32, name='X')
Y = tf.placeholder(tf.float32, name='Y')
  1. 我们为权重和偏差创建 TensorFlow 变量,初始值为零:
# Variables for coefficients initialized to 0
b = tf.Variable(0.0)
w = tf.Variable(0.0)
  1. 我们定义了用于预测的线性回归模型:
# The Linear Regression Model
Y_hat = X * w + b
  1. 定义loss函数:
# Loss function
loss = tf.square(Y - Y_hat, name='loss')
  1. 我们选择梯度下降优化器:
# Gradient Descent with learning rate of 0.01 to minimize loss
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01).minimize(loss)
  1. 声明初始化操作:
# Initializing Variables
init_op = tf.global_variables_initializer()
total = []
  1. 现在,我们开始计算图。 我们进行了 100 个周期的训练:
# Computation Graph
with tf.Session() as sess:
    # Initialize variables
    sess.run(init_op)
    writer = tf.summary.FileWriter('graphs', sess.graph)
    # train the model for 100 epochs
    for i in range(100):
        total_loss = 0
        for x,y in zip(X_train,Y_train):
            _, l = sess.run ([optimizer, loss],            feed_dict={X:x, Y:y})
            total_loss += l
        total.append(total_loss / n_samples)
        print('Epoch {0}: Loss {1}'.format(i, total_loss/n_samples))
     writer.close()
     b_value, w_value = sess.run([b,w])
  1. 查看结果:
Y_pred = X_train * w_value + b_value
print('Done')
# Plot the result
plt.plot(X_train, Y_train, 'bo', label='Real Data')
plt.plot(X_train,Y_pred,  'r', label='Predicted Data')
plt.legend()
plt.show()
plt.plot(total)
plt.show()

工作原理

从图中可以看出,我们的简单线性回归器试图将线性线拟合到给定的数据集:

在下图中,我们可以看到,随着我们的模型学习到数据,loss函数如预期的那样下降:

以下是我们的简单线性回归器的 TensorBoard 图:

该图具有两个名称作用域节点,即VariableVariable_1,它们是分别表示偏差和权重的高级节点。 名为gradient的节点也是高级节点。 扩展节点,我们可以看到它接受了七个输入并计算了gradient,然后GradientDescentOptimizer使用了这些梯度来计算权重和偏差并应用更新:

更多

好吧,我们执行了简单的线性回归,但是如何找出模型的表现呢? 有多种方法可以做到这一点。 从统计上讲,我们可以计算 R 方或将我们的数据分为训练和交叉验证集,并检查验证集的准确率(损失项)。

房价估计 -- 多元线性回归

我们可以通过对权重和占位符的声明进行一些修改来对同一数据进行多元线性回归。 在多元线性回归的情况下,由于每个特征都有不同的值范围,因此规范化必不可少。 这是使用所有 13 种输入特征对波士顿房价数据集进行多元线性回归的代码。

操作步骤

这是我们进行秘籍的方法:

  1. 第一步是导入我们需要的所有包:
import tensorflow as tf
 import numpy as np
 import matplotlib.pyplot as plt
  1. 由于所有特征的数据范围都不同,因此我们需要对特征数据进行规范化。 我们为其定义了归一化函数。 同样,在这里,我们通过添加总是固定为一个值的另一输入来将偏差与权重相结合。 为此,我们定义函数append_bias_reshape()。 有时会使用此技术来简化编程:
def normalize(X)
    """ Normalizes the array X """
    mean = np.mean(X)
    std = np.std(X)
    X = (X - mean)/std
    return X

def append_bias_reshape(features,labels):
    m = features.shape[0]
    n = features.shape[1]
    x = np.reshape(np.c_[np.ones(m),features],[m,n + 1])
    y = np.reshape(labels,[m,1])
    return x, y
  1. 现在,我们使用 TensorFlow contrib 数据集加载波士顿房价数据集,并将其分为X_trainY_train。 观察到这次X_train包含所有特征。 我们可以在此处选择对数据进行规范化,也可以使用附加偏差并为网络重塑数据:
# Data
boston = tf.contrib.learn.datasets.load_dataset('boston')
X_train, Y_train = boston.data, boston.target
X_train = normalize(X_train)
X_train, Y_train = append_bias_reshape(X_train, Y_train)
m = len(X_train)  
#Number of training examples
n = 13 + 1   
# Number of features + bias
  1. 声明 TensorFlow 占位符以获取训练数据。 观察X占位符形状的变化。
# Placeholder for the Training Data
X = tf.placeholder(tf.float32, name='X', shape=[m,n])
Y = tf.placeholder(tf.float32, name='Y')
  1. 我们为权重和偏差创建 TensorFlow 变量。 这次,权重用随机数初始化:
# Variables for coefficients
w = tf.Variable(tf.random_normal([n,1]))
  1. 定义要用于预测的线性回归模型。 现在我们需要矩阵乘法来完成任务:
# The Linear Regression Model
Y_hat = tf.matmul(X, w) 
  1. 为了更好的区分,我们定义loss函数:
# Loss function
loss = tf.reduce_mean(tf.square(Y - Y_hat, name='loss'))
  1. 选择合适的优化器:
# Gradient Descent with learning rate of 0.01 to minimize loss
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01).minimize(loss)
  1. 定义初始化操作:
# Initializing Variables
init_op = tf.global_variables_initializer()
total = []
  1. 启动计算图:
with tf.Session() as sess:
    # Initialize variables
    sess.run(init_op)
    writer = tf.summary.FileWriter('graphs', sess.graph)
    # train the model for 100 epcohs
    for i in range(100):
       _, l = sess.run([optimizer, loss], feed_dict={X: X_train, Y: Y_train})
       total.append(l)
       print('Epoch {0}: Loss {1}'.format(i, l))
     writer.close()
     w_value, b_value = sess.run([w, b])
  1. 绘制loss函数:
plt.plot(total)
plt.show()

同样在这里,我们发现损失随着训练的进行而减少:

工作原理

在此秘籍中,我们使用了所有 13 个特征来训练模型。 简单线性回归和多元线性回归之间的重要区别在于权重,系数的数量始终等于输入特征的数量。 以下是我们构建的多元线性回归模型的 TensorBoard 图:

更多

我们现在可以使用从模型中学到的系数来预测房价:

N= 500
X_new = X_train [N,:]
Y_pred =  (np.matmul(X_new, w_value) + b_value).round(1)
print('Predicted value: ${0}  Actual value: / ${1}'.format(Y_pred[0]*1000, Y_train[N]*1000) , '\nDone')

MNIST 数据集上的逻辑回归

此秘籍基于这个页面提供的 MNIST 的逻辑回归,但我们将添加一些 TensorBoard 摘要以更好地理解它。 你们大多数人必须已经熟悉 MNIST 数据集-就像机器学习的 ABC 一样。 它包含手写数字的图像和每个图像的标签,说明它是哪个数字。

对于逻辑回归,我们对输出 Y 使用一热编码。因此,我们有 10 位代表输出; 每个位可以具有 0 或 1 的值,并且为 1 热点意味着对于标签 Y 中的每个图像,10 个位中只有 1 个位的值为 1,其余为 0。 在这里,您可以看到手写数字 8 的图像及其热编码值[0 0 0 0 0 0 0 0 0 1 0]

操作步骤

这是我们进行秘籍的方法:

  1. 与往常一样,第一步是导入所需的模块:
import tensorflow as tf
import matplotlib.pyplot as plt,  matplotlib.image as mpimg
  1. 我们从模块input_data中给出的 TensorFlow 示例中获取 MNIST 的输入数据。 one_hot标志设置为True以启用标签的one_hot编码。 这导致生成两个张量,形状为[55000, 784]mnist.train.images和形状为[55000, 10]mnist.train.labelsmnist.train.images的每个条目都是像素强度,其值在 0 到 1 之间:
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
  1. 为训练数据集输入x创建占位符,并在 TensorFlow 图上标记y
x = tf.placeholder(tf.float32, [None, 784], name='X')
y = tf.placeholder(tf.float32, [None, 10],name='Y')
  1. 创建学习变量​​,权重和偏置:
W = tf.Variable(tf.zeros([784, 10]), name='W')
b = tf.Variable(tf.zeros([10]), name='b')
  1. 创建逻辑回归模型。 TensorFlow OP 被赋予name_scope("wx_b")
with tf.name_scope("wx_b") as scope:
    y_hat = tf.nn.softmax(tf.matmul(x,W) + b)
  1. 添加摘要 OP,以在训练时收集数据。 我们使用直方图摘要,以便我们可以看到权重和偏差随时间相对于彼此的值如何变化。 我们将可以在 TensorBoard 直方图选项卡中看到以下内容:
w_h = tf.summary.histogram("weights", W)
b_h = tf.summary.histogram("biases", b)
  1. 定义cross-entropyloss函数,并添加名称范围和摘要以更好地可视化。 在这里,我们使用标量汇总来获取loss函数随时间的变化。 标量摘要在“事件”选项卡下可见:
with tf.name_scope('cross-entropy') as scope:
    loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y, logits=y_hat)
    tf.summary.scalar('cross-entropy', loss)
  1. 使用具有学习率0.01的 TensorFlow GradientDescentOptimizer。 再次,为了更好地可视化,我们定义了name_scope
with tf.name_scope('Train') as scope:
    optimizer = tf.train.GradientDescentOptimizer(0.01).minimize(loss)
  1. 声明变量的初始化操作:
# Initializing the variables
init = tf.global_variables_initializer()
  1. 我们结合了所有汇总操作:
merged_summary_op = tf.summary.merge_all()
  1. 现在,我们定义会话并将摘要存储在定义的文件夹中:
with tf.Session() as sess:
    sess.run(init)  # initialize all variables
    summary_writer = tf.summary.FileWriter('graphs', sess.graph)  # Create an event file
    # Training
    for epoch in range(max_epochs):
        loss_avg = 0
        num_of_batch = int(mnist.train.num_examples/batch_size)
        for i in range(num_of_batch):
            batch_xs, batch_ys = mnist.train.next_batch(100)  # get the next batch of data
            _, l, summary_str = sess.run([optimizer,loss, merged_summary_op], feed_dict={x: batch_xs, y: batch_ys})  # Run the optimizer
           loss_avg += l
           summary_writer.add_summary(summary_str, epoch*num_of_batch + i)  # Add all summaries per batch
        loss_avg = loss_avg/num_of_batch
        print('Epoch {0}: Loss {1}'.format(epoch, loss_avg))
    print('Done')
    print(sess.run(accuracy, feed_dict={x: mnist.test.images,y: mnist.test.labels}))
  1. 在 30 个周期后,我们的准确率为 86.5%,在 50 个周期后为 89.36%,在 100 个周期后,准确率提高到 90.91%。

工作原理

我们使用张量tensorboard --logdir=garphs启动 TensorBoard。 在浏览器中,导航到网址localhost:6006,以查看 TensorBoard。 先前模型的图形如下:

在“直方图”标签下,我们可以看到权重偏置的直方图:

权重偏置的分布如下:

我们可以看到,随着时间的推移,偏差和权重都发生了变化。 从我们的案例来看,偏差更大,从 TensorBoard 中的分布可以看出。

在“事件”选项卡下,我们可以看到标量摘要,在这种情况下为交叉熵。 下图显示交叉熵损失随时间减少:

另见

如果您有兴趣了解更多信息,这些是一些很好的资源:

三、神经网络:感知机

自最近十年以来,神经网络一直处于机器学习研究和应用的最前沿。 深层神经网络DNN),传递学习以及计算效率高的 GPU 的可用性已帮助在图像识别,语音识别甚至文本生成领域取得了重大进展。 在本章中,我们将专注于基本的神经网络感知机,即人工神经元的完全连接的分层架构。 本章将包括以下秘籍:

  • 激活函数
  • 单层感知机
  • 反向传播算法的梯度计算
  • 使用 MLP 的 MNIST 分类器
  • 使用 MLP 进行函数逼近-预测波士顿房价
  • 调整超参数
  • 更高级别的 API -- Keras

介绍

神经网络,通常也称为连接器模型,是受人脑启发的。 像人的大脑一样,神经网络是通过称为权重的突触强度相互连接的大量人工神经元的集合。 正如我们通过长辈提供给我们的示例进行学习一样,人工神经网络也可以通过作为训练数据集提供给他们的示例进行学习。 有了足够数量的训练数据集,人工神经网络可以概括信息,然后也可以将其用于看不见的数据。 太棒了,它们听起来像魔术!

神经网络并不是什么新鲜事物。 第一个神经网络模型由 McCulloch Pitts(MCP)最早在 1943 年提出。 建造了第一台计算机!)该模型可以执行 AND/OR/NOT 之类的逻辑运算。 MCP 模型具有固定的权重和偏差; 没有学习的可能。 几年后,Frank Rosenblatt 在 1958 年解决了这个问题。 他提出了第一个学习神经网络,称为感知机

从那时起,众所周知,添加多层神经元并建立一个深而密集的网络将有助于神经网络解决复杂的任务。 正如母亲为孩子的成就感到自豪一样,科学家和工程师对使用神经网络NN。 这些声明不是虚假的,但是由于硬件计算的限制和复杂的网络结构,当时根本不可能实现它们。 这导致了 1970 年代和 1980 年代的 AI 寒冬。 在这些寒战中,由于很少或几乎没有对基于 AI 的项目提供资金,因此该领域的进展放缓了。

随着 DNN 和 GPU 的出现,情况发生了变化。 今天,我们拥有的网络可以在较少的调整参数的情况下实现更好的表现,诸如丢弃和迁移学习之类的技术可以进一步减少训练时间,最后,硬件公司正在提出专门的硬件芯片来执行基于 NN 的快速计算。

人工神经元是所有神经网络的核心。 它由两个主要部分组成-加法器(对加权后的神经元的所有输入求和),以及处理单元,对加权后的总和进行加权,并基于称为激活函数的预定义函数生成输出。 。 每个人工神经元都有其自己的一组权重和阈值(偏差)。 它通过不同的学习算法来学习这些权重和阈值:

来源

当仅存在此类神经元的一层时,它称为感知机。 输入层称为第零层,因为它仅缓冲输入。 存在的唯一神经元层形成输出层。 输出层的每个神经元都有自己的权重和阈值。 当存在许多这样的层时,该网络称为多层感知机MLP)。 一个 MLP 具有一个或多个隐藏层。 这些隐藏层具有不同数量的隐藏神经元。 每个隐藏层的神经元具有相同的激活函数:

上图显示了一个 MLP,它具有四个输入,五个隐藏层,每个隐藏层分别具有 4、5、6、4 和 3 个神经元,而在输出层中具有三个神经元。 在 MLP 中,下层的所有神经元都与其上一层的所有神经元相连。 因此,MLP 也称为全连接层。 MLP 中的信息流始终是从输入到输出。 由于没有反馈或跳跃,因此这些网络也称为前馈网络

使用梯度下降算法训练感知机。 在第 2 章“回归”中,您了解了梯度下降; 在这里,我们对其进行更深入的研究。 感知机通过有监督的学习算法进行学习,也就是说,网络由训练数据集中存在的所有输入的期望输出提供。 在输出中,我们定义一个误差函数或目标函数J(W),这样,当网络完全学习了所有训练数据时,目标函数将最小。

更新输出层和隐藏层的权重,以使目标函数的梯度减小:

为了更好地理解它,请对山丘,高原和坑坑洼洼的景观进行可视化处理。 目的是扎根(目标函数的全局最小值)。 如果您站在山顶上而必须下山,那么很明显的选择是,您将沿着山下坡,即向负坡度(或负坡度)移动。 以相同的方式,感知机中的权重与目标函数的梯度的负值成比例地变化。

梯度值越高,权重值的变化越大,反之亦然。 现在,这一切都很好,但是当梯度达到零,因此权重没有变化时,我们到达高原时就会遇到问题。 当我们进入一个小坑(局部极小值)时,我们也可能遇到问题,因为当我们尝试移动到任一侧时,坡度将增加,从而迫使网络停留在坑中。

如第 2 章,“回归”中所讨论的,梯度下降有多种变体,旨在提高收敛性,避免了陷入局部极小值或高原的问题(增加动量,可变学习率)。

TensorFlow 借助不同的优化器自动计算这些梯度。 但是,需要注意的重要一点是,由于 TensorFlow 将计算梯度,而梯度也将涉及激活函数的导数,因此重要的是,您选择的激活函数是可微的,并且在整个训练场景中最好具有非零梯度 。

感知机梯度下降的主要方法之一不同于第 2 章,“回归”,应用是为输出层定义目标函数,但可用于查找目标层,以及隐藏层的神经元的权重变化。 这是使用反向传播BPN)算法完成的,其中输出端的误差会向后传播到隐藏层,并用于确定权重变化。 您将很快了解更多信息。

激活函数

每个神经元必须具有激活函数。 它们使神经元具有建模复杂非线性数据集所需的非线性特性。 该函数获取所有输入的加权和,并生成一个输出信号。 您可以将其视为输入和输出之间的转换。 使用适当的激活函数,我们可以将输出值限制在定义的范围内。

如果xⱼ是第j个输入,则Wⱼ的第j行输入到我们的神经元,并且b是我们神经元的偏置,即神经元的输出(从生物学的角度来说,是神经元的发射) 通过激活函数,在数学上表示为:

在此, g表示激活函数。 激活函数∑(W[j]x[j]) + b的参数称为神经元的激活

准备

我们对给定输入刺激的反应受神经元激活函数的控制。 有时我们的回答是二进制的是或否。 例如,当开个玩笑时,我们要么笑要么不笑。 在其他时间,响应似乎是线性的,例如由于疼痛而哭泣。 有时,响应似乎在一定范围内。

模仿类似的行为,人工神经元使用了许多不同的激活函数。 在本秘籍中,您将学习如何在 TensorFlow 中定义和使用一些常见的激活函数。

操作步骤

我们继续执行激活函数,如下所示:

  1. 阈值激活函数:这是最简单的激活函数。 在此,如果神经元的活动性大于零,则神经元会触发;否则,神经元会触发。 否则,它不会触发。 这是阈值激活函数随神经元活动变化而变化的图,以及在 TensorFlow 中实现阈值激活函数的代码:
import tensorflow as tf 
import numpy as np 
import matplotlib.pyplot as plt 

# Threshold Activation function 
def threshold (x):  
    cond = tf.less(x, tf.zeros(tf.shape(x), dtype = x.dtype))  
    out = tf.where(cond, tf.zeros(tf.shape(x)), tf.ones(tf.shape(x)))  
    return out 
# Plotting Threshold Activation Function 
h = np.linspace(-1,1,50) 
out = threshold(h) 
init = tf.global_variables_initializer()
with tf.Session() as sess:  
     sess.run(init)  
     y = sess.run(out) 
     plt.xlabel('Activity of Neuron') 
     plt.ylabel('Output of Neuron') 
     plt.title('Threshold Activation Function') 
     plt.plot(h, y)

以下是上述代码的输出:

  1. Sigmoid 激活函数:在这种情况下,神经元的输出由函数g(x) = 1 / (1 + exp(-x))指定。 在 TensorFlow 中,有一种方法tf.sigmoid,它提供了 Sigmoid 激活。 此函数的范围在 0 到 1 之间。形状上看起来像字母 S ,因此名称为 Sigmoid:
# Plotting Sigmoidal Activation function 
h = np.linspace(-10,10,50) 
out = tf.sigmoid(h) 
init = tf.global_variables_initializer()
with tf.Session() as sess:  
    sess.run(init)  
    y = sess.run(out) 
    plt.xlabel('Activity of Neuron') 
    plt.ylabel('Output of Neuron') 
    plt.title('Sigmoidal Activation Function') 
    plt.plot(h, y)

以下是以下代码的输出:

  1. 双曲正切激活函数:在数学上,它是(1 - exp(-2x) / (1 + exp(-2x))。在形状上,它类似于 Sigmoid 函数,但是它以 0 为中心,范围为 -1 至 1。TensorFlow 具有内置函数tf.tanh,用于双曲正切激活函数:
# Plotting Hyperbolic Tangent Activation function 
h = np.linspace(-10,10,50) 
out = tf.tanh(h) 
init = tf.global_variables_initializer()
with tf.Session() as sess:  
    sess.run(init)  
    y = sess.run(out) 
    plt.xlabel('Activity of Neuron') 
    plt.ylabel('Output of Neuron') 
    plt.title('Hyperbolic Tangent Activation Function') 
    plt.plot(h, y)

以下是上述代码的输出:

  1. 线性激活函数:在这种情况下,神经元的输出与神经元的活动相同。 此函数不受任何限制:
# Linear Activation Function
b = tf.Variable(tf.random_normal([1,1], stddev=2))
w = tf.Variable(tf.random_normal([3,1], stddev=2))
linear_out = tf.matmul(X_in, w) + b
init = tf.global_variables_initializer()
with tf.Session() as sess:
    sess.run(init)
    out = sess.run(linear_out)

print(out)

  1. 整流线性单元ReLU)激活函数再次内置在 TensorFlow 库中。 激活函数类似于线性激活函数,但有一个大变化-对于活动的负值,神经元不触发(零输出),对于活动的正值,神经元的输出与给定的活动相同:
# Plotting ReLU Activation function
h = np.linspace(-10,10,50)
out = tf.nn.relu(h)
init = tf.global_variables_initializer()
with tf.Session() as sess:  
    sess.run(init)  
    y = sess.run(out) 
plt.xlabel('Activity of Neuron') 
plt.ylabel('Output of Neuron') 
plt.title('ReLU Activation Function') 
plt.plot(h, y)

以下是 ReLu 激活函数的输出:

  1. Softmax 激活函数是归一化的指数函数。 一个神经元的输出不仅取决于其自身的活动,还取决于该层中存在的所有其他神经元的活动总和。 这样的一个优点是,它使神经元的输出保持较小,因此梯度不会爆炸。 数学上,它是y[i] = exp(x[i]) / ∑j exp(x[j])
# Plotting Softmax Activation function 
h = np.linspace(-5,5,50) 
out = tf.nn.softmax(h) 
init = tf.global_variables_initializer()
with tf.Session() as sess:  
    sess.run(init)  
    y = sess.run(out) 
    plt.xlabel('Activity of Neuron') 
    plt.ylabel('Output of Neuron') 
    plt.title('Softmax Activation Function') 
    plt.plot(h, y)

以下是上述代码的输出:

工作原理

以下是函数的说明:

  • 门控激活函数由 McCulloch Pitts Neuron 和初始感知机使用。 它不可微且在x = 0处不连续。 因此,不可能使用此激活函数来使用梯度下降或其变体进行训练。
  • Sigmoid 激活函数曾经非常流行。 如果看曲线,它看起来像是阈值激活函数的连续版本。 它具有消失的梯度问题,即,函数的梯度在两个边缘附近变为零。 这使得训练和优化变得困难。
  • 双曲正切激活函数再次和 Sigmoid 类似,并具有非线性特性。 该函数以零为中心,并且与 Sigmoid 曲线相比具有更陡峭的导数。 像 Sigmoid 一样,这也遭受消失的梯度问题的困扰。
  • 线性激活函数顾名思义是线性的。 该函数从两侧都是无界的[-inf, inf]。 其线性是其主要问题。 线性函数的总和将是线性函数,线性函数的线性函数也将是线性函数。 因此,使用此函数,无法掌握复杂数据集中存在的非线性。
  • ReLU 激活函数是线性激活函数的整流版本,当在多层中使用时,此整流可以捕获非线性。 使用 ReLU 的主要优点之一是它导致稀疏激活。 在任何时候,所有具有负活动的神经元都不会放电。 这使网络在计算方面更轻便。 ReLU 神经元患有垂死的 ReLU 问题,也就是说,不激发的神经元的梯度将变为零,因此将无法进行任何训练并保持静止(死)。 尽管存在这个问题,如今的 ReLU 还是隐藏层最常用的激活函数之一。
  • Softmax 激活函数通常用作输出层的激活函数。 该函数的范围为[0, 1]。 它用于表示多类分类问题中某类的概率。 所有单元的输出总和将始终为 1。

更多

神经网络已用于各种任务。 这些任务可以大致分为两类:函数逼近(回归)和分类。 根据手头的任务,一个激活函数可能会优于另一个。 通常,最好将 ReLU 神经元用于隐藏层。 对于分类任务,softmax 通常是更好的选择,对于回归问题,最好使用 Sigmoid 或双曲正切。

另见

单层感知机

简单的感知机是单层神经网络。 它使用阈值激活函数,并且正如 Marvin Minsky 论文所证明的那样,只能解决线性可分离的问题。 尽管这将单层感知机的应用限制为仅是线性可分离的问题,但看到它学习仍然总是令人惊奇。

准备

由于感知机使用阈值激活函数,因此我们无法使用 TensorFlow 优化器来更新权重。 我们将不得不使用权重更新规则:

这是学习率。 为了简化编程,可以将偏置作为附加权重添加,输入固定为 +1。 然后,前面的等式可用于同时更新权重和偏差。

操作步骤

这是我们处理单层感知机的方法:

  1. 导入所需的模块:
import tensorflow as tf 
import numpy as np 

  1. 定义要使用的超参数:
# Hyper parameters 
eta = 0.4  # learning rate parameter 
epsilon = 1e-03 # minimum accepted error 
max_epochs = 100 # Maximum Epochs 

  1. 定义threshold函数:
# Threshold Activation function 
def threshold (x): 
    cond = tf.less(x, tf.zeros(tf.shape(x), dtype = x.dtype)) 
    out = tf.where(cond, tf.zeros(tf.shape(x)), tf.ones(tf.shape(x))) 
    return out 
  1. 指定训练数据。 在此示例中,我们采用三个输入神经元(ABC)并对其进行训练以学习逻辑AB + BC
# Training Data  Y = AB + BC, sum of two linear functions. 
T, F = 1., 0\. 
X_in = [ 
    [T, T, T, T], 
    [T, T, F, T], 
    [T, F, T, T], 
    [T, F, F, T], 
    [F, T, T, T], 
    [F, T, F, T], 
    [F, F, T, T], 
    [F, F, F, T], 
    ] 
Y = [ 
    [T], 
    [T], 
    [F], 
    [F], 
    [T], 
    [F], 
    [F], 
    [F] 
] 

  1. 定义要使用的变量,计算图以计算更新,最后执行计算图:
W = tf.Variable(tf.random_normal([4,1], stddev=2, seed = 0)) 
h = tf.matmul(X_in, W)  
Y_hat = threshold(h) 
error = Y - Y_hat 
mean_error = tf.reduce_mean(tf.square(error)) 
dW =  eta * tf.matmul(X_in, error, transpose_a=True) 
train = tf.assign(W, W+dW) 
init = tf.global_variables_initializer() 
err = 1 
epoch = 0 
with tf.Session() as sess: 
    sess.run(init) 
    while err > epsilon and epoch < max_epochs: 
        epoch += 1 
        err, _ = sess.run([mean_error, train]) 
        print('epoch: {0}  mean error: {1}'.format(epoch, err)) 

    print('Training complete') 

以下是上述代码的输出:

更多

如果我们使用 Sigmoid 激活函数代替阈值激活函数,您会怎么办?

你猜对了; 首先,我们可以使用 TensorFlow 优化器来更新权重。 其次,网络的行为类似于逻辑回归器。

反向传播算法的梯度计算

BPN 算法是神经网络中研究最多的算法之一。 它用于将误差从输出层传播到隐藏层的神经元,然后将其用于更新权重。 整个学习可以分为两阶段-前向阶段和后向阶段。

向前传递:输入被馈送到网络,信号从输入层通过隐藏层传播,最后传播到输出层。 在输出层,计算误差和loss函数。

向后传递:在向后传递中,首先为输出层神经元然后为隐藏层神经元计算loss函数的梯度。 然后使用梯度更新权重。

重复两次遍历,直到达到收敛为止。

准备

首先为网络呈现M个训练对(X, Y),并以X作为输入, Y为所需的输出。 输入通过激活函数g(h)从输入传播到隐藏层,直到输出层。 输出Y_hat是网络的输出,误差为Y - Y_hat

loss函数J(W)如下:

在此, i在输出层(1 到 N)的所有神经元上变化。 W[ij]的权重变化,将输出层第i个神经元连接到隐藏层第j个神经元,然后可以使用J(W)的梯度并使用链规则进行区分来确定隐藏层神经元:

此处, Oⱼ是隐藏层神经元的输出, jh表示活动。 这很容易,但是现在我们如何找到W[jk],它连接第n个隐藏层的神经元k和第n+1隐藏层的神经元j?流程是相同的,我们将使用loss函数的梯度和链规则进行微分,但是这次我们将针对W[jk]进行计算:

现在方程式就位了,让我们看看如何在 TensorFlow 中做到这一点。 在本秘籍中,我们使用相同的旧 MNIST 数据集

操作步骤

现在让我们开始学习反向传播算法:

  1. 导入模块:
import tensorflow as tf 
from tensorflow.examples.tutorials.mnist import input_data 
  1. 加载数据集; 我们通过设置one_hot = True使用一键编码标签:
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True) 

  1. 定义超参数和其他常量。 在这里,每个手写数字的大小为28 x 28 = 784像素。 数据集分为 10 类,因为数字可以是 0 到 9 之间的任何数字。这两个是固定的。 学习率,最大周期数,要训练的迷你批次的批次大小以及隐藏层中神经元的数量都是超参数。 可以与他们一起玩耍,看看它们如何影响网络行为:
# Data specific constants
 n_input = 784 # MNIST data input (img shape: 28*28)
 n_classes = 10 # MNIST total classes (0-9 digits)

 # Hyperparameters
 max_epochs = 10000
 learning_rate = 0.5
 batch_size = 10
 seed = 0
 n_hidden = 30  # Number of neurons in the hidden layer 

  1. 我们将需要sigmoid函数的导数进行权重更新,因此我们对其进行定义:
def sigmaprime(x):
     return tf.multiply(tf.sigmoid(x), tf.subtract(tf.constant(1.0), tf.sigmoid(x))) 
  1. 为训练数据创建占位符:
x_in = tf.placeholder(tf.float32, [None, n_input])
y = tf.placeholder(tf.float32, [None, n_classes])
  1. 创建模型:
def multilayer_perceptron(x, weights, biases):
     # Hidden layer with RELU activation
     h_layer_1 = tf.add(tf.matmul(x, weights['h1']), biases['h1'])
     out_layer_1 = tf.sigmoid(h_layer_1)
     # Output layer with linear activation
     h_out = tf.matmul(out_layer_1, weights['out']) + biases['out']
     return tf.sigmoid(h_out), h_out, out_layer_1, h_layer_1 
  1. 定义weightsbiases的变量:
weights = {
     'h1': tf.Variable(tf.random_normal([n_input, n_hidden], seed = seed)),
     'out': tf.Variable(tf.random_normal([n_hidden, n_classes], seed = seed)) }

 biases = {
     'h1': tf.Variable(tf.random_normal([1, n_hidden], seed = seed)),
     'out': tf.Variable(tf.random_normal([1, n_classes], seed = seed))} 
  1. 创建用于向前通过,误差,梯度和更新计算的计算图:
# Forward Pass
 y_hat, h_2, o_1, h_1 = multilayer_perceptron(x_in, weights, biases)

 # Error
 err = y_hat - y

 # Backward Pass
 delta_2 = tf.multiply(err, sigmaprime(h_2))
 delta_w_2 = tf.matmul(tf.transpose(o_1), delta_2)

 wtd_error = tf.matmul(delta_2, tf.transpose(weights['out']))
 delta_1 = tf.multiply(wtd_error, sigmaprime(h_1))
 delta_w_1 = tf.matmul(tf.transpose(x_in), delta_1)

 eta = tf.constant(learning_rate)

 # Update weights
 step = [
     tf.assign(weights['h1'],tf.subtract(weights['h1'], tf.multiply(eta, delta_w_1)))
   , tf.assign(biases['h1'],tf.subtract(biases['h1'], tf.multiply(eta, tf.reduce_mean(delta_1, axis=[0]))))
   , tf.assign(weights['out'], tf.subtract(weights['out'], tf.multiply(eta, delta_w_2)))
   , tf.assign(biases['out'], tf.subtract(biases['out'], tf.multiply(eta,tf.reduce_mean(delta_2, axis=[0]))))
 ] 
  1. accuracy定义操作:
acct_mat = tf.equal(tf.argmax(y_hat, 1), tf.argmax(y, 1))
accuracy = tf.reduce_sum(tf.cast(acct_mat, tf.float32)) 
  1. 初始化变量:
init = tf.global_variables_initializer() 
  1. 执行图:
with tf.Session() as sess:
     sess.run(init)
     for epoch in range(max_epochs):
         batch_xs, batch_ys = mnist.train.next_batch(batch_size)
         sess.run(step, feed_dict = {x_in: batch_xs, y : batch_ys})
         if epoch % 1000 == 0:
             acc_test = sess.run(accuracy, feed_dict =
                        {x_in: mnist.test.images,
                         y : mnist.test.labels})
             acc_train = sess.run(accuracy, feed_dict=
             {x_in: mnist.train.images,
              y: mnist.train.labels})
             print('Epoch: {0}  Accuracy Train%: {1}  Accuracy Test%: {2}'
                   .format(epoch,acc_train/600,(acc_test/100))) 

结果如下:

工作原理

在这里,我们正在以 10 的批量大小训练网络。如果增加它,网络表现就会下降。 同样,在测试数据上检查训练网络的准确率; 对其进行测试的测试数据的大小为 1,000。

更多

我们的一个隐藏层多层感知机在训练数据上的准确率为 84.45,在测试数据上的准确率为 92.1。 很好,但还不够好。 MNIST 数据库用作机器学习中分类问题的基准。 接下来,我们了解使用 TensorFlow 的内置优化器如何影响网络表现。

另见

使用 MLP 的 MNIST 分类器

TensorFlow 支持自动分化; 我们可以使用 TensorFlow 优化器来计算和应用梯度。 它使用梯度自动更新定义为变量的张量。 在此秘籍中,我们将使用 TensorFlow 优化器来训练网络。

准备

在反向传播算法秘籍中,我们定义了层,权重,损耗,梯度,并手动通过梯度进行更新。 为了更好地理解,手动使用方程式进行操作是一个好主意,但是随着网络中层数的增加,这可能会非常麻烦。

在本秘籍中,我们将使用强大的 TensorFlow 功能(例如 Contrib(层))来定义神经网络层,并使用 TensorFlow 自己的优化器来计算和应用梯度。 我们在第 2 章和“回归”中了解了如何使用不同的 TensorFlow 优化器。 contrib 可用于向神经网络模型添加各种层,例如添加构建块。 我们在这里使用的一种方法是tf.contrib.layers.fully_connected,在 TensorFlow 文档中定义如下:

fully_connected(
     inputs,
     num_outputs,
     activation_fn=tf.nn.relu,
     normalizer_fn=None,
     normalizer_params=None,
     weights_initializer=initializers.xavier_initializer(),
     weights_regularizer=None,
     biases_initializer=tf.zeros_initializer(),
     biases_regularizer=None,
     reuse=None,
     variables_collections=None,
     outputs_collections=None,
     trainable=True,
     scope=None
 ) 

这将添加一个完全连接的层。

fully_connected creates a variable called weights, representing a fully connected weight matrix, which is multiplied by the inputs to produce a tensor of hidden units. If a normalizer_fn is provided (such as batch_norm), it is then applied. Otherwise, if normalizer_fn is None and a biases_initializer is provided then a biases variable would be created and added to the hidden units. Finally, if activation_fn is not None, it is applied to the hidden units as well.

操作步骤

我们按以下步骤进行:

  1. 第一步是更改loss函数; 尽管对于分类,最好使用交叉熵loss函数。 我们目前继续均方误差MSE):
loss = tf.reduce_mean(tf.square(y - y_hat, name='loss'))
  1. 接下来,我们使用GradientDescentOptimizer
optimizer = tf.train.GradientDescentOptimizer(learning_rate= learning_rate)
 train = optimizer.minimize(loss) 
  1. 仅通过这两个更改,对于同一组超参数,测试数据集的准确率仅为 61.3%。 增加max_epoch,我们可以提高精度,但这将不是 TensorFlow 功能的有效利用。
  2. 这是一个分类问题,因此最好使用交叉熵损失,用于隐藏层的 ReLU 激活函数以及用于输出层的 softmax。 进行所需的更改,完整代码如下:
import tensorflow as tf
 import tensorflow.contrib.layers as layers

 from tensorflow.python import debug as tf_debug

 # Network Parameters
 n_hidden = 30
 n_classes = 10
 n_input = 784

 # Hyperparameters
 batch_size = 200
 eta = 0.001
 max_epoch = 10

 # MNIST input data
 from tensorflow.examples.tutorials.mnist import input_data
 mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)

 def multilayer_perceptron(x):
     fc1 = layers.fully_connected(x, n_hidden, activation_fn=tf.nn.relu, scope='fc1')
     #fc2 = layers.fully_connected(fc1, 256, activation_fn=tf.nn.relu, scope='fc2')
     out = layers.fully_connected(fc1, n_classes, activation_fn=None, scope='out')
     return out

 # build model, loss, and train op
 x = tf.placeholder(tf.float32, [None, n_input], name='placeholder_x')
 y = tf.placeholder(tf.float32, [None, n_classes], name='placeholder_y')
 y_hat = multilayer_perceptron(x)

 loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=y_hat, labels=y))
 train = tf.train.AdamOptimizer(learning_rate= eta).minimize(loss)
 init = tf.global_variables_initializer()

 with tf.Session() as sess:
     sess.run(init)
     for epoch in range(10):
         epoch_loss = 0.0
         batch_steps = int(mnist.train.num_examples / batch_size)
         for i in range(batch_steps):
             batch_x, batch_y = mnist.train.next_batch(batch_size)
             _, c = sess.run([train, loss],
                                feed_dict={x: batch_x, y: batch_y})
             epoch_loss += c / batch_steps
         print ('Epoch %02d, Loss = %.6f' % (epoch, epoch_loss))

     # Test model
     correct_prediction = tf.equal(tf.argmax(y_hat, 1), tf.argmax(y, 1))
     accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
     print ("Accuracy%:", accuracy.eval({x: mnist.test.images, y: mnist.test.labels})) 

工作原理

改进的 MNIST MLP 分类器在测试数据集上的准确率达到了 96%,只有一个隐藏层并且在 10 个周期内。 仅在几行代码中我们就获得了约 96% 的准确率,这就是 TensorFlow 的强大功能:

使用 MLP 预测波士顿房价的函数近似

Hornik 等人的工作证明了以下:

"multilayer feedforward networks with as few as one hidden layer are indeed capable of universal approximation in a very precise and satisfactory sense."

在本秘籍中,我们将向您展示如何使用 MLP 进行函数逼近; 具体来说,我们将预测波士顿的房价。 我们已经熟悉了数据集; 在第 2 章,“回归”中,我们使用回归技术进行房价预测,现在我们将使用 MLP 进行相同的操作。

准备

对于函数逼近,loss函数应为 MSE。 输入应该标准化,而隐藏层可以是 ReLU,而输出层则最好是 Sigmoid 。

操作步骤

这是我们从使用 MLP 进行函数逼近开始的方法:

  1. 导入所需的模块-sklearn用于数据集,预处理数据,并将其拆分为训练和测试; Pandas 用于了解数据集; matplotlibseaborn用于可视化:
import tensorflow as tf 
import tensorflow.contrib.layers as layers 
from sklearn import datasets 
import matplotlib.pyplot as plt 
from sklearn.model_selection  import train_test_split 
from sklearn.preprocessing import MinMaxScaler 
import pandas as pd 
import seaborn as sns 
%matplotlib inline
  1. 加载数据集并创建一个 Pandas 数据帧以了解数据:
# Data 
boston = datasets.load_boston() 
df = pd.DataFrame(boston.data, columns=boston.feature_names) 
df['target'] = boston.target 
  1. 让我们获取有关数据的一些详细信息:
#Understanding Data 
df.describe() 

下图很好地说明了这一概念:

  1. 查找不同输入特征和目标之间的关联:
# Plotting correlation 
color map _ , ax = plt.subplots( figsize =( 12 , 10 ) ) 
corr = df.corr(method='pearson') 
cmap = sns.diverging_palette( 220 , 10 , as_cmap = True ) 
_ = sns.heatmap( corr, cmap = cmap, square=True, cbar_kws={ 'shrink' : .9 }, ax=ax, annot = True, annot_kws = { 'fontsize' : 12 })

以下是上述代码的输出:

  1. 从前面的代码中,我们可以看到RMPTRATIOLSTAT这三个参数的相关性在大小上大于 0.5。 我们选择它们进行训练。 将数据集拆分为训练和测试数据集。 我们还使用MinMaxScaler归一化我们的数据集。 需要注意的一个重要变化是,由于我们的神经网络使用了 Sigmoid 激活函数(Sigmoid 的输出只能在 0-1 之间),因此我们也必须将目标值Y标准化:
# Create Test Train Split 
X_train, X_test, y_train, y_test = train_test_split(df [['RM', 'LSTAT', 'PTRATIO']], df[['target']], test_size=0.3, random_state=0) 
# Normalize data 
X_train = MinMaxScaler().fit_transform(X_train) 
y_train = MinMaxScaler().fit_transform(y_train) 
X_test = MinMaxScaler().fit_transform(X_test) 
y_test = MinMaxScaler().fit_transform(y_test)
  1. 定义常量和超参数:
#Network Parameters 
m = len(X_train) 
n = 3 # Number of features 
n_hidden = 20 # Number of hidden neurons 
# Hyperparameters 
batch_size = 200 
eta = 0.01 
max_epoch = 1000
  1. 创建具有一个隐藏层的多层感知机模型:
def multilayer_perceptron(x): 
    fc1 = layers.fully_connected(x, n_hidden, activation_fn=tf.nn.relu, scope='fc1') 
    out = layers.fully_connected(fc1, 1, activation_fn=tf.sigmoid, scope='out') 
    return out
  1. 声明训练数据的占位符,并定义损失和优化器:
# build model, loss, and train op 
x = tf.placeholder(tf.float32, name='X', shape=[m,n]) 
y = tf.placeholder(tf.float32, name='Y') 
y_hat = multilayer_perceptron(x) 
correct_prediction = tf.square(y - y_hat) 
mse = tf.reduce_mean(tf.cast(correct_prediction, "float")) 
train = tf.train.AdamOptimizer(learning_rate= eta).minimize(mse) 
init = tf.global_variables_initializer()
  1. 执行计算图:
# Computation Graph 
with tf.Session() as sess: # Initialize variables
     sess.run(init) writer = tf.summary.FileWriter('graphs', sess.graph) 
# train the model for 100 epcohs 
    for i in range(max_epoch): 
        _, l, p = sess.run([train, loss, y_hat], feed_dict={x: X_train, y: y_train}) 
        if i%100 == 0: 
            print('Epoch {0}: Loss {1}'.format(i, l))
    print("Training Done") 
print("Optimization Finished!") 
# Test model correct_prediction = tf.square(y - y_hat) 
# Calculate accuracy 
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
print(" Mean Error:", accuracy.eval({x: X_train, y: y_train})) plt.scatter(y_train, p) 
writer.close()

工作原理

该模型只有一个隐藏层,因此可以预测训练数据集的价格,平均误差为 0.0071。 下图显示了房屋的估计价格与实际价格之间的关系:

更多

在这里,我们使用 TensorFlow ops Layers(Contrib)来构建神经网络层。 由于避免了分别声明每一层的权重和偏差,因此使我们的工作稍微容易一些。 如果我们使用像 Keras 这样的 API,可以进一步简化工作。 这是在 Keras 中使用 TensorFlow 作为后端的相同代码:

#Network Parameters
m = len(X_train)
n = 3 # Number of features
n_hidden = 20 # Number of hidden neurons

# Hyperparameters
batch = 20
eta = 0.01
max_epoch = 100
# Build Model 
model = Sequential() 
model.add(Dense(n_hidden,  
model.add(Dense(1, activation='sigmoid')) 
model.summary() 
# Summarize the model 
#Compile model 
model.compile(loss='mean_squared_error', optimizer='adam') 
#Fit the model 
model.fit(X_train, y_train, validation_data=(X_test, y_test),epochs=max_epoch, batch_size=batch, verbose=1) 
#Predict the values and calculate RMSE and R2 score 
y_test_pred = model.predict(X_test) 
y_train_pred = model.predict(X_train) 
r2 = r2_score( y_test, y_test_pred ) 
rmse = mean_squared_error( y_test, y_test_pred ) 
print( "Performance Metrics R2 : {0:f}, RMSE : {1:f}".format( r2, rmse ) )

前面的代码在预测值和实际值之间给出了以下结果。 我们可以看到,通过消除异常值可以改善结果(某些房屋的最高价格与其他参数无关,位于最右边的点):

调整超参数

正如您现在必须已经观察到的那样,神经网络的表现在很大程度上取决于超参数。 因此,重要的是要了解这些参数如何影响网络。 超参数的常见示例是学习率,正则化器,正则化系数,隐藏层的尺寸,初始权重值,甚至是为优化权重和偏差而选择的优化器。

操作步骤

这是我们进行秘籍的方法:

  1. 调整超参数的第一步是构建模型。 完全按照我们以前的方式在 TensorFlow 中构建模型。
  2. 添加一种将模型保存在model_file中的方法。 在 TensorFlow 中,可以使用Saver对象完成此操作。 然后将其保存在会话中:
... saver = tf.train.Saver() ... with tf.Session() as sess: ... #Do the training steps ... save_path = saver.save(sess, "/tmp/model.ckpt") print("Model saved in file: %s" % save_path)
  1. 接下来,确定要调整的超参数。
  2. 为超参数选择可能的值。 在这里,您可以进行随机选择,等距选择或手动选择。 这三个分别称为随机搜索,网格搜索或用于优化超参数的手动搜索。 例如,这是针对学习率的:
# Random Choice: generate 5 random values of learning rate 
# lying between 0 and 1 
learning_rate =  
#Grid Search: generate 5 values starting from 0, separated by 
# 0.2 
learning_rate = [i for i in np.arange(0,1,0.2)] 
#Manual Search: give any values you seem plausible manually learning_rate = [0.5, 0.6, 0.32, 0.7, 0.01]
  1. 我们选择对我们选择的loss函数具有最佳响应的参数。 因此,我们可以在开始时将loss函数的最大值定义为best_loss(在精度的情况下,您将从模型中选择所需的最小精度):
best_loss = 2 
# It can be any number, but it would be better if you keep it same as the loss you achieved from your base model defined in steps 1 and 2
  1. 将模型包装在for循环中以提高学习率; 然后保存任何可以更好地估计损失的模型:
... # Load and preprocess data 
... # Hyperparameters 
Tuning epochs = [50, 60, 70] 
batches = [5, 10, 20] 
rmse_min = 0.04 
for epoch in epochs: 
    for batch in batches: 
        model = get_model() 
        model.compile(loss='mean_squared_error', optimizer='adam') 
        model.fit(X_train, y_train, validation_data=(X_test, y_test),epochs=epoch, batch_size=batch, verbose=1) 
        y_test_pred = model.predict(X_test) 
        rmse = mean_squared_error( y_test, y_test_pred ) 
        if rmse < rmse_min: 
            rmse_min = rmse 
            # serialize model to JSON 
            model_json = model.to_json() 
            with open("model.json", "w") as json_file: 
                json_file.write(model_json) 
                # serialize weights to HDF5  
                model.save_weights("model.hdf5") 
                print("Saved model to disk")

更多

还有另一种称为贝叶斯优化的方法,该方法也可以用于调整超参数。 在其中,我们定义了一个采集函数以及一个高斯过程。 高斯过程使用一组先前评估的参数以及由此产生的精度来假设大约未观测到的参数。 使用此信息的采集功能建议使用下一组参数。 有一个包装程序甚至可用于基于梯度的超参数优化

另见

更高级别的 API -- Keras

Keras 是将 TensorFlow 作为后端使用的高级 API。 向其添加层就像添加一行代码一样容易。 在建立模型架构之后,您可以使用一行代码来编译和拟合模型。 以后,它可以用于预测。 变量,占位符甚至会话的声明均由 API 管理。

操作步骤

我们对 Keras 进行如下操作:

  1. 第一步,我们定义模型的类型。 Keras 提供了两种类型的模型:顺序模型 API 和模型类 API。 Keras 提供了各种类型的神经网络层:
# Import the model and layers needed  
from keras.model import Sequential 
from keras.layers import Dense 

model = Sequential() 
  1. 借助model.add()将层添加到模型中。 Keras 为密集连接的神经网络layer Dense(units, activation=None, use_bias=True, kernel_initializer='glorot_uniform', bias_initializer='zeros', kernel_regularizer=None, bias_regularizer=None, activity_regularizer=None, kernel_constraint=None, bias_constraint=None)提供了一个密集层的选项。 根据 Keras 文档:

Dense implements the operation: output = activation(dot(input, kernel) + bias) where activation is the element-wise activation function passed as the activation argument, kernel is a weights matrix created by the layer, and bias is a bias vector created by the layer (only applicable if use_bias is True).

  1. 我们可以使用它来添加任意数量的层,每个隐藏的层都由上一层提供。 我们只需要为第一层指定输入尺寸:
#This will add a fully connected neural network layer with 32 neurons, each taking 13 inputs, and with activation function ReLU 
mode.add(Dense(32, input_dim=13, activation='relu')) )) 
model.add(10, activation='sigmoid')
  1. 定义模型后,我们需要选择loss函数和优化器。 Keras 提供了多种loss_functionsmean_squared_errormean_absolute_errormean_absolute_percentage_errorcategorical_crossentropy; 和优化程序:SGD,RMSprop,Adagrad,Adadelta,Adam 等。 决定了这两个条件后,我们可以使用compile(self, optimizer, loss, metrics=None, sample_weight_mode=None)配置学习过程:
model.compile(optimizer='rmsprop', 
          loss='categorical_crossentropy', 
          metrics=['accuracy']) 
  1. 接下来,使用fit方法训练模型:
model.fit(data, labels, epochs=10, batch_size=32) 
  1. 最后,可以借助predict方法predict(self, x, batch_size=32, verbose=0)进行预测:
model.predict(test_data, batch_size=10) 

更多

Keras 提供了添加卷积层,池化层,循环层甚至本地连接层的选项。 Keras 文档中提供了每种方法的详细说明。

另见

McCulloch, Warren S., and Walter Pitts. A logical calculus of the ideas immanent in nervous activity The bulletin of mathematical biophysics 5.4 (1943): 115-133. http://vordenker.de/ggphilosophy/mcculloch_a-logical-calculus.pdf

Rosenblatt, Frank (1957), The Perceptron--a perceiving and recognizing automaton. Report 85-460-1, Cornell Aeronautical Laboratory. https://blogs.umass.edu/brain-wars/files/2016/03/rosenblatt-1957.pdf

The Thinking Machine, CBS Broadcast https://www.youtube.com/watch?v=jPHUlQiwD9Y

四、卷积神经网络

卷积神经网络CNN 或有时称为 ConvNets)令人着迷。 在短时间内,它们成为一种破坏性技术,打破了从文本,视频到语音的多个领域中的所有最新技术成果,远远超出了最初用于图像处理的范围。 在本章中,我们将介绍一些方法,如下所示:

  • 创建一个卷积网络对手写 MNIST 编号进行分类
  • 创建一个卷积网络对 CIFAR-10 进行分类
  • 使用 VGG19 迁移风格用于图像重绘
  • 使用预训练的 VGG16 网络进行迁移学习
  • 创建 DeepDream 网络

介绍

CNN 由许多神经网络层组成。 卷积和池化两种不同类型的层通常是交替的。 网络中每个过滤器的深度从左到右增加。 最后一级通常由一个或多个完全连接的层组成:

如图所示,卷积神经网络的一个示例

卷积网络背后有三个主要的直觉:局部接受域共享权重池化。 让我们一起回顾一下。

局部接受域

如果我们要保留通常在图像中发现的空间信息,则使用像素矩阵表示每个图像会很方便。 然后,编码局部结构的一种简单方法是将相邻输入神经元的子矩阵连接到属于下一层的单个隐藏神经元中。 单个隐藏的神经元代表一个局部感受野。 请注意,此操作名为卷积,它为这种类型的网络提供了名称。

当然,我们可以通过重叠子矩阵来编码更多信息。 例如,假设每个子矩阵的大小为5 x 5,并且这些子矩阵用于28 x 28像素的 MNIST 图像。 然后,我们将能够在下一个隐藏层中生成23 x 23个局部感受野神经元。 实际上,在触摸图像的边界之前,可以仅将子矩阵滑动 23 个位置。

让我们定义从一层到另一层的特征图。 当然,我们可以有多个可以从每个隐藏层中独立学习的特征图。 例如,我们可以从28 x 28个输入神经元开始处理 MNIST 图像,然后在下一个隐藏的区域中调用k个特征图,每个特征图的大小为23 x 23神经元(步幅为5 x 5)。

权重和偏置

假设我们想通过获得独立于输入图像中放置同一特征的能力来摆脱原始像素表示的困扰。 一个简单的直觉是对隐藏层中的所有神经元使用相同的权重和偏差集。 这样,每一层将学习从图像派生的一组位置无关的潜在特征。

一个数学示例

一种了解卷积的简单方法是考虑应用于矩阵的滑动窗口函数。 在下面的示例中,给定输入矩阵I和内核K,我们得到了卷积输出。 将3 x 3内核K(有时称为过滤器特征检测器)与输入矩阵逐元素相乘,得到输出卷积矩阵中的一个单元格。 通过在I上滑动窗口即可获得所有其他单元格:

卷积运算的一个示例:用粗体显示计算中涉及的单元

在此示例中,我们决定在触摸I的边界后立即停止滑动窗口(因此输出为3 x 3)。 或者,我们可以选择用零填充输入(以便输出为5 x 5)。 该决定与所采用的填充选择有关。

另一个选择是关于步幅,这与我们的滑动窗口采用的移位类型有关。 这可以是一个或多个。 较大的跨度将生成较少的内核应用,并且较小的输出大小,而较小的跨度将生成更多的输出并保留更多信息。

过滤器的大小,步幅和填充类型是超参数,可以在网络训练期间进行微调。

TensorFlow 中的卷积网络

在 TensorFlow 中,如果要添加卷积层,我们将编写以下内容:

tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, data_format=None, name=None)

以下是参数:

  • input:张量必须为以下类型之一:float32float64
  • filter:张量必须与输入具有相同的类型。
  • strides:整数列表。 长度为 1 的 4D。输入每个维度的滑动窗口的步幅。 必须与格式指定的尺寸顺序相同。
  • padding:来自SAMEVALID的字符串。 要使用的填充算法的类型。
  • use_cudnn_on_gpu:可选的布尔值。 默认为True
  • data_format:来自NHWCNCHW的可选字符串。 默认为NHWC。 指定输入和输出数据的数据格式。 使用默认格式NHWC时,数据按以下顺序存储:[batchin_heightin_widthin_channels]。 或者,格式可以是NCHW,数据存储顺序为:[batchin_channelsin_height, in_width]。
  • name:操作的名称(可选)。

下图提供了卷积的示例:

卷积运算的一个例子

汇聚层

假设我们要总结特征图的输出。 同样,我们可以使用从单个特征图生成的输出的空间连续性,并将子矩阵的值聚合为一个单个输出值,以综合方式描述与该物理区域相关的含义。

最大池

一个简单而常见的选择是所谓的最大池化运算符,它仅输出在该区域中观察到的最大激活。 在 TensorFlow 中,如果要定义大小为2 x 2的最大池化层,我们将编写以下内容:

tf.nn.max_pool(value, ksize, strides, padding, data_format='NHWC', name=None)

这些是参数:

  • value:形状为[batchheightwidthchannels]且类型为tf.float32的 4-D 张量。
  • ksize:长度>= 4的整数的列表。输入张量每个维度的窗口大小。
  • strides:长度>= 4的整数的列表。输入张量每个维度的滑动窗口的步幅。
  • paddingVALIDSAME的字符串。
  • data_format:字符串。 支持NHWCNCHW
  • name:操作的可选名称。

下图给出了最大池化操作的示例:

池化操作示例

平均池化

另一个选择是“平均池化”,它可以将一个区域简单地汇总为在该区域中观察到的激活平均值。

TensorFlow 实现了大量池化层,可在线获取完整列表。简而言之,所有池化操作仅是对给定区域的汇总操作。

卷积网络摘要

CNN 基本上是卷积的几层,具有非线性激活函数,并且池化层应用于结果。 每层应用不同的过滤器(数百或数千)。 要理解的主要观察结果是未预先分配滤波器,而是在训练阶段以最小化合适损失函数的方式来学习滤波器。 已经观察到,较低的层将学会检测基本特征,而较高的层将逐渐检测更复杂的特征,例如形状或面部。 请注意,得益于合并,后一层中的单个神经元可以看到更多的原始图像,因此它们能够组成在前几层中学习的基本特征。

到目前为止,我们已经描述了 ConvNets 的基本概念。 CNN 在沿时间维度的一维中对音频和文本数据应用卷积和池化操作,在沿(高度 x 宽度)维的图像中对二维图像应用卷积和池化操作,对于沿(高度 x 宽度 x 时间)维的视频中的三个维度应用卷积和池化操作。 对于图像,在输入体积上滑动过滤器会生成一个贴图,该贴图为每个空间位置提供过滤器的响应。

换句话说,卷积网络具有堆叠在一起的多个过滤器,这些过滤器学会了独立于图像中的位置来识别特定的视觉特征。 这些视觉特征在网络的初始层很简单,然后在网络的更深层越来越复杂。g操作

创建一个卷积网络对手写 MNIST 编号进行分类

在本秘籍中,您将学习如何创建一个简单的三层卷积网络来预测 MNIST 数字。 深度网络由具有 ReLU 和最大池化的两个卷积层以及两个完全连接的最终层组成。

准备

MNIST 是一组 60,000 张代表手写数字的图像。 本秘籍的目的是高精度地识别这些数字。

操作步骤

让我们从秘籍开始:

  1. 导入tensorflowmatplotlibrandomnumpy。 然后,导入minst数据并执行一键编码。 请注意,TensorFlow 具有一些内置库来处理MNIST,我们将使用它们:
from __future__ import  division, print_function 
import tensorflow as tf 
import matplotlib.pyplot as plt 
import numpy as np 
# Import MNIST data 
from tensorflow.examples.tutorials.mnist import input_data 
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True) 
  1. 内省一些数据以了解MNIST是什么。 这使我们知道了训练数据集中有多少张图像,测试数据集中有多少张图像。 我们还将可视化一些数字,只是为了理解它们的表示方式。 多单元输出可以使我们直观地认识到即使对于人类来说,识别手写数字也有多困难。
def train_size(num): 
    print ('Total Training Images in Dataset = ' + str(mnist.train.images.shape)) 
    print ('--------------------------------------------------') 
    x_train = mnist.train.images[:num,:] 
    print ('x_train Examples Loaded = ' + str(x_train.shape)) 
    y_train = mnist.train.labels[:num,:] 
    print ('y_train Examples Loaded = ' + str(y_train.shape)) 
    print('') 
    return x_train, y_train 
def test_size(num): 
    print ('Total Test Examples in Dataset = ' + str(mnist.test.images.shape)) 
    print ('--------------------------------------------------') 
    x_test = mnist.test.images[:num,:] 
    print ('x_test Examples Loaded = ' + str(x_test.shape)) 
    y_test = mnist.test.labels[:num,:] 
    print ('y_test Examples Loaded = ' + str(y_test.shape)) 
    return x_test, y_test 
def display_digit(num): 
    print(y_train[num]) 
    label = y_train[num].argmax(axis=0) 
    image = x_train[num].reshape([28,28]) 
    plt.title('Example: %d  Label: %d' % (num, label)) 
    plt.imshow(image, cmap=plt.get_cmap('gray_r')) 
    plt.show() 
def display_mult_flat(start, stop): 
    images = x_train[start].reshape([1,784]) 
    for i in range(start+1,stop): 
        images = np.concatenate((images, x_train[i].reshape([1,784]))) 
    plt.imshow(images, cmap=plt.get_cmap('gray_r')) 
    plt.show() 
x_train, y_train = train_size(55000) 
display_digit(np.random.randint(0, x_train.shape[0])) 
display_mult_flat(0,400) 

让我们看一下前面代码的输出:

MNIST 手写数字的示例

  1. 设置学习参数batch_sizedisplay_step。 另外,假设 MNIST 图像共享28 x 28像素,请设置n_input = 784,表示输出数字[0-9]的输出n_classes = 10,且丢弃概率= 0.85
# Parameters 
learning_rate = 0.001 
training_iters = 500 
batch_size = 128 
display_step = 10 
# Network Parameters 
n_input = 784 
# MNIST data input (img shape: 28*28) 
n_classes = 10 
# MNIST total classes (0-9 digits) 
dropout = 0.85 
# Dropout, probability to keep units 
  1. 设置 TensorFlow 计算图输入。 让我们定义两个占位符以存储预测和真实标签:
x = tf.placeholder(tf.float32, [None, n_input]) 
y = tf.placeholder(tf.float32, [None, n_classes]) 
keep_prob = tf.placeholder(tf.float32)  
  1. 使用输入x,权重W,偏差b和给定的步幅定义卷积层。 激活函数为 ReLU,填充为SAME
def conv2d(x, W, b, strides=1): 
    x = tf.nn.conv2d(x, W, strides=[1, strides, strides, 1], padding='SAME') 
    x = tf.nn.bias_add(x, b) 
    return tf.nn.relu(x)
  1. 使用输入xksizeSAME填充定义一个最大池化层:
def maxpool2d(x, k=2): 
    return tf.nn.max_pool(x, ksize=[1, k, k, 1], strides=[1, k, k, 1], padding='SAME') 
  1. 用两个卷积层定义一个卷积网络,然后是一个完全连接的层,一个退出层和一个最终输出层:
def conv_net(x, weights, biases, dropout): 
    # reshape the input picture 
    x = tf.reshape(x, shape=[-1, 28, 28, 1]) 
    # First convolution layer 
    conv1 = conv2d(x, weights['wc1'], biases['bc1']) 
    # Max Pooling used for downsampling 
    conv1 = maxpool2d(conv1, k=2) 
    # Second convolution layer 
    conv2 = conv2d(conv1, weights['wc2'], biases['bc2']) 
    # Max Pooling used for downsampling 
    conv2 = maxpool2d(conv2, k=2) 
    # Reshape conv2 output to match the input of fully connected layer  
    fc1 = tf.reshape(conv2, [-1, weights['wd1'].get_shape().as_list()[0]]) 
    # Fully connected layer 
    fc1 = tf.add(tf.matmul(fc1, weights['wd1']), biases['bd1']) 
    fc1 = tf.nn.relu(fc1) 
    # Dropout 
    fc1 = tf.nn.dropout(fc1, dropout) 
    # Output the class prediction 
    out = tf.add(tf.matmul(fc1, weights['out']), biases['out']) 
    return out
  1. 定义层权重和偏差。 第一转换层具有5 x 5卷积,1 个输入和 32 个输出。 第二个卷积层具有5 x 5卷积,32 个输入和 64 个输出。 全连接层具有7 x 7 x 64输入和 1,024 输出,而第二层具有 1,024 输入和 10 输出,对应于最终数字类别。 所有权重和偏差均使用randon_normal分布进行初始化:
weights = { 
    # 5x5 conv, 1 input, and 32 outputs 
    'wc1': tf.Variable(tf.random_normal([5, 5, 1, 32])), 
    # 5x5 conv, 32 inputs, and 64 outputs 
    'wc2': tf.Variable(tf.random_normal([5, 5, 32, 64])), 
    # fully connected, 7`7`64 inputs, and 1024 outputs 
    'wd1': tf.Variable(tf.random_normal([7`7`64, 1024])), 
    # 1024 inputs, 10 outputs for class digits 
    'out': tf.Variable(tf.random_normal([1024, n_classes])) 
} 
biases = { 
    'bc1': tf.Variable(tf.random_normal([32])), 
    'bc2': tf.Variable(tf.random_normal([64])), 
    'bd1': tf.Variable(tf.random_normal([1024])), 
    'out': tf.Variable(tf.random_normal([n_classes])) 
} 
  1. 使用给定的权重和偏差构建卷积网络。 基于cross_entropylogits定义loss函数,并使用 Adam 优化器来最小化成本。 优化后,计算精度:
pred = conv_net(x, weights, biases, keep_prob) 
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=pred, labels=y)) 
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost) 
correct_prediction = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1)) 
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) 
init = tf.global_variables_initializer()
  1. 启动图并迭代training_iterats次,每次在输入中输入batch_size来运行优化器。 请注意,我们使用mnist.train数据进行训练,该数据与minst分开。 每个display_step都会计算出当前的部分精度。 最后,在 2,048 张测试图像上计算精度,没有丢弃。
train_loss = [] 
train_acc = [] 
test_acc = [] 
with tf.Session() as sess: 
    sess.run(init) 
    step = 1 
    while step <= training_iters: 
        batch_x, batch_y = mnist.train.next_batch(batch_size) 
        sess.run(optimizer, feed_dict={x: batch_x, y: batch_y, 
                                       keep_prob: dropout}) 
        if step % display_step == 0: 
            loss_train, acc_train = sess.run([cost, accuracy],  
                                             feed_dict={x: batch_x, 
                                                        y: batch_y, 
                                                        keep_prob: 1.}) 
            print "Iter " + str(step) + ", Minibatch Loss= " + \ 
                  "{:.2f}".format(loss_train) + ", Training Accuracy= " + \ 
                  "{:.2f}".format(acc_train) 
            # Calculate accuracy for 2048 mnist test images.  
            # Note that in this case no dropout 
            acc_test = sess.run(accuracy,  
                                feed_dict={x: mnist.test.images, 
                                      y: mnist.test.labels, 
                                      keep_prob: 1.}) 
            print "Testing Accuracy:" + \ 
               "{:.2f}".format(acc_train) 
            train_loss.append(loss_train) 
            train_acc.append(acc_train) 
            test_acc.append(acc_test)             
        step += 1
  1. 绘制每次迭代的 Softmax 损失以及训练和测试精度:
eval_indices = range(0, training_iters, display_step) 
# Plot loss over time 
plt.plot(eval_indices, train_loss, 'k-') 
plt.title('Softmax Loss per iteration') 
plt.xlabel('Iteration') 
plt.ylabel('Softmax Loss') 
plt.show() 
# Plot train and test accuracy 
plt.plot(eval_indices, train_acc, 'k-', label='Train Set Accuracy') 
plt.plot(eval_indices, test_acc, 'r--', label='Test Set Accuracy') 
plt.title('Train and Test Accuracy') 
plt.xlabel('Generation') 
plt.ylabel('Accuracy') 
plt.legend(loc='lower right') 
plt.show() 

以下是前面代码的输出。 我们首先看一下每次迭代的 Softmax:

损失减少的一个例子

接下来我们看一下训练和文本的准确率:

训练和测试准确率提高的示例

工作原理

使用卷积网络,我们将 MNIST 数据集的表现提高了近 95%。 我们的卷积网络由两层组成,分别是卷积,ReLU 和最大池化,然后是两个完全连接的带有丢弃的层。 训练以 Adam 为优化器,以 128 的大小批量进行,学习率为 0.001,最大迭代次数为 500。

创建一个卷积网络对 CIFAR-10 进行分类

在本秘籍中,您将学习如何对从 CIFAR-10 拍摄的图像进行分类。 CIFAR-10 数据集由 10 类 60,000 张32 x 32彩色图像组成,每类 6,000 张图像。 有 50,000 张训练图像和 10,000 张测试图像。 下图取自这里

CIFAR 图像示例

准备

在本秘籍中,我们使用tflearn-一个更高级别的框架-抽象了一些 TensorFlow 内部结构,使我们可以专注于深度网络的定义。 TFLearn 可从这里获得,该代码是标准发行版的一部分

操作步骤

我们按以下步骤进行:

  1. 为卷积网络,dropoutfully_connectedmax_pool导入一些utils和核心层。 此外,导入一些对图像处理和图像增强有用的模块。 请注意,TFLearn 为卷积网络提供了一些已经定义的更高层,这使我们可以专注于代码的定义:
from __future__ import division, print_function, absolute_import 
import tflearn 
from tflearn.data_utils import shuffle, to_categorical 
from tflearn.layers.core import input_data, dropout, fully_connected 
from tflearn.layers.conv import conv_2d, max_pool_2d 
from tflearn.layers.estimator import regression 
from tflearn.data_preprocessing import ImagePreprocessing 
from tflearn.data_augmentation import ImageAugmentation 
  1. 加载 CIFAR-10 数据,并将其分为X列数据,Y列标签,用于测试的X_test和用于测试标签的Y_test。 随机排列XY可能会很有用,以避免取决于特定的数据配置。 最后一步是对XY进行一次热编码:
# Data loading and preprocessing 
from tflearn.datasets import cifar10 
(X, Y), (X_test, Y_test) = cifar10.load_data() 
X, Y = shuffle(X, Y) 
Y = to_categorical(Y, 10) 
Y_test = to_categorical(Y_test, 10)
  1. ImagePreprocessing()用于零中心(在整个数据集上计算平均值)和 STD 归一化(在整个数据集上计算 std)。 TFLearn 数据流旨在通过在 GPU 执行模型训练时在 CPU 上预处理数据来加快训练速度。
# Real-time data preprocessing 
img_prep = ImagePreprocessing() 
img_prep.add_featurewise_zero_center() 
img_prep.add_featurewise_stdnorm()
  1. 通过左右随机执行以及随机旋转来增强数据集。 此步骤是一个简单的技巧,用于增加可用于训练的数据:
# Real-time data augmentation 
img_aug = ImageAugmentation() 
img_aug.add_random_flip_leftright() 
img_aug.add_random_rotation(max_angle=25.) 
  1. 使用先前定义的图像准备和扩充来创建卷积网络。 网络由三个卷积层组成。 第一个使用 32 个卷积滤波器,滤波器的大小为 3,激活函数为 ReLU。 之后,有一个max_pool层用于缩小尺寸。 然后有两个级联的卷积滤波器与 64 个卷积滤波器,滤波器的大小为 3,激活函数为 ReLU。 之后,有一个用于缩小规模的max_pool,一个具有 512 个神经元且具有激活函数 ReLU 的全连接网络,其次是丢弃的可能性为 50%。 最后一层是具有 10 个神经元和激活函数softmax的完全连接的网络,用于确定手写数字的类别。 请注意,已知这种特定类型的卷积网络对于 CIFAR-10 非常有效。 在这种特殊情况下,我们将 Adam 优化器与categorical_crossentropy和学习率0.001结合使用:
# Convolutional network building 
network = input_data(shape=[None, 32, 32, 3], 
                     data_preprocessing=img_prep, 
                     data_augmentation=img_aug) 
network = conv_2d(network, 32, 3, activation='relu') 
network = max_pool_2d(network, 2) 
network = conv_2d(network, 64, 3, activation='relu') 
network = conv_2d(network, 64, 3, activation='relu') 
network = max_pool_2d(network, 2) 
network = fully_connected(network, 512, activation='relu') 
network = dropout(network, 0.5) 
network = fully_connected(network, 10, activation='softmax') 
network = regression(network, optimizer='adam', 
                     loss='categorical_crossentropy', 
                     learning_rate=0.001)
  1. 实例化卷积网络并使用batch_size=96将训练运行 50 个周期:
# Train using classifier 
model = tflearn.DNN(network, tensorboard_verbose=0) 
model.fit(X, Y, n_epoch=50, shuffle=True, validation_set=(X_test, Y_test), 
          show_metric=True, batch_size=96, run_id='cifar10_cnn') 

工作原理

TFLearn 隐藏了 TensorFlow 公开的许多实现细节,并且在许多情况下,它使我们可以专注于具有更高抽象级别的卷积网络的定义。 我们的管道在 50 次迭代中达到了 88% 的精度。 下图是 Jupyter 笔记本中执行的快照:

Jupyter 执行 CIFAR10 分类的示例

更多

要安装 TFLearn,请参阅《安装指南》,如果您想查看更多示例,可以在线获取一长串久经考验的解决方案

使用 VGG19 迁移风格用于图像重绘

在本秘籍中,您将教计算机如何绘画。 关键思想是拥有绘画模型图像,神经网络可以从该图像推断绘画风格。 然后,此风格将迁移到另一张图片,并相应地重新粉刷。 该秘籍是对log0开发的代码的修改,可以在线获取

准备

我们将实现在论文《一种艺术风格的神经算法》中描述的算法,作者是 Leon A. Gatys,亚历山大 S. Ecker 和 Matthias Bethge。 因此,最好先阅读该论文。 此秘籍将重复使用在线提供的预训练模型 VGG19,该模型应在本地下载。 我们的风格图片将是一幅可在线获得的梵高著名画作,而我们的内容图片则是从维基百科下载的玛丽莲梦露的照片。 内容图像将根据梵高的风格重新绘制。

操作步骤

让我们从秘籍开始:

  1. 导入一些模块,例如numpyscipytensorflowmatplotlib。 然后导入PIL来处理图像。 请注意,由于此代码在 Jupyter 笔记本上运行,您可以从网上下载该片段,因此添加了片段%matplotlib inline
import os 
import sys 
import numpy as np 
import scipy.io 
import scipy.misc 
import tensorflow as tf 
import matplotlib.pyplot as plt 
from matplotlib.pyplot 
import imshow 
from PIL 
import Image %matplotlib inline from __future__ 
import division
  1. 然后,设置用于学习风格的图像的输入路径,并根据风格设置要重绘的内容图像的输入路径:
OUTPUT_DIR = 'output/' 
# Style image 
STYLE_IMAGE = 'data/StarryNight.jpg' 
# Content image to be repainted 
CONTENT_IMAGE = 'data/Marilyn_Monroe_in_1952.jpg'
  1. 然后,我们设置图像生成过程中使用的噪声比,以及在重画内容图像时要强调的内容损失和风格损失。 除此之外,我们存储通向预训练的 VGG 模型的路径和在 VGG 预训练期间计算的平均值。 这个平均值是已知的,可以从 VGG 模型的输入中减去:
# how much noise is in the image 
NOISE_RATIO = 0.6 
# How much emphasis on content loss. 
BETA = 5 
# How much emphasis on style loss. 
ALPHA = 100 
# the VGG 19-layer pre-trained model 
VGG_MODEL = 'data/imagenet-vgg-verydeep-19.mat' 
# The mean used when the VGG was trained 
# It is subtracted from the input to the VGG model. MEAN_VALUES = np.array([123.68, 116.779, 103.939]).reshape((1,1,1,3))
  1. 显示内容图像只是为了了解它的样子:
content_image = scipy.misc.imread(CONTENT_IMAGE) imshow(content_image)

这是前面代码的输出(请注意,此图像位于这个页面中):

  1. 调整风格图像的大小并显示它只是为了了解它的状态。 请注意,内容图像和风格图像现在具有相同的大小和相同数量的颜色通道:
style_image = scipy.misc.imread(STYLE_IMAGE) 
# Get shape of target and make the style image the same 
target_shape = content_image.shape 
print "target_shape=", target_shape 
print "style_shape=", style_image.shape 
#ratio = target_shape[1] / style_image.shape[1] 
#print "resize ratio=", ratio 
style_image = scipy.misc.imresize(style_image, target_shape) 
scipy.misc.imsave(STYLE_IMAGE, style_image) 
imshow(style_image)

这是前面代码的输出:

文森特·梵高画作的一个例子

  1. 下一步是按照原始论文中的描述定义 VGG 模型。 请注意,深度学习网络相当复杂,因为它结合了具有 ReLU 激活函数和最大池的多个卷积网络层。 另外需要注意的是,在原始论文《风格迁移》(Leon A. Gatys,Alexander S. Ecker 和 Matthias Bethge 撰写的《一种艺术风格的神经算法》)中,许多实验表明,平均合并实际上优于最大池化。 因此,我们将改用平均池:
def load_vgg_model(path, image_height, image_width, color_channels):
   """
   Returns the VGG model as defined in the paper
       0 is conv1_1 (3, 3, 3, 64)
       1 is relu
       2 is conv1_2 (3, 3, 64, 64)
       3 is relu    
       4 is maxpool
       5 is conv2_1 (3, 3, 64, 128)
       6 is relu
       7 is conv2_2 (3, 3, 128, 128)
       8 is relu
       9 is maxpool
       10 is conv3_1 (3, 3, 128, 256)
       11 is relu
       12 is conv3_2 (3, 3, 256, 256)
       13 is relu
       14 is conv3_3 (3, 3, 256, 256)
       15 is relu
       16 is conv3_4 (3, 3, 256, 256)
       17 is relu
       18 is maxpool
       19 is conv4_1 (3, 3, 256, 512)
       20 is relu
       21 is conv4_2 (3, 3, 512, 512)
       22 is relu
       23 is conv4_3 (3, 3, 512, 512)
       24 is relu
       25 is conv4_4 (3, 3, 512, 512)
       26 is relu
       27 is maxpool
       28 is conv5_1 (3, 3, 512, 512)
       29 is relu
       30 is conv5_2 (3, 3, 512, 512)
       31 is relu
       32 is conv5_3 (3, 3, 512, 512)
       33 is relu
       34 is conv5_4 (3, 3, 512, 512)
       35 is relu
       36 is maxpool
       37 is fullyconnected (7, 7, 512, 4096)       38 is relu
       39 is fullyconnected (1, 1, 4096, 4096)
       40 is relu
       41 is fullyconnected (1, 1, 4096, 1000)
       42 is softmax
   """
   vgg = scipy.io.loadmat(path)
   vgg_layers = vgg['layers']   

   def _weights(layer, expected_layer_name):
       """       Return the weights and bias from the VGG model for a given layer.
"""
       W = vgg_layers[0][layer][0][0][0][0][0]
       b = vgg_layers[0][layer][0][0][0][0][1]
       layer_name = vgg_layers[0][layer][0][0][-2]
       assert layer_name == expected_layer_name
       return W, b

   def _relu(conv2d_layer):
       """
       Return the RELU function wrapped over a TensorFlow layer. Expects a
       Conv2d layer input.
       """
       return tf.nn.relu(conv2d_layer)

   def _conv2d(prev_layer, layer, layer_name):
       """
       Return the Conv2D layer using the weights, biases from the VGG
       model at 'layer'.
       """
       W, b = _weights(layer, layer_name)
       W = tf.constant(W)
       b = tf.constant(np.reshape(b, (b.size)))
       return tf.nn.conv2d(
           prev_layer, filter=W, strides=[1, 1, 1, 1], padding='SAME') + b

   def _conv2d_relu(prev_layer, layer, layer_name):
       """
       Return the Conv2D + RELU layer using the weights, biases from the VGG
       model at 'layer'.
       """
       return _relu(_conv2d(prev_layer, layer, layer_name))

   def _avgpool(prev_layer):
       """
       Return the AveragePooling layer.
       """
       return tf.nn.avg_pool(prev_layer, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')

   # Constructs the graph model.
   graph = {}
   graph['input']   = tf.Variable(np.zeros((1,
                                            image_height, image_width, color_channels)),
                                  dtype = 'float32')
   graph['conv1_1']  = _conv2d_relu(graph['input'], 0, 'conv1_1')
   graph['conv1_2']  = _conv2d_relu(graph['conv1_1'], 2, 'conv1_2')
   graph['avgpool1'] = _avgpool(graph['conv1_2'])
   graph['conv2_1']  = _conv2d_relu(graph['avgpool1'], 5, 'conv2_1')
   graph['conv2_2']  = _conv2d_relu(graph['conv2_1'], 7, 'conv2_2')
   graph['avgpool2'] = _avgpool(graph['conv2_2'])
   graph['conv3_1']  = _conv2d_relu(graph['avgpool2'], 10, 'conv3_1')
   graph['conv3_2']  = _conv2d_relu(graph['conv3_1'], 12, 'conv3_2')
   graph['conv3_3']  = _conv2d_relu(graph['conv3_2'], 14, 'conv3_3')
   graph['conv3_4']  = _conv2d_relu(graph['conv3_3'], 16, 'conv3_4')
   graph['avgpool3'] = _avgpool(graph['conv3_4'])
   graph['conv4_1']  = _conv2d_relu(graph['avgpool3'], 19, 'conv4_1')
   graph['conv4_2']  = _conv2d_relu(graph['conv4_1'], 21, 'conv4_2')
   graph['conv4_3']  = _conv2d_relu(graph['conv4_2'], 23, 'conv4_3')
   graph['conv4_4']  = _conv2d_relu(graph['conv4_3'], 25, 'conv4_4')
   graph['avgpool4'] = _avgpool(graph['conv4_4'])
   graph['conv5_1']  = _conv2d_relu(graph['avgpool4'], 28, 'conv5_1')
   graph['conv5_2']  = _conv2d_relu(graph['conv5_1'], 30, 'conv5_2')
   graph['conv5_3']  = _conv2d_relu(graph['conv5_2'], 32, 'conv5_3')
   graph['conv5_4']  = _conv2d_relu(graph['conv5_3'], 34, 'conv5_4')
   graph['avgpool5'] = _avgpool(graph['conv5_4'])
   return graph
  1. 定义内容loss函数,如原始论文中所述:
def content_loss_func(sess, model): 
""" Content loss function as defined in the paper. """ 

def _content_loss(p, x): 
# N is the number of filters (at layer l). 
N = p.shape[3] 
# M is the height times the width of the feature map (at layer l). 
M = p.shape[1] * p.shape[2] return (1 / (4 * N * M)) * tf.reduce_sum(tf.pow(x - p, 2)) 
return _content_loss(sess.run(model['conv4_2']), model['conv4_2'])
  1. 定义我们要重用的 VGG 层。 如果我们希望具有更柔和的特征,则需要增加较高层的权重(conv5_1)和降低较低层的权重(conv1_1)。 如果我们想拥有更难的特征,我们需要做相反的事情:
STYLE_LAYERS = [ 
('conv1_1', 0.5), 
('conv2_1', 1.0), 
('conv3_1', 1.5), 
('conv4_1', 3.0), 
('conv5_1', 4.0), 
]
  1. 定义风格损失函数,如原始论文中所述:
def style_loss_func(sess, model):
   """
   Style loss function as defined in the paper.
   """

   def _gram_matrix(F, N, M):
    """
       The gram matrix G.
       """
       Ft = tf.reshape(F, (M, N))
       return tf.matmul(tf.transpose(Ft), Ft)

   def _style_loss(a, x):
       """
       The style loss calculation.
       """
       # N is the number of filters (at layer l).
       N = a.shape[3]
       # M is the height times the width of the feature map (at layer l).
       M = a.shape[1] * a.shape[2]
       # A is the style representation of the original image (at layer l).
       A = _gram_matrix(a, N, M)
       # G is the style representation of the generated image (at layer l).
       G = _gram_matrix(x, N, M)
       result = (1 / (4 * N**2 * M**2)) * tf.reduce_sum(tf.pow(G - A, 2))
       return result
       E = [_style_loss(sess.run(model[layer_name]), model[layer_name])
           for layer_name, _ in STYLE_LAYERS]
       W = [w for _, w in STYLE_LAYERS]
       loss = sum([W[l] * E[l] for l in range(len(STYLE_LAYERS))])
   return loss
  1. 定义一个函数以生成噪声图像,并将其与内容图像按给定比例混合。 定义两种辅助方法来预处理和保存图像:
def generate_noise_image(content_image, noise_ratio = NOISE_RATIO):
 """   Returns a noise image intermixed with the content image at a certain ratio.
"""
   noise_image = np.random.uniform(
           -20, 20,
           (1,
            content_image[0].shape[0],
            content_image[0].shape[1],
            content_image[0].shape[2])).astype('float32')
   # White noise image from the content representation. Take a weighted average
   # of the values
   input_image = noise_image * noise_ratio + content_image * (1 - noise_ratio)
   return input_image

def process_image(image):
   # Resize the image for convnet input, there is no change but just
   # add an extra dimension.
   image = np.reshape(image, ((1,) + image.shape))
   # Input to the VGG model expects the mean to be subtracted.
   image = image - MEAN_VALUES
   return image

def save_image(path, image):
   # Output should add back the mean.
   image = image + MEAN_VALUES
   # Get rid of the first useless dimension, what remains is the image.
   image = image[0]
   image = np.clip(image, 0, 255).astype('uint8')
   scipy.misc.imsave(path, image)
  1. 开始一个 TensorFlow 交互式会话:
sess = tf.InteractiveSession()
  1. 加载处理后的内容图像并显示:
content_image = load_image(CONTENT_IMAGE) imshow(content_image[0])

我们得到以下代码的输出(请注意,我们使用了来自这里的图像):

  1. 加载处理后的风格图像并显示它:
style_image = load_image(STYLE_IMAGE) imshow(style_image[0])

内容如下:

  1. 加载model并显示:
model = load_vgg_model(VGG_MODEL, style_image[0].shape[0], style_image[0].shape[1], style_image[0].shape[2]) print(model)
  1. 生成用于启动重新绘制的随机噪声图像:
input_image = generate_noise_image(content_image) imshow(input_image[0])
  1. 运行 TensorFlow 会话:
sess.run(tf.initialize_all_variables())
  1. 用相应的图像构造content_losssytle_loss
# Construct content_loss using content_image. sess.run(model['input'].assign(content_image))
content_loss = content_loss_func(sess, model) 
# Construct style_loss using style_image. sess.run(model['input'].assign(style_image)) 
style_loss = style_loss_func(sess, model)
  1. total_loss构造为content_losssytle_loss的加权组合:
# Construct total_loss as weighted combination of content_loss and sytle_loss 
total_loss = BETA * content_loss + ALPHA * style_loss
  1. 建立一个优化器以最大程度地减少总损失。 在这种情况下,我们采用 Adam 优化器:
# The content is built from one layer, while the style is from five 
# layers. Then we minimize the total_loss 
optimizer = tf.train.AdamOptimizer(2.0) 
train_step = optimizer.minimize(total_loss)
  1. 使用输入图像启动网络:
sess.run(tf.initialize_all_variables()) sess.run(model['input'].assign(input_image))
  1. 对模型运行固定的迭代次数,并生成中间的重绘图像:
sess.run(tf.initialize_all_variables())
sess.run(model['input'].assign(input_image))
print "started iteration"
for it in range(ITERATIONS):
   sess.run(train_step)
   print it , " "
   if it%100 == 0:
       # Print every 100 iteration.
       mixed_image = sess.run(model['input'])
       print('Iteration %d' % (it))
       print('sum : ',
sess.run(tf.reduce_sum(mixed_image)))
       print('cost: ', sess.run(total_loss))
       if not os.path.exists(OUTPUT_DIR):
           os.mkdir(OUTPUT_DIR)
       filename = 'output/%d.png' % (it)
       save_image(filename, mixed_image)
  1. 在此图像中,我们显示了在 200、400 和 600 次迭代后如何重新绘制内容图像:

风格迁移的例子

工作原理

在本秘籍中,我们已经看到了如何使用风格转换来重绘内容图像。 风格图像已作为神经网络的输入提供,该网络学习了定义画家采用的风格的关键方面。 这些方面已用于将风格迁移到内容图像。

更多

自 2015 年提出原始建议以来,风格转换一直是活跃的研究领域。已经提出了许多新想法来加速计算并将风格转换扩展到视频分析。 其中有两个结果值得一提

这篇文章是 Logan Engstrom 的快速风格转换,介绍了一种非常快速的实现,该实现也可以与视频一起使用。

通过 deepart 网站,您可以播放自己的图像,并以自己喜欢的艺术家的风格重新绘制图片。 还提供了 Android 应用,iPhone 应用和 Web 应用。

将预训练的 VGG16 网络用于迁移学习

在本秘籍中,我们将讨论迁移学习,这是一种非常强大的深度学习技术,在不同领域中都有许多应用。 直觉非常简单,可以用类推来解释。 假设您想学习一种新的语言,例如西班牙语,那么从另一种语言(例如英语)已经知道的内容开始可能会很有用。

按照这种思路,计算机视觉研究人员现在通常使用经过预训练的 CNN 来生成新颖任务的表示形式,其中数据集可能不足以从头训练整个 CNN。 另一个常见的策略是采用经过预先训练的 ImageNet 网络,然后将整个网络微调到新颖的任务。 此处提出的示例的灵感来自 Francois Chollet 在 Keras 的著名博客文章

准备

想法是使用在大型数据集(如 ImageNet)上预训练的 VGG16 网络。 请注意,训练在计算上可能会相当昂贵,因此可以重用已经预先训练的网络:

A VGG16 Network

那么,如何使用 VGG16? Keras 使该库变得容易,因为该库具有可作为库使用的标准 VGG16 应用,并且自动下载了预先计算的权重。 请注意,我们明确省略了最后一层,并用我们的自定义层替换了它,这将在预构建的 VGG16 的顶部进行微调。 在此示例中,您将学习如何对 Kaggle 提供的猫狗图像进行分类。

操作步骤

我们按以下步骤进行:

  1. 从 Kaggle(https://www.kaggle.com/c/dogs-vs-cats/data)下载猫狗数据,并创建一个包含两个子目录的数据目录,trainvalidation,每个子目录都有两个附加子目录, 狗和猫。
  2. 导入 Keras 模块,这些模块将在以后的计算中使用,并保存一些有用的常量:
from keras import applications
from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers
from keras.models import Sequential, Model
from keras.layers import Dropout, Flatten, Dense
from keras import optimizers
img_width, img_height = 256, 256
batch_size = 16
epochs = 50
train_data_dir = 'data/dogs_and_cats/train'
validation_data_dir = 'data/dogs_and_cats/validation'
#OUT CATEGORIES
OUT_CATEGORIES=1
#number of train, validation samples
nb_train_samples = 2000
nb_validation_samples =
  1. 将预训练的图像加载到 ImageNet VGG16 网络上,并省略最后一层,因为我们将在预构建的 VGG16 的顶部添加自定义分类网络并替换最后的分类层:
# load the VGG16 model pretrained on imagenet 
base_model = applications.VGG16(weights = "imagenet", include_top=False, input_shape = (img_width, img_height, 3)) 
base_model.summary()

这是前面代码的输出:

  1. 冻结一定数量的较低层用于预训练的 VGG16 网络。 在这种情况下,我们决定冻结最初的 15 层:
# Freeze the 15 lower layers for layer in base_model.layers[:15]: layer.trainable = False
  1. 添加一组自定义的顶层用于分类:
# Add custom to layers # build a classifier model to put on top of the convolutional model 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(OUT_CATEGORIES, activation='sigmoid'))
  1. 定制网络应单独进行预训练,在这里,为简单起见,我们省略了这一部分,将这一任务留给了读者:
#top_model.load_weights(top_model_weights_path)
  1. 创建一个新网络,该网络与预训练的 VGG16 网络和我们的预训练的自定义网络并置:
# creating the final model, a composition of 
# pre-trained and 
model = Model(inputs=base_model.input, outputs=top_model(base_model.output)) 
# compile the model 
model.compile(loss = "binary_crossentropy", optimizer = optimizers.SGD(lr=0.0001, momentum=0.9), metrics=["accuracy"])
  1. 重新训练并列的新模型,仍将 VGG16 的最低 15 层冻结。 在这个特定的例子中,我们还使用图像增幅器来增加训练集:
# Initiate the train and test generators with data Augumentation
train_datagen = ImageDataGenerator(
rescale = 1./255,
horizontal_flip = True)
test_datagen = ImageDataGenerator(rescale=1\. / 255)
train_generator = train_datagen.flow_from_directory(
   train_data_dir,
   target_size=(img_height, img_width),
   batch_size=batch_size,
   class_mode='binary')
validation_generator = test_datagen.flow_from_directory(
   validation_data_dir,
   target_size=(img_height, img_width),
   batch_size=batch_size,
   class_mode='binary', shuffle=False)
model.fit_generator(
   train_generator,
   steps_per_epoch=nb_train_samples // batch_size,
   epochs=epochs,
   validation_data=validation_generator,
   validation_steps=nb_validation_samples // batch_size,
   verbose=2, workers=12)
  1. 在并置的网络上评估结果:
score = model.evaluate_generator(validation_generator, nb_validation_samples/batch_size) 
scores = model.predict_generator(validation_generator, nb_validation_samples/batch_size)

工作原理

标准的 VGG16 网络已经在整个 ImageNet 上进行了预训练,并具有从互联网下载的预先计算的权重。 然后,将该网络与也已单独训练的自定义网络并置。 然后,并列的网络作为一个整体进行了重新训练,使 VGG16 的 15 个较低层保持冻结。

这种组合非常有效。 通过对网络在 ImageNet 上学到的知识进行迁移学习,将其应用于我们的新特定领域,从而执行微调分类任务,它可以节省大量的计算能力,并重复使用已为 VGG16 执行的工作。

更多

根据特定的分类任务,需要考虑一些经验法则:

  • 如果新数据集很小并且类似于 ImageNet 数据集,那么我们可以冻结所有 VGG16 网络并仅重新训练自定义网络。 通过这种方式,我们还将并置网络的过拟合风险降至最低:

#冻结base_model.layers中所有较低的层:layer.trainable = False

  • 如果新数据集很大且类似于 ImageNet 数据集,则我们可以重新训练整个并列的网络。 我们仍然将预先计算的权重作为起点,并进行一些迭代以进行微调:

#取消冻结model.layers中所有较低层的层:layer.trainable = True

  • 如果新数据集与 ImageNet 数据集非常不同,则在实践中,使用预训练模型中的权重进行初始化可能仍然很好。 在这种情况下,我们将有足够的数据和信心来调整整个网络。 可以在这里在线找到更多信息。

创建 DeepDream 网络

Google 于 2014 年训练了神经网络以应对 ImageNet 大规模视觉识别挑战ILSVRC),并于 2015 年 7 月将其开源。“深入了解卷积”中介绍了原始算法。 网络学会了每个图像的表示。 较低的层学习诸如线条和边缘之类的底层特征,而较高的层则学习诸如眼睛,鼻子,嘴等更复杂的图案。 因此,如果尝试在网络中代表更高的级别,我们将看到从原始 ImageNet 提取的各种不同特征的混合,例如鸟的眼睛和狗的嘴巴。 考虑到这一点,如果我们拍摄一张新图像并尝试使与网络上层的相似性最大化,那么结果就是一张新的有远见的图像。 在这个有远见的图像中,较高层学习的某些模式在原始图像中被梦到(例如,想象中)。 这是此类有远见的图像的示例:

如以下所示的 Google DeepDreams 示例

准备

从网上下载预训练的 Inception 模型

操作步骤

我们按以下步骤进行操作:

  1. 导入numpy进行数值计算,导入functools定义已填充一个或多个参数的部分函数,​​导入 Pillow 进行图像处理,并导入matplotlib呈现图像:
import numpy as np from functools 
import partial import PIL.Image 
import tensorflow as tf 
import matplotlib.pyplot as plt
  1. 设置内容图像和预训练模型的路径。 从只是随机噪声的种子图像开始:
content_image = 'data/gulli.jpg' 
# start with a gray image with a little noise 
img_noise = np.random.uniform(size=(224,224,3)) + 100.0 
model_fn = 'data/tensorflow_inception_graph.pb'
  1. 在图表中加载从互联网下载的 Inception 网络。 初始化 TensorFlow 会话,使用FastGFile(..)加载图,然后使用ParseFromstring(..)解析图。 之后,使用placeholder(..)方法创建一个输入作为占位符。 imagenet_mean是一个预先计算的常数,将从我们的内容图像中删除以标准化数据。 实际上,这是在训练过程中观察到的平均值,归一化可以更快地收敛。 该值将从输入中减去,并存储在t_preprocessed变量中,该变量然后用于加载图定义:
# load the graph
graph = tf.Graph()
sess = tf.InteractiveSession(graph=graph)
with tf.gfile.FastGFile(model_fn, 'rb') as f:
       graph_def = tf.GraphDef()
       graph_def.ParseFromString(f.read())
t_input = tf.placeholder(np.float32, name='input') # define
the input tensor
imagenet_mean = 117.0
t_preprocessed = tf.expand_dims(t_input-imagenet_mean, 0)
tf.import_graph_def(graph_def, {'input':t_preprocessed})  
  1. 定义一些util函数以可视化图像并将 TF-graph 生成函数转换为常规 Python 函数(请参见以下示例以调整大小):
# helper
#pylint: disable=unused-variable
def showarray(a):
   a = np.uint8(np.clip(a, 0, 1)*255)
   plt.imshow(a)
   plt.show()   
def visstd(a, s=0.1):
   '''Normalize the image range for visualization'''
   return (a-a.mean())/max(a.std(), 1e-4)*s + 0.5   

def T(layer):
   '''Helper for getting layer output tensor'''
   return graph.get_tensor_by_name("import/%s:0"%layer)   

def tffunc(*argtypes):
   '''Helper that transforms TF-graph generating function into a regular one.
   See "resize" function below.
   '''
   placeholders = list(map(tf.placeholder, argtypes))
   def wrap(f):
       out = f(*placeholders)
       def wrapper(*args, **kw):
           return out.eval(dict(zip(placeholders, args)), session=kw.get('session'))
       return wrapper
   return wrap   

def resize(img, size):
   img = tf.expand_dims(img, 0)
   return tf.image.resize_bilinear(img, size)[0,:,:,:]
resize = tffunc(np.float32, np.int32)(resize)
  1. 计算图像上的梯度上升。 为了提高效率,请应用分块计算,其中在不同分块上计算单独的梯度上升。 将随机移位应用于图像,以在多次迭代中模糊图块边界:
def calc_grad_tiled(img, t_grad, tile_size=512):
   '''Compute the value of tensor t_grad over the image in a tiled way.
   Random shifts are applied to the image to blur tile boundaries over
   multiple iterations.'''
   sz = tile_size
   h, w = img.shape[:2]
   sx, sy = np.random.randint(sz, size=2)
   img_shift = np.roll(np.roll(img, sx, 1), sy, 0)
   grad = np.zeros_like(img)
   for y in range(0, max(h-sz//2, sz),sz):
       for x in range(0, max(w-sz//2, sz),sz):
           sub = img_shift[y:y+sz,x:x+sz]
           g = sess.run(t_grad, {t_input:sub})
           grad[y:y+sz,x:x+sz] = g

   return np.roll(np.roll(grad, -sx, 1), -sy, 0)    
  1. 定义优化对象以减少输入层的均值。 gradient函数允许我们通过考虑输入张量来计算优化张量的符号梯度。 为了提高效率,将图像分成多个八度,然后调整大小并添加到八度数组中。 然后,对于每个八度,我们使用calc_grad_tiled函数:
def render_deepdream(t_obj, img0=img_noise,
                        iter_n=10, step=1.5, octave_n=4, octave_scale=1.4):
   t_score = tf.reduce_mean(t_obj) # defining the optimization objective
 t_grad = tf.gradients(t_score, t_input)[0] # behold the power of automatic differentiation!

   # split the image into a number of octaves
   img = img0
   octaves = []
   for _ in range(octave_n-1):
       hw = img.shape[:2]
       lo = resize(img,
np.int32(np.float32(hw)/octave_scale))
       hi = img-resize(lo, hw)
       img = lo
       octaves.append(hi)       
# generate details octave by octave
   for octave in range(octave_n):
       if octave>0:
           hi = octaves[-octave]
           img = resize(img, hi.shape[:2])+hi
       for _ in range(iter_n):
           g = calc_grad_tiled(img, t_grad)
           img += g*(step / (np.abs(g).mean()+1e-7))

           #this will usually be like 3 or 4 octaves
           #Step 5 output deep dream image via matplotlib
       showarray(img/255.0)
  1. 加载特定的内容图像并开始做梦。 在此示例中,作者的面孔已转变为类似于狼的事物:

DeepDream 转换的示例。 其中一位作家变成了狼

工作原理

神经网络存储训练图像的抽象:较低的层存储诸如线条和边缘之类的特征,而较高的层则存储诸如眼睛,面部和鼻子之类的更复杂的图像特征。 通过应用梯度上升过程,我们最大化了loss函数,并有助于发现类似于高层存储的图案的内容图像。 这导致了网络看到虚幻图像的梦想。

更多

许多网站都允许您直接玩 DeepDream。 我特别喜欢DeepArt.io,它允许您上传内容图像和风格图像并在云上进行学习。

另见

在 2015 年发布初步结果之后,还发布了许多有关 DeepDream 的新论文和博客文章:

DeepDream: A code example to visualize Neural Networks--https://research.googleblog.com/2015/07/deepdream-code-example-for-visualizing.html

When Robots Hallucinate, LaFrance, Adrienne--https://www.theatlantic.com/technology/archive/2015/09/robots-hallucinate-dream/403498/

此外,了解如何可视化预训练网络的每一层并更好地了解网络如何记忆较低层的基本特征以及较高层的较复杂特征可能会很有趣。 在线提供有关此主题的有趣博客文章:

五、高级卷积神经网络

在本章中,我们将讨论如何将卷积神经网络CNN)用于除图像以外的领域中的深度学习。 我们的注意力将首先集中在文本分析和自然语言处理NLP)上。 在本章中,我们将介绍一些用于以下方面的方法:

  • 创建卷积网络进行情感分析
  • 检查 VGG 预建网络学习了哪些过滤器
  • 使用 VGGNet,ResNet,Inception 和 Xception 对图像进行分类
  • 复用预先构建的深度学习模型来提取特征
  • 用于迁移学习的非常深的 Inception-v3 网络
  • 使用膨胀的 ConvNets,WaveNet 和 NSynth 生成音乐
  • 回答有关图像的问题(可视化问答)
  • 使用预训练网络通过六种不同方式来分类视频

介绍

在上一章中,我们了解了如何将 ConvNets 应用于图像。 在本章中,我们将类似的思想应用于文本。

文本和图像有什么共同点? 乍一看,很少。 但是,如果我们将句子或文档表示为矩阵,则此矩阵与每个单元都是像素的图像矩阵没有区别。 因此,下一个问题是,我们如何将文本表示为矩阵? 好吧,这很简单:矩阵的每一行都是一个向量,代表文本的基本单位。 当然,现在我们需要定义什么是基本单位。 一个简单的选择就是说基本单位是一个字符。 另一个选择是说基本单位是一个单词,另一个选择是将相似的单词聚合在一起,然后用代表符号表示每个聚合(有时称为簇或嵌入)。

请注意,无论我们的基本单位采用哪种具体选择,我们都需要从基本单位到整数 ID 的 1:1 映射,以便可以将文本视为矩阵。 例如,如果我们有一个包含 10 行文本的文档,并且每行都是 100 维嵌入,那么我们将用10 x 100的矩阵表示文本。 在这个非常特殊的图像中,如果该句子x包含位置y表示的嵌入,则打开像素。 您可能还会注意到,文本实际上不是矩阵,而是向量,因为位于文本相邻行中的两个单词几乎没有共同点。 确实,与图像的主要区别在于,相邻列中的两个像素最有可能具有某种相关性。

现在您可能会想:我知道您将文本表示为向量,但是这样做会使我们失去单词的位置,而这个位置应该很重要,不是吗?

好吧,事实证明,在许多实际应用中,知道一个句子是否包含特定的基本单位(一个字符,一个单词或一个合计)是非常准确的信息,即使我们不记住句子中的确切位置也是如此。 基本单元位于。

创建用于情感分析的卷积网络

在本秘籍中,我们将使用 TFLearn 创建基于 CNN 的情感分析深度学习网络。 如上一节所述,我们的 CNN 将是一维的。 我们将使用 IMDb 数据集,用于训练的 45,000 个高度受欢迎的电影评论和用于测试的 5,000 个集合。

准备

TFLearn 具有用于自动从网络下载数据集并促进卷积网络创建的库,因此让我们直接看一下代码。

操作步骤

我们按以下步骤进行:

  1. 导入 TensorFlow tflearn和构建网络所需的模块。 然后,导入 IMDb 库并执行一键编码和填充:
import tensorflow as tf
import tflearn
from tflearn.layers.core import input_data, dropout, fully_connected
from tflearn.layers.conv import conv_1d, global_max_pool
from tflearn.layers.merge_ops import merge
from tflearn.layers.estimator import regression
from tflearn.data_utils import to_categorical, pad_sequences
from tflearn.datasets import imdb
  1. 加载数据集,将句子填充到最大长度为 0 的位置,并对标签执行两个编码,分别对应于真值和假值的两个值。 注意,参数n_words是要保留在词汇表中的单词数。 所有多余的单词都设置为未知。 另外,请注意trainXtrainY是稀疏向量,因为每个评论很可能包含整个单词集的子集:
# IMDb Dataset loading
train, test, _ = imdb.load_data(path='imdb.pkl', n_words=10000,
valid_portion=0.1)
trainX, trainY = train
testX, testY = test
#pad the sequence
trainX = pad_sequences(trainX, maxlen=100, value=0.)
testX = pad_sequences(testX, maxlen=100, value=0.)
#one-hot encoding
trainY = to_categorical(trainY, nb_classes=2)
testY = to_categorical(testY, nb_classes=2)
  1. 打印一些维度以检查刚刚处理的数据并了解问题的维度是什么:
print ("size trainX", trainX.size)
print ("size testX", testX.size)
print ("size testY:", testY.size)
print ("size trainY", trainY.size)
size trainX 2250000
 size testX 250000
 size testY: 5000
 site trainY 45000
  1. 为数据集中包含的文本构建嵌入。 就目前而言,将此步骤视为一个黑盒子,该黑盒子接受这些单词并将它们映射到聚合(群集)中,以便相似的单词可能出现在同一群集中。 请注意,先前步骤的词汇是离散且稀疏的。 通过嵌入,我们将创建一个映射,该映射会将每个单词嵌入到连续的密集向量空间中。 使用此向量空间表示将为我们提供词汇表的连续,分布式表示。 当我们谈论 RNN 时,将详细讨论如何构建嵌入:
# Build an embedding
network = input_data(shape=[None, 100], name='input')
network = tflearn.embedding(network, input_dim=10000, output_dim=128)
  1. 建立一个合适的convnet。 我们有三个卷积层。 由于我们正在处理文本,因此我们将使用一维卷积网络,并且各层将并行运行。 每层采用大小为 128 的张量(嵌入的输出),并应用有效填充,激活函数 ReLU 和 L2 regularizer的多个滤波器(分别为 3、4、5)。 然后,将每个层的输出与合并操作连接在一起。 此后,添加一个最大池层,然后以 50% 的概率进行删除。 最后一层是具有 softmax 激活的完全连接层:
#Build the convnet
branch1 = conv_1d(network, 128, 3, padding='valid', activation='relu', regularizer="L2")
branch2 = conv_1d(network, 128, 4, padding='valid', activation='relu', regularizer="L2")
branch3 = conv_1d(network, 128, 5, padding='valid', activation='relu', regularizer="L2")
network = merge([branch1, branch2, branch3], mode='concat', axis=1)
network = tf.expand_dims(network, 2)
network = global_max_pool(network)
network = dropout(network, 0.5)
network = fully_connected(network, 2, activation='softmax')
  1. 学习阶段意味着使用categorical_crossentropy作为损失函数的 Adam 优化器:
network = regression(network, optimizer='adam', learning_rate=0.001,
loss='categorical_crossentropy', name='target')
  1. 然后,我们使用batch_size = 32运行训练,并观察训练和验证集达到的准确率。 如您所见,在预测电影评论所表达的情感方面,我们能够获得 79% 的准确率:
# Training
model = tflearn.DNN(network, tensorboard_verbose=0)
model.fit(trainX, trainY, n_epoch = 5, shuffle=True, validation_set=(testX, testY), show_metric=True, batch_size=32)
Training Step: 3519 | total loss: 0.09738 | time: 85.043s
 | Adam | epoch: 005 | loss: 0.09738 - acc: 0.9747 -- iter: 22496/22500
 Training Step: 3520 | total loss: 0.09733 | time: 86.652s
 | Adam | epoch: 005 | loss: 0.09733 - acc: 0.9741 | val_loss: 0.58740 - val_acc: 0.7944 -- iter: 22500/22500
 --

工作原理

用于句子分类的卷积神经网络,Yoon Kim,EMNLP 2014。 请注意,由于筛选器窗口对连续单词进行操作,因此本文提出的模型保留了一些有关位置的信息。 从论文中提取的以下图像以图形方式表示了网络之外的主要直觉。 最初,文本被表示为基于标准嵌入的向量,从而为我们提供了一维密集空间中的紧凑表示。 然后,使用多个标准一维卷积层处理矩阵。

请注意,模型使用多个过滤器(窗口大小不同)来获取多个特征。 之后,进行最大池操作,其思想是捕获最重要的特征-每个特征图的最大值。 为了进行正则化,该文章建议在倒数第二层上采用对权重向量的 L2 范数有约束的丢弃项。 最后一层将输出情感为正或负。

为了更好地理解该模型,有以下几点观察:

  • 过滤器通常在连续空间上卷积。 对于图像,此空间是像素矩阵表示形式,在高度和宽度上在空间上是连续的。 对于文本而言,连续空间无非是连续单词自然产生的连续尺寸。 如果仅使用单次编码表示的单词,则空间稀疏;如果使用嵌入,则由于聚集了相似的单词,因此生成的空间密集。
  • 图像通常具有三个通道(RGB),而文本自然只有一个通道,因为我们无需表示颜色。

更多

论文《用于句子分类的卷积神经网络》(Yoon Kim,EMNLP 2014)进行了广泛的实验。 尽管对超参数的调整很少,但具有一层卷积的简单 CNN 在句子分类方面的表现却非常出色。 该论文表明,采用一组静态嵌入(将在我们谈论 RNN 时进行讨论),并在其之上构建一个非常简单的卷积网络,实际上可以显着提高情感分析的表现:

如图所示的模型架构示例

使用 CNN 进行文本分析是一个活跃的研究领域。 我建议看看以下文章:

  • 《从头开始理解文本》(张翔,Yann LeCun)。 本文演示了我们可以使用 CNN 将深度学习应用于从字符级输入到抽象文本概念的文本理解。 作者将 CNN 应用于各种大规模数据集,包括本体分类,情感分析和文本分类,并表明它们可以在不了解单词,词组,句子或任何其他句法或语义结构的情况下实现惊人的表现。 一种人类的语言。 这些模型适用于英文和中文。

检查 VGG 预建网络了解了哪些过滤器

在本秘籍中,我们将使用 keras-vis,这是一个外部 Keras 包,用于直观检查预建的 VGG16 网络从中学到了什么不同的过滤器。 这个想法是选择一个特定的 ImageNet 类别,并了解 VGG16 网络如何学会代表它。

准备

第一步是选择用于在 ImageNet 上训练 VGG16 的特定类别。 假设我们采用类别 20,它对应于下图中显示的美国北斗星鸟:

美国北斗星的一个例子

可以在网上找到 ImageNet 映射作为 python 泡菜字典,其中 ImageNet 1000 类 ID 映射到了人类可读的标签。

操作步骤

我们按以下步骤进行:

  1. 导入 matplotlib 和 keras-vis 使用的模块。 此外,还导入预构建的 VGG16 模块。 Keras 使处理此预建网络变得容易:
from matplotlib import pyplot as plt
from vis.utils import utils
from vis.utils.vggnet import VGG16
from vis.visualization import visualize_class_activation
  1. 通过使用 Keras 中包含的并经过 ImageNet 权重训练的预构建层来访问 VGG16 网络:
# Build the VGG16 network with ImageNet weights
model = VGG16(weights='imagenet', include_top=True)
model.summary()
print('Model loaded.')
  1. 这就是 VGG16 网络在内部的外观。 我们有许多卷积网络,与 2D 最大池化交替使用。 然后,我们有一个展开层,然后是三个密集层。 最后一个称为预测,并且这一层应该能够检测到高级特征,例如人脸或我们的鸟类形状。 请注意,顶层已明确包含在我们的网络中,因为我们想可视化它学到的知识:
_________________________________________________________________
 Layer (type) Output Shape Param #
 =================================================================
 input_2 (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
 _________________________________________________________________
 flatten (Flatten) (None, 25088) 0
 _________________________________________________________________
 fc1 (Dense) (None, 4096) 102764544
 _________________________________________________________________
 fc2 (Dense) (None, 4096) 16781312
 _________________________________________________________________
 predictions (Dense) (None, 1000) 4097000
 =================================================================
 Total params: 138,357,544
 Trainable params: 138,357,544
 Non-trainable params: 0
 _________________________________________________________________
 Model loaded.

从外观上看,网络可以如下图所示:

VGG16 网络

  1. 现在,让我们着重于通过关注 American Dipper(ID 20)来检查最后一个预测层的内部外观:
layer_name = 'predictions'
layer_idx = [idx for idx, layer in enumerate(model.layers) if layer.name == layer_name][0]
# Generate three different images of the same output index.
vis_images = []
for idx in [20, 20, 20]:
img = visualize_class_activation(model, layer_idx, filter_indices=idx, max_iter=500)
img = utils.draw_text(img, str(idx))
vis_images.append(img)
  1. 让我们在给定特征的情况下显示特定层的生成图像,并观察网络如何在内部看到美国北斗星鸟的概念:

因此,这就是神经网络在内部代表鸟类的方式。 这是一种令人毛骨悚然的形象,但我发誓没有为网络本身提供任何特定种类的人造药物! 这正是这种特殊的人工网络自然学到的东西。

  1. 您是否仍然想了解更多? 好吧,让我们选择一个较早的层,并代表网络如何在内部看到相同的American Dipper训练类别:
layer_name = 'block3_conv1'
layer_idx = [idx for idx, layer in enumerate(model.layers) if layer.name == layer_name][0]
vis_images = []
for idx in [20, 20, 20]:
img = visualize_class_activation(model, layer_idx, filter_indices=idx, max_iter=500)
img = utils.draw_text(img, str(idx))
vis_images.append(img)
stitched = utils.stitch_images(vis_images)
plt.axis('off')
plt.imshow(stitched)
plt.title(layer_name)
plt.show()

以下是上述代码的输出:

不出所料,该特定层正在学习非常基本的特征,例如曲线。 但是,卷积网络的真正力量在于,随着我们对模型的深入研究,网络会推断出越来越复杂的特征。

工作原理

密集层的 keras-vis 可视化的关键思想是生成一个输入图像,该图像最大化与鸟类类相对应的最终密集层输出。 因此,实际上该模块的作用是解决问题。 给定具有权重的特定训练密集层,将生成一个新的合成图像,它最适合该层本身。

每个转换滤波器都使用类似的想法。 在这种情况下,请注意,由于卷积网络层在原始像素上运行,因此可以通过简单地可视化其权重来解释它。 后续的卷积过滤器对先前的卷积过滤器的输出进行操作,因此直接可视化它们不一定很有解释性。 但是,如果我们独立地考虑每一层,我们可以专注于仅生成可最大化滤波器输出的合成输入图像。

更多

GitHub 上的 keras-vis 存储库提供了一组很好的可视化示例,这些示例说明了如何内部检查网络,包括最近的显着性映射,其目的是在图像经常包含其他元素(例如草)时检测图像的哪个部分对特定类别(例如老虎)的训练贡献最大。 种子文章是《深度卷积网络:可视化图像分类模型和显着性图》(Karen Simonyan,Andrea Vedaldi,Andrew Zisserman),并在下面报告了从 Git 存储库中提取的示例,在该示例中,网络可以自行了解定义为老虎的图像中最突出的部分是:

显着性映射的示例

将 VGGNet,ResNet,Inception 和 Xception 用于图像分类

图像分类是典型的深度学习应用。 由于 ImageNet 图像数据库,该任务的兴趣有了最初的增长。 它按照 WordNet 层次结构(目前仅是名词)来组织,其中每个节点都由成百上千的图像描绘。 更准确地说,ImageNet 旨在将图像标记和分类为将近 22,000 个单独的对象类别。 在深度学习的背景下,ImageNet 通常指的是 ImageNet 大规模视觉识别挑战,或简称 ILSVRC 中包含的工作。在这种情况下,目标是训练一个模型,该模型可以将输入图像分类为 1,000 个单独的对象类别。 在此秘籍中,我们将使用超过 120 万个训练图像,50,000 个验证图像和 100,000 个测试图像的预训练模型。

VGG16 和 VGG19

《用于大型图像识别的超深度卷积网络》(Karen Simonyan,Andrew Zisserman,2014 年)中,引入了 VGG16 和 VGG19。 该网络使用3×3卷积层堆叠并与最大池交替,两个 4096 个全连接层,然后是 softmax 分类器。 16 和 19 代表网络中权重层的数量(列 D 和 E):

一个非常深的网络配置示例

在 2015 年,拥有 16 或 19 层就足以考虑网络的深度,而今天(2017 年)我们达到了数百层。 请注意,VGG 网络的训练速度非常慢,并且由于末端的深度和完全连接的层数,它们需要较大的权重空间。

ResNet

ResNet 已在《用于图像识别的深度残差学习》(何开明,张向宇,任少青,孙健,2015)中引入。 该网络非常深,可以使用称为残差模块的标准网络组件使用标准的随机下降梯度进行训练,然后使用该网络组件组成更复杂的网络(该网络在网络中称为子网络)。

与 VGG 相比,ResNet 更深,但是模型的大小更小,因为使用了全局平均池化操作而不是全密层。

Inception

《重新思考计算机视觉的初始架构》(Christian Szegedy,Vincent Vanhoucke,Sergey Ioffe,Jonathon Shlens,Zbigniew Wojna,2015 年)中引入了 Inception 。关键思想是在同一模块中具有多种大小的卷积作为特征提取并计算1×13×35×5卷积。 这些滤波器的输出然后沿着通道尺寸堆叠,并发送到网络的下一层。 下图对此进行了描述:

在“重新思考计算机视觉的 Inception 架构”中描述了 Inception-v3,而在《Inception-v4,Inception-ResNet 和残余连接对学习的影响》(Szegedy,Sergey Ioffe,Vincent Vanhoucke,Alex Alemi,2016 年)中描述了 Inception-v4。

Xception

Xception 是 Inception 的扩展,在《Xception:具有深度可分离卷积的深度学习》(FrançoisChollet,2016 年)中引入。 Xception 使用一种称为深度可分离卷积运算的新概念,该概念使其在包含 3.5 亿张图像和 17,000 个类别的大型图像分类数据集上的表现优于 Inception-v3。 由于 Xception 架构具有与 Inception-v3 相同数量的参数,因此表现的提高并不是由于容量的增加,而是由于模型参数的更有效使用。

准备

此秘籍使用 Keras,因为该框架已预先完成了上述模块的实现。 Keras 首次使用时会自动下载每个网络的权重,并将这些权重存储在本地磁盘上。 换句话说,您不需要重新训练网络,而是可以利用互联网上已经可用的训练。 在您希望将网络分类为 1000 个预定义类别的假设下,这是正确的。 在下一个秘籍中,我们将了解如何从这 1,000 个类别开始,并通过称为迁移学习的过程将它们扩展到自定义集合。

操作步骤

我们按以下步骤进行:

  1. 导入处理和显示图像所需的预建模型和其他模块:
from keras.applications import ResNet50
from keras.applications import InceptionV3
from keras.applications import Xception # TensorFlow ONLY
from keras.applications import VGG16
from keras.applications import VGG19
from keras.applications import imagenet_utils
from keras.applications.inception_v3 import preprocess_input
from keras.preprocessing.image import img_to_array
from keras.preprocessing.image import load_img
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.pyplot import imshow
from PIL import Image
%matplotlib inline
  1. 定义用于记忆用于训练网络的图像大小的映射。 这些是每个模型的众所周知的常数:
MODELS = {
"vgg16": (VGG16, (224, 224)),
"vgg19": (VGG19, (224, 224)),
"inception": (InceptionV3, (299, 299)),
"xception": (Xception, (299, 299)), # TensorFlow ONLY
"resnet": (ResNet50, (224, 224))
}
  1. 定义用于加载和转换每个图像的辅助函数。 注意,预训练网络已在张量上训练,该张量的形状还包括batch_size的附加维度。 因此,我们需要将此尺寸添加到图像中以实现兼容性:
def image_load_and_convert(image_path, model):
pil_im = Image.open(image_path, 'r')
imshow(np.asarray(pil_im))
# initialize the input image shape
# and the pre-processing function (this might need to be changed
inputShape = MODELS[model][1]
preprocess = imagenet_utils.preprocess_input
image = load_img(image_path, target_size=inputShape)
image = img_to_array(image)
# the original networks have been trained on an additional
# dimension taking into account the batch size
# we need to add this dimension for consistency
# even if we have one image only
image = np.expand_dims(image, axis=0)
image = preprocess(image)
return image
  1. 定义用于对图像进行分类的辅助函数,并在预测上循环,并显示 5 级预测以及概率:
def classify_image(image_path, model):
img = image_load_and_convert(image_path, model)
Network = MODELS[model][0]
model = Network(weights="imagenet")
preds = model.predict(img)
P = imagenet_utils.decode_predictions(preds)
# loop over the predictions and display the rank-5 predictions
# along with probabilities
for (i, (imagenetID, label, prob)) in enumerate(P[0]):
print("{}. {}: {:.2f}%".format(i + 1, label, prob * 100))

5.然后开始测试不同类型的预训练网络:

classify_image("images/parrot.jpg", "vgg16")

接下来,您将看到具有相应概率的预测列表:
1.金刚鹦鹉:99.92%
2.美洲豹:0.03%
3.澳洲鹦鹉:0.02%
4.蜂食者:0.02%
5.巨嘴鸟:0.00%

金刚鹦鹉的一个例子

classify_image("images/parrot.jpg", "vgg19")

1.金刚鹦鹉:99.77%
2.鹦鹉:0.07%
3.巨嘴鸟:0.06%
4.犀鸟:0.05%
5.贾卡马尔:0.01%

classify_image("images/parrot.jpg", "resnet")

1.金刚鹦鹉:97.93%
2.孔雀:0.86%
3.鹦鹉:0.23%
4. j:0.12%
5.杰伊:0.12%

classify_image("images/parrot_cropped1.jpg", "resnet")

1.金刚鹦鹉:99.98%
2.鹦鹉:0.00%
3.孔雀:0.00%
4.硫凤头鹦鹉:0.00%
5.巨嘴鸟:0.00%

classify_image("images/incredible-hulk-180.jpg", "resnet")

1. comic_book:99.76%
2. book_jacket:0.19%
3.拼图游戏:0.05%
4.菜单:0.00%
5.数据包:0.00%

如中所示的漫画分类示例

classify_image("images/cropped_panda.jpg", "resnet")

大熊猫:99.04%
2.英迪尔:0.59%
3.小熊猫:0.17%
4.长臂猿:0.07%
5. titi:0.05%

classify_image("images/space-shuttle1.jpg", "resnet")

1.航天飞机:92.38%
2.三角恐龙:7.15%
3.战机:0.11%
4.牛仔帽:0.10%
5.草帽:0.04%

classify_image("images/space-shuttle2.jpg", "resnet")

1.航天飞机:99.96%
2.导弹:0.03%
3.弹丸:0.00%
4.蒸汽机车:0.00%
5.战机:0.00%

classify_image("images/space-shuttle3.jpg", "resnet")

1.航天飞机:93.21%
2.导弹:5.53%
3.弹丸:1.26%
4.清真寺:0.00%
5.信标:0.00%

classify_image("images/space-shuttle4.jpg", "resnet")

1.航天飞机:49.61%
2.城堡:8.17%
3.起重机:6.46%
4.导弹:4.62%
5.航空母舰:4.24%

请注意,可能会出现一些错误。 例如:

classify_image("images/parrot.jpg", "inception")

1.秒表:100.00%
2.貂皮:0.00%
3.锤子:0.00%
4.黑松鸡:0.00%
5.网站:0.00%

classify_image("images/parrot.jpg", "xception")

1.背包:56.69%
2.军装:29.79%
3.围兜:8.02%
4.钱包:2.14%
5.乒乓球:1.52%

  1. 定义一个辅助函数,用于显示每个预构建和预训练网络的内部架构:
def print_model(model):
print ("Model:",model)
Network = MODELS[model][0]
model = Network(weights="imagenet")
model.summary()
print_model('vgg19')
('Model:', 'vgg19')
 _________________________________________________________________
 Layer (type) Output Shape Param #
 =================================================================
 input_14 (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_conv4 (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_conv4 (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_conv4 (Conv2D) (None, 14, 14, 512) 2359808
 _________________________________________________________________
 block5_pool (MaxPooling2D) (None, 7, 7, 512) 0
 _________________________________________________________________
 flatten (Flatten) (None, 25088) 0
 _________________________________________________________________
 fc1 (Dense) (None, 4096) 102764544
 _________________________________________________________________
 fc2 (Dense) (None, 4096) 16781312
 _________________________________________________________________
 predictions (Dense) (None, 1000) 4097000
 =================================================================
 Total params: 143,667,240
 Trainable params: 143,667,240
 Non-trainable params: 0

工作原理

我们使用了 Keras 应用,预训练的 Keras 学习模型,该模型随预训练的权重一起提供。 这些模型可用于预测,特征提取和微调。 在这种情况下,我们将模型用于预测。 我们将在下一个秘籍中看到如何使用模型进行微调,以及如何在最初训练模型时最初不可用的数据集上构建自定义分类器。

更多

截至 2017 年 7 月,Inception-v4 尚未在 Keras 中直接提供,但可以作为单独的模块在线下载。 安装后,该模块将在首次使用时自动下载砝码。

AlexNet 是最早的堆叠式深层网络之一,它仅包含八层,前五层是卷积层,然后是全连接层。 该网络是在 2012 年提出的,明显优于第二名(前五名的错误率为 16%,而第二名的错误率为 26% )。

关于深度神经网络的最新研究主要集中在提高准确率上。 较小的 DNN 架构具有同等的准确率,至少具有三个优点:

  • 较小的 CNN 在分布式训练期间需要较少的跨服务器通信。
  • 较小的 CNN 需要较少的带宽才能将新模型从云导出到提供模型的位置。
  • 较小的 CNN 在具有有限内存的 FPGA 和其他硬件上部署更可行。 为了提供所有这些优点,SqueezeNet 在​​论文 SqueezeNet: AlexNet-level accuracy with 50x fewer parameters and <0.5MB model size 中提出。 SqueezeNet 通过减少 50 倍的参数在 ImageNet 上达到 AlexNet 级别的准确率。 此外,借助模型压缩技术,我们可以将 SqueezeNet 压缩到小于 0.5 MB(比 AlexNet 小 510 倍)。 Keras 将 SqueezeNet 作为单独的模块在线实现

复用预建的深度学习模型来提取特征

在本秘籍中,我们将看到如何使用深度学习来提取相关特征

准备

一个非常简单的想法是通常使用 VGG16 和 DCNN 进行特征提取。 该代码通过从特定层提取特征来实现该想法。

操作步骤

我们按以下步骤进行:

  1. 导入处理和显示图像所需的预建模型和其他模块:
from keras.applications.vgg16 import VGG16
from keras.models import Model
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input
import numpy as np
  1. 从网络中选择一个特定的层,并获得作为输出生成的特征:
# pre-built and pre-trained deep learning VGG16 model
base_model = VGG16(weights='imagenet', include_top=True)
for i, layer in enumerate(base_model.layers):
print (i, layer.name, layer.output_shape)
# extract features from block4_pool block
model =
Model(input=base_model.input, output=base_model.get_layer('block4_pool').output)
  1. 提取给定图像的特征,如以下代码片段所示:
img_path = 'cat.jpg'
img = image.load_img(img_path, target_size=(224, 224))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)
# get the features from this block
features = model.predict(x)

工作原理

现在,您可能想知道为什么我们要从 CNN 的中间层提取特征。 关键的直觉是:随着网络学会将图像分类,各层学会识别进行最终分类所必需的特征。

较低的层标识较低阶的特征(例如颜色和边缘),较高的层将这些较低阶的特征组合为较高阶的特征(例如形状或对象)。 因此,中间层具有从图像中提取重要特征的能力,并且这些特征更有可能有助于不同种类的分类。

这具有多个优点。 首先,我们可以依靠公开提供的大规模训练,并将这种学习迁移到新颖的领域。 其次,我们可以节省昂贵的大型训练时间。 第三,即使我们没有针对该领域的大量训练示例,我们也可以提供合理的解决方案。 对于手头的任务,我们也有一个很好的起始网络形状,而不是猜测它。

用于迁移学习的非常深的 InceptionV3 网络

迁移学习是一种非常强大的深度学习技术,在不同领域中有更多应用。 直觉非常简单,可以用类推来解释。 假设您想学习一种新的语言,例如西班牙语,那么从另一种语言(例如英语)已经知道的内容开始可能会很有用。

按照这种思路,计算机视觉研究人员现在通常使用经过预训练的 CNN 来生成新任务的表示形式,其中数据集可能不足以从头训练整个 CNN。 另一个常见的策略是采用经过预先训练的 ImageNet 网络,然后将整个网络微调到新颖的任务。

InceptionV3 Net 是 Google 开发的非常深入的卷积网络。 Keras 实现了整个网络,如下图所示,并且已在 ImageNet 上进行了预训练。 该模型的默认输入大小在三个通道上为299x299

ImageNet v3 的示例

准备

此框架示例受到 Keras 网站上在线提供的方案的启发。 我们假设在与 ImageNet 不同的域中具有训练数据集 D。 D 在输入中具有 1,024 个特征,在输出中具有 200 个类别。

操作步骤

我们可以按照以下步骤进行操作:

  1. 导入处理所需的预建模型和其他模块:
from keras.applications.inception_v3 import InceptionV3
from keras.preprocessing import image
from keras.models import Model
from keras.layers import Dense, GlobalAveragePooling2D
from keras import backend as K
# create the base pre-trained model
base_model = InceptionV3(weights='imagenet', include_top=False)
  1. 我们使用训练有素的 Inception-v3,但我们不包括顶级模型,因为我们要在 D 上进行微调。顶层是具有 1,024 个输入的密集层,最后一个输出是具有 200 类输出的 softmax 密集层。 x = GlobalAveragePooling2D()(x)用于将输入转换为密集层要处理的正确形状。 实际上,base_model.output张量具有dim_ordering="th"的形状(样本,通道,行,列),dim_ordering="tf"具有(样本,行,列,通道),但是密集层需要GlobalAveragePooling2D计算(行,列)平均值,将它们转换为(样本,通道)。 因此,如果查看最后四层(在include_top=True中),则会看到以下形状:
# layer.name, layer.input_shape, layer.output_shape
('mixed10', [(None, 8, 8, 320), (None, 8, 8, 768), (None, 8, 8, 768), (None, 8, 8, 192)], (None, 8, 8, 2048))
('avg_pool', (None, 8, 8, 2048), (None, 1, 1, 2048))
('flatten', (None, 1, 1, 2048), (None, 2048))
('predictions', (None, 2048), (None, 1000))
  1. 当包含_top=False时,将除去最后三层并暴露mixed_10层,因此GlobalAveragePooling2D层将(None, 8, 8, 2048)转换为(None, 2048),其中(None, 2048)张量中的每个元素都是(None, 8, 8, 2048)张量中每个对应的(8, 8)张量的平均值:
# add a global spatial average pooling layer
 x = base_model.output
 x = GlobalAveragePooling2D()(x)
 # let's add a fully-connected layer as first layer
 x = Dense(1024, activation='relu')(x)
 # and a logistic layer with 200 classes as last layer
 predictions = Dense(200, activation='softmax')(x)
 # model to train
 model = Model(input=base_model.input, output=predictions)
  1. 所有卷积级别都经过预训练,因此我们在训练完整模型时将其冻结。
# i.e. freeze all convolutional Inception-v3 layers
for layer in base_model.layers:
layer.trainable = False
  1. 然后,对模型进行编译和训练几个周期,以便对顶层进行训练:
# compile the model (should be done *after* setting layers to non-trainable)
 model.compile(optimizer='rmsprop', loss='categorical_crossentropy')
# train the model on the new data for a few epochs
 model.fit_generator(...)
  1. 然后我们冻结 Inception 中的顶层并微调 Inception 层。 在此示例中,我们冻结了前 172 层(要调整的超参数):
# we chose to train the top 2 inception blocks, i.e. we will freeze
 # the first 172 layers and unfreeze the rest:
 for layer in model.layers[:172]:
 layer.trainable = False
 for layer in model.layers[172:]:
 layer.trainable = True
  1. 然后重新编译模型以进行微调优化。 我们需要重新编译模型,以使这些修改生效:
# we use SGD with a low learning rate
 from keras.optimizers import SGD
 model.compile(optimizer=SGD(lr=0.0001, momentum=0.9), loss='categorical_crossentropy')

 # we train our model again (this time fine-tuning the top 2 inception blocks
 # alongside the top Dense layers
 model.fit_generator(...)

工作原理

现在,我们有了一个新的深度网络,该网络可以重用标准的 Inception-v3 网络,但可以通过迁移学习在新的域 D 上进行训练。 当然,有许多参数需要微调以获得良好的精度。 但是,我们现在正在通过迁移学习重新使用非常庞大的预训练网络作为起点。 这样,我们可以通过重复使用 Keras 中已经可用的内容来节省对机器进行训练的需求。

更多

截至 2017 年,“计算机视觉”问题意味着在图像中查找图案的问题可以视为已解决,并且此问题影响了我们的生活。 例如:

  • 《皮肤科医师对具有深层神经网络的皮肤癌的分类》(Andre Esteva,Brett Kuprel,Roberto A. Novoa,Justin Ko,Susan M. Swetter,Helen M. Blau & Sebastian Thrun,2017 年)使用 129450 个临床图像的数据集训练 CNN,该图像由 2032 种不同疾病组成。 他们在 21 个经过董事会认证的皮肤科医生的活检验证的临床图像上对结果进行了测试,并使用了两个关键的二元分类用例:角质形成细胞癌与良性脂溢性角化病; 恶性黑色素瘤与良性痣。 CNN 在这两项任务上均达到了与所有测试过的专家相同的表现,展示了一种能够对皮肤癌进行分类的,具有与皮肤科医生相当的能力的人工智能。
  • 论文《通过多视图深度卷积神经网络进行高分辨率乳腺癌筛查》(Krzysztof J. Geras,Stacey Wolfson,S。Gene Kim,Linda Moy,Kyunghyun Cho)承诺通过其创新的架构来改善乳腺癌的筛查过程,该架构可以处理四个标准视图或角度,而不会牺牲高分辨率。 与通常用于自然图像的 DCN 架构(其可处理224 x 224像素的图像)相反,MV-DCN 也能够使用2600 x 2000像素的分辨率。

使用膨胀的卷积网络,WaveNet 和 NSynth 生成音乐

WaveNet 是用于生成原始音频波形的深度生成模型。 Google DeepMind 已引入了这一突破性技术,以教授如何与计算机通话。 结果确实令人印象深刻,在网上可以找到合成语音的示例,计算机可以在其中学习如何与名人的声音(例如 Matt Damon)交谈。

因此,您可能想知道为什么学习合成音频如此困难。 嗯,我们听到的每个数字声音都是基于每秒 16,000 个样本(有时是 48,000 个或更多),并且要建立一个预测模型,在此模型中,我们学会根据以前的所有样本来复制样本是一个非常困难的挑战。 尽管如此,仍有实验表明,WaveNet 改进了当前最先进的文本转语音(TTS)系统,使美国英语和中文普通话与人声的差异降低了 50%。

更酷的是,DeepMind 证明 WaveNet 也可以用于向计算机教授如何产生乐器声音(例如钢琴音乐)。

现在为一些定义。 TTS 系统通常分为两个不同的类别:

  • 串联 TTS,其中单个语音语音片段首先被存储,然后在必须重现语音时重新组合。 但是,该方法无法扩展,因为可能只重现存储的语音片段,并且不可能重现新的扬声器或不同类型的音频而不从一开始就记住这些片段。
  • 参数化 TTS,在其中创建一个模型来存储要合成的音频的所有特征。 在 WaveNet 之前,参数 TTS 生成的音频不如串联 TTS 生成的音频自然。 WaveNet 通过直接对音频声音的产生进行建模,而不是使用过去使用的中间信号处理算法,从而改善了现有技术。

原则上,WaveNet 可以看作是一维卷积层的栈(我们已经在第 4 章中看到了图像的 2D 卷积),步幅恒定为 1,没有池化层。 请注意,输入和输出在构造上具有相同的尺寸,因此卷积网络非常适合对顺序数据(例如音频)建模。 但是,已经显示出,为了在输出神经元中的接收场达到较大的大小,有必要使用大量的大型过滤器或过分增加网络的深度。 请记住,一层中神经元的接受场是前一层的横截面,神经元从该层提供输入。 因此,纯卷积网络在学习如何合成音频方面不是那么有效。

WaveNet 之外的主要直觉是所谓的因果因果卷积(有时称为原子卷积),这仅意味着在应用卷积层的滤波器时会跳过某些输入值。 Atrous 是法语表述为 à trous混蛋,意思是带有孔的。 因此,AtrousConvolution 是带孔的卷积。例如,在一个维度中,尺寸为 3 且扩张为 1 的滤波器w将计算出以下总和。

简而言之,在 D 扩散卷积中,步幅通常为 1,但是没有任何东西可以阻止您使用其他步幅。 下图给出了一个示例,其中膨胀(孔)尺寸增大了 0、1、2:

扩张网络的一个例子

由于采用了引入“空洞”的简单思想,可以在不具有过多深度网络的情况下,使用指数级增长的滤波器堆叠多个膨胀的卷积层,并学习远程输入依赖项。

因此,WaveNet 是一个卷积网络,其中卷积层具有各种膨胀因子,从而使接收场随深度呈指数增长,因此有效覆盖了数千个音频时间步长。

当我们训练时,输入是从人类扬声器录制的声音。 波形被量化为固定的整数范围。 WaveNet 定义了一个初始卷积层,仅访问当前和先前的输入。 然后,有一堆散布的卷积网络层,仍然仅访问当前和以前的输入。 最后,有一系列密集层结合了先前的结果,然后是用于分类输出的 softmax 激活函数。

在每个步骤中,都会从网络预测一个值,并将其反馈到输入中。 同时,为下一步计算新的预测。 损失函数是当前步骤的输出与下一步的输入之间的交叉熵。

NSynth是 Google Brain 集团最近发布的 WaveNet 的改进版本,其目的不是查看因果关系,而是查看输入块的整个上下文。 神经网络是真正的,复杂的,如下图所示,但是对于本介绍性讨论而言,足以了解该网络通过使用基于减少编码/解码过程中的错误的方法来学习如何再现其输入。 阶段:

如下所示的 NSynth 架构示例

准备

对于本秘籍,我们不会编写代码,而是向您展示如何使用一些在线可用的代码和一些不错的演示,您可以从 Google Brain 找到。 有兴趣的读者还可以阅读以下文章:《使用 WaveNet 自编码器的音符的神经音频合成》(杰西·恩格尔,辛琼·雷斯尼克,亚当·罗伯茨,桑德·迪勒曼,道格拉斯·埃克,卡伦·西蒙扬,穆罕默德·诺鲁兹,4 月 5 日 2017)。

操作步骤

我们按以下步骤进行:

  1. 通过创建单独的 conda 环境来安装 NSynth。 使用支持 Jupyter Notebook 的 Python 2.7 创建并激活 Magenta conda 环境:
conda create -n magenta python=2.7 jupyter
source activate magenta
  1. 安装magenta PIP 包和librosa(用于读取音频格式):
pip install magenta
pip install librosa
  1. 互联网安装预构建的模型,然后下载示例声音。 然后运行演示目录中包含的笔记本。 第一部分是关于包含稍后将在我们的计算中使用的模块的:
import os
import numpy as np
import matplotlib.pyplot as plt
from magenta.models.nsynth import utils
from magenta.models.nsynth.wavenet import fastgen
from IPython.display import Audio
%matplotlib inline
%config InlineBackend.figure_format = 'jpg'
  1. 然后,我们加载从互联网下载的演示声音,并将其放置在与笔记本计算机相同的目录中。 这将在约 2.5 秒内将 40,000 个样本加载到计算机中:
# from https://www.freesound.org/people/MustardPlug/sounds/395058/
fname = '395058__mustardplug__breakbeat-hiphop-a4-4bar-96bpm.wav'
sr = 16000
audio = utils.load_audio(fname, sample_length=40000, sr=sr)
sample_length = audio.shape[0]
print('{} samples, {} seconds'.format(sample_length, sample_length / float(sr)))
  1. 下一步是使用从互联网下载的预先训练的 NSynth 模型以非常紧凑的表示形式对音频样本进行编码。 每四秒钟音频将为我们提供78 x 16尺寸的编码,然后我们可以对其进行解码或重新合成。 我们的编码是张量(#files=1 x 78 x 16):
%time encoding = fastgen.encode(audio, 'model.ckpt-200000', sample_length)
INFO:tensorflow:Restoring parameters from model.ckpt-200000
 CPU times: user 1min 4s, sys: 2.96 s, total: 1min 7s
 Wall time: 25.7 s
print(encoding.shape)
(1, 78, 16)
  1. 让我们保存以后将用于重新合成的编码。 另外,让我们用图形表示来快速查看编码形状是什么,并将其与原始音频信号进行比较。 如您所见,编码遵循原始音频信号中呈现的节拍:
np.save(fname + '.npy', encoding)
fig, axs = plt.subplots(2, 1, figsize=(10, 5))
axs[0].plot(audio);
axs[0].set_title('Audio Signal')
axs[1].plot(encoding[0]);
axs[1].set_title('NSynth Encoding')

我们观察到以下音频信号和 Nsynth 编码:

  1. 现在,让我们对刚刚产生的编码进行解码。 换句话说,我们试图从紧凑的表示中再现原始音频,目的是理解重新合成的声音是否类似于原始声音。 确实,如果您运行实验并聆听原始音频和重新合成的音频,它们听起来非常相似:
%time fastgen.synthesize(encoding, save_paths=['gen_' + fname], samples_per_save=sample_length)

工作原理

WaveNet 是一种卷积网络,其中卷积层具有各种扩张因子,从而使接收场随深度呈指数增长,因此有效覆盖了数千个音频时间步长。 NSynth 是 WaveNet 的演进,其中原始音频使用类似 WaveNet 的处理进行编码,以学习紧凑的表示形式。 然后,使用这种紧凑的表示来再现原始音频。

更多

一旦我们学习了如何通过膨胀卷积创建音频的紧凑表示形式,我们就可以玩这些学习并从中获得乐趣。 您会在互联网上找到非常酷的演示:

  1. 例如,您可以看到模型如何学习不同乐器的声音

  1. 然后,您将看到如何将在一个上下文中学习的一个模型在另一个上下文中重新组合。 例如,通过更改说话者身份,我们可以使用 WaveNet 以不同的声音说同一件事
  2. 另一个非常有趣的实验是学习乐器模型,然后以一种可以重新创建以前从未听说过的新乐器的方式对其进行重新混合。 这真的很酷,它为通往新的可能性开辟了道路,坐在我里面的前电台 DJ 无法抗拒超级兴奋。 例如,在此示例中,我们将西塔琴与电吉他结合在一起,这是一种很酷的新乐器。 不够兴奋? 那么,如何将弓弦低音与狗的吠声结合起来呢?玩得开心!:

回答有关图像的问题(可视化问答)

在本秘籍中,我们将学习如何回答有关特定图像内容的问题。 这是一种强大的 Visual Q&A,它结合了从预先训练的 VGG16 模型中提取的视觉特征和词聚类(嵌入)的组合。 然后将这两组异类特征组合成一个网络,其中最后一层由密集和缺失的交替序列组成。 此秘籍适用于 Keras 2.0+。

因此,本秘籍将教您如何:

  • 从预先训练的 VGG16 网络中提取特征。
  • 使用预构建的单词嵌入将单词映射到相邻相似单词的空间中。
  • 使用 LSTM 层构建语言模型。 LSTM 将在第 6 章中讨论,现在我们将它们用作黑盒。
  • 组合不同的异构输入特征以创建组合的特征空间。 对于此任务,我们将使用新的 Keras 2.0 函数式 API。
  • 附加一些其他的密集和丢弃层,以创建多层感知机并增强我们的深度学习网络的功能。

为了简单起见,我们不会在 5 中重新训练组合网络,而是使用已经在线提供的预先训练的权重集。 有兴趣的读者可以在由 N 个图像,N 个问题和 N 个答案组成的自己的训练数据集上对网络进行再训练。 这是可选练习。 该网络的灵感来自《VQA:视觉问题解答》(Aishwarya Agrawal,Jiasen Lu,Stanislaw Antol,Margaret Mitchell,C.Lawrence Zitnick,Dhruv Batra,Devi Parikh,2015 年):

在视觉问题回答论文中看到的 Visual Q&A 示例

我们这种情况的唯一区别是,我们将图像层产生的特征与语言层产生的特征连接起来。

操作步骤

我们可以按照以下步骤进行操作:

  1. 加载秘籍所需的所有 Keras 模块。 其中包括用于词嵌入的 spaCy,用于图像特征提取的 VGG16 和用于语言建模的 LSTM。 其余的几个附加模块非常标准:
%matplotlib inline
import os, argparse
import numpy as np
import cv2 as cv2
import spacy as spacy
import matplotlib.pyplot as plt
from keras.models import Model, Input
from keras.layers.core import Dense, Dropout, Reshape
from keras.layers.recurrent import LSTM
from keras.layers.merge import concatenate
from keras.applications.vgg16 import VGG16
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input
from sklearn.externals import joblib
import PIL.Image
  1. 定义一些常量。 请注意,我们假设我们的问题语料库具有max_length_questions = 30,并且我们知道我们将使用 VGG16 提取 4,096 个描述输入图像的特征。 另外,我们知道单词嵌入在length_feature_space = 300的空间中。 请注意,我们将使用从互联网下载的一组预训练权重
# mapping id -> labels for categories
label_encoder_file_name =
'/Users/gulli/Books/TF/code/git/tensorflowBook/Chapter5/FULL_labelencoder_trainval.pkl'
# max length across corpus
max_length_questions = 30
# VGG output
length_vgg_features = 4096
# Embedding outout
length_feature_space = 300
# pre-trained weights
VQA_weights_file =
'/Users/gulli/Books/TF/code/git/tensorflowBook/Chapter5/VQA_MODEL_WEIGHTS.hdf5'

3.使用 VGG16 提取特征。 请注意,我们从 fc2 层中明确提取了它们。 给定输入图像,此函数返回 4,096 个特征:

'''image features'''
def get_image_features(img_path, VGG16modelFull):
'''given an image returns a tensor with (1, 4096) VGG16 features'''
# Since VGG was trained as a image of 224x224, every new image
# is required to go through the same transformation
img = image.load_img(img_path, target_size=(224, 224))
x = image.img_to_array(img)
# this is required because of the original training of VGG was batch
# even if we have only one image we need to be consistent
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)
features = VGG16modelFull.predict(x)
model_extractfeatures = Model(inputs=VGG16modelFull.input,
outputs=VGG16modelFull.get_layer('fc2').output)
fc2_features = model_extractfeatures.predict(x)
fc2_features = fc2_features.reshape((1, length_vgg_features))
return fc2_features

请注意,VGG16 的定义如下:

Layer (type) Output Shape Param #
 =================================================================
 input_5 (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
 _________________________________________________________________
 flatten (Flatten) (None, 25088) 0
 _________________________________________________________________
 fc1 (Dense) (None, 4096) 102764544
 _________________________________________________________________
 fc2 (Dense) (None, 4096) 16781312
 _________________________________________________________________
 predictions (Dense) (None, 1000) 4097000
 =================================================================
 Total params: 138,357,544
 Trainable params: 138,357,544
 Non-trainable params: 0
 _________________________________________
  1. 使用 spaCy 获取单词嵌入,并将输入的问题映射到一个空格(max_length_questions, 300),其中max_length_questions是我们语料库中问题的最大长度,而 300 是 spaCy 产生的嵌入的尺寸。 在内部,spaCy 使用一种称为 gloVe 的算法。 gloVe 将给定令牌简化为 300 维表示。 请注意,该问题使用右 0 填充填充到max_lengh_questions
'''embedding'''
def get_question_features(question):
''' given a question, a unicode string, returns the time series vector
with each word (token) transformed into a 300 dimension representation
calculated using Glove Vector '''
word_embeddings = spacy.load('en', vectors='en_glove_cc_300_1m_vectors')
tokens = word_embeddings(question)
ntokens = len(tokens)
if (ntokens > max_length_questions) :
ntokens = max_length_questions
question_tensor = np.zeros((1, max_length_questions, 300))
for j in xrange(len(tokens)):
question_tensor[0,j,:] = tokens[j].vector
return question_tensor
  1. 使用先前定义的图像特征提取器加载图像并获取其显着特征:
image_file_name = 'girl.jpg'
img0 = PIL.Image.open(image_file_name)
img0.show()
#get the salient features
model = VGG16(weights='imagenet', include_top=True)
image_features = get_image_features(image_file_name, model)
print image_features.shape
  1. 使用先前定义的句子特征提取器,编写一个问题并获得其显着特征:
question = u"Who is in this picture?"
language_features = get_question_features(question)
print language_features.shape
  1. 将两组异类特征组合为一个。 在这个网络中,我们有三个 LSTM 层,这些层将考虑我们语言模型的创建。 注意,LSTM 将在第 6 章中详细讨论,目前我们仅将它们用作黑匣子。 最后的 LSTM 返回 512 个特征,这些特征随后用作一系列密集层和缺失层的输入。 最后一层是具有 softmax 激活函数的密集层,其概率空间为 1,000 个潜在答案:
'''combine'''
def build_combined_model(
number_of_LSTM = 3,
number_of_hidden_units_LSTM = 512,
number_of_dense_layers = 3,
number_of_hidden_units = 1024,
activation_function = 'tanh',
dropout_pct = 0.5
):
#input image
input_image = Input(shape=(length_vgg_features,),
name="input_image")
model_image = Reshape((length_vgg_features,),
input_shape=(length_vgg_features,))(input_image)
#input language
input_language = Input(shape=(max_length_questions,length_feature_space,),
name="input_language")
#build a sequence of LSTM
model_language = LSTM(number_of_hidden_units_LSTM,
return_sequences=True,
name = "lstm_1")(input_language)
model_language = LSTM(number_of_hidden_units_LSTM,
return_sequences=True,
name = "lstm_2")(model_language)
model_language = LSTM(number_of_hidden_units_LSTM,
return_sequences=False,
name = "lstm_3")(model_language)
#concatenate 4096+512
model = concatenate([model_image, model_language])
#Dense, Dropout
for _ in xrange(number_of_dense_layers):
model = Dense(number_of_hidden_units,
kernel_initializer='uniform')(model)
model = Dropout(dropout_pct)(model)
model = Dense(1000,
activation='softmax')(model)
#create model from tensors
model = Model(inputs=[input_image, input_language], outputs = model)
return model
  1. 建立组合的网络并显示其摘要,以了解其内部外观。 加载预训练的权重并使用 rmsprop 优化器使用categorical_crossentropy损失函数来编译模型:
combined_model = build_combined_model()
combined_model.summary()
combined_model.load_weights(VQA_weights_file)
combined_model.compile(loss='categorical_crossentropy', optimizer='rmsprop')
____________________________
 Layer (type) Output Shape Param # Connected to
 ====================================================================================================
 input_language (InputLayer) (None, 30, 300) 0
 ____________________________________________________________________________________________________
 lstm_1 (LSTM) (None, 30, 512) 1665024 input_language[0][0]
 ____________________________________________________________________________________________________
 input_image (InputLayer) (None, 4096) 0
 ____________________________________________________________________________________________________
 lstm_2 (LSTM) (None, 30, 512) 2099200 lstm_1[0][0]
 ____________________________________________________________________________________________________
 reshape_3 (Reshape) (None, 4096) 0 input_image[0][0]
 ____________________________________________________________________________________________________
 lstm_3 (LSTM) (None, 512) 2099200 lstm_2[0][0]
 ____________________________________________________________________________________________________
 concatenate_3 (Concatenate) (None, 4608) 0 reshape_3[0][0]
 lstm_3[0][0]
 ____________________________________________________________________________________________________
 dense_8 (Dense) (None, 1024) 4719616 concatenate_3[0][0]
 ____________________________________________________________________________________________________
 dropout_7 (Dropout) (None, 1024) 0 dense_8[0][0]
 ____________________________________________________________________________________________________
 dense_9 (Dense) (None, 1024) 1049600 dropout_7[0][0]
 ____________________________________________________________________________________________________
 dropout_8 (Dropout) (None, 1024) 0 dense_9[0][0]
 ____________________________________________________________________________________________________
 dense_10 (Dense) (None, 1024) 1049600 dropout_8[0][0]
 ____________________________________________________________________________________________________
 dropout_9 (Dropout) (None, 1024) 0 dense_10[0][0]
 ____________________________________________________________________________________________________
 dense_11 (Dense) (None, 1000) 1025000 dropout_9[0][0]
 ====================================================================================================
 Total params: 13,707,240
 Trainable params: 13,707,240
 Non-trainable params: 0
  1. 使用预训练的组合网络进行预测。 请注意,在这种情况下,我们使用该网络已经在线可用的权重,但是感兴趣的读者可以在自己的训练集中重新训练组合的网络:
y_output = combined_model.predict([image_features, language_features])
# This task here is represented as a classification into a 1000 top answers
# this means some of the answers were not part of training and thus would
# not show up in the result.
# These 1000 answers are stored in the sklearn Encoder class
labelencoder = joblib.load(label_encoder_file_name)
for label in reversed(np.argsort(y_output)[0,-5:]):
print str(round(y_output[0,label]*100,2)).zfill(5), "% ", labelencoder.inverse_transform(label)

工作原理

视觉问题解答的任务是通过结合使用不同的深度神经网络来解决的。 预训练的 VGG16 已用于从图像中提取特征,而 LSTM 序列已用于从先前映射到嵌入空间的问题中提取特征。 VGG16 是用于图像特征提取的 CNN,而 LSTM 是用于提取表示序列的时间特征的 RNN。 目前,这两种方法的结合是处理此类网络的最新技术。 然后,在组合模型的顶部添加一个具有丢弃功能的多层感知机,以形成我们的深度网络。

更多

在互联网上,您可以找到 Avi Singh 进行的更多实验,其中比较了不同的模型,包括简单的“袋装”语言的“单词”与图像的 CNN,仅 LSTM 模型以及 LSTM + CNN 模型-类似于本秘籍中讨论的模型。 博客文章还讨论了每种模型的不同训练策略。

除此之外,有兴趣的读者可以在互联网上找到一个不错的 GUI,它建立在 Avi Singh 演示的顶部,使您可以交互式加载图像并提出相关问题。 还提供了 YouTube 视频

通过六种不同方式将预训练网络用于视频分类

对视频进行分类是一个活跃的研究领域,因为处理此类媒体需要大量数据。 内存需求经常达到现代 GPU 的极限,可能需要在多台机器上进行分布式训练。 目前,研究正在探索复杂性不断提高的不同方向,让我们对其进行回顾。

第一种方法包括一次将一个视频帧分类,方法是将每个视频帧视为使用 2D CNN 处理的单独图像。 这种方法只是将视频分类问题简化为图像分类问题。 每个视频帧产生分类输出,并且通过考虑每个帧的更频繁选择的类别对视频进行分类。

第二种方法包括创建一个单一网络,其中 2D CNN 与 RNN 结合在一起。 这个想法是 CNN 将考虑图像分量,而 RNN 将考虑每个视频的序列信息。 由于要优化的参数数量非常多,这种类型的网络可能很难训练。

第三种方法是使用 3D 卷积网络,其中 3D 卷积网络是在 3D 张量(timeimage_widthimage_height)上运行的 2D 卷积网络的扩展。 这种方法是图像分类的另一个自然扩展,但同样,3D 卷积网络可能很难训练。

第四种方法基于智能直觉。 代替直接使用 CNN 进行分类,它们可以用于存储视频中每个帧的脱机特征。 想法是,如先前的秘籍所示,可以通过迁移学习使特征提取非常有效。 提取所有特征后,可以将它们作为一组输入传递到 RNN,该 RNN 将学习多个帧中的序列并发出最终分类。

第五种方法是第四种方法的简单变体,其中最后一层是 MLP 而不是 RNN。 在某些情况下,就计算要求而言,此方法可能更简单且成本更低。

第六种方法是第四种方法的变体,其中特征提取的阶段是通过提取空间和视觉特征的 3D CNN 实现的。 然后将这些特征传递给 RNN 或 MLP。

选择最佳方法严格取决于您的特定应用,没有明确的答案。 前三种方法通常在计算上更昂贵,而后三种方法则更便宜并且经常获得更好的表现。

在本秘籍中,我们将通过描述论文 Temporal Activity Detection in Untrimmed Videos with Recurrent Neural Networks 来描述如何使用第六种方法。 这项工作旨在解决 ActivityNet 挑战 。 这项挑战着重于从用户生成的视频中识别高水平和面向目标的活动,类似于在互联网门户网站中找到的那些活动。 该挑战针对两项不同任务中的 200 个活动类别量身定制:

  • 未修剪的分类挑战:给定较长的视频,请预测视频中存在的活动的标签
  • 检测挑战:给定较长的视频,预测视频中存在的活动的标签和时间范围

呈现的架构包括两个阶段,如下图所示。 第一阶段将视频信息编码为单个视频表示形式,以用于小型视频剪辑。 为此,使用了 C3D 网络。 C3D 网络使用 3D 卷积从视频中提取时空特征,这些视频先前已被分成 16 帧剪辑。

一旦提取了视频特征,第二阶段就是对每个剪辑的活动进行分类。 为了执行这种分类,使用了 RNN,更具体地说是一个 LSTM 网络,该网络试图利用长期相关性并执行视频序列的预测。 此阶段已被训练:

C3D + RNN 的示例

操作步骤

对于此秘籍,我们仅汇总在线呈现的结果

  1. 第一步是克隆 git 仓库
git clone https://github.com/imatge-upc/activitynet-2016-cvprw.git
  1. 然后,我们需要下载 ActivityNet v1.3 数据集,其大小为 600 GB:
cd dataset
 # This will download the videos on the default directory
 sh download_videos.sh username password
 # This will download the videos on the directory you specify
 sh download_videos.sh username password /path/you/want
  1. 下一步是下载 CNN3d 和 RNN 的预训练权重:
cd data/models
 sh get_c3d_sports_weights.sh
 sh get_temporal_location_weights.sh
  1. 最后一步包括对视频进行分类:
python scripts/run_all_pipeline.py -i path/to/test/video.mp4

工作原理

如果您有兴趣在计算机上训练 CNN3D 和 RNN,则可以在互联网上找到此计算机管道使用的特定命令。

目的是介绍可用于视频分类的不同方法的高级视图。 同样,不仅有一个单一的秘籍,而且有多个选项,应根据您的特定需求仔细选择。

更多

CNN-LSTM 架构是新的 RNN 层,其中输入转换和循环转换的输入都是卷积。 尽管名称非常相似,但如上所述,CNN-LSTM 层与 CNN 和 LSTM 的组合不同。 该模型在论文《卷积 LSTM 网络:降水临近预报的机器学习方法》(史兴建,陈周荣,王浩,杨天彦,黄伟坚,胡旺春,2015 年)中进行了描述,并且在 2017 年,有些人开始尝试使用此模块进行视频实验,但这仍然是一个活跃的研究领域。

六、循环神经网络

在本章中,我们将介绍一些涵盖以下主题的秘籍:

  • 神经机器翻译-训练 seq2seq RNN
  • 神经机器翻译-推理 seq2seq RNN
  • 您只需要关注-seq2seq RNN 的另一个示例
  • 通过 RNN 学习写作莎士比亚
  • 学习使用 RNN 预测未来的比特币价值
  • 多对一和多对多 RNN 示例

介绍

在本章中,我们将讨论循环神经网络RNN)如何在保持顺序顺序重要的领域中用于深度学习。 我们的注意力将主要集中在文本分析和自然语言处理NLP)上,但我们还将看到用于预测比特币价值的序列示例。

通过采用基于时间序列的模型,可以描述许多实时情况。 例如,如果您考虑编写文档,则单词的顺序很重要,而当前单词肯定取决于先前的单词。 如果我们仍然专注于文本编写,很明显单词中的下一个字符取决于前一个字符(例如quick brown字符的下一个字母很有可能将会是字母fox),如下图所示。 关键思想是在给定当前上下文的情况下生成下一个字符的分布,然后从该分布中采样以生成下一个候选字符:

The quick brown fox句子进行预测的例子

一个简单的变体是存储多个预测,因此创建一棵可能的扩展树,如下图所示:

The quick brown fox句子的预测树的示例

但是,基于序列的模型可以在大量其他域中使用。 在音乐中,乐曲中的下一个音符肯定取决于前一个音符,而在视频中,电影中的下一个帧必定与前一帧有关。 此外,在某些情况下,当前的视频帧,单词,字符或音符不仅取决于前一个,而且还取决于后一个。

可以使用 RNN 描述基于时间序列的模型,其中对于给定输入Xᵢ,时间为i,产生输出Yᵢ,将时间[0,i-1]的以前状态的记忆反馈到网络。 反馈先前状态的想法由循环循环描述,如下图所示:

反馈示例

循环关系可以方便地通过展开网络来表示,如下图所示:

展开循环单元的例子

最简单的 RNN 单元由简单的 tanh 函数(双曲正切函数)组成,如下图所示:

个简单的 tanh 单元的例子

梯度消失和爆炸

训练 RNN 十分困难,因为存在两个稳定性问题。 由于反馈回路的缘故,梯度可能会迅速发散到无穷大,或者它可能会迅速发散到 0。在两种情况下,如下图所示,网络将停止学习任何有用的东西。 可以使用基于梯度修剪的相对简单的解决方案来解决梯度爆炸的问题。 梯度消失的问题更难解决,它涉及更复杂的 RNN 基本单元的定义,例如长短期记忆LSTM)或门控循环单元GRU)。 让我们首先讨论梯度爆炸和梯度裁剪:

梯度示例

梯度裁剪包括对梯度施加最大值,以使其无法无限增长。 下图所示的简单解决方案为梯度爆炸问题提供了简单的解决方案

梯度裁剪的例子

解决梯度消失的问题需要一种更复杂的内存模型,该模型可以选择性地忘记先前的状态,只记住真正重要的状态。 考虑下图,输入以[0,1]中的概率p写入存储器M中,并乘以加权输入。

以类似的方式,以[0,1]中的概率p读取输出,将其乘以加权输出。 还有一种可能性用来决定要记住或忘记的事情:

存储单元的一个例子

长短期记忆(LSTM)

LSTM 网络可以控制何时让输入进入神经元,何时记住在上一个时间步中学到的内容以及何时让输出传递到下一个时间戳。 所有这些决定都是自调整的,并且仅基于输入。 乍一看,LSTM 看起来很难理解,但事实并非如此。 让我们用下图来说明它是如何工作的:

LSTM 单元的一个例子

首先,我们需要一个逻辑函数σ(请参见第 2 章,“回归”)来计算介于 0 和 1 之间的值,并控制哪些信息流过 LSTM 门。 请记住,逻辑函数是可微的,因此允许反向传播。 然后,我们需要一个运算符,它采用两个相同维的矩阵并生成另一个矩阵,其中每个元素ij是原始两个矩阵的元素ij的乘积。 同样,我们需要一个运算符,它采用两个相同维度的矩阵并生成另一个矩阵,其中每个元素ij是原始两个矩阵的元素ij之和。 使用这些基本块,我们考虑时间i处的输入Xᵢ,并将其与上一步中的输出Y[i-1]并置。

方程f[t] = σ(W[f] · [y[i-1], x[t]] + b[f])实现了控制激活门的逻辑回归,并用于确定应从先前候选值C[i-1]获取多少信息。 传递给下一个候选值Cᵢ(此处W[f]b[f]矩阵和用于逻辑回归的偏差)。如果 Sigmoid 输出为 1,则表示不要忘记先前的单元格状态C[i-1];如果输出 0, 这将意味着忘记先前的单元状态C[i-1](0, 1)中的任何数字都将表示要传递的信息量。

然后我们有两个方程:s[i] = σ(W[s] · [Y[i-1], x[i]] + b[s]),用于通过控制由当前单元产生的多少信息(Ĉ[i] = tanh(W [C] · [Y[i-1], X[i] + b[c]))应该通过运算符添加到下一个候选值Cᵢ中,根据上图中表示的方案。

为了实现与运算符所讨论的内容,我们需要另一个方程,其中进行实际的加法+和乘法*C[i] = f[t] * C[i-1] + s[i] * Ĉ[i]

最后,我们需要确定当前单元格的哪一部分应发送到Yᵢ输出。 这很简单:我们再进行一次逻辑回归方程,然后通过运算来控制应使用哪一部分候选值输出。 在这里,有一点值得关注,使用 tanh 函数将输出压缩为[-1, 1]。 最新的步骤由以下公式描述:

现在,我了解到这看起来像很多数学运算,但有两个好消息。 首先,如果您了解我们想要实现的目标,那么数学部分并不是那么困难。 其次,您可以将 LSTM 单元用作标准 RNN 单元的黑盒替代,并立即获得解决梯度消失问题的好处。 因此,您实际上不需要了解所有数学知识。 您只需从库中获取 TensorFlow LSTM 实现并使用它即可。

门控循环单元(GRU)和窥孔 LSTM

近年来提出了许多 LSTM 单元的变体。 其中两个真的很受欢迎。 窥孔 LSTM 允许栅极层查看单元状态,如下图虚线所示,而门控循环单元GRU)将隐藏状态和单元状态和合并为一个单一的信息渠道。

同样,GRU 和 Peephole LSTM 都可以用作标准 RNN 单元的黑盒插件,而无需了解基础数学。 这两个单元都可用于解决梯度消失的问题,并可用于构建深度神经网络:

标准 LSTM,PeepHole LSTM 和 GRU 的示例

向量序列的运算

使 RNN 真正强大的是能够对向量序列进行操作的能力,其中 RNN 的输入和/或 RNN 的输出都可以是序列。 下图很好地表示了这一点,其中最左边的示例是传统的(非循环)网络,其后是带有输出序列的 RNN,然后是带有输入序列的 RNN,再是带有序列的 RNN 在不同步序列的输入和输出中,然后是在序列同步的输入和输出中具有序列的 RNN:

RNN 序列的一个例子

机器翻译是输入和输出中不同步序列的一个示例:网络将输入文本作为序列读取,在读取全文之后,会输出目标语言

视频分类是输入和输出中同步序列的示例:视频输入是帧序列,并且对于每个帧,输出中都提供了分类标签。

如果您想了解有关 RNN 有趣应用的更多信息,则必须阅读 Andrej Karpathy 发布的博客。 他训练了网络,以莎士比亚的风格撰写论文(用 Karpathy 的话说:几乎不能从实际的莎士比亚中识别出这些样本),撰写有关虚构主题的现实 Wikipedia 文章,撰写关于愚蠢和不现实问题的现实定理证明(用 Karpathy 的话:更多的幻觉代数几何),并写出现实的 Linux 代码片段(用 Karpathy 的话:他首先建模逐个字符地列举 GNU 许可证,其中包括一些示例,然后生成一些宏,然后深入研究代码)。

以下示例摘自这个页面

用 RNN 生成的文本示例

神经机器翻译 -- 训练 seq2seq RNN

序列到序列(seq2seq)是 RNN 的一种特殊类型,已成功应用于神经机器翻译,文本摘要和语音识别中。 在本秘籍中,我们将讨论如何实现神经机器翻译,其结果与 Google 神经机器翻译系统。 关键思想是输入整个文本序列,理解整个含义,然后将翻译输出为另一个序列。 读取整个序列的想法与以前的架构大不相同,在先前的架构中,将一组固定的单词从一种源语言翻译成目标语言。

本节的灵感来自 Minh-Thang Luong 的 2016 年博士学位论文《神经机器翻译》。 第一个关键概念是编码器-解码器架构的存在,其中编码器将源句子转换为代表含义的向量。 然后,此向量通过解码器以产生翻译。 编码器和解码器都是 RNN,它们可以捕获语言中的长期依赖关系,例如性别协议和语法结构,而无需先验地了解它们,并且不需要跨语言进行 1:1 映射。 这是一种强大的功能,可实现非常流畅的翻译:

编解码器的示例

让我们看一个 RNN 的示例,该语句将She loves cute cats翻译成Elle Aime les chat Mignons

有两种 RNN:一种充当编码器,另一种充当解码器。 源句She loves cute cats后跟一个分隔符-目标句是Elle aime les chats mignons。 这两个连接的句子在输入中提供给编码器进行训练,并且解码器将生成目标目标。 当然,我们需要像这样的多个示例来获得良好的训练:

NMT 序列模型的示例

现在,我们可以拥有许多 RNN 变体。 让我们看看其中的一些:

  • RNN 可以是单向或双向的。 后者将捕捉双方的长期关系。
  • RNN 可以具有多个隐藏层。 选择是关于优化的问题:一方面,更深的网络可以学到更多;另一方面,更深的网络可以学到更多。 另一方面,可能需要很长的时间来训练并且可能会过头。
  • RNN 可以具有一个嵌入层,该层将单词映射到一个嵌入空间中,在该空间中相似的单词恰好被映射得非常近。
  • RNNs 可以使用简单的或者循环的单元,或 LSTM,或窥视孔 LSTM,或越冬。

仍然参考博士学位论文《神经机器翻译》,我们可以使用嵌入层来将输入语句放入嵌入空间。 然后,有两个 RNN 粘在一起——源语言的编码器和目标语言的解码器。 如您所见,存在多个隐藏层,并且有两个流程:前馈垂直方向连接这些隐藏层,水平方向是将知识从上一步转移到下一层的循环部分:

神经机器翻译的例子

在本秘籍中,我们使用 NMT(神经机器翻译),这是一个可在 TensorFlow 顶部在线获得的翻译演示包。

准备

NMT 可在这个页面上找到,并且代码在 GitHub 上。

操作步骤

我们按以下步骤进行:

  1. 从 GitHub 克隆 NMT:
git clone https://github.com/tensorflow/nmt/
  1. 下载训练数据集。 在这种情况下,我们将使用训练集将越南语翻译为英语。 其他数据集可从这里获取其他语言,例如德语和捷克语:
nmt/scripts/download_iwslt15.sh /tmp/nmt_data
  1. 考虑这里,我们将定义第一个嵌入层。 嵌入层接受输入,词汇量 V 和输出嵌入空间的所需大小。 词汇量使得仅考虑 V 中最频繁的单词进行嵌入,而所有其他单词都映射到一个常见的未知项。 在我们的例子中,输入是主要时间的,这意味着最大时间是第一个输入参数
# Embedding
 embedding_encoder = variable_scope.get_variable(
 "embedding_encoder", [src_vocab_size, embedding_size], ...)
 # Look up embedding:
 # encoder_inputs: [max_time, batch_size]
 # encoder_emb_inp: [max_time, batch_size, embedding_size]
 encoder_emb_inp = embedding_ops.embedding_lookup(
 embedding_encoder, encoder_inputs)
  1. 仍然参考这里,我们定义了一个简单的编码器,它使用tf.nn.rnn_cell.BasicLSTMCell(num_units)作为基本 RNN 单元。 这非常简单,但是要注意,给定基本的 RNN 单元,我们使用tf.nn.dynamic_rnn创建 RNN:
# Build RNN cell
 encoder_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units)

 # Run Dynamic RNN
 # encoder_outpus: [max_time, batch_size, num_units]
 # encoder_state: [batch_size, num_units]
 encoder_outputs, encoder_state = tf.nn.dynamic_rnn(
 encoder_cell, encoder_emb_inp,
 sequence_length=source_sequence_length, time_major=True)
  1. 之后,我们需要定义解码器。 因此,第一件事是拥有一个带有tf.nn.rnn_cell.BasicLSTMCell的基本 RNN 单元,然后将其用于创建一个基本采样解码器tf.contrib.seq2seq.BasicDecoder,该基本采样解码器将用于与解码器tf.contrib.seq2seq.dynamic_decode进行动态解码:
# Build RNN cell
 decoder_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units)
# Helper
 helper = tf.contrib.seq2seq.TrainingHelper(
 decoder_emb_inp, decoder_lengths, time_major=True)
 # Decoder
 decoder = tf.contrib.seq2seq.BasicDecoder(
 decoder_cell, helper, encoder_state,
 output_layer=projection_layer)
 # Dynamic decoding
 outputs, _ = tf.contrib.seq2seq.dynamic_decode(decoder, ...)
 logits = outputs.rnn_output
  1. 网络的最后一个阶段是 softmax 密集阶段,用于将顶部隐藏状态转换为对率向量:
projection_layer = layers_core.Dense(
 tgt_vocab_size, use_bias=False)
  1. 当然,我们需要定义交叉熵函数和训练阶段使用的损失:
crossent = tf.nn.sparse_softmax_cross_entropy_with_logits(
 labels=decoder_outputs, logits=logits)
 train_loss = (tf.reduce_sum(crossent * target_weights) /
 batch_size)
  1. 下一步是定义反向传播所需的步骤,并使用适当的优化器(在本例中为 Adam)。 请注意,梯度已被裁剪,Adam 使用预定义的学习率:
# Calculate and clip gradients
 params = tf.trainable_variables()
 gradients = tf.gradients(train_loss, params)
 clipped_gradients, _ = tf.clip_by_global_norm(
 gradients, max_gradient_norm)
# Optimization
 optimizer = tf.train.AdamOptimizer(learning_rate)
 update_step = optimizer.apply_gradients(
 zip(clipped_gradients, params))
  1. 现在,我们可以运行代码并了解不同的执行步骤。 首先,创建训练图。 然后,训练迭代开始。 用于评估的度量标准是双语评估研究BLEU)。 此度量标准是评估已从一种自然语言机器翻译成另一种自然语言的文本质量的标准。 质量被认为是机器与人工输出之间的对应关系。 如您所见,该值随时间增长:
python -m nmt.nmt --src=vi --tgt=en --vocab_prefix=/tmp/nmt_data/vocab --train_prefix=/tmp/nmt_data/train --dev_prefix=/tmp/nmt_data/tst2012 --test_prefix=/tmp/nmt_data/tst2013 --out_dir=/tmp/nmt_model --num_train_steps=12000 --steps_per_stats=100 --num_layers=2 --num_units=128 --dropout=0.2 --metrics=bleu
# Job id 0
[...]
# creating train graph ...
num_layers = 2, num_residual_layers=0
cell 0 LSTM, forget_bias=1 DropoutWrapper, dropout=0.2 DeviceWrapper, device=/gpu:0
cell 1 LSTM, forget_bias=1 DropoutWrapper, dropout=0.2 DeviceWrapper, device=/gpu:0
cell 0 LSTM, forget_bias=1 DropoutWrapper, dropout=0.2 DeviceWrapper, device=/gpu:0
cell 1 LSTM, forget_bias=1 DropoutWrapper, dropout=0.2 DeviceWrapper, device=/gpu:0
start_decay_step=0, learning_rate=1, decay_steps 10000,decay_factor 0.98
[...]
# Start step 0, lr 1, Thu Sep 21 12:57:18 2017
# Init train iterator, skipping 0 elements
global step 100 lr 1 step-time 1.65s wps 3.42K ppl 1931.59 bleu 0.00
global step 200 lr 1 step-time 1.56s wps 3.59K ppl 690.66 bleu 0.00
[...]
global step 9100 lr 1 step-time 1.52s wps 3.69K ppl 39.73 bleu 4.89
global step 9200 lr 1 step-time 1.52s wps 3.72K ppl 40.47 bleu 4.89
global step 9300 lr 1 step-time 1.55s wps 3.62K ppl 40.59 bleu 4.89
[...]
# External evaluation, global step 9000
decoding to output /tmp/nmt_model/output_dev.
done, num sentences 1553, time 17s, Thu Sep 21 17:32:49 2017.
bleu dev: 4.9
saving hparams to /tmp/nmt_model/hparams
# External evaluation, global step 9000
decoding to output /tmp/nmt_model/output_test.
done, num sentences 1268, time 15s, Thu Sep 21 17:33:06 2017.
bleu test: 3.9
saving hparams to /tmp/nmt_model/hparams
[...]
global step 9700 lr 1 step-time 1.52s wps 3.71K ppl 38.01 bleu 4.89

工作原理

所有上述代码已在这个页面中定义。 关键思想是将两个 RNN 打包在一起。 第一个是编码器,它在嵌入空间中工作,非常紧密地映射相似的单词。 编码器理解训练示例的含义,并产生张量作为输出。 然后只需将编码器的最后一个隐藏层连接到解码器的初始层,即可将该张量传递给解码器。 注意力学习是由于我们基于与labels=decoder_outputs的交叉熵的损失函数而发生的。

该代码学习如何翻译,并通过 BLEU 度量标准通过迭代跟踪进度,如下图所示:

Tensorboard 中的 BLEU 指标示例

神经机器翻译 -- 用 seq2seq RNN 推理

在此秘籍中,我们使用先前秘籍的结果将源语言转换为目标语言。 这个想法非常简单:给源语句提供两个组合的 RNN(编码器+解码器)作为输入。 句子一结束,解码器将产生对率值,我们贪婪地产生与最大值关联的单词。 例如,从解码器产生单词moi作为第一个令牌,因为该单词具有最大对率值。 之后,会产生单词suis,依此类推:

具有概率的 NM 序列模型的示例

使用解码器的输出有多种策略:

  • 贪婪:产生对应最大对率的字
  • 采样:通过对产生的对率进行采样来产生单词
  • 集束搜索:一个以上的预测,因此创建了可能的扩展树

操作步骤

我们按以下步骤进行:

  1. 定义用于对解码器进行采样的贪婪策略。 这很容易,因为我们可以使用tf.contrib.seq2seq.GreedyEmbeddingHelper中定义的库。 由于我们不知道目标句子的确切长度,因此我们将启发式方法限制为最大长度为源句子长度的两倍:
# Helper
 helper = tf.contrib.seq2seq.GreedyEmbeddingHelper(
 embedding_decoder,
 tf.fill([batch_size], tgt_sos_id), tgt_eos_id)

 # Decoder
 decoder = tf.contrib.seq2seq.BasicDecoder(
 decoder_cell, helper, encoder_state,
 output_layer=projection_layer)
 # Dynamic decoding
 outputs, _ = tf.contrib.seq2seq.dynamic_decode(
 decoder, maximum_iterations=maximum_iterations)
 translations = outputs.sample_id
maximum_iterations = tf.round(tf.reduce_max(source_sequence_length) * 2)
  1. 现在,我们可以运行网络,输入一个从未见过的句子(inference_input_file=/tmp/my_infer_file),然后让网络翻译结果(inference_output_file=/tmp/nmt_model/output_infer):
python -m nmt.nmt \
 --out_dir=/tmp/nmt_model \
 --inference_input_file=/tmp/my_infer_file.vi \
 --inference_output_file=/tmp/nmt_model/output_infer

工作原理

将两个 RNN 打包在一起,以形成编码器-解码器 RNN 网络。 解码器产生对率,然后将其贪婪地转换为目标语言的单词。 例如,此处显示了从越南语到英语的自动翻译:

  • 用英语输入的句子:小时候,我认为朝鲜是世界上最好的国家,我经常唱歌&。 我们没有什么可嫉妒的。
  • 翻译成英语的输出句子:当我非常好时,我将去了解最重要的事情,而我不确定该说些什么。

您只需要注意力 -- seq2seq RNN 的另一个示例

在本秘籍中,我们介绍了注意力方法(Dzmitry Bahdanau,Kyunghyun Cho 和 Yoshua Bengio,ICLR 2015),这是神经网络翻译的最新解决方案。 ,它包括在编码器和解码器 RNN 之间添加其他连接。 实际上,仅将解码器与编码器的最新层连接会带来信息瓶颈,并且不一定允许通过先前的编码器层获取的信息通过。 下图说明了采用的解决方案:

NMT 注意力模型的示例

需要考虑三个方面:

  • 首先,将当前目标隐藏状态与所有先前的源状态一起使用以得出注意力权重,该注意力权重用于或多或少地关注序列中先前看到的标记
  • 其次,创建上下文向量以汇总注意力权重的结果
  • 第三,将上下文向量与当前目标隐藏状态组合以获得注意力向量

操作步骤

我们按以下步骤进行:

  1. 使用库tf.contrib.seq2seq.LuongAttention定义注意力机制,该库实现了 Minh-Thang Luong,Hieu Pham 和 Christopher D. Manning(2015 年)在《基于注意力的神经机器翻译有效方法》中定义的注意力模型:
# attention_states: [batch_size, max_time, num_units]
 attention_states = tf.transpose(encoder_outputs, [1, 0, 2])

 # Create an attention mechanism
 attention_mechanism = tf.contrib.seq2seq.LuongAttention(
 num_units, attention_states,
 memory_sequence_length=source_sequence_length)
  1. 通过注意力包装器,将定义的注意力机制用作解码器单元周围的包装器:
decoder_cell = tf.contrib.seq2seq.AttentionWrapper(
 decoder_cell, attention_mechanism,
 attention_layer_size=num_units)
  1. 运行代码以查看结果。 我们立即注意到,注意力机制在 BLEU 得分方面产生了显着改善:
python -m nmt.nmt \
> --attention=scaled_luong \
> --src=vi --tgt=en \
> --vocab_prefix=/tmp/nmt_data/vocab \
> --train_prefix=/tmp/nmt_data/train \
> --dev_prefix=/tmp/nmt_data/tst2012 \
> --test_prefix=/tmp/nmt_data/tst2013 \
> --out_dir=/tmp/nmt_attention_model \
> --num_train_steps=12000 \
> --steps_per_stats=100 \
> --num_layers=2 \
> --num_units=128 \
> --dropout=0.2 \
> --metrics=bleu
[...]
# Start step 0, lr 1, Fri Sep 22 22:49:12 2017
# Init train iterator, skipping 0 elements
global step 100 lr 1 step-time 1.71s wps 3.23K ppl 15193.44 bleu 0.00
[...]
# Final, step 12000 lr 0.98 step-time 1.67 wps 3.37K ppl 14.64, dev ppl 14.01, dev bleu 15.9, test ppl 12.58, test bleu 17.5, Sat Sep 23 04:35:42 2017
# Done training!, time 20790s, Sat Sep 23 04:35:42 2017.
# Start evaluating saved best models.
[..]
loaded infer model parameters from /tmp/nmt_attention_model/best_bleu/translate.ckpt-12000, time 0.06s
# 608
src: nhưng bạn biết điều gì không ?
ref: But you know what ?
nmt: But what do you know ?
[...]
# Best bleu, step 12000 step-time 1.67 wps 3.37K, dev ppl 14.01, dev bleu 15.9, test ppl 12.58, test bleu 17.5, Sat Sep 23 04:36:35 2017

工作原理

注意是一种机制,该机制使用由编码器 RNN 的内部状态获取的信息,并将该信息与解码器的最终状态进行组合。 关键思想是,通过这种方式,有可能或多或少地关注源序列中的某些标记。 下图显示了 BLEU 得分,引起了关注。

我们注意到,相对于我们第一个秘籍中未使用任何注意力的图表而言,它具有明显的优势:

Tensorboard 中注意力的 BLEU 指标示例

更多

值得记住的是 seq2seq 不仅可以用于机器翻译。 让我们看一些例子:

  • Lukasz Kaiser 在作为外语的语法中,使用 seq2seq 模型来构建选区解析器。 选区分析树将文本分为多个子短语。 树中的非终结符是短语的类型,终结符是句子中的单词,并且边缘未标记。
  • seq2seq 的另一个应用是 SyntaxNet,又名 Parsey McParserFace(语法分析器),它是许多 NLU 系统中的关键第一组件。 给定一个句子作为输入,它将使用描述单词的句法特征的词性POS)标签标记每个单词,并确定句子中单词之间的句法关系,在依存关系分析树中表示。 这些句法关系与所讨论句子的潜在含义直接相关。

下图使我们对该概念有了一个很好的了解:

SyntaxNet 的一个例子

通过 RNN 学习写作莎士比亚

在本秘籍中,我们将学习如何生成与威廉·莎士比亚(William Shakespeare)相似的文本。 关键思想很简单:我们将莎士比亚写的真实文本作为输入,并将其作为输入 RNN 的输入,该 RNN 将学习序列。 然后将这种学习用于生成新文本,该文本看起来像最伟大的作家用英语撰写的文本。

为了简单起见,我们将使用框架 TFLearn,它在 TensorFlow 上运行。 此示例是标准分发版的一部分,可从以下位置获得。开发的模型是 RNN 字符级语言模型,其中考虑的序列是字符序列而不是单词序列。

操作步骤

我们按以下步骤进行:

  1. 使用pip安装 TFLearn:
pip install -I tflearn
  1. 导入许多有用的模块并下载一个由莎士比亚撰写的文本示例。 在这种情况下,我们使用这个页面中提供的一种:
import os
import pickle
from six.moves import urllib
import tflearn
from tflearn.data_utils import *
path = "shakespeare_input.txt"
char_idx_file = 'char_idx.pickle'
if not os.path.isfile(path): urllib.request.urlretrieve("https://raw.githubusercontent.com/tflearn/tflearn.github.io/master/resources/shakespeare_input.txt", path)
  1. 使用string_to_semi_redundant_sequences()将输入的文本转换为向量,并返回解析的序列和目标以及相关的字典,该函数将返回一个元组(输入,目标,字典):
maxlen = 25
char_idx = None
if os.path.isfile(char_idx_file):
print('Loading previous char_idx')
char_idx = pickle.load(open(char_idx_file, 'rb'))
X, Y, char_idx = \
textfile_to_semi_redundant_sequences(path, seq_maxlen=maxlen, redun_step=3,
pre_defined_char_idx=char_idx)
pickle.dump(char_idx, open(char_idx_file,'wb'))
  1. 定义一个由三个 LSTM 组成的 RNN,每个 LSTM 都有 512 个节点,并返回完整序列,而不是仅返回最后一个序列输出。 请注意,我们使用掉线模块连接 LSTM 模块的可能性为 50%。 最后一层是密集层,其应用 softmax 的长度等于字典大小。 损失函数为categorical_crossentropy,优化器为 Adam:
g = tflearn.input_data([None, maxlen, len(char_idx)])
g = tflearn.lstm(g, 512, return_seq=True)
g = tflearn.dropout(g, 0.5)
g = tflearn.lstm(g, 512, return_seq=True)
g = tflearn.dropout(g, 0.5)
g = tflearn.lstm(g, 512)
g = tflearn.dropout(g, 0.5)
g = tflearn.fully_connected(g, len(char_idx), activation='softmax')
g = tflearn.regression(g, optimizer='adam', loss='categorical_crossentropy',
learning_rate=0.001)
  1. 给定步骤 4 中定义的网络,我们现在可以使用库flearn.models.generator.SequenceGeneratornetworkdictionary=char_idx, seq_maxlen=maxleclip_gradients=5.0, checkpoint_path='model_shakespeare')生成序列:
m = tflearn.SequenceGenerator(g, dictionary=char_idx,
seq_maxlen=maxlen,
clip_gradients=5.0,
checkpoint_path='model_shakespeare')
  1. 对于 50 次迭代,我们从输入文本中获取随机序列,然后生成一个新文本。 温度正在控制所创建序列的新颖性; 温度接近 0 看起来像用于训练的样本,而温度越高,新颖性越强:
for i in range(50):
seed = random_sequence_from_textfile(path, maxlen)
m.fit(X, Y, validation_set=0.1, batch_size=128,
n_epoch=1, run_id='shakespeare')
print("-- TESTING...")
print("-- Test with temperature of 1.0 --")
print(m.generate(600, temperature=1.0, seq_seed=seed))
print("-- Test with temperature of 0.5 --")
print(m.generate(600, temperature=0.5, seq_seed=seed))

工作原理

当新的未知或被遗忘的艺术品要归功于作者时,有著名的学者将其与作者的其他作品进行比较。 学者们要做的是在著名作品的文本序列中找到共同的模式,希望在未知作品中找到相似的模式。

这种方法的工作方式相似:RNN 了解莎士比亚作品中最特殊的模式是什么,然后将这些模式用于生成新的,从未见过的文本,这些文本很好地代表了最伟大的英语作者的风格。

让我们看一些执行示例:

python shakespeare.py
Loading previous char_idx
Vectorizing text...
Text total length: 4,573,338
Distinct chars : 67
Total sequences : 1,524,438
---------------------------------
Run id: shakespeare
Log directory: /tmp/tflearn_logs/

第一次迭代

在这里,网络正在学习一些基本结构,包括需要建立有关虚构字符(DIASURYONTHRNTLGIPRMARARILEN)的对话。 但是,英语仍然很差,很多单词不是真正的英语:

---------------------------------
Training samples: 1371994
Validation samples: 152444
--
Training Step: 10719 | total loss: 2.22092 | time: 22082.057s
| Adam | epoch: 001 | loss: 2.22092 | val_loss: 2.12443 -- iter: 1371994/1371994
-- TESTING...
-- Test with temperature of 1.0 --
'st thou, malice?
If thou caseghough memet oud mame meard'ke. Afs weke wteak, Dy ny wold' as to of my tho gtroy ard has seve, hor then that wordith gole hie, succ, caight fom?
DIA:
A gruos ceen, I peey
by my
Wiouse rat Sebine would.
waw-this afeean.
SURYONT:
Teeve nourterong a oultoncime bucice'is furtutun
Ame my sorivass; a mut my peant?
Am:
Fe, that lercom ther the nome, me, paatuy corns wrazen meas ghomn'ge const pheale,
As yered math thy vans:
I im foat worepoug and thit mije woml!
HRNTLGIPRMAR:
I'd derfomquesf thiy of doed ilasghele hanckol, my corire-hougangle!
Kiguw troll! you eelerd tham my fom Inow lith a
-- Test with temperature of 0.5 --
'st thou, malice?
If thou prall sit I har, with and the sortafe the nothint of the fore the fir with with the ceme at the ind the couther hit yet of the sonsee in solles and that not of hear fore the hath bur.
ARILEN:
More you a to the mare me peod sore,
And fore string the reouck and and fer to the so has the theat end the dore; of mall the sist he the bot courd wite be the thoule the to nenge ape and this not the the ball bool me the some that dears,
The be to the thes the let the with the thear tould fame boors and not to not the deane fere the womour hit muth so thand the e meentt my to the treers and woth and wi

经过几次迭代

在这里,网络开始学习对话的正确结构,并且使用Well, there shall the things to need the offer to our heartThere is not that be so then to the death To make the body and all the mind这样的句子,书面英语看起来更正确:

---------------------------------
Training samples: 1371994
Validation samples: 152444
--
Training Step: 64314 | total loss: 1.44823 | time: 21842.362s
| Adam | epoch: 006 | loss: 1.44823 | val_loss: 1.40140 -- iter: 1371994/1371994
--
-- Test with temperature of 0.5 --
in this kind.
THESEUS:
There is not that be so then to the death
To make the body and all the mind.
BENEDICK:
Well, there shall the things to need the offer to our heart,
To not are he with him: I have see the hands are to true of him that I am not,
The whom in some the fortunes,
Which she were better not to do him?
KING HENRY VI:
I have some a starter, and and seen the more to be the boy, and be such a plock and love so say, and I will be his entire,
And when my masters are a good virtues,
That see the crown of our worse,
This made a called grace to hear him and an ass,
And the provest and stand,

更多

博客文章循环神经网络的不合理有效性描述了一组引人入胜的示例 RNN 字符级语言模型,包括以下内容:

  • 莎士比亚文本生成类似于此示例
  • Wikipedia 文本生成类似于此示例,但是基于不同的训练文本
  • 代数几何(LaTex)文本生成类似于此示例,但基于不同的训练文本
  • Linux 源代码文本的生成与此示例相似,但是基于不同的训练文本
  • 婴儿命名文本的生成与此示例类似,但是基于不同的训练文本

学习使用 RNN 预测未来的比特币价值

在本秘籍中,我们将学习如何使用 RNN 预测未来的比特币价值。 关键思想是,过去观察到的值的时间顺序可以很好地预测未来的值。 对于此秘籍,我们将使用 MIT 许可下的这个页面上提供的代码。 给定时间间隔的比特币值通过 API 从这里下载。 这是 API 文档的一部分:

We offer historical data from our Bitcoin Price Index through the following endpoint: https://api.coindesk.com/v1/bpi/historical/close.json By default, this will return the previous 31 days' worth of data. This endpoint accepts the following optional parameters: ?index=[USD/CNY]The index to return data for. Defaults to USD. ?currency=<VALUE>The currency to return the data in, specified in ISO 4217 format. Defaults to USD. ?start=<VALUE>&end=<VALUE> Allows data to be returned for a specific date range. Must be listed as a pair of start and end parameters, with dates supplied in the YYYY-MM-DD format, e.g. 2013-09-01 for September 1st, 2013. ?for=yesterday Specifying this will return a single value for the previous day. Overrides the start/end parameter. Sample Request: https://api.coindesk.com/v1/bpi/historical/close.json?start=2013-09-01&end=2013-09-05 Sample JSON Response: {"bpi":{"2013-09-01":128.2597,"2013-09-02":127.3648,"2013-09-03":127.5915,"2013-09-04":120.5738,"2013-09-05":120.5333},"disclaimer":"This data was produced from the CoinDesk Bitcoin Price Index. BPI value data returned as USD.","time":{"updated":"Sep 6, 2013 00:03:00 UTC","updatedISO":"2013-09-06T00:03:00+00:00"}}

操作步骤

这是我们进行秘籍的方法:

  1. 克隆以下 GitHub 存储库。 这是一个鼓励用户尝试使用 seq2seq 神经网络架构的项目:
git clone https://github.com/guillaume-chevalier/seq2seq-signal-prediction.git
  1. 给定前面的存储库,请考虑以下函数,这些函数可加载和标准化 USD 或 EUR 比特币值的比特币历史数据。 这些特征在dataset.py中定义。 训练和测试数据根据 80/20 规则分开。 因此,测试数据的 20% 是最新的历史比特币值。 每个示例在特征轴/维度中包含 40 个 USD 数据点,然后包含 EUR 数据。 根据平均值和标准差对数据进行归一化。 函数generate_x_y_data_v4生成大小为batch_size的训练数据(分别是测试数据)的随机样本:
def loadCurrency(curr, window_size):
   """
   Return the historical data for the USD or EUR bitcoin value. Is done with an web API call.
   curr = "USD" | "EUR"
   """
   # For more info on the URL call, it is inspired by :
   # https://github.com/Levino/coindesk-api-node
   r = requests.get(
       "http://api.coindesk.com/v1/bpi/historical/close.json?start=2010-07-17&end=2017-03-03&currency={}".format(
           curr
       )
   )
   data = r.json()
   time_to_values = sorted(data["bpi"].items())
   values = [val for key, val in time_to_values]
   kept_values = values[1000:]
   X = []
   Y = []
   for i in range(len(kept_values) - window_size * 2):
       X.append(kept_values[i:i + window_size])
       Y.append(kept_values[i + window_size:i + window_size * 2])
   # To be able to concat on inner dimension later on:
   X = np.expand_dims(X, axis=2)
   Y = np.expand_dims(Y, axis=2)
   return X, Y
def normalize(X, Y=None):
   """
   Normalise X and Y according to the mean and standard
deviation of the X values only.
   """
   # # It would be possible to normalize with last rather than mean, such as:
   # lasts = np.expand_dims(X[:, -1, :], axis=1)
   # assert (lasts[:, :] == X[:, -1, :]).all(), "{}, {}, {}. {}".format(lasts[:, :].shape, X[:, -1, :].shape, lasts[:, :], X[:, -1, :])
   mean = np.expand_dims(np.average(X, axis=1) + 0.00001, axis=1)
   stddev = np.expand_dims(np.std(X, axis=1) + 0.00001, axis=1)
   # print (mean.shape, stddev.shape)
   # print (X.shape, Y.shape)
   X = X - mean
   X = X / (2.5 * stddev)
   if Y is not None:
       assert Y.shape == X.shape, (Y.shape, X.shape)
       Y = Y - mean
       Y = Y / (2.5 * stddev)
       return X, Y
   return X

def fetch_batch_size_random(X, Y, batch_size):
   """
   Returns randomly an aligned batch_size of X and Y among all examples.
   The external dimension of X and Y must be the batch size
(eg: 1 column = 1 example).
   X and Y can be N-dimensional.
   """
   assert X.shape == Y.shape, (X.shape, Y.shape)
   idxes = np.random.randint(X.shape[0], size=batch_size)
   X_out = np.array(X[idxes]).transpose((1, 0, 2))
   Y_out = np.array(Y[idxes]).transpose((1, 0, 2))
   return X_out, Y_out
X_train = []
Y_train = []
X_test = []
Y_test = []

def generate_x_y_data_v4(isTrain, batch_size):
   """
   Return financial data for the bitcoin.
   Features are USD and EUR, in the internal dimension.
   We normalize X and Y data according to the X only to not
   spoil the predictions we ask for.
   For every window (window or seq_length), Y is the prediction following X.
   Train and test data are separated according to the 80/20
rule.
   Therefore, the 20 percent of the test data are the most
   recent historical bitcoin values. Every example in X contains
   40 points of USD and then EUR data in the feature axis/dimension.
   It is to be noted that the returned X and Y has the same shape
   and are in a tuple.
   """
   # 40 pas values for encoder, 40 after for decoder's predictions.
   seq_length = 40
   global Y_train
   global X_train
   global X_test
   global Y_test
   # First load, with memoization:
   if len(Y_test) == 0:
       # API call:
       X_usd, Y_usd = loadCurrency("USD",
window_size=seq_length)
       X_eur, Y_eur = loadCurrency("EUR",
window_size=seq_length)
       # All data, aligned:
       X = np.concatenate((X_usd, X_eur), axis=2)
       Y = np.concatenate((Y_usd, Y_eur), axis=2)
       X, Y = normalize(X, Y)
       # Split 80-20:
       X_train = X[:int(len(X) * 0.8)]
       Y_train = Y[:int(len(Y) * 0.8)]
       X_test = X[int(len(X) * 0.8):]
       Y_test = Y[int(len(Y) * 0.8):]
   if isTrain:
       return fetch_batch_size_random(X_train, Y_train, batch_size)
   else:
       return fetch_batch_size_random(X_test,  Y_test,  batch_size)
  1. 生成训练,验证和测试数据,并定义许多超参数,例如batch_sizehidden_dim(RNN 中隐藏的神经元的数量)和layers_stacked_count(栈式循环单元的数量)。 此外,定义一些参数以微调优化器,例如优化器的学习率,迭代次数,用于优化器模拟退火的lr_decay,优化器的动量以及避免过拟合的 L2 正则化。 请注意,GitHub 存储库具有默认的batch_size = 5nb_iters = 150,但使用batch_size = 1000nb_iters = 100000获得了更好的结果:
from datasets import generate_x_y_data_v4
generate_x_y_data = generate_x_y_data_v4
import tensorflow as tf  
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
sample_x, sample_y = generate_x_y_data(isTrain=True, batch_size=3)
print("Dimensions of the dataset for 3 X and 3 Y training
examples : ")
print(sample_x.shape)
print(sample_y.shape)
print("(seq_length, batch_size, output_dim)")
print sample_x, sample_y
# Internal neural network parameters
seq_length = sample_x.shape[0]  # Time series will have the same past and future (to be predicted) lenght.
batch_size = 5  # Low value used for live demo purposes - 100 and 1000 would be possible too, crank that up!
output_dim = input_dim = sample_x.shape[-1]  # Output dimension (e.g.: multiple signals at once, tied in time)
hidden_dim = 12  # Count of hidden neurons in the recurrent units.
layers_stacked_count = 2  # Number of stacked recurrent cells, on the neural depth axis.
# Optmizer:
learning_rate = 0.007  # Small lr helps not to diverge during training.
nb_iters = 150  # How many times we perform a training step (therefore how many times we show a batch).
lr_decay = 0.92  # default: 0.9 . Simulated annealing.
momentum = 0.5  # default: 0.0 . Momentum technique in weights update
lambda_l2_reg = 0.003  # L2 regularization of weights - avoids overfitting
  1. 将网络定义为由基本 GRU 单元组成的编码器/解码器。 该网络由layers_stacked_count=2 RNN 组成,我们将使用 TensorBoard 可视化该网络。 请注意,hidden_dim = 12是循环单元中的隐藏神经元:
tf.nn.seq2seq = tf.contrib.legacy_seq2seq
tf.nn.rnn_cell = tf.contrib.rnn
tf.nn.rnn_cell.GRUCell = tf.contrib.rnn.GRUCell
tf.reset_default_graph()
# sess.close()
sess = tf.InteractiveSession()
with tf.variable_scope('Seq2seq'):
   # Encoder: inputs
   enc_inp = [
       tf.placeholder(tf.float32, shape=(None, input_dim), name="inp_{}".format(t))
          for t in range(seq_length)
   ]
   # Decoder: expected outputs
   expected_sparse_output = [
       tf.placeholder(tf.float32, shape=(None, output_dim), name="expected_sparse_output_".format(t))
         for t in range(seq_length)
   ]

   # Give a "GO" token to the decoder.
   # You might want to revise what is the appended value "+ enc_inp[:-1]".
   dec_inp = [ tf.zeros_like(enc_inp[0], dtype=np.float32, name="GO") ] + enc_inp[:-1]
   # Create a `layers_stacked_count` of stacked RNNs (GRU cells here).
   cells = []
   for i in range(layers_stacked_count):
       with tf.variable_scope('RNN_{}'.format(i)):
           cells.append(tf.nn.rnn_cell.GRUCell(hidden_dim))
           # cells.append(tf.nn.rnn_cell.BasicLSTMCell(...))
   cell = tf.nn.rnn_cell.MultiRNNCell(cells)   
   # For reshaping the input and output dimensions of the seq2seq RNN:
   w_in = tf.Variable(tf.random_normal([input_dim, hidden_dim]))
   b_in = tf.Variable(tf.random_normal([hidden_dim], mean=1.0))
   w_out = tf.Variable(tf.random_normal([hidden_dim, output_dim]))
   b_out = tf.Variable(tf.random_normal([output_dim]))   
reshaped_inputs = [tf.nn.relu(tf.matmul(i, w_in) + b_in) for i in enc_inp]   
# Here, the encoder and the decoder uses the same cell, HOWEVER,
   # the weights aren't shared among the encoder and decoder, we have two
   # sets of weights created under the hood according to that function's def.

   dec_outputs, dec_memory = tf.nn.seq2seq.basic_rnn_seq2seq(
       enc_inp,
       dec_inp,
       cell
   )   

output_scale_factor = tf.Variable(1.0, name="Output_ScaleFactor")
   # Final outputs: with linear rescaling similar to batch norm,
   # but without the "norm" part of batch normalization hehe.
   reshaped_outputs = [output_scale_factor*(tf.matmul(i, w_out) + b_out) for i in dec_outputs]  
   # Merge all the summaries and write them out to /tmp/bitcoin_logs (by default)
   merged = tf.summary.merge_all()
   train_writer = tf.summary.FileWriter('/tmp/bitcoin_logs',                                     sess.graph)
  1. 现在让我们运行 TensorBoard 并可视化由 RNN 编码器和 RNN 解码器组成的网络:
tensorboard --logdir=/tmp/bitcoin_logs

以下是代码流程:

Tensorboard 中的比特币价值预测代码示例

  1. 现在让我们将损失函数定义为具有正则化的 L2 损失,以避免过拟合并获得更好的泛化。 选择的优化器是 RMSprop,其值为learning_rate,衰减和动量,如步骤 3 所定义:
# Training loss and optimizer
with tf.variable_scope('Loss'):
   # L2 loss
   output_loss = 0
   for _y, _Y in zip(reshaped_outputs, expected_sparse_output):
       output_loss += tf.reduce_mean(tf.nn.l2_loss(_y - _Y))       
  # L2 regularization (to avoid overfitting and to have a  better generalization capacity)
   reg_loss = 0
   for tf_var in tf.trainable_variables():
       if not ("Bias" in tf_var.name or "Output_" in tf_var.name):
           reg_loss += tf.reduce_mean(tf.nn.l2_loss(tf_var))

   loss = output_loss + lambda_l2_reg * reg_loss

with tf.variable_scope('Optimizer'):
   optimizer = tf.train.RMSPropOptimizer(learning_rate, decay=lr_decay, momentum=momentum)
   train_op = optimizer.minimize(loss)
  1. 通过生成训练数据并在数据集中的batch_size示例上运行优化器来为批量训练做准备。 同样,通过从数据集中的batch_size示例生成测试数据来准备测试。 训练针对nb_iters+1迭代进行,每十个迭代中的一个用于测试结果:
def train_batch(batch_size):
   """
   Training step that optimizes the weights
   provided some batch_size X and Y examples from the dataset.
   """
   X, Y = generate_x_y_data(isTrain=True, batch_size=batch_size)
   feed_dict = {enc_inp[t]: X[t] for t in range(len(enc_inp))}
   feed_dict.update({expected_sparse_output[t]: Y[t] for t in range(len(expected_sparse_output))})
   _, loss_t = sess.run([train_op, loss], feed_dict)
   return loss_t

def test_batch(batch_size):
   """
   Test step, does NOT optimizes. Weights are frozen by not
   doing sess.run on the train_op.
   """
   X, Y = generate_x_y_data(isTrain=False, batch_size=batch_size)
   feed_dict = {enc_inp[t]: X[t] for t in range(len(enc_inp))}
   feed_dict.update({expected_sparse_output[t]: Y[t] for t in range(len(expected_sparse_output))})
   loss_t = sess.run([loss], feed_dict)
   return loss_t[0]

# Training
train_losses = []
test_losses = []
sess.run(tf.global_variables_initializer())

for t in range(nb_iters+1):
   train_loss = train_batch(batch_size)
   train_losses.append(train_loss)   
   if t % 10 == 0:
       # Tester
       test_loss = test_batch(batch_size)
       test_losses.append(test_loss)
       print("Step {}/{}, train loss: {}, \tTEST loss: {}".format(t, nb_iters, train_loss, test_loss))
print("Fin. train loss: {}, \tTEST loss: {}".format(train_loss, test_loss))
  1. 可视化n_predictions结果。 我们将以黄色形象化nb_predictions = 5预测,以x形象化蓝色的实际值ix。 请注意,预测从直方图中的最后一个蓝点开始,从视觉上,您可以观察到,即使这个简单的模型也相当准确:
# Test
nb_predictions = 5
print("Let's visualize {} predictions with our signals:".format(nb_predictions))
X, Y = generate_x_y_data(isTrain=False, batch_size=nb_predictions)
feed_dict = {enc_inp[t]: X[t] for t in range(seq_length)}
outputs = np.array(sess.run([reshaped_outputs], feed_dict)[0])
for j in range(nb_predictions):
   plt.figure(figsize=(12, 3))   
   for k in range(output_dim):
       past = X[:,j,k]
       expected = Y[:,j,k]
       pred = outputs[:,j,k]       
       label1 = "Seen (past) values" if k==0 else "_nolegend_"
       label2 = "True future values" if k==0 else "_nolegend_"
       label3 = "Predictions" if k==0 else "_nolegend_"
       plt.plot(range(len(past)), past, "o--b", label=label1)
       plt.plot(range(len(past), len(expected)+len(past)), expected, "x--b", label=label2)
       plt.plot(range(len(past), len(pred)+len(past)), pred, "o--y", label=label3)   
   plt.legend(loc='best')
   plt.title("Predictions v.s. true values")
   plt.show()

我们得到的结果如下:

比特币价值预测的一个例子

工作原理

带有 GRU 基本单元的编码器-解码器层堆叠 RNN 用于预测比特币值。 RNN 非常擅长学习序列,即使使用基于 2 层和 12 个 GRU 单元的简单模型,比特币的预测确实相当准确。 当然,此预测代码并非鼓励您投资比特币,而只是讨论深度学习方法。 而且,需要更多的实验来验证我们是否存在数据过拟合的情况。

更多

预测股市价值是一个不错的 RNN 应用,并且有许多方便的包,例如:

多对一和多对多 RNN 示例

在本秘籍中,我们通过提供 RNN 映射的各种示例来总结与 RNN 讨论过的内容。 为了简单起见,我们将采用 Keras 并演示如何编写一对一,一对多,多对一和多对多映射,如下图所示:

RNN 序列的一个例子

操作步骤

我们按以下步骤进行:

  1. 如果要创建一对一映射,则这不是 RNN,而是密集层。 假设已经定义了一个模型,并且您想添加一个密集网络。 然后可以在 Keras 中轻松实现:
model = Sequential()
model.add(Dense(output_size, input_shape=input_shape))
  1. 如果要创建一对多选项,可以使用RepeatVector(...)实现。 请注意,return_sequences是一个布尔值,用于决定是返回输出序列中的最后一个输出还是完整序列:
model = Sequential()
model.add(RepeatVector(number_of_times,input_shape=input_shape)) 
model.add(LSTM(output_size, return_sequences=True))
  1. 如果要创建多对一选项,则可以使用以下 LSTM 代码段实现:
model = Sequential()
model.add(LSTM(1, input_shape=(timesteps, data_dim)))
  1. 如果要创建多对多选项,当输入和输出的长度与循环步数匹配时,可以使用以下 LSTM 代码段来实现:
model = Sequential() 
model.add(LSTM(1, input_shape=(timesteps, data_dim), return_sequences=True))

工作原理

Keras 使您可以轻松编写各种形状的 RNN,包括一对一,一对多,多对一和多对多映射。 上面的示例说明了用 Keras 实现它们有多么容易。

七、无监督学习

到目前为止,我们在本书中涵盖的所有模型都是基于监督学习范式的。 训练数据集包括输入和该输入的所需标签。 相反,本章重点介绍无监督的学习范式。 本章将包括以下主题:

  • 主成分分析
  • K 均值聚类
  • 自组织图
  • 受限玻尔兹曼机
  • 使用 RBM 的推荐系统
  • 用于情感检测的 DBN

介绍

在机器学习中,存在三种不同的学习范式:监督学习,无监督学习和强化学习。

监督学习(也称为与老师一起学习)中,向网络提供输入和各自所需的输出。 例如,在 MNIST 数据集中,手写数字的每个图像都有一个标签,表示与之关联的数字值。

强化学习(也称为与批评家学习)中,没有为网络提供所需的输出; 相反,环境会提供奖励或惩罚方面的反馈。 当其输出正确时,环境奖励网络,而当输出不正确时,环境对其进行惩罚。

无监督学习(也称为无老师学习)中,没有向网络提供有关其输出的信息。 网络接收输入,但是既不提供期望的输出,也不提供来自环境的奖励; 网络自己学习输入的隐藏结构。 无监督学习非常有用,因为正常情况下可用的数据没有标签。 它可以用于模式识别,特征提取,数据聚类和降维等任务。 在本章和下一章中,您将学习基于无监督学习的不同机器学习和 NN 技术。

主成分分析

主成分分析PCA)是用于降维的最流行的多元统计技术。 它分析了由几个因变量组成的训练数据,这些因变量通常是相互关联的,并以一组称为主成分的新正交变量的形式从训练数据中提取重要信息。 我们可以使用两种方法执行 PCA -- 特征值分解奇异值分解SVD)。

准备

PCA 将n维输入数据还原为r维输入数据,其中r < n。 简单来说,PCA 涉及平移原点并执行轴的旋转,以使其中一个轴(主轴)与数据点的差异最小。 通过执行此变换,然后以高方差落下(删除)正交轴,可以从原始数据集中获得降维数据集。 在这里,我们采用 SVD 方法降低 PCA 尺寸。 考虑Xn维数据,具有p个点X[p,n]。 任何实数(p × n)矩阵都可以分解为:

X = U ∑ Vᐪ

在这里, UV是正交矩阵(即U · Uᐪ = Vᐪ · V = E),大小分别为p × nn × n是大小为n × n的对角矩阵。 接下来,将矩阵切成r列,得到∑[r]; 使用UV,我们找到了降维数据点Y[r]

Y[r] = U ∑[r]

此处提供的代码已从以下 GitHub 链接进行改编

操作步骤

我们按以下步骤进行操作:

  1. 导入所需的模块。 我们肯定会使用 TensorFlow; 我们还需要numpy进行一些基本矩阵计算,并需要matplotlibmpl_toolkitseaborn进行绘图:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import seaborn as sns
%matplotlib inline
  1. 我们加载数据集-我们将使用我们最喜欢的 MNIST 数据集:
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/")
  1. 我们定义了一个TF_PCA类,它将实现我们的所有工作。 该类的初始化如下:
def __init__(self, data,  dtype=tf.float32):
        self._data = data
        self._dtype = dtype
        self._graph = None
        self._X = None
        self._u = None
        self._singular_values = None
        self._sigma = None)
  1. 给定输入数据的 SVD 用fit方法计算。 该方法定义了计算图,并执行该计算图以计算奇异值和正交矩阵U。需要self.data来输入占位符self._Xtf.svd以降序返回形状[..., p]ssingular_values)。 我们使用tf.diag将其转换为对角矩阵:
def fit(self):
        self._graph = tf.Graph()
        with self._graph.as_default():
            self._X = tf.placeholder(self._dtype, shape=self._data.shape)
            # Perform SVD
            singular_values, u, _ = tf.svd(self._X)
            # Create sigma matrix
            sigma = tf.diag(singular_values)
        with tf.Session(graph=self._graph) as session:
            self._u, self._singular_values, self._sigma = session.run([u, singular_values, sigma], feed_dict={self._X: self._data})
  1. 现在我们有了sigma矩阵,正交U矩阵和奇异值,我们通过定义reduce方法来计算降维数据。 该方法需要两个输入参数之一n_dimensionskeep_infon_dimensions参数表示我们要保留在降维数据集中的维数。 另一方面,keep_info参数确定我们要保留的信息的百分比(值为 0.8 表示我们要保留 80% 的原始数据)。 该方法创建一个切片 Sigma 矩阵的图,并计算降维数据集Y[r]
def reduce(self, n_dimensions=None, keep_info=None):
        if keep_info:
            # Normalize singular values
            normalized_singular_values = self._singular_values / sum(self._singular_values)
            # information per dimension
            info = np.cumsum(normalized_singular_values)            # Get the first index which is above the given information threshold
           it = iter(idx for idx, value in enumerate(info) if value >= keep_info)
            n_dimensions = next(it) + 1 
       with self.graph.as_default():
            # Cut out the relevant part from sigma
            sigma = tf.slice(self._sigma, [0, 0], [self._data.shape[1], n_dimensions])
            # PCA
            pca = tf.matmul(self._u, sigma)

        with tf.Session(graph=self._graph) as session:
            return session.run(pca, feed_dict={self._X: self._data})
  1. 我们的TF_PCA类已准备就绪。 现在,我们将使用它来将 MNIST 数据从尺寸为 784(28 x 28)的每个输入减少为尺寸为 3 的每个点的新数据。这里,我们仅保留了 10% 的信息以便于查看,但是通常需要保留大约 80% 的信息:
tf_pca.fit()
pca = tf_pca.reduce(keep_info=0.1)  # The reduced dimensions dependent upon the % of information
print('original data shape', mnist.train.images.shape)
print('reduced data shape', pca.shape) 

以下是以下代码的输出:

  1. 现在,让我们在三维空间中绘制 55,000 个数据点:
Set = sns.color_palette("Set2", 10)
color_mapping = {key:value for (key,value) in enumerate(Set)}
colors = list(map(lambda x: color_mapping[x], mnist.train.labels))
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(pca[:, 0], pca[:, 1],pca[:, 2], c=colors)

工作原理

前面的代码对 MNIST 图像执行降维。 每个原始图像的尺寸为28 x 28; 使用 PCA 方法,我们可以将其减小到较小的尺寸。 通常,对于图像数据,降维是必要的。 之所以如此,是因为图像很大,并且包含大量的冗余数据。

更多

TensorFlow 提供了一种称为嵌入的技术,该技术是将对象映射到向量中。 TensorBoard 的嵌入式投影仪允许我们以交互方式可视化模型中的嵌入。 嵌入式投影仪提供了三种降低尺寸的方法:PCA,t-SNE 和自定义。 我们可以使用 TensorBoard 的嵌入投影器实现与上一个类似的结果。 我们需要从tensorflow.contrib.tensorboard.plugins导入projector类,以从tensorflow.contrib.tensorboard.plugins导入projector进行相同的操作。 我们可以通过三个简单的步骤来做到这一点:

  1. 加载要探索其嵌入的数据:
mnist = input_data.read_data_sets('MNIST_data')
images = tf.Variable(mnist.test.images, name='images')
  1. 创建一个metadata文件((metadata文件是制表符分隔的.tsv文件):
with open(metadata, 'w') as metadata_file:
    for row in mnist.test.labels:
        metadata_file.write('%d\n' % row)
  1. 将嵌入内容保存在所需的Log_DIR中:
with tf.Session() as sess:
    saver = tf.train.Saver([images])

    sess.run(images.initializer)
    saver.save(sess, os.path.join(LOG_DIR, 'images.ckpt'))

    config = projector.ProjectorConfig()
    # One can add multiple embeddings.
    embedding = config.embeddings.add()
    embedding.tensor_name = images.name
    # Link this tensor to its metadata file (e.g. labels).
    embedding.metadata_path = metadata
    # Saves a config file that TensorBoard will read during startup.
    projector.visualize_embeddings(tf.summary.FileWriter(LOG_DIR), config)

嵌入已准备就绪,现在可以使用 TensorBoard 看到。 通过 CLI tensorboard --logdir=log启动 TensorBoard,在 Web 浏览器中打开 TensorBoard,然后转到EMBEDDINGS选项卡。 这是使用 PCA 的 TensorBoard 投影,前三个主要成分为轴:

另见

K 均值聚类

顾名思义,K 均值聚类是一种对数据进行聚类的技术,即将数据划分为指定数量的数据点。 这是一种无监督的学习技术。 它通过识别给定数据中的模式来工作。 还记得哈利波特成名的分拣帽子吗? 书中的工作是聚类-将新生(未标记)的学生分成四个不同的类:格兰芬多,拉文克劳,赫奇帕奇和斯莱特林。

人类非常擅长将对象分组在一起。 聚类算法试图为计算机提供类似的功能。 有许多可用的聚类技术,例如“层次”,“贝叶斯”或“局部”。 K 均值聚类属于部分聚类; 它将数据划分为k簇。 每个簇都有一个中心,称为重心。 簇数k必须由用户指定。

K 均值算法以以下方式工作:

  1. 随机选择k个数据点作为初始质心(集群中心)
  2. 将每个数据点分配给最接近的质心; 可以找到接近度的不同方法,最常见的是欧几里得距离
  3. 使用当前簇成员资格重新计算质心,以使平方和的距离减小
  4. 重复最后两个步骤,直到达到收敛

准备

我们将使用 TensorFlow KmeansClustering估计器类来实现 K 均值。 它在这个链接中定义。它创建一个模型来运行 K 均值和推理。 根据 TensorFlow 文档,一旦创建了KmeansClustering类对象,就可以使用以下__init__方法实例化该对象:

__init__(
num_clusters,
model_dir=None,
initial_clusters=RANDOM_INIT,
distance_metric=SQUARED_EUCLIDEAN_DISTANCE,
random_seed=0,
use_mini_batch=True,
mini_batch_steps_per_iteration=1,
kmeans_plus_plus_num_retries=2,
relative_tolerance=None,
config=None
)

TensorFlow 文档对这些参数的定义如下:

Args:
num_clusters: The number of clusters to train.
model_dir: The directory to save the model results and log files.
initial_clusters: Specifies how to initialize the clusters for training. See clustering_ops.kmeans for the possible values.
distance_metric: The distance metric used for clustering. See clustering_ops.kmeans for the possible values.
random_seed: Python integer. Seed for PRNG used to initialize centers.
use_mini_batch: If true, use the mini-batch k-means algorithm. Or else assume full batch.
mini_batch_steps_per_iteration: The number of steps after which the updated cluster centers are synced back to a master copy. See clustering_ops.py for more details.
kmeans_plus_plus_num_retries: For each point that is sampled during kmeans++ initialization, this parameter specifies the number of additional points to draw from the current distribution before selecting the best. If a negative value is specified, a heuristic is used to sample O(log(num_to_sample)) additional points.
relative_tolerance: A relative tolerance of change in the loss between iterations. Stops learning if the loss changes less than this amount. Note that this may not work correctly if use_mini_batch=True.
config: See Estimator.

TensorFlow 支持欧几里得距离和余弦距离作为质心的量度。 TensorFlow KmeansClustering提供了各种与KmeansClustering对象进行交互的方法。 在本秘籍中,我们将使用fit()clusters()predict_clusters_idx()方法:

fit(
 x=None,
 y=None,
 input_fn=None,
 steps=None,
 batch_size=None,
 monitors=None,
 max_steps=None
)

根据 TensorFlow 文档,对于KmeansClustering估计器,我们需要向fit()提供input_fn()cluster方法返回聚类中心,predict_cluster_idx方法返回预测的聚类索引。

操作步骤

这是我们进行秘籍的方法:

  1. 和以前一样,我们从加载必要的模块开始。 我们将像往常一样需要 TensorFlow,NumPy 和 Matplotlib。 在本秘籍中,我们使用的是鸢尾花数据集,该数据集包含三个类别,每个类别有 50 个实例,其中每个类别都代表一种鸢尾花植物。 我们可以从这里下载数据作为.csv文件,也可以使用 sklearn 的数据集模块(scikit-learn) 做任务:
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
# dataset Iris
from sklearn import datasets

%matplotlib inline
  1. 我们加载数据集:
# import some data to play with
iris = datasets.load_iris()
x = iris.data[:, :2] # we only take the first two features.
y = iris.target
  1. 让我们看看该数据集的外观:
# original data without clustering
plt.scatter(hw_frame[:,0], hw_frame[:,1])
plt.xlabel('Sepia Length')
plt.ylabel('Sepia Width')

以下是以下代码的输出:

  1. 我们可以看到在数据中没有明显可见的聚类。 现在我们定义input_fn,它将用于提供fit方法。 我们的输入函数返回一个 TensorFlow 常数,该常数被分配了x的值和形状,并且类型为float
def input_fn():
 return tf.constant(np.array(x), tf.float32, x.shape),None
  1. 现在我们使用KmeansClustering类; 在这里,我们已经知道类的数量为 3,因此我们将num_clusters=3设置为。 通常,我们不知道集群的数量。 在这种情况下,常用的方法是肘部法则
kmeans = tf.contrib.learn.KMeansClustering(num_clusters=3, relative_tolerance=0.0001, random_seed=2) 
kmeans.fit(input_fn=input_fn)
  1. 我们使用clusters()方法找到聚类,并使用predict_cluster_idx()方法为每个输入点分配聚类索引:
clusters = kmeans.clusters()
assignments = list(kmeans.predict_cluster_idex(input_fn=input_fn))
  1. 现在让我们可视化由 K 均值创建的聚类。 为此,我们创建一个包装器函数ScatterPlot,该函数将XY值以及每个数据点的簇和簇索引一起使用:
def ScatterPlot(X, Y, assignments=None, centers=None):
 if assignments is None:
 assignments = [0] * len(X)
 fig = plt.figure(figsize=(14,8))
 cmap = ListedColormap(['red', 'green', 'blue'])
 plt.scatter(X, Y, c=assignments, cmap=cmap)
 if centers is not None:
 plt.scatter(centers[:, 0], centers[:, 1], c=range(len(centers)), 
 marker='+', s=400, cmap=cmap) 
 plt.xlabel('Sepia Length')
 plt.ylabel('Sepia Width')

我们用它来绘制我们的clusters

ScatterPlot(x[:,0], x[:,1], assignments, clusters)

情节如下:

+标记是三个簇的质心。

工作原理

前面的秘籍使用 TensorFlow 的 K 均值聚类估计器将给定数据聚类为聚类。 在这里,由于我们知道集群的数量,我们决定保留num_clusters=3,但是在大多数情况下,如果使用未标记的数据,则永远无法确定存在多少集群。 可以使用弯头法确定最佳簇数。 该方法基于以下原则:我们应选择能减少平方误差和SSE)距离的簇数。 如果k是簇数,则随着k增加,SSE 减少,SSE = 0; 当k等于数据点数时,每个点都是其自己的簇。 我们想要一个k较低的值,以使 SSE 也较低。 在 TensorFlow 中,我们可以使用KmeansClustering类中定义的score()方法找到 SSE; 该方法将距离的总和返回到最近的聚类:

sum_distances = kmeans.score(input_fn=input_fn, steps=100)

对于鸢尾数据,如果我们针对不同的k值绘制 SSE,则可以看到对于k = 3而言,SSE 的方差最高; 之后,它开始减小,因此肘点为k = 3

更多

K 均值聚类非常流行,因为它快速,简单且健壮。 它还有一些缺点:最大的缺点是用户必须指定簇的数量。 其次,该算法不能保证全局最优。 第三,它对异常值非常敏感。

另见

自组织图

自组织映射SOM),有时也称为 Kohonen 网络胜者通吃单元WTU),是一种非常特殊的神经网络,受人脑的独特特征驱动。 在我们的大脑中,不同的感觉输入以拓扑有序的方式表示。 与其他神经网络不同,神经元并非都通过权重相互连接,而是会影响彼此的学习。 SOM 的最重要方面是神经元以拓扑方式表示学习的输入。

在 SOM 中,神经元通常放置在(1D 或 2D)晶格的节点上。 更大的尺寸也是可能的,但实际上很少使用。 晶格中的每个神经元都通过权重矩阵连接到所有输入单元。 在这里,您可以看到一个具有3 x 4(12 个神经元)和七个输入的 SOM。 为了清楚起见,仅显示将所有输入连接到一个神经元的权重向量。 在这种情况下,每个神经元将具有七个元素,从而形成大小为(12 x 7)的组合权重矩阵:

SOM 通过竞争性学习来学习。 可以将其视为 PCA 的非线性概括,因此,像 PCA 一样,可以用于降维。

准备

为了实现 SOM,让我们首先了解它是如何工作的。 第一步,将网络的权重初始化为某个随机值,或者通过从输入中获取随机样本进行初始化。 占据晶格中空间的每个神经元将被分配特定的位置。 现在,作为输入出现,与输入距离最小的神经元被宣布为 Winner(WTU)。 这是通过测量所有神经元的权重向量(W)和输入向量(X)之间的距离来完成的:

在此,dⱼ是神经元j的权重与输入X的距离。 最小d值的神经元是赢家。

接下来,以一种方式调整获胜神经元及其相邻神经元的权重,以确保如果下次出现相同的输入,则相同的神经元将成为获胜者。 为了确定哪些相邻神经元需要修改,网络使用邻域函数Λ(r); 通常,选择高斯墨西哥帽函数作为邻域函数。 邻域函数在数学上表示如下:

在这里,σ是神经元的时间依赖性半径,d是其与获胜神经元的距离:

邻域函数的另一个重要属性是其半径随时间减小。 结果,一开始,许多相邻神经元的权重被修改,但是随着网络的学习,最终在学习过程中,一些神经元的权重(有时只有一个或没有)被修改。 权重变化由以下公式给出:

dW = η * Λ(X - W)

我们继续所有输入的过程,并重复给定的迭代次数。 随着迭代的进行,我们将学习率和半径减小一个取决于迭代次数的因素。

操作步骤

我们按以下步骤进行:

  1. 与往常一样,我们从导入必要的模块开始:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
  1. 接下来,我们声明一个类 WTU,它将执行所有任务。 用m x n 2D SOM 格的大小,dim输入数据中的维数以及迭代总数来实例化该类:
def __init__(self, m, n, dim, num_iterations, eta = 0.5, sigma = None):
    """
    m x n : The dimension of 2D lattice in which neurons are     arranged
    dim : Dimension of input training data
    num_iterations: Total number of training iterations
    eta : Learning rate
    sigma: The radius of neighbourhood function.
    """
    self._m = m
    self._n = n
    self._neighbourhood = []
    self._topography = []
    self._num_iterations = int(num_iterations) 
    self._learned = False
  1. __init__本身中,我们定义了计算图和会话。
  2. 如果网络未提供任何sigma值,它将采用默认值,该值通常是 SOM 晶格最大尺寸的一半:
if sigma is None:
    sigma = max(m,n)/2.0 # Constant radius
else:
    sigma = float(sigma)
  1. 接下来,在图中,我们声明权重矩阵的变量,输入的占位符以及计算获胜者并更新其及其邻居权重的计算步骤。 由于 SOM 提供了地形图,因此我们还添加了操作以获得神经元的地形位置:
self._graph = tf.Graph()

# Build Computation Graph of SOM
 with self._graph.as_default():
# Weight Matrix and the topography of neurons
    self._W = tf.Variable(tf.random_normal([m*n, dim], seed = 0))
    self._topography = tf.constant(np.array(list(self._neuron_location(m, n))))

    # Placeholders for training data
    self._X = tf.placeholder('float', [dim])

    # Placeholder to keep track of number of iterations
    self._iter = tf.placeholder('float')

    # Finding the Winner and its location
    d = tf.sqrt(tf.reduce_sum(tf.pow(self._W - tf.stack([self._X 
          for i in range(m*n)]),2),1))
    self.WTU_idx = tf.argmin(d,0)
    slice_start = tf.pad(tf.reshape(self.WTU_idx, [1]),np.array([[0,1]]))
    self.WTU_loc = tf.reshape(tf.slice(self._topography, slice_start,             [1,2]), [2])
    # Change learning rate and radius as a function of iterations
    learning_rate = 1 - self._iter/self._num_iterations
    _eta_new = eta * learning_rate
    _sigma_new = sigma * learning_rate

    # Calculating Neighbourhood function
    distance_square = tf.reduce_sum(tf.pow(tf.subtract(
    self._topography, tf.stack([self.WTU_loc for i in range(m * n)])), 2), 1)
    neighbourhood_func = tf.exp(tf.negative(tf.div(tf.cast(
distance_square, "float32"), tf.pow(_sigma_new, 2))))

    # multiply learning rate with neighbourhood func
    eta_into_Gamma = tf.multiply(_eta_new, neighbourhood_func)

    # Shape it so that it can be multiplied to calculate dW
    weight_multiplier = tf.stack([tf.tile(tf.slice(
eta_into_Gamma, np.array([i]), np.array([1])), [dim])
for i in range(m * n)])
    delta_W = tf.multiply(weight_multiplier,
tf.subtract(tf.stack([self._X for i in range(m * n)]),self._W))
    new_W = self._W + delta_W
    self._training = tf.assign(self._W,new_W)

   # Initialize All variables
   init = tf.global_variables_initializer()
   self._sess = tf.Session()
   self._sess.run(init)
  1. 我们为该类定义一个fit方法,该方法执行在该类的默认图中声明的训练操作。 该方法还计算质心网格:
def fit(self, X):
 """
 Function to carry out training
 """
 for i in range(self._num_iterations):
   for x in X:
       self._sess.run(self._training, feed_dict= {self._X:x, self._iter: i})

 # Store a centroid grid for easy retreival
 centroid_grid = [[] for i in range(self._m)]
 self._Wts = list(self._sess.run(self._W))
 self._locations = list(self._sess.run(self._topography))
 for i, loc in enumerate(self._locations):
      centroid_grid[loc[0]].append(self._Wts[i])
 self._centroid_grid = centroid_grid

 self._learned = True
  1. 我们定义一个函数来确定获胜神经元在 2D 晶格中的索引和位置:
def winner(self, x):
    idx = self._sess.run([self.WTU_idx,self.WTU_loc], feed_dict = {self._X:x})
    return idx
  1. 我们定义一些更多的辅助函数,以执行晶格中神经元的 2D 映射并将输入向量映射到 2D 晶格中的相关神经元:
def _neuron_location(self,m,n):
    """
    Function to generate the 2D lattice of neurons
    """
    for i in range(m):
       for j in range(n):
           yield np.array([i,j])

def get_centroids(self):
    """
    Function to return a list of 'm' lists, with each inner     list containing the 'n' corresponding centroid locations     as 1-D NumPy arrays.
    """
    if not self._learned:
       raise ValueError("SOM not trained yet")
    return self._centroid_grid
def map_vects(self, X):
    """
    Function to map each input vector to the relevant neuron         in the lattice
    """
    if not self._learned:
        raise ValueError("SOM not trained yet")
    to_return = []
    for vect in X:
       min_index = min([i for i in range(len(self._Wts))],
       key=lambda x: np.linalg.norm(vect -
self._Wts[x]))
       to_return.append(self._locations[min_index])
return to_return
  1. 现在我们的 WTU 类已经准备好,我们从.csv文件中读取数据并对其进行规范化:
def normalize(df):
    result = df.copy()
    for feature_name in df.columns:
       max_value = df[feature_name].max()
       min_value = df[feature_name].min()
       result[feature_name] = (df[feature_name] - min_value) / (max_value - min_value)
    return result

# Reading input data from file
import pandas as pd
df = pd.read_csv('colors.csv') # The last column of data file is a label
data = normalize(df[['R', 'G', 'B']]).values
name = df['Color-Name'].values
n_dim = len(df.columns) - 1

# Data for Training
colors = data
color_names = name
  1. 最后,我们使用我们的类执行降维并将其布置在美丽的地形图中:
som = WTU(30, 30, n_dim, 400, sigma=10.0)
som.fit(colors)

# Get output grid
image_grid = som.get_centroids()

# Map colours to their closest neurons
mapped = som.map_vects(colors)

# Plot
plt.imshow(image_grid)
plt.title('Color Grid SOM')
for i, m in enumerate(mapped):
     plt.text(m[1], m[0], color_names[i], ha='center', va='center', bbox=dict(facecolor='white', alpha=0.5, lw=0))

情节如下:

工作原理

SOM 在计算上很昂贵,因此对于非常大的数据集并没有真正的用处。 尽管如此,它们仍然易于理解,并且可以很好地找到输入数据之间的相似性。 因此,它们已被用于图像分割和确定 NLP 中的单词相似度图。

另见

受限玻尔兹曼机

受限玻尔兹曼机RBM)是两层神经网络,第一层称为可见层,第二层称为隐藏层。 它们被称为浅层神经网络,因为它们只有两层深。 它们最初是由 1986 年由保罗·斯莫伦斯基(Paul Smolensky)提出的(他称其为 Harmony Networks),后来由 Geoffrey Hinton 提出,于 2006 年提出了对比发散CD)作为训练他们的方法。 可见层中的所有神经元都与隐藏层中的所有神经元相连,但是存在限制-同一层中没有神经元可以连接。 所有神经元本质上都是二进制的:

资料来源:Qwertyus 自己的作品,CC BY-SA 3.0

RBM 可用于降维,特征提取和协作过滤。 RBM 中的训练可分为三个部分:前进,后退和比较。

准备

让我们看看制作 RBM 所需的表达式:

正向传递:可见单元(V)上的信息通过权重(W)和偏差(c)传递给隐藏的对象单元(h₀)。 隐藏单元是否可以触发取决于随机概率(σ是随机概率):

p(h[i]|v[0]) = σ(Vᐪ · W + c)[i]

向后传递:隐藏的单元表示(h₀)然后通过相同的权重W但不同的偏置c传递回可见单元,它们在其中重构输入。 再次,对输入进行采样:

p(v[i]|h[0]) = σ(Wᐪ · h[0] + b)[i]*

将这两个遍重复 k 步或直到达到收敛。 根据研究人员的说法,k = 1给出了很好的结果,因此我们将保持k = 1

可见向量V和隐藏向量的联合构型具有如下能量:

自由能还与每个可见向量V相关,为与具有V的所有构型具有相同概率的单个配置所需的能量:

使用对比度发散目标函数,即Mean(F(Voriginal))- Mean(F(Vreconstructed)),权重的变化由此给出:

在此,η是学习率。 对于偏差bc存在相似的表达式。

操作步骤

我们按以下步骤进行:

  1. 导入模块:
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
%matplotlib inline
  1. 声明 RBM 类,它将完成主要任务。 __init__将建立完整的图,正向和反向传递以及目标函数; 我们将使用 TensorFlow 内置的优化器来更新权重和偏差:
class RBM(object):
    def __init__(self, m, n):
        """
        m: Number of neurons in visible layer
        n: number of neurons in hidden layer
        """
        self._m = m
        self._n = n
        # Create the Computational graph
        # Weights and biases
        self._W = tf.Variable(tf.random_normal(shape=(self._m,self._n)))
        self._c = tf.Variable(np.zeros(self._n).astype(np.float32)) #bias for hidden layer
        self._b = tf.Variable(np.zeros(self._m).astype(np.float32)) #bias for Visible layer
         # Placeholder for inputs
        self._X = tf.placeholder('float', [None, self._m])
         # Forward Pass
        _h = tf.nn.sigmoid(tf.matmul(self._X, self._W) + self._c)
        self.h = tf.nn.relu(tf.sign(_h -         tf.random_uniform(tf.shape(_h))))
        #Backward pass
        _v = tf.nn.sigmoid(tf.matmul(self.h, tf.transpose(self._W)) + self._b)
        self.V = tf.nn.relu(tf.sign(_v - tf.random_uniform(tf.shape(_v))))
        # Objective Function
        objective = tf.reduce_mean(self.free_energy(self._X)) - tf.reduce_mean(
self.free_energy(self.V))
        self._train_op = tf.train.GradientDescentOptimizer(1e-3).minimize(objective)
        # Cross entropy cost
        reconstructed_input = self.one_pass(self._X)
        self.cost = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(
labels=self._X, logits=reconstructed_input))
  1. 我们在RBM类中定义fit()方法。 在__init__中声明了所有操作后,训练只是在会话中调用train_op。 我们使用批量训练:
 def fit(self, X, epochs = 1, batch_size = 100):
        N, D = X.shape
        num_batches = N // batch_size

        obj = []
        for i in range(epochs):
            #X = shuffle(X)
            for j in range(num_batches):
                batch = X[j * batch_size: (j * batch_size + batch_size)]
                _, ob = self.session.run([self._train_op,self.cost ], feed_dict={self._X: batch})
                if j % 10 == 0:
                    print('training epoch {0} cost {1}'.format(j,ob)) 
                obj.append(ob)
        return obj
  1. 还有其他辅助函数可计算对率误差并从网络返回重建的图像:
def set_session(self, session):
    self.session = session

def free_energy(self, V):
    b = tf.reshape(self._b, (self._m, 1))
    term_1 = -tf.matmul(V,b)
    term_1 = tf.reshape(term_1, (-1,))
    term_2 = -tf.reduce_sum(tf.nn.softplus(tf.matmul(V,self._W) +
        self._c))
    return term_1 + term_2

def one_pass(self, X):
    h = tf.nn.sigmoid(tf.matmul(X, self._W) + self._c)
    return tf.matmul(h, tf.transpose(self._W)) + self._b

def reconstruct(self,X):
    x = tf.nn.sigmoid(self.one_pass(X))
    return self.session.run(x, feed_dict={self._X: X})

  1. 我们加载 MNIST 数据集:
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
trX, trY, teX, teY = mnist.train.images, mnist.train.labels, mnist.test.images, mnist.test.labels
  1. 接下来,我们在 MNIST 数据集上训练RBM
Xtrain = trX.astype(np.float32)
Xtest = teX.astype(np.float32)
_, m = Xtrain.shape
rbm = RBM(m, 100)
#Initialize all variables
init = tf.global_variables_initializer()
with tf.Session() as sess:
    sess.run(init)
    rbm.set_session(sess)
    err = rbm.fit(Xtrain)
    out = rbm.reconstruct(Xest[0:100])  # Let us reconstruct Test Data
  1. 不同周期的函数误差:

工作原理

由于其具有重建图像的能力,RBM 可用于从现有数据中生成更多数据。 通过制作一个小的助手绘图代码,我们可以看到原始和重建的 MNIST 图像:

row, col = 2, 8
idx = np.random.randint(0, 100, row * col // 2)
f, axarr = plt.subplots(row, col, sharex=True, sharey=True, figsize=(20,4))
for fig, row in zip([Xtest_noisy,out], axarr):
    for i,ax in zip(idx,row):
        ax.imshow(fig[i].reshape((28, 28)), cmap='Greys_r')
        ax.get_xaxis().set_visible(False)
        ax.get_yaxis().set_visible(False)

我们得到的结果如下:

另见

使用 RBM 的推荐系统

网上零售商广泛使用推荐系统向客户推荐产品。 例如,亚马逊会告诉您购买此商品的其他客户对什么感兴趣,或者 Netflix 根据您所观看的内容以及有相同兴趣的其他 Netflix 用户所观看的内容推荐电视连续剧和电影。 这些推荐器系统在协作筛选的基础上工作。 在协作过滤中,系统根据用户的过去行为来构建模型。 我们将使用上一个秘籍中的 RBM 构建一个使用协作过滤来推荐电影的推荐器系统。 这项工作中的一个重要挑战是,大多数用户不会对所有产品/电影进行评分,因此大多数数据都将丢失。 如果有 M 个产品和 N 个用户,则我们需要构建一个数组N x M,其中包含用户的已知等级并将所有未知值设为零。

准备

为了使用协作过滤创建推荐系统,我们需要修改数据。 作为说明,我们将使用来自这里的电影数据集。 数据由两个.dat文件组成:movies.datratings.datmovies.dat文件包含 3 列:3883 个电影的 MovieID,Title 和 Genre。 ratings.dat文件包含四列:UserID,MovieID,Rating 和 Time。 我们需要合并这两个数据文件,以便能够构建一个数组,其中对于每个用户,我们对所有 3,883 部电影都有一个评分。 问题在于用户通常不会对所有电影进行评级,因此我们仅对某些电影进行非零(标准化)评级。 其余部分设为零,因此不会对隐藏层有所贡献。

操作步骤

  1. 我们将使用在先前秘籍中创建的RBM类。 让我们定义我们的 RBM 网络; 可见单元的数量将是电影的数量,在我们的示例中为 3883(movies_df是包含movies.dat文件中的数据的数据帧):
m = len(movies_df)  # Number of visible units
n = 20  # Number of Hidden units
recommender = rbm.RBM(m,n)
  1. 我们使用 Pandas 合并和groupby命令创建了一个列表trX,该列表包含大约 1,000 个用户的规范化电影评分。 列表的大小为1000 x 3883。我们使用它来训练我们的 RBM:
Xtrain = np.array(trX) 
init = tf.global_variables_initializer()
with tf.Session() as sess:
 sess.run(init)
 recommender.set_session(sess)
 err = recommender.fit(Xtrain, epochs=10)
  1. 每个周期的跨逻辑误差减少:

  1. 网络现已接受训练; 我们使用它为索引为 150 的随机用户(可能是任何现有用户)获得推荐:
user_index = 150
x = np.array([Xtrain[user_index, :]])
init = tf.global_variables_initializer()
with tf.Session() as sess:
 sess.run(init)
 recommender.set_session(sess)
 out = recommender.reconstruct(x.astype(np.float32))
  1. 结果与现有数据帧合并,我们可以看到该用户的推荐分数:

更多

杰弗里·欣顿(Geoffrey Hinton)教授领导的多伦多大学团队赢得了 Netflix 最佳协作过滤竞赛的冠军,该协作过滤使用 RBM 来预测电影的用户收视率。 可以从他们的论文中获取其工作的详细信息

一个 RBM 的隐藏单元的输出可以馈送到另一个 RBM 的可见单元,可以重复此过程以形成 RBM 的栈。 这导致栈式 RBM 。 假定不存在其他堆叠式 RBM,则对其进行独立训练。 大量栈式 RBM 构成了深度信念网络(DBN)。 可以使用有监督或无监督的训练来训练 DBN。 您将在下一个秘籍中了解有关它们的更多信息。

用于情感检测的 DBN

在本秘籍中,我们将学习如何首先堆叠 RBM 来制作 DBN,然后训练它来检测情感。 秘籍中有趣的部分是我们采用了两种不同的学习范例:首先,我们使用无监督学习对 RBM 进行了预训练,最后,我们有了一个 MLP 层,该层是使用监督学习进行了训练的。

准备

我们使用已经在秘籍受限玻尔兹曼机中创建的 RBM 类,只需进行一次更改即可,现在无需在训练后重建图像。 取而代之的是,我们栈式 RBM 将仅将数据转发至 DBN 的最后一个 MLP 层。 这是通过从类中删除reconstruct()函数并将其替换为rbm_output()函数来实现的:

def rbm_output(self,X):
    x = tf.nn.sigmoid(tf.matmul(X, self._W) + self._c)
    return self.session.run(x, feed_dict={self._X: X})

对于数据,我们考虑了 Kaggle 面部表情识别数据,该数据可从这里获得。 此处给出的数据描述为:

数据由48 x 48像素的面部灰度图像组成。 面部已自动注册,因此面部或多或少居中,并且在每个图像中占据大约相同的空间量。 任务是根据面部表情中显示的情感将每个面孔分类为七个类别之一(0 为愤怒,1 恶心,2 为恐惧,3 为快乐,4 为悲伤,5 为惊奇,6 为中性) 。

train.csv包含两列,“情感”和“像素”。 “情感”列包含图像中存在的情感的数字代码,范围从 0 到 6(含)。 “像素”列包含每个图像用引号引起来的字符串。 该字符串的内容是按行主要顺序分隔的像素值。 test.csv仅包含“像素”列,您的任务是预测情感列。

训练集包含 28,709 个示例。 用于排行榜的公共测试集包含 3,589 个示例。 最终测试集用于确定比赛的获胜者,另外还有 3,589 个示例。

该数据集由 Pierre-Luc Carrier 和 Aaron Courville 进行,是正在进行的研究项目的一部分。 他们为研讨会的组织者提供了他们数据集的初步版本,供比赛使用。

完整的数据合而为一。 名为fer2013.csvcsv文件。 我们从中分离出训练,验证和测试数据:

data = pd.read_csv('data/fer2013.csv')
tr_data = data[data.Usage == "Training"]
test_data = data[data.Usage == "PublicTest"]
mask = np.random.rand(len(tr_data)) < 0.8
train_data = tr_data[mask]
val_data = tr_data[~mask]

我们将需要预处理数据,即将像素和情感标签分开。 为此,我们制作了两个函数dense_to_one_hot (),它对标签执行了单热编码。 第二个函数是preprocess_data(),它将单个像素分离为一个数组。 在这两个函数的帮助下,我们生成了训练,验证和测试数据集的输入特征和标签:

def dense_to_one_hot(labels_dense, num_classes):
     num_labels = labels_dense.shape[0]
     index_offset = np.arange(num_labels) * num_classes
     labels_one_hot = np.zeros((num_labels, num_classes))
     labels_one_hot.flat[index_offset + labels_dense.ravel()] = 1
     return labels_one_hot
def preprocess_data(dataframe):
     pixels_values = dataframe.pixels.str.split(" ").tolist()
     pixels_values = pd.DataFrame(pixels_values, dtype=int)
     images = pixels_values.values
     images = images.astype(np.float32)
     images = np.multiply(images, 1.0/255.0)
     labels_flat = dataframe["emotion"].values.ravel()
     labels_count = np.unique(labels_flat).shape[0]
     labels = dense_to_one_hot(labels_flat, labels_count)
     labels = labels.astype(np.uint8)
     return images, labels

使用前面代码中定义的函数,我们以训练所需的格式获取数据。 基于本文针对 MNIST 提到的相似原理,我们构建了情感检测 DBN

操作步骤

我们按以下步骤进行:

  1. 我们需要导入标准模块 TensorFlow,NumPy 和 Pandas,以读取.csv文件和 Matplolib:
import tensorflow as tf
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
  1. 训练,验证和测试数据是使用辅助函数获得的:
X_train, Y_train = preprocess_data(train_data)
X_val, Y_val = preprocess_data(val_data)
X_test, Y_test = preprocess_data(test_data)
  1. 让我们来探讨一下我们的数据。 我们绘制平均图像并找到每个训练,验证和测试数据集中的图像数量:
# Explore Data
mean_image = X_train.mean(axis=0)
std_image = np.std(X_train, axis=0)
print("Training Data set has {} images".format(len(X_train)))
print("Validation Data set has {} images".format(len(X_val)))
print("Test Data set has {} images".format(len(X_test)))
plt.imshow(mean_image.reshape(48,48), cmap='gray')

我们得到的结果如下:

  1. 我们还会看到训练样本中的图像及其各自的标签:
classes = ['angry','disgust','fear','happy','sad','surprise','neutral']
num_classes = len(classes)
samples_per_class = 7
for y,cls in enumerate(classes):
     idxs = np.flatnonzero(np.argmax(Y_train, axis =1) == y)
     idxs = np.random.choice(idxs, samples_per_class, replace=False)
     for i, idx in enumerate(idxs):
         plt_idx = i * num_classes + y + 1
         plt.subplot(samples_per_class, num_classes, plt_idx)
         plt.imshow(X_train[idx].reshape(48,48), cmap='gray') #pixel height and width
         plt.axis('off')
         if i == 0:
             plt.title(cls)
plt.show()

情节如下:

  1. 接下来,我们定义 RBM 栈; 每个 RBM 都将先前 RBM 的输出作为其输入:
RBM_hidden_sizes = [1500, 700, 400] #create 4 layers of RBM with size 1500, 700, 400 and 100
#Set input as training data
inpX = X_train
#Create list to hold our RBMs
rbm_list = []
#Size of inputs is the number of inputs in the training set
input_size = inpX.shape[1]
#For each RBM we want to generate
for i, size in enumerate(RBM_hidden_sizes):
     print ('RBM: ',i,' ',input_size,'->', size)
     rbm_list.append(RBM(input_size, size))
     input_size = size

这将生成三个 RBM:第一个 RBM 具有 2304(48×48)个输入和 1500 个隐藏单元,第二个 RBM 具有 1500 个输入和 700 个隐藏单元,最后第三个 RBM 具有 700 个输入和 400 个隐藏单元。

  1. 我们逐一训练每个 RBM。 该技术也称为贪婪训练。 在原始论文中,用于在 MNIST 上训练每个 RBM 的周期数是 30,因此在这里,增加周期也应会改善网络的表现:
# Greedy wise training of RBMs
init = tf.global_variables_initializer()
for rbm in rbm_list:
     print ('New RBM:')
     #Train a new one
     with tf.Session() as sess:
         sess.run(init)
         rbm.set_session(sess)
         err = rbm.fit(inpX, 5)
         inpX_n = rbm.rbm_output(inpX)
         print(inpX_n.shape)
         inpX = inpX_n
  1. 我们定义一个DBN类。 在类中,我们用三层 RBM 和另外两层 MLP 构建完整的 DBN。 从预训练的 RBM 中加载 RBM 层的权重。 我们还声明了训练和预测 DBN 的方法; 为了进行微调,网络尝试最小化均方损失函数:
class DBN(object):

     def __init__(self, sizes, X, Y, eta = 0.001, momentum = 0.0, epochs = 10, batch_size = 100):
         #Initialize hyperparameters
         self._sizes = sizes
         print(self._sizes)
         self._sizes.append(1000) # size of the first FC layer
         self._X = X
         self._Y = Y
         self.N = len(X)
         self.w_list = []
         self.c_list = []
         self._learning_rate = eta
         self._momentum = momentum
         self._epochs = epochs
         self._batchsize = batch_size
         input_size = X.shape[1]

         #initialization loop
         for size in self._sizes + [Y.shape[1]]:
             #Define upper limit for the uniform distribution range
             max_range = 4 * math.sqrt(6\. / (input_size + size))

             #Initialize weights through a random uniform distribution
             self.w_list.append(
             np.random.uniform( -max_range, max_range, [input_size,         size]).astype(np.float32))

             #Initialize bias as zeroes
             self.c_list.append(np.zeros([size], np.float32))
             input_size = size

         # Build DBN
         #Create placeholders for input, weights, biases, output
         self._a = [None] * (len(self._sizes) + 2)
         self._w = [None] * (len(self._sizes) + 1)
         self._c = [None] * (len(self._sizes) + 1)
         self._a[0] = tf.placeholder("float", [None, self._X.shape[1]])
         self.y = tf.placeholder("float", [None, self._Y.shape[1]])

         #Define variables and activation function
         for i in range(len(self._sizes) + 1):
             self._w[i] = tf.Variable(self.w_list[i])
             self._c[i] = tf.Variable(self.c_list[i])
         for i in range(1, len(self._sizes) + 2):
             self._a[i] = tf.nn.sigmoid(tf.matmul(self._a[i - 1], self._w[i - 1]) + self._c[i - 1])

         #Define the cost function
         cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=self.y, logits= self._a[-1]))
         #cost = tf.reduce_mean(tf.square(self._a[-1] - self.y))

         #Define the training operation (Momentum Optimizer minimizing the Cost function)
         self.train_op = tf.train.AdamOptimizer(learning_rate=self._learning_rate).minimize(cost)

         #Prediction operation
         self.predict_op = tf.argmax(self._a[-1], 1)

     #load data from rbm
     def load_from_rbms(self, dbn_sizes,rbm_list):
         #Check if expected sizes are correct
         assert len(dbn_sizes) == len(self._sizes)

         for i in range(len(self._sizes)):
             #Check if for each RBN the expected sizes are correct
             assert dbn_sizes[i] == self._sizes[i]

         #If everything is correct, bring over the weights and biases
         for i in range(len(self._sizes)-1):
             self.w_list[i] = rbm_list[i]._W
             self.c_list[i] = rbm_list[i]._c

     def set_session(self, session):
         self.session = session

    #Training method
     def train(self, val_x, val_y):
         #For each epoch
         num_batches = self.N // self._batchsize

         batch_size = self._batchsize
         for i in range(self._epochs):
             #For each step
             for j in range(num_batches):
                 batch = self._X[j * batch_size: (j * batch_size + batch_size)]
                 batch_label = self._Y[j * batch_size: (j * batch_size + batch_size)]

                 self.session.run(self.train_op, feed_dict={self._a[0]: batch, self.y: batch_label})

                 for j in range(len(self._sizes) + 1):
                     #Retrieve weights and biases
                     self.w_list[j] = sess.run(self._w[j])
                     self.c_list[j] = sess.run(self._c[j])

             train_acc = np.mean(np.argmax(self._Y, axis=1) ==
 self.session.run(self.predict_op, feed_dict={self._a[0]: self._X, self.y: self._Y}))

             val_acc = np.mean(np.argmax(val_y, axis=1) ==
 self.session.run(self.predict_op, feed_dict={self._a[0]: val_x, self.y: val_y}))
             print (" epoch " + str(i) + "/" + str(self._epochs) + " Training Accuracy: " +  str(train_acc) + " Validation Accuracy: " + str(val_acc))

     def predict(self, X):
         return self.session.run(self.predict_op, feed_dict={self._a[0]: X})
  1. 现在,我们训练实例化DBN对象并对其进行训练。 并预测测试数据的标签:
nNet = DBN(RBM_hidden_sizes, X_train, Y_train, epochs = 80)
with tf.Session() as sess:
     #Initialize Variables
     sess.run(tf.global_variables_initializer())
     nNet.set_session(sess)
     nNet.load_from_rbms(RBM_hidden_sizes,rbm_list)
     nNet.train(X_val, Y_val)
     y_pred = nNet.predict(X_test)

工作原理

RBM 使用无监督学习来学习模型的隐藏表示/特征,然后对与预训练 RBM 一起添加的全连接层进行微调。

这里的精度在很大程度上取决于图像表示。 在前面的秘籍中,我们没有使用图像处理,仅使用了 0 到 1 之间缩放的灰度图像。但是,如果我们按照以下论文所述添加图像处理,则会进一步提高精度。 因此,我们在preprocess_data函数中将每个图像乘以 100.0/255.0,然后将以下几行代码添加到主代码中:

std_image = np.std(X_train, axis=0)
X_train = np.divide(np.subtract(X_train,mean_image), std_image)
X_val = np.divide(np.subtract(X_val,mean_image), std_image)
X_test = np.divide(np.subtract(X_test,mean_image), std_image)

更多

在前面的示例中,没有进行预处理,这三个数据集的准确率大约为 40%。 但是,当我们添加预处理时,训练数据的准确率将提高到 90%,但是对于验证和测试,我们仍然可以获得约 45% 的准确率。

可以引入许多更改来改善结果。 首先,我们在秘籍中使用的数据集是只有 22,000 张图像的 Kaggle 数据集。 如果观察这些图像,则会发现仅过滤面部的步骤会改善结果。 如下文所述,另一种策略是增加隐藏层的大小而不是减小它们的大小

在识别情感方面确实非常成功的另一个更改是使用面部关键点而不是整个面部训练

使用前面的秘籍,您可以尝试这些更改并探索表现如何提高。 愿 GPU 力量与您同在!

八、自编码器

自编码器是前馈,非循环神经网络,可通过无监督学习来学习。 他们具有学习数据的紧凑表示的固有能力。 它们是深度信念网络的中心,可在图像重建,聚类,机器翻译等领域找到应用。 在本章中,您将学习和实现自编码器的不同变体,并最终学习如何堆叠自编码器。 本章包括以下主题:

  • 普通自编码器
  • 稀疏自编码器
  • 去噪自编码器
  • 卷积自编码器
  • 栈式自编码器

介绍

自编码器,也称为空竹网络自动关联器,最初由 Hinton 和 PDP 小组于 1980 年代提出。 它们是前馈网络,没有任何反馈,并且它们是通过无监督学习来学习的。 像第 3 章的多人感知机,神经网络感知机一样,它们使用反向传播算法进行学习,但有一个主要区别-目标与输入相同。

我们可以认为自编码器由两个级联网络组成-第一个网络是编码器,它接受输入x,然后使用变换h将其编码为编码信号y

y = h(x)

第二网络使用编码信号y作为其输入,并执行另一个变换f以获得重构信号r

r = f(y) = f(h(x))

我们将误差e定义为原始输入x与重构信号r之间的差,e = x - r。然后,网络通过减少均方误差MSE)进行学习,并且像 MLP 一样,该误差会传播回隐藏层。 下图显示了自编码器,其中编码器和解码器分别突出显示。 自编码器可以具有权重分配,也就是说,解码器和编码器的权重只是彼此的换位,这可以在训练参数数量较少时帮助网络更快地学习,但同时会降低编码器的自由度。 网络。 它们与第 7 章“无监督学习”的 RBM 非常相似,但有一个很大的区别-自编码器中神经元的状态是确定性的,而在 RBM 中,神经元是概率性的:

根据隐藏层的大小,自编码器分为不完整(隐藏层的神经元少于输入层)或过完整(隐藏层的神经元多于输入层)。 。 根据对损失施加的限制/约束,我们有多种类型的自编码器:稀疏自编码器,降噪自编码器和卷积自编码器。 在本章中,您将了解自编码器中的这些变体,并使用 TensorFlow 实现它们。

自编码器的明显应用之一是在降维领域[2]。 结果表明,与 PCA 相比,自编码器产生了更好的结果。 自编码器还可以用于特征提取[3],文档检索[2],分类和异常检测。

另见

  • Rumelhart, David E., Geoffrey E. Hinton, and Ronald J. Williams. Learning internal representations by error propagation. No. ICS-8506. California Univ San Diego La Jolla Inst for Cognitive Science, 1985. (http://www.cs.toronto.edu/~fritz/absps/pdp8.pdf)

  • Hinton, Geoffrey E., and Ruslan R. Salakhutdinov. Reducing the dimensionality of data with neural networks, science 313.5786 (2006): 504-507. (https://pdfs.semanticscholar.org/7d76/b71b700846901ac4ac119403aa737a285e36.pdf)

  • Masci, Jonathan, et al. Stacked convolutional auto-encoders for hierarchical feature extraction. Artificial Neural Networks and Machine Learning–ICANN 2011 (2011): 52-59. (https://www.researchgate.net/profile/Jonathan_Masci/publication/221078713_Stacked_Convolutional_Auto-Encoders_for_Hierarchical_Feature_Extraction/links/0deec518b9c6ed4634000000/Stacked-Convolutional-Auto-Encoders-for-Hierarchical-Feature-Extraction.pdf)

  • Japkowicz, Nathalie, Catherine Myers, and Mark Gluck. A novelty detection approach to classification. IJCAI. Vol. 1. 1995. (http://www.ijcai.org/Proceedings/95-1/Papers/068.pdf)

普通自编码器

Hinton 提出的普通自编码器仅包含一个隐藏层。 隐藏层中神经元的数量少于输入(或输出)层中神经元的数量。 这导致对网络中信息流产生瓶颈效应,因此我们可以将隐藏层视为瓶颈层,从而限制了要存储的信息。 自编码器中的学习包括在隐藏层上开发输入信号的紧凑表示,以便输出层可以忠实地再现原始输入:

具有单个隐藏层的自编码器

准备

此秘籍将使用自编码器进行图像重建; 我们将在 MNIST 数据库上训练自编码器,并将其用于重建测试图像。

操作步骤

我们按以下步骤进行:

  1. 与往常一样,第一步是导入所有必需的模块:
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
%matplotlib inline
  1. 接下来,我们从 TensorFlow 示例中获取 MNIST 数据-这里要注意的重要一点是,标签不是一次性编码的,仅仅是因为我们没有使用标签来训练网络。 自编码器通过无监督学习来学习:
mnist = input_data.read_data_sets("MNIST_data/")
trX, trY, teX, teY = mnist.train.images, mnist.train.labels, mnist.test.images, mnist.test.labels
  1. 接下来,我们声明一个类AutoEncoder; 该类具有init方法来为自编码器初始化权重,偏差和占位符。 我们还可以使用init方法构建完整的图。 该类还具有用于encoderdecoder,设置会话(set_session)和fit的方法。 我们在此处构建的自编码器使用简单的 MSE 作为loss函数,我们尝试使用AdamOptimizer对其进行优化:
class AutoEncoder(object):
def __init__(self, m, n, eta = 0.01):
"""
m: Number of neurons in input/output layer
n: number of neurons in hidden layer
"""
self._m = m
self._n = n
self.learning_rate = eta

# Create the Computational graph

# Weights and biases
self._W1 = tf.Variable(tf.random_normal(shape=(self._m,self._n)))
self._W2 = tf.Variable(tf.random_normal(shape=(self._n,self._m)))
self._b1 = tf.Variable(np.zeros(self._n).astype(np.float32)) #bias for hidden layer
self._b2 = tf.Variable(np.zeros(self._m).astype(np.float32)) #bias for output layer

# Placeholder for inputs
self._X = tf.placeholder('float', [None, self._m])

self.y = self.encoder(self._X)
self.r = self.decoder(self.y)
error = self._X - self.r

self._loss = tf.reduce_mean(tf.pow(error, 2))
self._opt = tf.train.AdamOptimizer(self.learning_rate).minimize(self._loss)

def encoder(self, x):
h = tf.matmul(x, self._W1) + self._b1
return tf.nn.sigmoid(h)

def decoder(self, x):
h = tf.matmul(x, self._W2) + self._b2
return tf.nn.sigmoid(h)

def set_session(self, session):
self.session = session

def reduced_dimension(self, x):
h = self.encoder(x)
return self.session.run(h, feed_dict={self._X: x})

def reconstruct(self,x):
h = self.encoder(x)
r = self.decoder(h)
return self.session.run(r, feed_dict={self._X: x})

def fit(self, X, epochs = 1, batch_size = 100):
N, D = X.shape
num_batches = N // batch_size

obj = []
for i in range(epochs):
#X = shuffle(X)
for j in range(num_batches):
    batch = X[j * batch_size: (j * batch_size + batch_size)]
    _, ob = self.session.run([self._opt,self._loss], feed_dict={self._X: batch})
    if j % 100 == 0 and i % 100 == 0:
        print('training epoch {0} batch {2} cost {1}'.format(i,ob, j)) 
obj.append(ob)
return obj

为了能够在训练后使用自编码器,我们还定义了两个工具函数:reduced_dimension提供编码器网络的输出,reconstruct重构最终图像。

  1. 我们将输入数据转换为float进行训练,初始化所有变量,然后开始计算会话。 在计算中,我们目前仅测试自编码器的重构能力:
Xtrain = trX.astype(np.float32)
Xtest = teX.astype(np.float32)
_, m = Xtrain.shape

autoEncoder = AutoEncoder(m, 256)

#Initialize all variables
init = tf.global_variables_initializer()
with tf.Session() as sess:
    sess.run(init)
    autoEncoder.set_session(sess)
    err = autoEncoder.fit(Xtrain, epochs=10)
    out = autoEncoder.reconstruct(Xtest[0:100])
  1. 我们可以通过绘制误差与周期的关系图来验证我们的网络在训练时是否确实优化了 MSE。 为了获得良好的训练,应该使用epochs来减少误差:
plt.plot(err)
plt.xlabel('epochs')
plt.ylabel('cost')

该图如下所示:

我们可以看到,随着网络的学习,损耗/成本正在降低,到我们达到 5,000 个周期时,损耗/成本几乎在一条线上振荡。 这意味着进一步增加周期将是无用的。 如果现在要改善训练,则应该更改超参数,例如学习率,批量大小和使用的优化程序。

  1. 现在让我们看一下重建的图像。 在这里,您可以同时看到由我们的自编码器生成的原始图像和重建图像:
# Plotting original and reconstructed images
row, col = 2, 8
idx = np.random.randint(0, 100, row * col // 2)
f, axarr = plt.subplots(row, col, sharex=True, sharey=True, figsize=(20,4))
for fig, row in zip([Xtest,out], axarr):
    for i,ax in zip(idx,row):
        ax.imshow(fig[i].reshape((28, 28)), cmap='Greys_r')
        ax.get_xaxis().set_visible(False)
        ax.get_yaxis().set_visible(False)

我们得到以下结果:

工作原理

有趣的是,在前面的代码中,我们将输入的尺寸从 784 减少到 256,并且我们的网络仍可以重建原始图像。 让我们比较一下具有相同隐藏层尺寸的 RBM(第 7 章,无监督学习)的表现:

我们可以看到,自编码器重建的图像比 RBM 重建的图像更清晰。 原因是,在自编码器中,还有其他权重(从隐藏层到解码器输出层的权重)需要训练,因此保留了学习知识。 随着自编码器了解更多,即使两者都将信息压缩到相同的尺寸,它的表现也比 RBM 更好。

更多

诸如 PCA 之类的自编码器可以用于降维,但是 PCA 仅可以表示线性变换,但是我们可以在自编码器中使用非线性激活函数,从而在编码中引入非线性。 这是从 Hinton 论文复制的结果,该结果使用神经网络降低了数据的维数。 该结果将 PCA(A)的结果与栈式 RBM 作为具有 784-1000-500-250-2 架构的自编码器的结果进行了比较:

正如我们稍后将看到的,当使用栈式自编码器制作自编码器时,每个自编码器最初都经过单独的预训练,然后对整个网络进行微调以获得更好的表现。

稀疏自编码器

我们在前面的秘籍中看到的自编码器的工作方式更像是一个身份网络-它们只是重构输入。 重点是在像素级别重建图像,唯一的限制是瓶颈层中的单元数; 有趣的是,像素级重建不能确保网络将从数据集中学习抽象特征。 通过添加更多约束,我们可以确保网络从数据集中学习抽象特征。

在稀疏自编码器中,将稀疏惩罚项添加到重构误差中,以确保在任何给定时间触发瓶颈层中较少的单元。 如果m是输入模式的总数,那么我们可以定义一个数量ρ_hat(您可以在 Andrew Ng 的讲座中检查数学细节),它测量每个隐藏层单元的净活动(平均触发多少次)。 基本思想是放置一个约束ρ_hat,使其等于稀疏性参数ρ。这导致损失函数中添加了稀疏性的正则项,因此现在loss函数如下:

loss = Mean squared error + Regularization for sparsity parameter

如果ρ_hat偏离ρ,则此正则化项将对网络造成不利影响;做到这一点的一种标准方法是使用ρρ_hat之间的 Kullback-LeiberKL)差异。

准备

在开始秘籍之前,让我们进一步探讨 KL 的差异,D[KL]。 它是两个分布之间差异的非对称度量,在我们的情况下为ρρ_hat。当ρρ_hat相等时,则为零,否则,当ρ_hatρ分叉时,它单调增加。在数学上,它表示为:

这是固定ρ = 0.3D[KL]的图,我们可以看到当ρ_hat = 0.3时,D[KL] = 0;否则在两端单调增长:

操作步骤

我们按以下步骤进行:

  1. 我们导入必要的模块:
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
%matplotlib inline
  1. 从 TensorFlow 示例中加载 MNIST 数据集:
mnist = input_data.read_data_sets("MNIST_data/")
trX, trY, teX, teY = mnist.train.images, mnist.train.labels, mnist.test.images, mnist.test.labels
  1. 定义SparseAutoEncoder类,它与前面的秘籍中的AutoEncoder类非常相似,除了引入了 KL 散度损失之外:
def kl_div(self, rho, rho_hat):
 term2_num = tf.constant(1.)- rho
 term2_den = tf.constant(1.) - rho_hat
 kl = self.logfunc(rho,rho_hat) + self.logfunc(term2_num, term2_den)
 return kl

 def logfunc(self, x1, x2):
 return tf.multiply( x1, tf.log(tf.div(x1,x2)))

我们将 KL 约束添加到损失中,如下所示:

alpha = 7.5e-5
kl_div_loss = tf.reduce_sum(self.kl_div(0.02, tf.reduce_mean(self.y,0)))
loss = self._loss + alpha * kl_div_loss

在此,alpha是赋予稀疏性约束的权重。 该类的完整代码如下:

class SparseAutoEncoder(object):
 def __init__(self, m, n, eta = 0.01):
 """
 m: Number of neurons in input/output layer
 n: number of neurons in hidden layer
 """
 self._m = m
 self._n = n
 self.learning_rate = eta

 # Create the Computational graph

 # Weights and biases
 self._W1 = tf.Variable(tf.random_normal(shape=(self._m,self._n)))
 self._W2 = tf.Variable(tf.random_normal(shape=(self._n,self._m)))
 self._b1 = tf.Variable(np.zeros(self._n).astype(np.float32)) #bias for hidden layer
 self._b2 = tf.Variable(np.zeros(self._m).astype(np.float32)) #bias for output layer

 # Placeholder for inputs
 self._X = tf.placeholder('float', [None, self._m])

 self.y = self.encoder(self._X)
 self.r = self.decoder(self.y)
 error = self._X - self.r

 self._loss = tf.reduce_mean(tf.pow(error, 2))
 alpha = 7.5e-5
 kl_div_loss = tf.reduce_sum(self.kl_div(0.02,   tf.reduce_mean(self.y,0)))
 loss = self._loss + alpha * kl_div_loss 
 self._opt = tf.train.AdamOptimizer(self.learning_rate).minimize(loss)

 def encoder(self, x):
 h = tf.matmul(x, self._W1) + self._b1
 return tf.nn.sigmoid(h)

 def decoder(self, x):
 h = tf.matmul(x, self._W2) + self._b2
 return tf.nn.sigmoid(h)

 def set_session(self, session):
 self.session = session

 def reduced_dimension(self, x):
 h = self.encoder(x)
 return self.session.run(h, feed_dict={self._X: x})

 def reconstruct(self,x):
 h = self.encoder(x)
 r = self.decoder(h)
 return self.session.run(r, feed_dict={self._X: x})

 def kl_div(self, rho, rho_hat):
 term2_num = tf.constant(1.)- rho
 term2_den = tf.constant(1.) - rho_hat
 kl = self.logfunc(rho,rho_hat) + self.logfunc(term2_num, term2_den)
 return kl

 def logfunc(self, x1, x2):
 return tf.multiply( x1, tf.log(tf.div(x1,x2)))

 def fit(self, X, epochs = 1, batch_size = 100):
 N, D = X.shape
 num_batches = N // batch_size

 obj = []
 for i in range(epochs):
     #X = shuffle(X)
     for j in range(num_batches):
         batch = X[j * batch_size: (j * batch_size + batch_size)]
         _, ob = self.session.run([self._opt,self._loss], feed_dict={self._X: batch})
         if j % 100 == 0:
             print('training epoch {0} batch {2} cost {1}'.format(i,ob, j)) 
obj.append(ob)
 return obj
  1. 接下来,我们声明SparseAutoEncoder类的对象,对训练数据进行拟合,并计算重建的图像:
Xtrain = trX.astype(np.float32)
Xtest = teX.astype(np.float32)
_, m = Xtrain.shape
sae = SparseAutoEncoder(m, 256)
#Initialize all variables
init = tf.global_variables_initializer()
with tf.Session() as sess:
 sess.run(init)
 sae.set_session(sess)
 err = sae.fit(Xtrain, epochs=10)
 out = sae.reconstruct(Xtest[0:100])
  1. 让我们看看随着网络学习,均方重构损失的变化:
plt.plot(err)
plt.xlabel('epochs')
plt.ylabel('Reconstruction Loss (MSE)')

情节如下:

  1. 让我们看一下重建的图像:
# Plotting original and reconstructed images
row, col = 2, 8
idx = np.random.randint(0, 100, row * col // 2)
f, axarr = plt.subplots(row, col, sharex=True, sharey=True, figsize=(20,4))
for fig, row in zip([Xtest,out], axarr):
    for i,ax in zip(idx,row):
        ax.imshow(fig[i].reshape((28, 28)), cmap='Greys_r')
        ax.get_xaxis().set_visible(False)
        ax.get_yaxis().set_visible(False)

我们得到以下结果:

工作原理

您必须已经注意到,稀疏自编码器的主要代码与普通自编码器的主要代码完全相同,这是因为稀疏自编码器只有一个主要变化-增加了 KL 发散损耗以确保稀疏性。 隐藏的(瓶颈)层。 但是,如果比较这两个重构,则可以发现即使在隐藏层中具有相同数量的单元,稀疏自编码器也比标准编码器好得多:

训练 MNIST 数据集的原始自编码器后的重建损失为 0.022,而稀疏自编码器则为 0.006。 因此,添加约束会迫使网络学习数据的隐藏表示。

更多

输入的紧凑表示形式以权重存储; 让我们可视化网络学习到的权重。 这分别是标准自编码器和稀疏自编码器的编码器层的权重。 我们可以看到,在标准自编码器中,许多隐藏单元的权重非常大,表明它们工作过度:

另见

去噪自编码器

我们在前两个秘籍中探讨过的两个自编码器是未完成的自编码器的示例,因为与输入(输出)层相比,它们中的隐藏层具有较低的尺寸。 去噪自编码器属于过完整自编码器的类别,因为当隐藏层的尺寸大于输入层的尺寸时,它会更好地工作。

去噪自编码器从损坏的(嘈杂的)输入中学习; 它为编码器网络提供噪声输入,然后将来自解码器的重建图像与原始输入进行比较。 这个想法是,这将帮助网络学习如何去噪输入。 它不再只是按像素进行比较,而是为了进行去噪,还将学习相邻像素的信息。

准备

去噪自编码器还将具有 KL 散度惩罚项; 它在两个主要方面与先前秘籍的稀疏自编码器有所不同。 首先,n_hidden > m瓶颈层中的隐藏单元数大于输入层m中的单元数n_hidden > m。 其次,编码器的输入已损坏。 为了在 TensorFlow 中做到这一点,我们添加了invalid函数,这给输入增加了噪音:

def corruption(x, noise_factor = 0.3): #corruption of the input
    noisy_imgs = x + noise_factor * np.random.randn(*x.shape)
    noisy_imgs = np.clip(noisy_imgs, 0., 1.)
    return noisy_imgs

操作步骤

  1. 像往常一样,第一步是导入必要的模块-TensorFlow,numpy 来操纵输入数据,matplotlib 来进行绘制,等等:
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
import math
%matplotlib inline
  1. 从 TensorFlow 示例中加载数据。 在本章的所有秘籍中,我们都使用标准的 MNIST 数据库进行说明,以便为您提供不同自编码器之间的基准。
mnist = input_data.read_data_sets("MNIST_data/")
trX, trY, teX, teY = mnist.train.images, mnist.train.labels, mnist.test.images, mnist.test.labels
  1. 接下来,我们定义此秘籍的主要组成部分DenoisingAutoEncoder类。 该类与我们在前面的秘籍中创建的SparseAutoEncoder类非常相似。 在这里,我们为噪点图像添加了一个占位符; 该噪声输入被馈送到编码器。 现在,当输入的是噪点图像时,重构误差就是原始清晰图像与解码器输出之间的差。 我们在此保留稀疏惩罚条款。 因此,拟合函数将原始图像和噪声图像都作为其参数。
class DenoisingAutoEncoder(object):
def __init__(self, m, n, eta = 0.01):
"""
m: Number of neurons in input/output layer
n: number of neurons in hidden layer
"""
self._m = m
self._n = n
self.learning_rate = eta

# Create the Computational graph

# Weights and biases
self._W1 = tf.Variable(tf.random_normal(shape=(self._m,self._n)))
self._W2 = tf.Variable(tf.random_normal(shape=(self._n,self._m)))
self._b1 = tf.Variable(np.zeros(self._n).astype(np.float32)) #bias for hidden layer
self._b2 = tf.Variable(np.zeros(self._m).astype(np.float32)) #bias for output layer

# Placeholder for inputs
self._X = tf.placeholder('float', [None, self._m])

self._X_noisy = tf.placeholder('float', [None, self._m])

self.y = self.encoder(self._X_noisy)
self.r = self.decoder(self.y)
error = self._X - self.r

self._loss = tf.reduce_mean(tf.pow(error, 2))
#self._loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(labels =self._X, logits = self.r))
alpha = 0.05
kl_div_loss = tf.reduce_sum(self.kl_div(0.02, tf.reduce_mean(self.y,0)))
loss = self._loss + alpha * kl_div_loss 
self._opt = tf.train.AdamOptimizer(self.learning_rate).minimize(loss)

def encoder(self, x):
h = tf.matmul(x, self._W1) + self._b1
return tf.nn.sigmoid(h)

def decoder(self, x):
h = tf.matmul(x, self._W2) + self._b2
return tf.nn.sigmoid(h)

def set_session(self, session):
self.session = session

def reconstruct(self,x):
h = self.encoder(x)
r = self.decoder(h)
return self.session.run(r, feed_dict={self._X: x})

def kl_div(self, rho, rho_hat):
term2_num = tf.constant(1.)- rho
term2_den = tf.constant(1.) - rho_hat
kl = self.logfunc(rho,rho_hat) + self.logfunc(term2_num, term2_den)
return kl

def logfunc(self, x1, x2):
return tf.multiply( x1, tf.log(tf.div(x1,x2)))

def corrupt(self,x):
return x * tf.cast(tf.random_uniform(shape=tf.shape(x), minval=0,maxval=2),tf.float32)

def getWeights(self):
return self.session.run([self._W1, self._W2,self._b1, self._b2])

def fit(self, X, Xorg, epochs = 1, batch_size = 100):
N, D = X.shape
num_batches = N // batch_size

obj = []
for i in range(epochs):
#X = shuffle(X)
for j in range(num_batches):
batch = X[j * batch_size: (j * batch_size + batch_size)]
batchO = Xorg[j * batch_size: (j * batch_size + batch_size)]
_, ob = self.session.run([self._opt,self._loss], feed_dict={self._X: batchO, self._X_noisy: batch})
if j % 100 == 0:
print('training epoch {0} batch {2} cost {1}'.format(i,ob, j)) 
obj.append(ob)
return obj

也可以向自编码器对象添加噪声。 在这种情况下,您将使用类self._X_noisy = self.corrupt(self._X) * 0.3 + self._X * (1 - 0.3)中定义的损坏方法,并且fit方法也将更改为以下内容:

def fit(self, X, epochs = 1, batch_size = 100):
        N, D = X.shape
        num_batches = N // batch_size

        obj = []
        for i in range(epochs):
            #X = shuffle(X)
            for j in range(num_batches):
                batch = X[j * batch_size: (j * batch_size + batch_size)]
                _, ob = self.session.run([self._opt,self._loss], feed_dict={self._X: batch})
                if j % 100 == 0:
                    print('training epoch {0} batch {2} cost {1}'.format(i,ob, j)) 
                obj.append(ob)
        return obj
  1. 现在,我们使用前面定义的损坏函数来生成一个嘈杂的图像并将其提供给会话:
n_hidden = 800
Xtrain = trX.astype(np.float32)
Xtrain_noisy = corruption(Xtrain).astype(np.float32)
Xtest = teX.astype(np.float32)
#noise = Xtest * np.random.randint(0, 2, Xtest.shape).astype(np.float32)
Xtest_noisy = corruption(Xtest).astype(np.float32) #Xtest * (1-0.3)+ noise *(0.3)
_, m = Xtrain.shape

dae = DenoisingAutoEncoder(m, n_hidden)

#Initialize all variables
init = tf.global_variables_initializer()
with tf.Session() as sess:
 sess.run(init)
 dae.set_session(sess)
 err = dae.fit(Xtrain_noisy, Xtrain, epochs=10)
 out = dae.reconstruct(Xtest_noisy[0:100])
 W1, W2, b1, b2 = dae.getWeights()
 red = dae.reduced_dimension(Xtrain)
  1. 随着网络的学习,重建损失减少:
plt.plot(err)
plt.xlabel('epochs')
plt.ylabel('Reconstruction Loss (MSE)')

情节如下:

  1. 当来自测试数据集的嘈杂图像呈现给训练网络时,重建图像如下:
# Plotting original and reconstructed images
row, col = 2, 8
idx = np.random.randint(0, 100, row * col // 2)
f, axarr = plt.subplots(row, col, sharex=True, sharey=True, figsize=(20,4))
for fig, row in zip([Xtest_noisy,out], axarr):
 for i,ax in zip(idx,row):
 ax.imshow(fig[i].reshape((28, 28)), cmap='Greys_r')
 ax.get_xaxis().set_visible(False)
 ax.get_yaxis().set_visible(False)

我们得到以下结果:

另见

卷积自编码器

研究人员发现卷积神经网络CNN)与图像效果最佳,因为它们可以提取隐藏在图像中的空间信息。 因此,很自然地假设,如果编码器和解码器网络由 CNN 组成,它将比其余的自编码器更好地工作,因此我们有了卷积自编码器CAE)。 在第 4 章“卷积神经网络”中,说明了卷积和最大池化的过程,我们将以此为基础来了解卷积自编码器的工作原理。

CAE 是其中编码器和解码器均为 CNN 网络的一种 CAE。 编码器的卷积网络学习将输入编码为一组信号,然后解码器 CNN 尝试从中重建输入。 它们充当通用特征提取器,并学习从输入捕获特征所需的最佳过滤器。

准备

从第 4 章“卷积神经网络”中,您了解到,随着添加卷积层,传递到下一层的信息在空间范围上会减少,但是在自编码器中,重建的图像应该有输入图像的相同的大小和深度。 这意味着解码器应以某种方式对图像进行大小调整和卷积以重建原始图像。 与卷积一起增加空间范围的一种方法是借助转置的卷积层。 通过tf.nn.conv2d_transpose可以轻松地在 TensorFlow 中实现这些功能,但是发现转置的卷积层会在最终图像中产生伪像。 奥古斯都·奥德纳(Augustus Odena)等。 [1]在他们的工作中表明,可以通过使用最近邻或双线性插值(上采样)再加上卷积层来调整层的大小来避免这些伪像。 他们通过tf.image.resize_images实现了最近邻插值,取得了最佳结果; 我们将在此处采用相同的方法。

操作步骤

  1. 与往常一样,第一步包括必要的模块:
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
import math
%matplotlib inline
  1. 加载输入数据:
mnist = input_data.read_data_sets("MNIST_data/")
trX, trY, teX, teY = mnist.train.images, mnist.train.labels, mnist.test.images, mnist.test.labels
  1. 定义网络参数。 在这里,我们还计算每个最大池层的输出的空间尺寸。 我们需要以下信息来对解码器网络中的图像进行升采样:
# Network Parameters
h_in, w_in = 28, 28 # Image size height and width
k = 3 # Kernel size
p = 2 # pool
s = 2 # Strides in maxpool
filters = {1:32,2:32,3:16}
activation_fn=tf.nn.relu
# Change in dimensions of image after each MaxPool
h_l2, w_l2 = int(np.ceil(float(h_in)/float(s))) , int(np.ceil(float(w_in)/float(s))) # Height and width: second encoder/decoder layer
h_l3, w_l3 = int(np.ceil(float(h_l2)/float(s))) , int(np.ceil(float(w_l2)/float(s))) # Height and width: third encoder/decoder layer
  1. 为输入(嘈杂的图像)和目标(对应的清晰图像)创建占位符:
X_noisy = tf.placeholder(tf.float32, (None, h_in, w_in, 1), name='inputs')
X = tf.placeholder(tf.float32, (None, h_in, w_in, 1), name='targets')
  1. 建立编码器和解码器网络:
### Encoder
conv1 = tf.layers.conv2d(X_noisy, filters[1], (k,k), padding='same', activation=activation_fn)
# Output size h_in x w_in x filters[1]
maxpool1 = tf.layers.max_pooling2d(conv1, (p,p), (s,s), padding='same')
# Output size h_l2 x w_l2 x filters[1] 
conv2 = tf.layers.conv2d(maxpool1, filters[2], (k,k), padding='same', activation=activation_fn)
# Output size h_l2 x w_l2 x filters[2] 
maxpool2 = tf.layers.max_pooling2d(conv2,(p,p), (s,s), padding='same')
# Output size h_l3 x w_l3 x filters[2] 
conv3 = tf.layers.conv2d(maxpool2,filters[3], (k,k), padding='same', activation=activation_fn)
# Output size h_l3 x w_l3 x filters[3]
encoded = tf.layers.max_pooling2d(conv3, (p,p), (s,s), padding='same')
# Output size h_l3/s x w_l3/s x filters[3] Now 4x4x16

### Decoder
upsample1 = tf.image.resize_nearest_neighbor(encoded, (h_l3,w_l3))
# Output size h_l3 x w_l3 x filters[3]
conv4 = tf.layers.conv2d(upsample1, filters[3], (k,k), padding='same', activation=activation_fn)
# Output size h_l3 x w_l3 x filters[3]
upsample2 = tf.image.resize_nearest_neighbor(conv4, (h_l2,w_l2))
# Output size h_l2 x w_l2 x filters[3] 
conv5 = tf.layers.conv2d(upsample2, filters[2], (k,k), padding='same', activation=activation_fn)
# Output size h_l2 x w_l2 x filters[2] 
upsample3 = tf.image.resize_nearest_neighbor(conv5, (h_in,w_in))
# Output size h_in x w_in x filters[2]
conv6 = tf.layers.conv2d(upsample3, filters[1], (k,k), padding='same', activation=activation_fn)
# Output size h_in x w_in x filters[1]

logits = tf.layers.conv2d(conv6, 1, (k,k) , padding='same', activation=None)

# Output size h_in x w_in x 1
decoded = tf.nn.sigmoid(logits, name='decoded')

loss = tf.nn.sigmoid_cross_entropy_with_logits(labels=X, logits=logits)
cost = tf.reduce_mean(loss)
opt = tf.train.AdamOptimizer(0.001).minimize(cost)
  1. 启动会话:
sess = tf.Session()
  1. 将模型拟合给定输入:
epochs = 10
batch_size = 100
# Set's how much noise we're adding to the MNIST images
noise_factor = 0.5
sess.run(tf.global_variables_initializer())
err = []
for i in range(epochs):
 for ii in range(mnist.train.num_examples//batch_size):
 batch = mnist.train.next_batch(batch_size)
 # Get images from the batch
 imgs = batch[0].reshape((-1, h_in, w_in, 1))

 # Add random noise to the input images
 noisy_imgs = imgs + noise_factor * np.random.randn(*imgs.shape)
 # Clip the images to be between 0 and 1
 noisy_imgs = np.clip(noisy_imgs, 0., 1.)

 # Noisy images as inputs, original images as targets
 batch_cost, _ = sess.run([cost, opt], feed_dict={X_noisy: noisy_imgs,X: imgs})
 err.append(batch_cost)
 if ii%100 == 0:
 print("Epoch: {0}/{1}... Training loss {2}".format(i, epochs, batch_cost))
  1. 网络学习到的误差如下:
plt.plot(err)
plt.xlabel('epochs')
plt.ylabel('Cross Entropy Loss')

绘图如下:

  1. 最后,让我们看一下重建的图像:
fig, axes = plt.subplots(rows=2, cols=10, sharex=True, sharey=True, figsize=(20,4))
in_imgs = mnist.test.images[:10]
noisy_imgs = in_imgs + noise_factor * np.random.randn(*in_imgs.shape)
noisy_imgs = np.clip(noisy_imgs, 0., 1.)
reconstructed = sess.run(decoded, feed_dict={X_noisy: noisy_imgs.reshape((10, 28, 28, 1))})
for images, row in zip([noisy_imgs, reconstructed], axes):
 for img, ax in zip(images, row):
 ax.imshow(img.reshape((28, 28)), cmap='Greys_r')
 ax.get_xaxis().set_visible(False)
 ax.get_yaxis().set_visible(False)

这是前面代码的输出:

  1. 关闭会话:
sess.close()

工作原理

前面的 CAE 是降噪 CAE,与仅由一个瓶颈层组成的简单降噪自编码器相比,我们可以看到它在降噪图像方面更好。

更多

研究人员已将 CAE 用于语义分割。 有趣的读物是 Badrinayanan 等人在 2015 年发表的论文 Segnet:一种用于图像分割的深度卷积编码器-解码器架构。 该网络使用 VGG16 的卷积层作为其编码器网络,并包含一层解码器,每个解码器对应一个解码器层次作为其解码器网络。 解码器使用从相应的编码器接收的最大池索引,并对输入特征图执行非线性上采样。 本文的链接在本秘籍的另请参见部分以及 GitHub 链接中给出。

另见

  1. https://distill.pub/2016/deconv-checkerboard/
  2. https://pgaleone.eu/neural-networks/2016/11/24/convolutional-autoencoders/
  3. https://arxiv.org/pdf/1511.00561.pdf
  4. https://github.com/arahusky/Tensorflow-Segmentation

栈式自编码器

到目前为止,涵盖的自编码器(CAE 除外)仅由单层编码器和单层解码器组成。 但是,我们可能在编码器和解码器网络中具有多层; 使用更深的编码器和解码器网络可以使自编码器表示复杂的特征。 这样获得的结构称为栈式自编码器(深度自编码器); 由一个编码器提取的特征将作为输入传递到下一个编码器。 可以将栈式自编码器作为一个整体网络进行训练,以最大程度地减少重构误差,或者可以首先使用您先前学习的无监督方法对每个单独的编码器/解码器网络进行预训练,然后对整个网络进行微调。 已经指出,通过预训练,也称为贪婪分层训练,效果更好。

准备

在秘籍中,我们将使用贪婪分层方法来训练栈式自编码器; 为了简化任务,我们将使用共享权重,因此相应的编码器/解码器权重将相互转换。

操作步骤

我们按以下步骤进行:

  1. 第一步是导入所有必要的模块:
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
%matplotlib inline
  1. 加载数据集:
mnist = input_data.read_data_sets("MNIST_data/")
trX, trY, teX, teY = mnist.train.images, mnist.train.labels, mnist.test.images, mnist.test.labels
  1. 接下来,我们定义类StackedAutoencoder__init__类方法包含一个列表,该列表包含从第一个输入自编码器和学习率开始的每个自编码器中的许多神经元。 由于每一层的输入和输出都有不同的尺寸,因此我们选择字典数据结构来表示每一层的权重,偏差和输入:
class StackedAutoEncoder(object):
 def __init__(self, list1, eta = 0.02):
 """
 list1: [input_dimension, hidden_layer_1, ....,hidden_layer_n]
 """
 N = len(list1)-1
 self._m = list1[0]
 self.learning_rate = eta

 # Create the Computational graph
 self._W = {}
 self._b = {}
 self._X = {}
 self._X['0'] = tf.placeholder('float', [None, list1[0]])

 for i in range(N):
 layer = '{0}'.format(i+1)
 print('AutoEncoder Layer {0}: {1} --> {2}'.format(layer, list1[i], list1[i+1]))
 self._W['E' + layer] = tf.Variable(tf.random_normal(shape=(list1[i], list1[i+1])),name='WtsEncoder'+layer)
 self._b['E'+ layer] = tf.Variable(np.zeros(list1[i+1]).astype(np.float32),name='BiasEncoder'+layer)
 self._X[layer] = tf.placeholder('float', [None, list1[i+1]])
 self._W['D' + layer] = tf.transpose(self._W['E' + layer]) # Shared weights
 self._b['D' + layer] = tf.Variable(np.zeros(list1[i]).astype(np.float32),name='BiasDecoder' + layer)

 # Placeholder for inputs
 self._X_noisy = tf.placeholder('float', [None, self._m])
  1. 我们建立一个计算图来定义每个自编码器的优化参数,同时进行预训练。 当先前的自编码器的编码器的输出为其输入时,它涉及为每个自编码器定义重建损耗。 为此,我们定义类方法pretrainone_pass,它们分别为每个栈式自编码器返回训练操作器和编码器的输出:
 self.train_ops = {}
 self.out = {}

 for i in range(N):
 layer = '{0}'.format(i+1)
 prev_layer = '{0}'.format(i)
 opt = self.pretrain(self._X[prev_layer], layer)
 self.train_ops[layer] = opt
 self.out[layer] = self.one_pass(self._X[prev_layer], self._W['E'+layer], self._b['E'+layer], self._b['D'+layer])
  1. 我们建立计算图以对整个栈式自编码器进行微调。 为此,我们使用类方法encoderdecoder
self.y = self.encoder(self._X_noisy,N) #Encoder output 
self.r = self.decoder(self.y,N) # Decoder ouput

optimizer = tf.train.AdamOptimizer(self.learning_rate) 
error = self._X['0'] - self.r # Reconstruction Error

self._loss = tf.reduce_mean(tf.pow(error, 2))
self._opt = optimizer.minimize(self._loss)
  1. 最后,我们定义类方法fit,以执行每个自编码器的分批预训练,然后进行微调。 在进行预训练时,我们使用未损坏的输入,而对于微调,我们使用损坏的输入。 这使我们能够使用栈式自编码器甚至从嘈杂的输入中进行重构:
def fit(self, Xtrain, Xtr_noisy, layers, epochs = 1, batch_size = 100):
 N, D = Xtrain.shape
 num_batches = N // batch_size
 X_noisy = {}
 X = {}
 X_noisy ['0'] = Xtr_noisy
 X['0'] = Xtrain

 for i in range(layers):
 Xin = X[str(i)]
 print('Pretraining Layer ', i+1)
 for e in range(5):
 for j in range(num_batches):
 batch = Xin[j * batch_size: (j * batch_size + batch_size)]
 self.session.run(self.train_ops[str(i+1)], feed_dict= {self._X[str(i)]: batch})
 print('Pretraining Finished')
 X[str(i+1)] = self.session.run(self.out[str(i+1)], feed_dict = {self._X[str(i)]: Xin})

 obj = []
 for i in range(epochs):
 for j in range(num_batches):
 batch = Xtrain[j * batch_size: (j * batch_size + batch_size)]
 batch_noisy = Xtr_noisy[j * batch_size: (j * batch_size + batch_size)]
 _, ob = self.session.run([self._opt,self._loss], feed_dict={self._X['0']: batch, self._X_noisy: batch_noisy})
 if j % 100 == 0 :
 print('training epoch {0} batch {2} cost {1}'.format(i,ob, j)) 
 obj.append(ob)
 return obj
  1. 不同的类方法如下:
def encoder(self, X, N):
 x = X
 for i in range(N):
 layer = '{0}'.format(i+1)
 hiddenE = tf.nn.sigmoid(tf.matmul(x, self._W['E'+layer]) + self._b['E'+layer])
 x = hiddenE
 return x

def decoder(self, X, N):
 x = X
 for i in range(N,0,-1):
 layer = '{0}'.format(i)
 hiddenD = tf.nn.sigmoid(tf.matmul(x, self._W['D'+layer]) + self._b['D'+layer])
 x = hiddenD
 return x

def set_session(self, session):
 self.session = session

def reconstruct(self,x, n_layers):
 h = self.encoder(x, n_layers)
 r = self.decoder(h, n_layers)
 return self.session.run(r, feed_dict={self._X['0']: x})

def pretrain(self, X, layer ):
 y = tf.nn.sigmoid(tf.matmul(X, self._W['E'+layer]) + self._b['E'+layer])
 r =tf.nn.sigmoid(tf.matmul(y, self._W['D'+layer]) + self._b['D'+layer])

 # Objective Function
 error = X - r # Reconstruction Error
  loss = tf.reduce_mean(tf.pow(error, 2))
 opt = tf.train.AdamOptimizer(.001).minimize(loss, var_list = 
 [self._W['E'+layer],self._b['E'+layer],self._b['D'+layer]])
  return opt

def one_pass(self, X, W, b, c):
 h = tf.nn.sigmoid(tf.matmul(X, W) + b)
 return h
  1. 我们使用降噪自编码器秘籍中定义的破坏函数来破坏图像,最后创建一个StackAutoencoder并对其进行训练:
Xtrain = trX.astype(np.float32)
Xtrain_noisy = corruption(Xtrain).astype(np.float32)
Xtest = teX.astype(np.float32)
Xtest_noisy = corruption(Xtest).astype(np.float32) 
_, m = Xtrain.shape

list1 = [m, 500, 50] # List with number of neurons in Each hidden layer, starting from input layer
n_layers = len(list1)-1
autoEncoder = StackedAutoEncoder(list1)

#Initialize all variables
init = tf.global_variables_initializer()

with tf.Session() as sess:
 sess.run(init)
 autoEncoder.set_session(sess)
 err = autoEncoder.fit(Xtrain, Xtrain_noisy, n_layers, epochs=30)
 out = autoEncoder.reconstruct(Xtest_noisy[0:100],n_layers)
  1. 这里给出了随着堆叠自编码器的微调,重建误差与历时的关系。 您可以看到,由于进行了预训练,我们已经从非常低的重建损失开始了:
plt.plot(err)
plt.xlabel('epochs')
plt.ylabel('Fine Tuning Reconstruction Error')

情节如下:

  1. 现在让我们检查网络的表现。 当网络中出现嘈杂的测试图像时,这是去噪后的手写图像:

工作原理

在栈式自编码器上进行的实验表明,应以较低的学习率值进行预训练。 这样可以确保在微调期间具有更好的收敛性和表现。

更多

整章都是关于自编码器的,尽管目前它们仅用于降维和信息检索,但它们引起了很多兴趣。 首先,因为它们不受监督,其次,因为它们可以与 FCN 一起使用。 它们可以帮助我们应对维度的诅咒。 研究人员已经证明,它们也可以用于分类和异常检测。

另见

九、强化学习

本章介绍强化学习RL)-学习最少,但最有前途的学习范例。 本章包括以下主题:

  • 了解 OpenAI Gym
  • 实现神经网络智能体来玩吃豆人
  • 用 Q 学习平衡推车
  • 深度 Q 网络和 Atari 游戏
  • 用策略梯度玩 Pong 游戏

介绍

2016 年 3 月,由 Google DeepMind 制作的程序 AlphaGo 以 4 比 1 击败了世界上最好的围棋选手,十八届世界冠军李·塞多尔(Lee Sedol)。 ,具有:

208,168,199,381,979,984,699,478,633,344,862,770,286,522,
453,884,530,548,425,639,456,820,927,419,612,738,015,378,
525,648,451,698,519,643,907,259,916,015,628,128,546,089,
888,314,427, 129,715,319,317,557,736,620,397,247,064,840,935

可能的法律委员会职位。 玩和赢得围棋无法通过简单的蛮力完成。 它需要技巧,创造力,以及专业围棋选手所说的直觉。

AlphaGo 在基于 RL 算法的深度神经网络与最先进的树搜索算法相结合的帮助下实现了这一非凡的成就。 本章介绍 RL 和我们用于执行 RL 的一些算法。

因此,出现的第一个问题是什么是 RL,它与我们在前几章中探讨的有监督和无监督学习有何不同?

拥有宠物的任何人都知道,训练宠物的最佳策略是奖励其期望的行为,并惩罚其不良行为。 RL 也称为与批评者进行的学习,它是一种学习范例,其中智能体以相同的方式进行学习。 这里的智能体对应我们的网络(程序); 它可以执行一组动作a),这会导致环境的状态s)发生变化。 ,则智能体会感知其是否获得奖励或惩罚。

例如,在狗的情况下,狗是我们的智能体,狗的自愿肌肉运动是动作,地面是环境。 狗给我们骨头作为奖励,从而感觉到我们对其动作的反应:

改编自强化学习:萨顿(Sutton)和巴托(Barto)的引言即使我们的大脑在前脑底部有一组称为“基底神经节”的皮层下核,根据神经科学,它们负责选择动作,即帮助 我们决定在任何给定时间执行几个可能的动作中的哪一个。

智能体的目的是使报酬最大化并减少惩罚。 做出此决策涉及各种挑战,最重要的挑战是如何最大化未来的回报,也称为临时得分分配问题。 智能体根据某些策略(π)决定其操作; 智能体根据其与环境的交互来学习此策略(π)。 有各种策略学习算法; 我们将在本章中探索其中的一些。 智能体通过反复试验的过程来推断最优策略(π*),并且要学习最优策略,智能体需要与之交互的环境; 我们将使用提供不同环境的 OpenAI Gym。

在这里,我们仅对 RL 中涉及的基本概念进行了回顾; 我们假设您熟悉马尔可夫概念的决策过程,折现因子和值函数(状态值和动作值)。

在本章以及随后的秘籍中,我们将一集定义为游戏的一次运行,例如,解决一个数独游戏。 通常,智能体会播放许多剧集以学习一种最佳策略,该策略可使奖励最大化。

看到 RL 特工在没有任何游戏隐性知识的情况下,如何在这些游戏中学会玩游戏,不仅玩游戏,甚至击败人类,真是太神奇了。

了解 OpenAI Gym

我们将使用 OpenAI Gym 为我们的智能体提供一个环境。 OpenAI Gym 是一个开源工具包,用于开发和比较 RL 算法。 它包含各种模拟环境,可用于训练智能体和开发新的 RL 算法。

准备

首先要做的是安装 OpenAI Gym; 使用pip install gym可以完成最少的安装。 OpenAI 运动场提供了多种环境,例如 Atari,棋盘游戏以及 2D 或 3D 物理引擎。 最小安装可在 Windows 上运行,并且仅支持基本环境-算法,toy_textclassic_control-但如果您要探索其他环境,则它们将需要更多的依赖项。 OSX 和 Ubuntu 支持完整版本。 可以在 OpenAI Gym 的 GitHub 链接上阅读详细说明。

操作步骤

让我们从秘籍开始:

  1. OpenAI Gym 提供的核心接口是统一环境接口。 智能体可以使用三种基本方法与环境进行交互,即重置,逐步和渲染。 reset方法重置环境并返回观察值。 step方法将环境步进一个时间步,并返回观察,奖励,完成和信息。 render方法呈现一帧环境,例如弹出一个窗口。
  2. 要使用 OpenAI Gym,您需要先将其导入:
import gym
  1. 接下来,我们创建我们的第一个环境:
env_name = 'Breakout-v3'
env = gym.make(env_name)
  1. 我们使用reset方法启动环境:
obs = env.reset()
  1. 让我们检查一下环境的形状:
print(obs.shape)
  1. 可以使用命令actions = env.action_space检查可能的操作数量。 从此结果可以看出,对于 Breakout-v4,我们有四个可能的动作:NoOpFireLeftRight。 可以通过调用env.action_space.n命令获得操作总数。
  2. 让我们定义一个具有随机策略的智能体。 智能体会随机选择四个可能的动作中的任何一个:
def random_policy(n):
 action = np.random.randint(0,n)
 return action
  1. 接下来,我们使用obs, reward, done, info = env.step(action)允许我们的随机智能体播放 1,000 步:
for step in range(1000): # 1000 steps max
 action = random_policy(env.action_space.n)
 obs, reward, done, info = env.step(action)
 env.render()
 if done:
     img = env.render(mode='rgb_array')
     plt.imshow(img)
     plt.show()
     print("The game is over in {} steps".format(step))
     break

obs告诉智能体程序环境是什么样的; 对于我们的环境,它对应于大小为210 x 160 x 3的 RGB 图像。在每个步骤中,智能体将获得 0 或 1 奖励,根据 OpenAI Gym Wiki,其reward[-inf, inf]。 游戏结束后,环境将done返回为Trueinfo可用于调试,但智能体不使用。 env.render()命令弹出一个窗口,显示环境的当前状态。 当包含此命令时,您可以通过弹出窗口查看座席如何尝试玩耍和学习。 最好在座席接受训练时对此进行评论,以节省时间。

  1. 最后,关闭环境:
env.close()

工作原理

前面的代码实现了一个随机智能体; 智能体会随机选择以下四个动作之一:

要观察的另一件重要事情是,在这种环境中,动作空间是离散的,而观察空间是盒子类型的。 OpenAI 中关于空间(动作/观察)的术语“离散”和“框”是指允许的值。 离散空间允许固定范围的非负数,在我们的情况下为(0,1,2,3)。 另一方面,盒子空间表示一个n维盒子,因此对于吃豆人来说,任何有效观察结果都是210×160×3数字的数组。

更多

OpenAI Gym 由许多不同的环境组成,其活跃的贡献社区在其中添加了许多环境。 要获取所有现有环境的列表,可以运行以下简单代码

from gym import envs
env_ids = [spec.id for spec in envs.registry.all()]
print("Total Number of environments are", len(env_ids))
for env_id in sorted(env_ids):
    print(env_id)

目前,OpenAI Gym 共有 777 个环境。 这是吃豆人使用与之前相同的随机智能体的图像:

另见

  • 可以从这里获取有关不同环境的详细信息。
  • 这个链接中为某些环境维护了 Wiki 页面
  • 可以从这个链接获得有关安装说明和依赖项的详细信息。

实现神经网络智能体来玩吃豆人

让我们首先构建一个简单的神经网络智能体来玩“吃豆人”游戏。 我们将创建具有一组随机权重和偏差的智能体。 然后,这些特工将尝试玩游戏; 我们选择能够发挥最长平均持续时间的特工,并假设他们是最佳策略。

准备

此秘籍中的智能体没有学习任何策略; 他们根据初始权重(固定策略)做出决策。 智能体根据神经网络给出的概率来选择动作。 每个智能体做出的决定仅基于对环境的当前观察。

我们通过完全连接的神经网络来实现。 NN 的输入由环境的观察空间决定; 输出神经元的数量由可能的离散动作的数量决定。 吃豆人游戏有九种可能的离散动作-无操作,右转,左转,上转,向下转,左移,右移,上移和下移-因此我们的 NN 具有九个输出神经元。

操作步骤

让我们从秘籍开始:

  1. 与往常一样,第一步是导入模块。 在这种情况下,除了通常的模块之外,我们还将导入gym,以便我们可以使用它提供的不同环境:
import gym
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
  1. 接下来,我们创建一个RlAgent类。 该类由三种方法组成-__init__方法初始化 NN 大小并创建计算图。 在这里,我们使用了 TensorFlow 函数tf.multinomial来决定采取的可能措施。 该函数根据我们网络的 9 个输出神经元的 Sigmoid 值返回操作。 这样可以确保网络根据概率选择最终操作。 predict方法返回由 NN 预测的动作。 get_weights方法可帮助我们获得获胜者智能体的权重和偏置:
class RlAgent(object):
 def __init__(self,m,n,ini=False,W=None, b=None ):
     self._graph = tf.Graph()
     with self._graph.as_default():
     self._X = tf.placeholder(tf.float32,shape=(1,m))
     if ini==False:
         self.W = tf.Variable(tf.random_normal([m,n]), trainable=False)
         self.bias =         tf.Variable(tf.random_normal([1,n]),trainable=False)
     else:
         self.W = W
         self.bias = b
     out = tf.nn.sigmoid(tf.matmul(self._X,self.W)+ self.bias)
     self._result = tf.multinomial(out,1)
     init = tf.global_variables_initializer()

    self._sess = tf.Session()
    self._sess.run(init)

    def predict(self, X):
         action = self._sess.run(self._result, feed_dict= {self._X: X})
         return action

    def get_weights(self):
         W, b = self._sess.run([self.W, self.bias])
         return W, b
  1. 我们定义了一些辅助函数来玩一个完整的游戏play_one_episode
def play_one_episode(env, agent):
    obs = env.reset()
    img_pre = preprocess_image(obs)
     done = False
     t = 0
    while not done and t < 10000:
         env.render()  # This can be commented to speed up 
         t += 1
         action = agent.predict(img_pre)
         #print(t,action)
         obs, reward, done, info = env.step(action)
         img_pre = preprocess_image(obs)
         if done:
             break
    return t
  1. play_multiple_episodes函数创建智能体的一个实例,并使用该智能体进行许多游戏,并返回其平均游戏时间:
def play_multiple_episodes(env, T,ini=False, W=None, b=None):
    episode_lengths = np.empty(T)
    obs = env.reset()
    img_pre = preprocess_image(obs)
    if ini== False:
        agent = RlAgent(img_pre.shape[1],env.action_space.n)
    else:
        agent = RlAgent(img_pre.shape[1],env.action_space.n,ini, W, b)
    for i in range(T):
        episode_lengths[i] = play_one_episode(env, agent)
    avg_length = episode_lengths.mean()
    print("avg length:", avg_length)
    if ini == False:
        W, b = agent.get_weights()
    return avg_length, W, b
  1. random_search函数调用play_multiple_episodes; 每次调用play_multiple_episodes时,都会使用一组新的随机权重和偏差来实例化新智能体。 这些随机创建的 NN 智能体之一将胜过其他智能体,这将是我们最终选择的智能体:
def random_search(env):
    episode_lengths = []
    best = 0
    for t in range(10):
        print("Agent {} reporting".format(t))
        avg_length, wts, bias = play_multiple_episodes(env, 10)
        episode_lengths.append(avg_length)
        if avg_length > best:
            best_wt = wts
            best_bias = bias
            best = avg_length
    return episode_lengths, best_wt, best_bias
  1. 每次执行步骤时,环境都会返回一个观察场。 该观察具有三个颜色通道。 为了将其馈送到 NN,需要对观测值进行预处理,目前,我们唯一要做的预处理是将其转换为灰度,增加对比度并将其整形为行向量:
def preprocess_image(img):
    img = img.mean(axis =2) # to grayscale
    img[img==150] = 0  # Bring about a better contrast
    img = (img - 128)/128 - 1 # Normalize image from -1 to 1
    m,n = img.shape
    return img.reshape(1,m*n)
  1. NN 智能体一一实例化,然后选择最佳智能体。 为了提高计算效率,我们目前仅搜索 10 个智能体,每个智能体玩 10 场比赛。 玩得最长的游戏被认为是最好的:
if __name__ == '__main__':
    env_name = 'Breakout-v0'
    #env_name = 'MsPacman-v0'
    env = gym.make(env_name)
    episode_lengths, W, b = random_search(env)
    plt.plot(episode_lengths)
    plt.show()
    print("Final Run with best Agent")
    play_multiple_episodes(env,10, ini=True, W=W, b=b)

结果如下:

我们可以看到我们的随机智能体也可以平均 615.5 的长度玩游戏。 不错!

用 Q 学习平衡推车

如导言所述,我们有一个由状态ss ∈ S,其中S是所有可能状态的集合)描述的环境,可以执行动作aa ∈ A,其中A由所有可能的动作组成),从而导致主体从一种状态移动到另一种状态 。 智能体因其行为而受到奖励,智能体的目标是使奖励最大化。 在 Q 学习中,智能体通过计算最大化报酬的状态-动作组合的数量(R)来学习要采取的动作(策略,π)。 在选择行动时,智能体不仅要考虑现在的奖励,而且要考虑未来的折扣。

Q: S × A → R

智能体以Q的任意初始值开头,并且随着智能体选择动作a并获得奖励r,它会更新状态s(取决于过去状态s和动作a)和Q值:

Q(s, a) = (1 - α) · Q(s, a) + α(r + γ · max[a']Q(s', a'))

在此,α是学习率,γ是折扣因子。 第一项保留Q的旧值,第二项提供Q值的改进估计值(它包括当前奖励和未来行动的折现奖励)。 当结果状态不理想时,这将降低Q值,从而确保智能体在下次遇到此状态时不会选择相同的动作。 类似地,当期望结果状态时,相应的Q值将增加。

Q 学习的最简单实现涉及维护和更新状态-作用值查找表; 表的大小将为N×M,其中N是所有可能状态的数量,M是所有可能动作的数量。 对于大多数环境,此表将很大。 表越大,搜索所需的时间就越多,并且存储表所需的内存也就越多,因此这不是可行的解决方案。 在本秘籍中,我们将使用 Q 学习的 NN 实现。 在此,将神经网络用作函数逼近器来预测值函数(Q)。 NN 具有等于可能动作数的输出节点,并且它们的输出表示相应动作的值函数。

准备

我们将训练一个线性神经网络来解决'CartPole-v0'环境。 目的是平衡手推车上的杆。 观测状态由四个连续值参数组成:推车位置[-2.4, 2.4],推车速度[-∞, ∞],极角[~-41.8º, ~41.8º]和极限速度[-∞, ∞]。 可以通过向左或向右推推车来实现平衡,因此动作空间由两个可能的动作组成。 您可以看到CartPole-v0环境空间:

现在,对于 Q 学习,我们需要找到一种量化连续值观测状态的方法。 这是使用类FeatureTransform实现的; 该类首先生成 20,000 个观察空间示例的随机样本。 随机生成的观察空间示例使用 scikit StandardScaler类进行了标准化。 然后以不同的方差使用 scikit 的RBFSampler来覆盖观察空间的不同部分。 FeatureTransformer类由随机观察空间示例实例化,该示例用于使用fit_transform函数方法训练RBFSampler

后来,使用transform方法将连续观察空间转换为这种特征化表示:

class FeatureTransformer:
 def __init__(self, env):
   obs_examples = np.random.random((20000, 4))
   print(obs_examples.shape)
   scaler = StandardScaler()
   scaler.fit(obs_examples)

   # Used to converte a state to a featurizes represenation.
   # We use RBF kernels with different variances to cover different parts of the space
   featurizer = FeatureUnion([
       ("cart_position", RBFSampler(gamma=0.02, n_components=500)),
       ("cart_velocity", RBFSampler(gamma=1.0, n_components=500)),
       ("pole_angle", RBFSampler(gamma=0.5, n_components=500)),
       ("pole_velocity", RBFSampler(gamma=0.1, n_components=500))
       ])
    feature_examples =          featurizer.fit_transform(scaler.transform(obs_examples))
    print(feature_examples.shape)

    self.dimensions = feature_examples.shape[1]
    self.scaler = scaler
    self.featurizer = featurizer

def transform(self, observations):
    scaled = self.scaler.transform(observations)
    return self.featurizer.transform(scaled)

操作步骤

我们按以下步骤进行:

  1. 第一步是导入必要的模块。 这次,除了我们通常的 TensorFlow,Numpy 和 Matplotlib,我们还将从 scikit 导入 Gym 和一些类:
import numpy as np
import tensorflow as tf
import gym
import matplotlib.pyplot as plt
from sklearn.pipeline import FeatureUnion
from sklearn.preprocessing import StandardScaler
from sklearn.kernel_approximation import RBFSampler
  1. 在 Q 学习中,我们使用 NN 作为函数逼近器来估计值函数。 我们定义一个线性NeuralNetwork类; NN 将把变换后的观测空间作为输入并预测估计的Q值。 由于我们有两个可能的动作,因此我们需要两个不同的神经网络对象来获取预测的状态动作值。 该类包括训练单个 NN 和预测输出的方法:
class NeuralNetwork:
 def __init__(self, D):
     eta = 0.1
     self.W = tf.Variable(tf.random_normal(shape=(D, 1)), name='w')
     self.X = tf.placeholder(tf.float32, shape=(None, D), name='X')
     self.Y = tf.placeholder(tf.float32, shape=(None,), name='Y')

     # make prediction and cost
     Y_hat = tf.reshape(tf.matmul(self.X, self.W), [-1])
     err = self.Y - Y_hat
     cost = tf.reduce_sum(tf.pow(err,2))

    # ops we want to call later
    self.train_op = tf.train.GradientDescentOptimizer(eta).minimize(cost)
    self.predict_op = Y_hat

    # start the session and initialize params
    init = tf.global_variables_initializer()
    self.session = tf.Session()
    self.session.run(init)

def train(self, X, Y):
    self.session.run(self.train_op, feed_dict={self.X: X, self.Y: Y})

def predict(self, X):
    return self.session.run(self.predict_op, feed_dict={self.X: X})
  1. 下一个重要的类是Agent类,它使用NeuralNetwork类来创建学习智能体。 实例化类创建具有两个线性 NN 的智能体,每个线性 NN 具有 2,000 个输入神经元和 1 个输出神经元。 (从本质上讲,这意味着该智能体具有 2 个神经元,每个神经元具有 2,000 个输入,因为 NN 的输入层不执行任何处理)。 Agent类具有定义为预测两个 NN 的输出并更新两个 NN 权重的方法。 此处的智能体在训练阶段使用 Epsilon 贪婪策略进行探索。 在每个步骤中,智能体程序会根据 epsilon(eps)的值选择具有最高Q值的操作或随机操作。 epsilon 在训练过程中经过退火处理,因此,最初,智能体会采取许多随机动作(探索),但随着训练的进行,会采取具有最大Q值的动作(探索)。 这称为探索与开发的权衡取舍:我们允许智能体在被利用的操作过程中探索随机操作,这使智能体可以尝试新的随机操作并从中学习:
class Agent:
 def __init__(self, env, feature_transformer):
 self.env = env
 self.agent = []
 self.feature_transformer = feature_transformer
 for i in range(env.action_space.n):
 model = NeuralNetwork(feature_transformer.dimensions)
 self.agent.append(model)

def predict(self, s):
 X = self.feature_transformer.transform([s])
 return np.array([m.predict(X)[0] for m in self.agent])

def update(self, s, a, G):
 X = self.feature_transformer.transform([s])
 self.agent[a].train(X, [G])

def sample_action(self, s, eps):
 if np.random.random() < eps:
     return self.env.action_space.sample()
 else:
     return np.argmax(self.predict(s))
  1. 接下来,我们定义一个函数来播放一集; 它类似于我们先前使用的play_one函数,但现在我们使用 Q 学习来更新智能体的权重。 我们首先使用env.reset()重置环境,然后开始游戏,直到完成游戏为止(并进行了最大迭代以确保程序结束)。 像以前一样,智能体为当前观察状态选择一个动作,并在环境上执行该动作(env.step(action))。 现在的区别在于,根据先前状态和采取操作后的状态,使用G = r + γ · max[a']Q(s', a')更新 NN 权重,以便它可以预测与某个动作相对应的准确期望值。 为了获得更好的稳定性,我们修改了奖励-杆位下降时,座席会获得 -400 的奖励,否则,每一步都会获得 +1 的奖励:
def play_one(env, model, eps, gamma):
 obs = env.reset()
 done = False
 totalreward = 0
 iters = 0
 while not done and iters < 2000:
 action = model.sample_action(obs, eps)
 prev_obs = obs
 obs, reward, done, info = env.step(action)
 env.render()   # Can comment it to speed up.

if done:
 reward = -400

# update the model
 next = model.predict(obs)
 assert(len(next.shape) == 1)
 G = reward + gamma*np.max(next)
 model.update(prev_obs, action, G)

if reward == 1:
 totalreward += reward
iters += 1
  1. 现在所有函数和类均已就绪,我们定义了智能体和环境(在本例中为'CartPole-v0')。 该智能体总共播放 1000 集,并通过使用值函数与环境交互来学习:
if __name__ == '__main__':
    env_name = 'CartPole-v0'
    env = gym.make(env_name)
    ft = FeatureTransformer(env)
    agent = Agent(env, ft)
    gamma = 0.97

    N = 1000
    totalrewards = np.empty(N)
    running_avg = np.empty(N)
    for n in range(N):
        eps = 1.0 / np.sqrt(n + 1)
        totalreward = play_one(env, agent, eps, gamma)
        totalrewards[n] = totalreward
        running_avg[n] = totalrewards[max(0, n - 100):(n + 1)].mean()
        if n % 100 == 0:
            print("episode: {0}, total reward: {1} eps: {2} avg reward (last 100): {3}".format(n, totalreward, eps,
                                                                                               running_avg[n]), )

    print("avg reward for last 100 episodes:", totalrewards[-100:].mean())
    print("total steps:", totalrewards.sum())

    plt.plot(totalrewards)
    plt.xlabel('episodes')
    plt.ylabel('Total Rewards')
    plt.show()

    plt.plot(running_avg)

    plt.xlabel('episodes')
    plt.ylabel('Running Average')
    plt.show()
    env.close()

  1. 以下是智能体通过游戏获悉的总奖励和移动平均奖励的图。 根据 Cart-Pole Wiki 的说法,奖励 200 分表示该特工在接受 1,000 次训练后赢得了该剧集。 我们的特工播放 100 集时平均获得 195.7 的平均奖励,这是一项了不起的壮举:

更多

可以使用相同的逻辑为 OpenAI 的其他环境创建智能体。 但是,对于诸如 Breakout 或 Pac-Man 之类的 Atari 游戏,观察空间并不只是由四个数字组成的数组。 相反,它非常大(210×160 = 33,600 像素,具有 3 个 RGB 值); 如果没有某种形式的量化,则这种简单的 NN 可能的状态是无限的,并且不会产生良好的结果。 我们将在深度 Q 学习秘籍中使用 CNN 解决此问题。

另见

尽管有很多有关 Q 学习的 Web 链接,但一些有用的链接如下:

深度 Q 网络和 Atari 游戏

深度 Q 网络DQN)是 Q 学习与卷积神经网络CNN)的结合。 由 Mnih 等人在 2013 年提出。 CNN 网络具有提取空间信息的能力,因此能够从原始像素数据中学习成功的控制策略。 我们已经在第 4 章,“卷积神经网络”中使用了 CNN,因此我们直接从这里开始。

此秘籍基于 DeepMind 的原始 DQN 论文《使用深度强化学习玩 Atari》。 在本文中,他们使用了一种称为“经验重放”的概念,该概念涉及随机采样先前的游戏动作(状态,动作奖励,下一状态)。位于前脑底部的核称为“基底神经节”, 根据神经科学,它们负责选择动作,即帮助我们确定在任何给定时间执行几种可能动作中的哪一种。

准备

如先前的秘籍所述,“用 Q 学习平衡 CartPole”,对于像《吃豆人》或 Breakout 之类的 Atari 游戏,我们需要预处理观察状态空间,该状态空间由 33,600 个像素组成,具有 3 个 RGB 值。 这些像素中的每个像素都可以采用 0 到 255 之间的任何值。我们的preprocess函数应该能够量化像素的可能值,同时减少观察状态空间。

我们利用 Scipy 的imresize函数对图像进行下采样。 以下函数preprocess在将图像馈送到 DQN 之前:

def preprocess(img):
    img_temp = img[31:195]  # Choose the important area of the image
    img_temp = img_temp.mean(axis=2)  # Convert to Grayscale#
    # Downsample image using nearest neighbour interpolation
    img_temp = imresize(img_temp, size=(IM_SIZE, IM_SIZE), interp='nearest')
    return img_temp

IM_SIZE是一个全局参数-在代码中,我们将其值为 80。该函数具有描述每个过程的注释。 在这里,您可以看到预处理前后的观察空间:

要注意的另一重要事项是,当前的观察空间不能完全显示游戏情况。 例如,参见上图,您无法确定桨叶是向左还是向右移动。 因此,为了完全理解游戏的当前状态,我们需要考虑动作和观察的顺序。 在秘籍中,我们考虑了四个动作和观察序列,以确定当前情况并训练智能体。 这是借助state_update函数完成的,该函数将当前的观察状态附加到先前的状态,从而生成一系列状态:

def update_state(state, obs):
    obs_small = preprocess(obs)
    return np.append(state[1:], np.expand_dims(obs_small, 0), axis=0)

最后,为了解决训练时的稳定性问题,我们使用了target_network的概念,它是 DQN 的副本,但更新频率不高。 我们使用目标网络来生成 DQN 网络的目标值函数,而 DQN 在每个步骤/片段都进行更新,并且target_network在固定间隔后进行更新(与 DQN 相同)。 由于所有更新都在 TensorFlow 会话中进行,因此我们使用名称范围来区分target_network和 DQN 网络。

操作步骤

  1. 我们导入必要的模块。 我们正在使用sys模块的stdout.flush()来帮助我们强制 Python 刷新标准输出(在我们的情况下为计算机监视器)中的数据。 random模块用于从经验重放缓冲区(我们存储过去经验的缓冲区)中获取随机样本。 datetime模块用于跟踪训练时间:
import gym
import sys
import random
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
from datetime import datetime
from scipy.misc import imresize
  1. 我们定义训练的超参数; 您可以通过更改它们进行试验。 这些参数定义了经验重放缓冲区的最小和最大大小以及更新目标网络之后的情节数量:
MAX_EXPERIENCES = 500000
MIN_EXPERIENCES = 50000
TARGET_UPDATE_PERIOD = 10000
IM_SIZE = 80
K = 4
  1. 定义了类DQN; 其构造器使用tf.contrib.layers.conv2d函数构建 CNN 网络,并定义成本和训练操作:
class DQN:
    def __init__(self, K, scope, save_path= 'models/atari.ckpt'):

        self.K = K
        self.scope = scope
        self.save_path = save_path

        with tf.variable_scope(scope):

            # inputs and targets
            self.X = tf.placeholder(tf.float32, shape=(None, 4, IM_SIZE, IM_SIZE), name='X')

            # tensorflow convolution needs the order to be:
            # (num_samples, height, width, "color")
            # so we need to tranpose later
            self.G = tf.placeholder(tf.float32, shape=(None,), name='G')
            self.actions = tf.placeholder(tf.int32, shape=(None,), name='actions')

            # calculate output and cost
            # convolutional layers
            Z = self.X / 255.0
            Z = tf.transpose(Z, [0, 2, 3, 1])
            cnn1 = tf.contrib.layers.conv2d(Z, 32, 8, 4, activation_fn=tf.nn.relu)
            cnn2 = tf.contrib.layers.conv2d(cnn1, 64, 4, 2, activation_fn=tf.nn.relu)
            cnn3 = tf.contrib.layers.conv2d(cnn2, 64, 3, 1, activation_fn=tf.nn.relu)

            # fully connected layers
            fc0 = tf.contrib.layers.flatten(cnn3)
            fc1 = tf.contrib.layers.fully_connected(fc0, 512)

            # final output layer
            self.predict_op = tf.contrib.layers.fully_connected(fc1, K)

            selected_action_values = tf.reduce_sum(self.predict_op * tf.one_hot(self.actions, K),
                reduction_indices=[1]
            )

            self.cost = tf.reduce_mean(tf.square(self.G - selected_action_values))
            self.train_op = tf.train.RMSPropOptimizer(0.00025, 0.99, 0.0, 1e-6).minimize(self.cost)
  1. 该类具有设置会话set_session(),预测动作值函数predict(),更新网络update()以及使用 Epsilon 贪婪算法sample_action()选择动作的方法:
def set_session(self, session):
    self.session = session

def predict(self, states):
    return self.session.run(self.predict_op, feed_dict={self.X: states})

def update(self, states, actions, targets):
    c, _ = self.session.run(
        [self.cost, self.train_op],
        feed_dict={
            self.X: states,
            self.G: targets,
            self.actions: actions
        }
    )
    return c

def sample_action(self, x, eps):
    """Implements epsilon greedy algorithm"""
    if np.random.random() < eps:
        return np.random.choice(self.K)
    else:
        return np.argmax(self.predict([x])[0])
  1. 我们还定义了加载和保存网络的方法,因为训练可能会花费一些时间:
def load(self):
    self.saver = tf.train.Saver(tf.global_variables())
    load_was_success = True
    try:
        save_dir = '/'.join(self.save_path.split('/')[:-1])
        ckpt = tf.train.get_checkpoint_state(save_dir)
        load_path = ckpt.model_checkpoint_path
        self.saver.restore(self.session, load_path)
    except:
        print("no saved model to load. starting new session")
        load_was_success = False
    else:
        print("loaded model: {}".format(load_path))
        saver = tf.train.Saver(tf.global_variables())
        episode_number = int(load_path.split('-')[-1])

def save(self, n):
    self.saver.save(self.session, self.save_path, global_step=n)
    print("SAVED MODEL #{}".format(n))
  1. 将主 DQN 网络的参数复制到目标网络的方法如下:
def copy_from(self, other):
    mine = [t for t in tf.trainable_variables() if t.name.startswith(self.scope)]
    mine = sorted(mine, key=lambda v: v.name)
    others = [t for t in tf.trainable_variables() if t.name.startswith(other.scope)]
    others = sorted(others, key=lambda v: v.name)

    ops = []
    for p, q in zip(mine, others):
        actual = self.session.run(q)
        op = p.assign(actual)
        ops.append(op)

    self.session.run(ops)
  1. 我们定义一个函数learn(),它预测值函数并更新原始 DQN 网络:
def learn(model, target_model, experience_replay_buffer, gamma, batch_size):
    # Sample experiences
    samples = random.sample(experience_replay_buffer, batch_size)
    states, actions, rewards, next_states, dones = map(np.array, zip(*samples))

    # Calculate targets
    next_Qs = target_model.predict(next_states)
    next_Q = np.amax(next_Qs, axis=1)
    targets = rewards + np.invert(dones).astype(np.float32) * gamma * next_Q

    # Update model
    loss = model.update(states, actions, targets)
    return loss
  1. 既然我们已经在主代码中定义了所有特征,我们就可以使用它们来构建和训练 DQN 网络以玩 Atari 游戏。 该代码经过了很好的注释,并且是对先前 Q 学习代码的扩展,并增加了经验重放缓冲区,因此您应该不难理解它:
if __name__ == '__main__':
    # hyperparameters
    gamma = 0.99
    batch_sz = 32
    num_episodes = 500
    total_t = 0
    experience_replay_buffer = []
    episode_rewards = np.zeros(num_episodes)
    last_100_avgs = []

    # epsilon for Epsilon Greedy Algorithm
    epsilon = 1.0
    epsilon_min = 0.1
    epsilon_change = (epsilon - epsilon_min) / 500000

    # Create Atari Environment
    env = gym.envs.make("Breakout-v0")

    # Create original and target  Networks
    model = DQN(K=K, gamma=gamma, scope="model")
    target_model = DQN(K=K, gamma=gamma, scope="target_model")

    with tf.Session() as sess:
        model.set_session(sess)
        target_model.set_session(sess)
        sess.run(tf.global_variables_initializer())
        model.load()

        print("Filling experience replay buffer...")
        obs = env.reset()
        obs_small = preprocess(obs)
        state = np.stack([obs_small] * 4, axis=0)

        # Fill experience replay buffer
        for i in range(MIN_EXPERIENCES):

            action = np.random.randint(0,K)
            obs, reward, done, _ = env.step(action)
            next_state = update_state(state, obs)

            experience_replay_buffer.append((state, action, reward, next_state, done))

            if done:
                obs = env.reset()
                obs_small = preprocess(obs)
                state = np.stack([obs_small] * 4, axis=0)

            else:
                state = next_state

        # Play a number of episodes and learn
        for i in range(num_episodes):
            t0 = datetime.now()

            # Reset the environment
            obs = env.reset()
            obs_small = preprocess(obs)
            state = np.stack([obs_small] * 4, axis=0)
            assert (state.shape == (4, 80, 80))
            loss = None

            total_time_training = 0
            num_steps_in_episode = 0
            episode_reward = 0

            done = False
            while not done:

                # Update target network
                if total_t % TARGET_UPDATE_PERIOD == 0:
                    target_model.copy_from(model)
                    print("Copied model parameters to target network. total_t = %s, period = %s" % (
                        total_t, TARGET_UPDATE_PERIOD))

                # Take action
                action = model.sample_action(state, epsilon)
                obs, reward, done, _ = env.step(action)
                obs_small = preprocess(obs)
                next_state = np.append(state[1:], np.expand_dims(obs_small, 0), axis=0)

                episode_reward += reward

                # Remove oldest experience if replay buffer is full
                if len(experience_replay_buffer) == MAX_EXPERIENCES:
                    experience_replay_buffer.pop(0)

                # Save the recent experience
                experience_replay_buffer.append((state, action, reward, next_state, done))

                # Train the model and keep measure of time
                t0_2 = datetime.now()
                loss = learn(model, target_model, experience_replay_buffer, gamma, batch_sz)
                dt = datetime.now() - t0_2

                total_time_training += dt.total_seconds()
                num_steps_in_episode += 1

                state = next_state
                total_t += 1

                epsilon = max(epsilon - epsilon_change, epsilon_min)

            duration = datetime.now() - t0

            episode_rewards[i] = episode_reward
            time_per_step = total_time_training / num_steps_in_episode

            last_100_avg = episode_rewards[max(0, i - 100):i + 1].mean()
            last_100_avgs.append(last_100_avg)
            print("Episode:", i,"Duration:", duration, "Num steps:", num_steps_in_episode,
                  "Reward:", episode_reward, "Training time per step:", "%.3f" % time_per_step,
                  "Avg Reward (Last 100):", "%.3f" % last_100_avg,"Epsilon:", "%.3f" % epsilon)

            if i % 50 == 0:
                model.save(i)
            sys.stdout.flush()

    #Plots
    plt.plot(last_100_avgs)
    plt.xlabel('episodes')
    plt.ylabel('Average Rewards')
    plt.show()
    env.close()

从上图中我们可以看到,特工通过训练获得了更高的报酬,并且通过每 100 集的平均报酬图可以清楚地看到情况:

这只是在训练的前 500 集之后; 为了获得更好的效果,您将需要训练更长的时间,约 10,000 集。

更多

训练智能体需要花费很多时间,这既浪费时间又消耗内存。 OpenAI Gym 提供了一个包装器来将游戏另存为视频,因此,除了使用渲染之外,您还可以使用包装器来保存视频并随后监视智能体的学习方式。 AI 工程师和发烧友可以上传这些视频以显示结果。 为此,我们需要首先导入包装器,然后创建环境,最后使用监视器。 默认情况下,它将存储 1、8、27、64 等视频,然后每第 1000 集(带有完美立方体的情节编号)存储; 默认情况下,每项训练都保存在一个文件夹中。 为此要添加的代码如下:

import gym
from gym import wrappers
env = gym.make('Breakout-v0)
env = wrappers.Monitor(env, '/save-path')

如果您想在下一个训练中使用相同的文件夹,可以将force=True添加到监视器。

另见

  • Mnih, Volodymyr, and others, Playing Atari with deep reinforcement learning, arXiv preprint arXiv:1312.5602 (2013) (https://arxiv.org/pdf/1312.5602.pdf)
  • Mnih, Volodymyr, et al. Human-level control through deep reinforcement learning, Nature 518.7540 (2015): 529-533
  • 玩 Atari 的 DQN 的一个很酷的实现

使用策略梯度玩 Pong 游戏

到目前为止,策略梯度是最常用的 RL 算法之一。 研究表明,经过适当调优后,它们的表现要优于 DQN,同时不会遭受过多的内存和计算缺点。 与 Q 学习不同,策略梯度使用参数化策略,该策略可以选择操作而不咨询值函数。 在策略梯度中,我们讨论了表现度量η(θ[p]); 目标是最大程度地提高表现,因此根据梯度上升算法更新 NN 的权重。 但是,TensorFlow 没有maximum优化器,因此我们使用表现梯度的负值-∇η(θ[p])并将其最小化。

准备

Pong 的游戏是一个两人游戏,目标是将球弹回另一位玩家。 智能体可以上下移动操纵杆(是的,是标准NoOp)。 OpenAI 环境中的一名玩家是一位体面的 AI 玩家,他知道如何很好地玩游戏。 我们的目标是使用策略梯度来训练第二个智能体。 我们的经纪人精通所玩的每款游戏。 虽然代码已构建为只能运行 500 集,但我们应该添加一条规定以将智能体状态保存在指定的检查点,并且在下一次运行时,首先加载上一个检查点。 为此,我们首先声明一个保护程序,然后使用 TensorFlow saver.save方法保存当前的网络状态(检查点),最后从最后保存的检查点加载网络。 为完成本秘籍的部分,在“操作步骤”一节中定义的以下PolicyNetwork类方法可以执行此工作:

def load(self):
    self.saver = tf.train.Saver(tf.global_variables())
    load_was_success = True  # yes, I'm being optimistic
    try:
        save_dir = '/'.join(self.save_path.split('/')[:-1])
        ckpt = tf.train.get_checkpoint_state(save_dir)
        load_path = ckpt.model_checkpoint_path
        self.saver.restore(self.session, load_path)
    except:
        print("no saved model to load. starting new session")
        load_was_success = False
    else:
        print("loaded model: {}".format(load_path))
        saver = tf.train.Saver(tf.global_variables())
        episode_number = int(load_path.split('-')[-1])

为了每 50 集保存一次模型,我们使用以下方法:

def save(self):
    self.saver.save(self.session, self.save_path, global_step=n)
    print("SAVED MODEL #{}".format(n))

操作步骤

  1. 此秘籍的代码基于 Andrej Karpathy 博客,并且其中一部分已由 Sam Greydanus 的代码进行了改编。
  2. 我们有通常的导入:
import numpy as np
import gym
import matplotlib.pyplot as plt
import tensorflow as tf
  1. 我们定义我们的PolicyNetwork类。 在类构建期间,还将初始化模型超参数。 __init__方法定义输入状态self.tf_x的占位符; 预测作用,self.tf.y; 相应的奖励,self.tf_epr; 网络权重; 并预测行动值,训练和更新。 您可以看到该类构造还启动了一个交互式 TensorFlow 会话:
class PolicyNetwork(object):
    def __init__(self, N_SIZE, h=200, gamma=0.99, eta=1e-3, decay=0.99, save_path = 'models2/pong.ckpt' ):

        self.gamma = gamma
        self.save_path = save_path
        # Placeholders for passing state....
        self.tf_x = tf.placeholder(dtype=tf.float32, shape=[None, N_SIZE * N_SIZE], name="tf_x")
        self.tf_y = tf.placeholder(dtype=tf.float32, shape=[None, n_actions], name="tf_y")
        self.tf_epr = tf.placeholder(dtype=tf.float32, shape=[None, 1], name="tf_epr")

        # Weights
        xavier_l1 = tf.truncated_normal_initializer(mean=0, stddev=1\. / N_SIZE, dtype=tf.float32)
        self.W1 = tf.get_variable("W1", [N_SIZE * N_SIZE, h], initializer=xavier_l1)
        xavier_l2 = tf.truncated_normal_initializer(mean=0, stddev=1\. / np.sqrt(h), dtype=tf.float32)
        self.W2 = tf.get_variable("W2", [h, n_actions], initializer=xavier_l2)

        # Build Computation
        # tf reward processing (need tf_discounted_epr for policy gradient wizardry)
        tf_discounted_epr = self.tf_discount_rewards(self.tf_epr)
        tf_mean, tf_variance = tf.nn.moments(tf_discounted_epr, [0], shift=None, name="reward_moments")
        tf_discounted_epr -= tf_mean
        tf_discounted_epr /= tf.sqrt(tf_variance + 1e-6)

        # Define Optimizer, compute and apply gradients
        self.tf_aprob = self.tf_policy_forward(self.tf_x)
        loss = tf.nn.l2_loss(self.tf_y - self.tf_aprob)
        optimizer = tf.train.RMSPropOptimizer(eta, decay=decay)
        tf_grads = optimizer.compute_gradients(loss, var_list=tf.trainable_variables(), grad_loss=tf_discounted_epr)
        self.train_op = optimizer.apply_gradients(tf_grads)

        # Initialize Variables
        init = tf.global_variables_initializer()

        self.session = tf.InteractiveSession()
        self.session.run(init)
        self.load()
  1. 我们定义了一种计算折现奖励的方法。 这确保智能体不仅考虑当前奖励,而且考虑未来奖励。 任意时间t的折现奖励为R[t] = ∑γ[k]r[t + k],其中总和超过k∈[0, ∞],并且γ是贴现因子,值在 0 到 1 之间。在我们的代码中,我们使用了gamma = 0.99
def tf_discount_rewards(self, tf_r):  # tf_r ~ [game_steps,1]
    discount_f = lambda a, v: a * self.gamma + v;
    tf_r_reverse = tf.scan(discount_f, tf.reverse(tf_r, [0]))
    tf_discounted_r = tf.reverse(tf_r_reverse, [0])
    return tf_discounted_r
  1. 在给定输入观察状态的情况下,我们定义了tf_policy_forward方法来提供将桨向上移动的概率。 我们使用两层神经网络实现它。 网络获取处理过的游戏状态图像,并生成一个数字,表示将球拍向上移动的可能性。 在 TensorFlow 中,由于仅在 TensorFlow 会话中计算网络图,因此我们定义了另一种方法predict_UP来计算概率:
def tf_policy_forward(self, x): #x ~ [1,D]
     h = tf.matmul(x, self.W1)
     h = tf.nn.relu(h)
     logp = tf.matmul(h, self.W2)
     p = tf.nn.softmax(logp)
     return p

def predict_UP(self,x):
    feed = {self.tf_x: np.reshape(x, (1, -1))}
    aprob = self.session.run(self.tf_aprob, feed);
    return aprob
  1. PolicyNetwork智能体使用update方法更新权重:
def update(self, feed):
    return self.session.run(self.train_op, feed)
  1. 我们定义一个辅助函数来预处理观察状态空间:
# downsampling
def preprocess(I):
    """ prepro 210x160x3 uint8 frame into 6400 (80x80) 1D float vector """
    I = I[35:195] # crop
    I = I[::2,::2,0] # downsample by factor of 2
    I[I == 144] = 0  # erase background (background type 1)
    I[I == 109] = 0  # erase background (background type 2)
    I[I != 0] = 1    # everything else (paddles, ball) just set to 1
    return I.astype(np.float).ravel()

  1. 其余的很简单-我们创建一个游戏环境,定义要持有的数组(状态,动作,奖励,状态),并使智能体学习大量情节(休息或连续不断),这完全取决于您的计算能力和资源)。 这里要注意的重要一点是,智能体没有按动作步骤学习。 相反,智能体使用一个情节的完整(状态,动作,奖励,状态)集来纠正其策略。 这可能会占用大量内存:
if __name__ == '__main__':
    # Create Game Environment
    env_name = "Pong-v0"
    env = gym.make(env_name)
    env = wrappers.Monitor(env, '/tmp/pong', force=True)
    n_actions = env.action_space.n  # Number of possible actions
    # Initializing Game and State(t-1), action, reward, state(t)
    xs, rs, ys = [], [], []
    obs = env.reset()
    prev_x = None

    running_reward = None
    running_rewards = []
    reward_sum = 0
    n = 0
    done = False
    n_size = 80
    num_episodes = 500

    #Create Agent
    agent = PolicyNetwork(n_size)

    # training loop
    while not done and n< num_episodes:
        # Preprocess the observation
        cur_x = preprocess(obs)
        x = cur_x - prev_x if prev_x is not None else np.zeros(n_size*n_size)
        prev_x = cur_x

        #Predict the action
        aprob = agent.predict_UP(x) ; aprob = aprob[0,:]

        action = np.random.choice(n_actions, p=aprob)
        #print(action)
        label = np.zeros_like(aprob) ; label[action] = 1

        # Step the environment and get new measurements
        obs, reward, done, info = env.step(action)
        env.render()
        reward_sum += reward

        # record game history
        xs.append(x) ; ys.append(label) ; rs.append(reward)

        if done:
            # update running reward
            running_reward = reward_sum if running_reward is None else running_reward * 0.99 + reward_sum * 0.01
            running_rewards.append(running_reward)
            feed = {agent.tf_x: np.vstack(xs), agent.tf_epr: np.vstack(rs), agent.tf_y: np.vstack(ys)}
            agent.update(feed)
            # print progress console
            if n % 10 == 0:
                print ('ep {}: reward: {}, mean reward: {:3f}'.format(n, reward_sum, running_reward))
            else:
                print ('\tep {}: reward: {}'.format(n, reward_sum))

            # Start next episode and save model
            xs, rs, ys = [], [], []
            obs = env.reset()
            n += 1 # the Next Episode

            reward_sum = 0
            if n % 50 == 0:
                agent.save()
            done = False

    plt.plot(running_rewards)
    plt.xlabel('episodes')
    plt.ylabel('Running Averge')
    plt.show()
    env.close()

下图显示了智能体在前 500 个情节中学习时的平均运行奖励:

工作原理

权重使用 Xavier 初始化进行了初始化,这确保了我们的权重既不会太大也不会太小。 两种情况都阻碍了网络的学习。 在 Xavier 初始化中,为权重分配一个具有零均值和特定方差2/(nin+nout)的值,其中ninnout该层的输入和输出数。 要了解有关 Xavier 初始化的更多信息,请参阅 Glorot 和 Bengio 在 2009 年发表的论文。 有关详细信息,请参见“另见”部分。

更多

看到智能体第一次学习演奏的任何人都会对此感到惊讶-看起来很像人。 最初的举动总是很笨拙。 缓慢地,坐席会学习走哪条路,尽管速度很慢并且经常会错过球。 但是,随着学习的继续,智能体将成为专家。

但这与我们很不一样。 一旦学会玩游戏,我们便可以在其他任何类似情况下轻松使用该知识。 RL 智能体将无法执行此操作-即使是简单的事情(例如更改环境空间的大小)也会将其恢复为零。 迁移学习是研究人员正在研究的一种技术,它可以帮助主体在另一环境空间中的一个环境中使用它所学到的知识,也许有一天可以为真正的人工智能奠定基础。

AlphaGo Zero

最近,DeepMind 发表了有关 AlphaGo Zero(AlphaGo 的最新版本)的文章。 根据他们发布的结果,AlphaGo Zero 甚至更强大,并且是历史上最强大的围棋选手。 AlphaGo 从表格状态开始,即从空白状态开始,并且仅使用棋盘状态和与其对抗的游戏来调整神经网络并预测正确的动作。

AlphaGo Zero 使用深层神经网络,该网络将原始板表示形式(当前和历史)作为输入,并输出移动概率和值。 因此,该神经网络结合了策略网络和值网络的作用。 该网络是通过自玩游戏进行训练的,这与以前的 AlphaGo 版本不同(它们是使用监督学习进行训练的)。 在每个位置上,由神经网络指导执行蒙特卡洛树搜索(MCTS)。 通过使用 MCTS 播放每个动作的自演强化学习算法来训练神经网络。

最初,神经网络的权重是随机初始化的。 在每个迭代步骤中,都会生成许多自玩游戏。 在每个时间步,使用神经网络的先前迭代对可能的策略执行 MCTS 搜索,然后通过对搜索概率进行采样来进行移动。 重复此过程直到该特定游戏终止。 存储游戏状态,采取的策略以及游戏每个时间步骤的奖励。 并行地,根据自播放的先前迭代的所有时间步长之间均匀采样的数据训练神经网络。 调整神经网络的权重,以最小化预测值和自赢者之间的误差,并使神经网络移动概率与搜索概率的相似性最大化。

在配备 4 个 TPU 的单台机器上仅进行了 3 天的训练,AlphaGo Zero 以 100-0 击败 AlphaGo。 AlphaGo Zero 完全基于 RL。 可以在 2017 年 10 月发表于《自然》上的论文《掌握无人掌握的围棋游戏》中阅读其实现的详细信息。

另见

十、移动计算

在本章中,我们将讨论在移动设备上使用深度学习的问题,并为以下内容提供一些方法:

  • 安装适用于 macOS 和 Android 的 TensorFlow Mobile
  • 玩转 TensorFlow 和 Android 示例
  • 为 MacOS 和 iPhone 安装 TensorFlow Mobile
  • 为移动设备优化 TensorFlow 图
  • 转换移动设备的 TensorFlow 图

介绍

在本节中,我们将介绍移动深度学习的一些用例。 这与台式机或云深度学习的情况大不相同,在台式机或云深度学习中,GPU 和电力通常可用。 实际上,在移动设备上,保存电池非常重要,并且 GPU 经常不可用。 但是,深度学习在许多情况下可能非常有用。 让我们回顾一下:

  • 图像识别:现代手机具有功能强大的摄像头,用户热衷于尝试对图像和图片产生效果。 通常,了解图片中的内容也很重要,并且有多种适用于此的预训练模型,如专用于 CNN 的章节所述。 这里给出了用于图像识别的模型的一个很好的例子

  • 对象定位:识别运动对象是一项关键操作,对于视频和图像处理是必需的。 例如,可以想象如果在图像中识别出多个人,那么相机将使用多个对焦点。 这里提供了对象本地化示例的集合

  • 光学字符识别:在许多活动(例如文本分类和推荐)中,识别手写字符都是至关重要的。 深度学习可以为开展这些活动提供根本帮助。 在专用于 CNN 的章节中,我们研究了 MNIST 识别的一些示例。 关于 MNIST 的信息也可以在这个页面中找到。

  • 语音识别:语音识别是访问现代电话的常用界面。 因此,深度学习用于识别语音和口头命令。 在过去的几年中,这方面的进展令人印象深刻。

  • 翻译:处理多种语言是现代多元文化世界的一部分。 手机在各种语言之间进行即时翻译的准确率越来越高,深度学习帮助打破了障碍,而这在几年前是无法想象的。 在专门针对 RNN 的一章中,我们研究了一些机器翻译示例。

  • 手势识别:电话开始使用手势作为接收命令的界面。 当然,有一些模型。

  • 压缩:压缩是手机的关键方面。 可以想象,在通过网络发送图像或视频之前减少空间是有益的。 同样,在本地存储在设备上之前压缩数据可能会很方便。 在所有这些情况下,深度学习都可以提供帮助。 使用 RNNS 进行压缩的模型位于这里

TensorFlow,移动和云

如上所述,电话通常没有 GPU,因此节省电池电量非常重要。 为了减轻成本,需要将许多昂贵的计算卸载到云中。 当然,要折衷考虑各种因素,包括在移动设备上执行深度学习模型的成本,将数据移至云的成本,用于此传输的电池成本以及云计算的成本。 没有单一的解决方案,最佳策略取决于您的具体情况。

安装适用于 macOS 和 Android 的 TensorFlow Mobile

在本秘籍中,我们将学习如何为移动环境设置 TensorFlow。 我的环境是 macOS,我为 Android 开发。 但是,在以下秘籍中将描述其他配置。

准备

我们将使用 Android Studio,这是适用于 Google Android 操作系统的官方集成开发环境IDE)。

操作步骤

我们继续按以下步骤安装适用于 macOS 和 Android 的 TensorFlow mobile:

  1. 这里安装 Android Studio。

  2. 创建一个新的项目名称AndroidExampleTensorflow,如以下屏幕截图所示:

在 AndroidStudio 中创建 TensorFlow 移动应用的示例,第一步如下图所示:选择电话和表格选项:

在 AndroidStudio 中创建 TensorFlow 移动应用的示例,第二步并选择一个空活动,如下图所示:

在 AndroidStudio 中创建 TensorFlow 移动应用的示例,第三步然后自定义MainActivity,如下图所示:

在 AndroidStudio 中创建 TensorFlow 移动应用的示例,第四步称为“基础神经节”,根据神经科学,它负责选择动作,即帮助我们确定在任何给定时间执行几个可能动作中的哪个。

  1. 将以下行插入build.gradle应用中,如以下代码所示:
// added for automatically connect to TensorFlow via maven
repositories {
jcenter()
maven {
url 'https://google.bintray.com/TensorFlow'
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:26.+'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
// added for automatically compile TensorFlow
compile 'org.TensorFlow:TensorFlow-android:+'
testCompile 'junit:junit:4.12'
}

以下屏幕截图显示了插入的代码:

  1. 运行项目并获得结果:

使用 AndroidStudio 进行编译的示例,其中显示了连接的设备。

在 AndroidStudio 中创建 TensorFlow 移动应用的示例。 一个简单的Hello World应用

工作原理

使用 Android Studio 设置 Android TensorFlow 非常简单。 您只需要在应用的build.gradle文件中添加一些配置行,Android Studio 就会代表您执行所有操作。

更多

如果要直接从 TensorFlow 源构建,则需要安装 Bazel 和 TensorFlow。 Bazel 是一个快速,可扩展,多语言和可扩展的构建系统。 Google 内部使用了构建工具 Blaze,并将 Blaze 工具的开源部分称为 Bazel。 名称是 Blaze 的字谜。

此页面将指导您完成该过程

如果您正在运行 macOS,则过程非常简单:

  1. 按照这个页面上的说明安装 Bazel。 对于 macOS,我们将使用 Homebrew:
/usr/bin/ruby -e "$(curl -fsSL \
 https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install bazel
bazel version
brew upgrade bazel
  1. 从 GitHub 克隆 TensorFlow 发行版。
git clone https://github.com/TensorFlow/TensorFlow.git

玩转 TensorFlow 和 Android 示例

在本秘籍中,我们将考虑 TensorFlow 发行版中提供的标准 Android 示例并将其安装在我们的移动设备上。

准备

TensorFlow 移动 Android 应用可在 GitHub 上的以下地址获得。 2017 年 10 月,该页面包含以下示例:

  • TF 分类:使用 Google Inception 模型实时对相机帧进行分类,并在相机图像上以重叠显示顶部结果。
  • TF 检测:演示使用 TensorFlow 对象检测 API 训练的 SSD-Mobilenet 模型。 这是在现代卷积目标检测器的速度/精度折衷中引入的,以实时定位和跟踪摄像机预览中的目标(来自 80 个类别)。
  • TF 风格化:使用基于艺术风格的学习表示的模型将相机预览图像重新设置为许多不同艺术风格的图像。
  • TF 语音:运行在音频训练教程中构建的简单语音识别模型。 监听一小部分单词,并在识别它们时在 UI 中突出显示它们。

操作步骤

我们按以下步骤进行:

  1. 安装包的最佳方法是使用每晚创建的预构建 APK。 将浏览器指向这里并下载TensorFlow_demo.apk,如以下屏幕截图所示:

  1. 在您的设备上安装应用。 在我的示例中,我将使用 Android Studio 中可用的 Pixel XL 仿真设备。 这是直接从 Android Studio 内部模拟的终端设备。 命令adb devices列出所有连接的设备。 在这种情况下,我有一个 Pixel XL 模拟器,可以安装TensorFlow_demo apk
adb devices
List of devices attached
emulator-5554 device
adb install -r TensorFlow_demo.apk

安装后,仿真器将具有一组新的 TensorFlow 应用可供使用,如下图所示。

  1. 运行您喜欢的应用。 例如,以下图像是 TF Stylize 的示例,用于通过 Transfer Learning 将相机预览图像重新设置为多种不同艺术风格的图像:

下图是 TF 语音的示例(请记住为仿真器激活麦克风):

工作原理

如果您使用夜间构建演示和adb工具在设备上安装 APK,则安装 Android 的 TensorFlow 示例非常容易。

为 MacOS 和 iPhone 安装 TensorFlow Mobile

在本秘籍中,我们将学习如何在移动环境中设置 TensorFlow。 我的环境是 macOS,这里的想法是为 iOS 和 iPhone 开发。

准备

我们将使用 Xcode 开发环境和 CocoaPods 来预安装 TensorFlow。 我将假定您的环境中已经安装了 Xcode。 如果没有,请从这里下载。

操作步骤

我们将按照以下步骤进行操作:

  1. 使用以下命令安装 CocoaPods
sudo gem install cocoapods
pod setup
Setting up CocoaPods master repo
$ /usr/local/git/current/bin/git clone https://github.com/CocoaPods/Specs.git master --progress
Cloning into 'master'...
remote: Counting objects: 1602077, done.
remote: Compressing objects: 100% (243/243), done.
remote: Total 1602077 (delta 125), reused 172 (delta 74), pack-reused 1601747
Receiving objects: 100% (1602077/1602077), 432.12 MiB | 1.83 MiB/s, done.
Resolving deltas: 100% (849517/849517), done.
Checking out files: 100% (188907/188907), done.
  1. 使用 CocoaPods 安装 TensorFlow 发行版:
cd TensorFlow/TensorFlow/examples/ios/benchmark
pod install
Analyzing dependencies
Downloading dependencies
Installing TensorFlow-experimental (1.1.1)
Generating Pods project
Integrating client project
[!] Please close any current Xcode sessions and use `tf_benchmark_example.xcworkspace` for this project from now on.
Sending stats
Pod installation complete! There is 1 dependency from the Podfile and 1 total pod installed.
  1. 从 Inception v1 下载一些样本数据。 将标签和图文件提取到simplecamera文件夹内的数据文件夹中:
mkdir -p ~/graphs
 curl -o ~/graphs/inception5h.zip \
 https://storage.googleapis.com/download.TensorFlow.org/models/inception5h.zip \
 && unzip ~/graphs/inception5h.zip -d ~/graphs/inception5h
 cp ~/graphs/inception5h/* TensorFlow/examples/ios/benchmark/data/
 cp ~/graphs/inception5h/* TensorFlow/examples/ios/camera/data/
 cp ~/graphs/inception5h/* TensorFlow/examples/ios/simple/data/
  1. 从中下载用作测试的图像并将其复制到基准目录:

https://upload.wikimedia.org/wikipedia/commons/5/55/Grace_Hopper.jpg

cp grace_hopper.jpg ../../benchmark/data/

Grace Hopper 的图片

  1. 打开以前使用的示例项目。 以下命令将打开已经可用的 TensorFlow 的 Xcode 之后,运行编译,如以下代码和图像所示:
open tf_benchmark_example.xcworkspace

  1. 在 iPhone 模拟器中查看结果。 根据 Inception v1 类别,将步骤 4 中使用的图像识别为军服的图像:

用于 Tensorflow 计算的 Iphone 应用示例

工作原理

Xcode 和 CocoaPods 用于编译 TensorFlow 应用,该应用用于对不同 Inception 类别中的图像进行分类。 结果使用 iPhone 模拟器可视化。

更多

您可以直接在应用中使用 TensorFlow。 可在此处获得更多信息

为移动设备优化 TensorFlow 图

在本秘籍中,我们将考虑不同的选项来优化在移动设备上运行的 TensorFlow 代码。 从减小模型的大小到量化,分析了不同的选项。

准备

我们将使用 Bazel 构建 TensorFlow 的不同组件。 因此,第一步是确保同时安装了 Bazel 和 TensorFlow。

操作步骤

我们按以下步骤进行优化:

  1. 这里安装 Android Studio。

  2. 按照这个页面上的说明安装 Bazel。 对于 macOS,我们将使用 Homebrew:

/usr/bin/ruby -e "$(curl -fsSL \
 https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install bazel
bazel version
brew upgrade bazel
  1. 从 GitHub 克隆 TensorFlow 发行版:
git clone https://github.com/TensorFlow/TensorFlow.git
  1. 构建一个图转换器,汇总一下图本身:
cd ~/TensorFlow/
bazel build TensorFlow/tools/graph_transforms:summarize_graph
[2,326 / 2,531] Compiling TensorFlow/core/kernels/cwise_op_greater.cc
INFO: From Linking TensorFlow/tools/graph_transforms/summarize_graph:
clang: warning: argument unused during compilation: '-pthread' [-Wunused-command-line-argument]
Target //TensorFlow/tools/graph_transforms:summarize_graph up-to-date:
bazel-bin/TensorFlow/tools/graph_transforms/summarize_graph
INFO: Elapsed time: 1521.260s, Critical Path: 103.87s
  1. 下载 TensorFlow 图以用作示例。 在这种情况下,我们将使用 Inception v1 TensorFlow 图:
mkdir -p ~/graphs
 curl -o ~/graphs/inception5h.zip \
 https://storage.googleapis.com/download.TensorFlow.org/models/inception5h.zip \
 && unzip ~/graphs/inception5h.zip -d ~/graphs/inception5h
  1. 汇总 Inception 图并注意常参数的数量:1,346 万。 它们每个都存储有 32 位浮点数,这非常昂贵:
bazel-bin/TensorFlow/tools/graph_transforms/summarize_graph --in_graph=/Users/gulli/graphs/TensorFlow_inception_graph.pb
Found 1 possible inputs: (name=input, type=float(1), shape=[])
No variables spotted.
Found 3 possible outputs: (name=output, op=Identity) (name=output1, op=Identity) (name=output2, op=Identity)
Found 13462015 (13.46M) const parameters, 0 (0) variable parameters, and 0 control_edges
370 nodes assigned to device '/cpu:0'Op types used: 142 Const, 64 BiasAdd, 61 Relu, 59 Conv2D, 13 MaxPool, 9 Concat, 5 Reshape, 5 MatMul, 3 Softmax, 3 Identity, 3 AvgPool, 2 LRN, 1 Placeholder
To use with TensorFlow/tools/benchmark:benchmark_model try these arguments:
bazel run TensorFlow/tools/benchmark:benchmark_model -- --graph=/Users/gulli/graphs/TensorFlow_inception_graph.pb --show_flops --input_layer=input --input_layer_type=float --input_layer_shape= --output_layer=output,output1,output2
  1. 编译该工具以将常量操作截断至 8 位:
bazel build TensorFlow/tools/graph_transforms:transform_graph
INFO: From Linking TensorFlow/tools/graph_transforms/transform_graph:
clang: warning: argument unused during compilation: '-pthread' [-Wunused-command-line-argument]
Target //TensorFlow/tools/graph_transforms:transform_graph up-to-date:
bazel-bin/TensorFlow/tools/graph_transforms/transform_graph
INFO: Elapsed time: 294.421s, Critical Path: 28.83s
  1. 运行该工具以量化 Inception V1 图:
bazel-bin/TensorFlow/tools/graph_transforms/transform_graph --in_graph=/Users/gulli/graphs/inception5h/TensorFlow_inception_graph.pb --out_graph=/tmp/TensorFlow_inception_quantized.pb --inputs='Mul:0' --outputs='softmax:0' --transforms='quantize_weights'
2017-10-15 18:56:01.192498: I TensorFlow/tools/graph_transforms/transform_graph.cc:264] Applying quantize_weights
  1. 比较两个模型:
ls -lah /Users/gulli/graphs/inception5h/TensorFlow_inception_graph.pb
-rw-r----- 1 gulli 5001 51M Nov 19 2015 /Users/gulli/graphs/inception5h/TensorFlow_inception_graph.pb
ls -lah /tmp/TensorFlow_inception_quantized.pb
-rw-r--r-- 1 gulli wheel 13M Oct 15 18:56 /tmp/TensorFlow_inception_quantized.pb

工作原理

量化通过将常量操作从 32 位缩减为 8 位来帮助减小模型的大小。 通常,该模型不会遭受表现的显着降低。 但是,这必须根据具体情况进行验证。

为移动设备分析 TensorFlow 图

在本秘籍中,我们将考虑不同的选项来优化 TensorFlow 代码以在移动设备上运行。 从减小模型的大小到量化,分析了不同的选项。

准备

我们将使用 Bazel 构建 TensorFlow 的不同组件。 因此,第一步是确保同时安装了 Bazel 和 TensorFlow。

操作步骤

我们进行如下分析:

  1. 这里安装 Android Studio。

  2. 按照这个页面上的说明安装 Bazel。 对于 macOS,我们将使用 Homebrew:

/usr/bin/ruby -e "$(curl -fsSL \
 https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install bazel
bazel version
brew upgrade bazel
  1. 从 GitHub 克隆 TensorFlow 发行版:
git clone https://github.com/TensorFlow/TensorFlow.git
  1. 构建图变压器,该图变压器对图本身进行配置:
cd ~/TensorFlow/
bazel build -c opt TensorFlow/tools/benchmark:benchmark_model
INFO: Found 1 target...
Target //TensorFlow/tools/benchmark:benchmark_model up-to-date:
bazel-bin/TensorFlow/tools/benchmark/benchmark_model
INFO: Elapsed time: 0.493s, Critical Path: 0.01s
  1. 通过在桌面上运行以下命令来对模型进行基准测试:
bazel-bin/TensorFlow/tools/benchmark/benchmark_model --graph=/Users/gulli/graphs/TensorFlow_inception_graph.pb --show_run_order=false --show_time=false --show_memory=false --show_summary=true --show_flops=true
Graph: [/Users/gulli/graphs/TensorFlow_inception_graph.pb]
Input layers: [input:0]
Input shapes: [1,224,224,3]
Input types: [float]
Output layers: [output:0]
Num runs: [1000]
Inter-inference delay (seconds): [-1.0]
Inter-benchmark delay (seconds): [-1.0]
Num threads: [-1]
Benchmark name: []
Output prefix: []
Show sizes: [0]
Warmup runs: [2]
Loading TensorFlow.
Got config, 0 devices
Running benchmark for max 2 iterations, max -1 seconds without detailed stat logging, with -1s sleep between inferences
count=2 first=279182 curr=41827 min=41827 max=279182 avg=160504 std=118677
Running benchmark for max 1000 iterations, max 10 seconds without detailed stat logging, with -1s sleep between inferences
count=259 first=39945 curr=44189 min=36539 max=51743 avg=38651.1 std=1886
Running benchmark for max 1000 iterations, max 10 seconds with detailed stat logging, with -1s sleep between inferences
count=241 first=40794 curr=39178 min=37634 max=153345 avg=41644.8 std=8092
Average inference timings in us: Warmup: 160504, no stats: 38651, with stats: 41644

  1. 通过在运行 64 位 ARM 处理器的目标 android 设备上运行以下命令来对模型进行基准测试。 请注意,以下命令将初始图推送到设备上并运行可在其中执行基准测试的外壳程序:
bazel build -c opt --config=android_arm64 \ TensorFlow/tools/benchmark:benchmark_model
adb push bazel-bin/TensorFlow/tools/benchmark/benchmark_model \ /data/local/tmp
adb push /tmp/TensorFlow_inception_graph.pb /data/local/tmp/
adb push ~gulli/graphs/inception5h/TensorFlow_inception_graph.pb /data/local/tmp/
/Users/gulli/graphs/inception5h/TensorFlow_inception_graph.pb: 1 file pushed. 83.2 MB/s (53884595 bytes in 0.618s)
adb shell
generic_x86:/ $
/data/local/tmp/benchmark_model --graph=/data/local/tmp/TensorFlow_inception_graph.pb --show_run_order=false --show_time=false --show_memory=false --show_summary=true

工作原理

正如预期的那样,该模型在 Conv2D 操作上花费了大量时间。 总体而言,这大约占我台式机平均时间的 77.5%。 如果在移动设备上运行此程序,那么花时间执行神经网络中的每一层并确保它们处于受控状态至关重要。 要考虑的另一个方面是内存占用。 在这种情况下,桌面执行约为 10 Mb。

转换移动设备的 TensorFlow 图

在本秘籍中,我们将学习如何转换 TensorFlow 图,以便删除所有仅训练节点。 这将减小图的大小,使其更适合于移动设备。

What is a graph transform tool? According to https://github.com/tensorflow/tensorflow/blob/master/tensorflow/tools/graph_transforms/README.md "When you have finished training a model and want to deploy it in production, you'll often want to modify it to better run in its final environment. For example if you're targeting a phone you might want to shrink the file size by quantizing the weights, or optimize away batch normalization or other training-only features. The Graph Transform framework offers a suite of tools for modifying computational graphs, and a framework to make it easy to write your own modifications".

准备

我们将使用 Bazel 构建 TensorFlow 的不同组件。 因此,第一步是确保同时安装了 Bazel 和 TensorFlow。

操作步骤

这是我们如何转换 TensorFlow 的方法:

  1. 这里安装 Android Studio。

  2. 按照这个页面上的说明安装 Bazel。 对于 macOS,我们将使用 Homebrew:

/usr/bin/ruby -e "$(curl -fsSL \
 https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install bazel
bazel version
brew upgrade bazel
  1. 从 GitHub 克隆 TensorFlow 发行版:
git clone https://github.com/TensorFlow/TensorFlow.git
  1. 构建一个图转换器,它汇总了图本身:
bazel run TensorFlow/tools/graph_transforms:summarize_graph -- --in_graph=/Users/gulli/graphs/inception5h/TensorFlow_inception_graph.pb
WARNING: /Users/gulli/TensorFlow/TensorFlow/core/BUILD:1783:1: in includes attribute of cc_library rule //TensorFlow/core:framework_headers_lib: '../../external/nsync/public' resolves to 'external/nsync/public' not below the relative path of its package 'TensorFlow/core'. This will be an error in the future. Since this rule was created by the macro 'cc_header_only_library', the error might have been caused by the macro implementation in /Users/gulli/TensorFlow/TensorFlow/TensorFlow.bzl:1054:30.
INFO: Found 1 target...
Target //TensorFlow/tools/graph_transforms:summarize_graph up-to-date:
bazel-bin/TensorFlow/tools/graph_transforms/summarize_graph
INFO: Elapsed time: 0.395s, Critical Path: 0.01s
INFO: Running command line: bazel-bin/TensorFlow/tools/graph_transforms/summarize_graph '--in_graph=/Users/gulli/graphs/inception5h/TensorFlow_inception_graph.pb'
Found 1 possible inputs: (name=input, type=float(1), shape=[])
No variables spotted.
Found 3 possible outputs: (name=output, op=Identity) (name=output1, op=Identity) (name=output2, op=Identity)
Found 13462015 (13.46M) const parameters, 0 (0) variable parameters, and 0 control_edges
370 nodes assigned to device '/cpu:0'Op types used: 142 Const, 64 BiasAdd, 61 Relu, 59 Conv2D, 13 MaxPool, 9 Concat, 5 Reshape, 5 MatMul, 3 Softmax, 3 Identity, 3 AvgPool, 2 LRN, 1 Placeholder
To use with TensorFlow/tools/benchmark:benchmark_model try these arguments:
bazel run TensorFlow/tools/benchmark:benchmark_model -- --graph=/Users/gulli/graphs/inception5h/TensorFlow_inception_graph.pb --show_flops --input_layer=input --input_layer_type=float --input_layer_shape= --output_layer=output,output1,output2
  1. 剥去用于训练的所有节点,当在移动设备上使用图进行推理时,不需要这些节点:
bazel run TensorFlow/tools/graph_transforms:transform_graph -- --in_graph=/Users/gulli/graphs/inception5h/TensorFlow_inception_graph.pb --out_graph=/tmp/optimized_inception_graph.pb --transforms="strip_unused_nodes fold_constants(ignore_errors=true) fold_batch_norms fold_old_batch_norms"
WARNING: /Users/gulli/TensorFlow/TensorFlow/core/BUILD:1783:1: in includes attribute of cc_library rule //TensorFlow/core:framework_headers_lib: '../../external/nsync/public' resolves to 'external/nsync/public' not below the relative path of its package 'TensorFlow/core'. This will be an error in the future. Since this rule was created by the macro 'cc_header_only_library', the error might have been caused by the macro implementation in /Users/gulli/TensorFlow/TensorFlow/TensorFlow.bzl:1054:30.
INFO: Found 1 target...
Target //TensorFlow/tools/graph_transforms:transform_graph up-to-date:
bazel-bin/TensorFlow/tools/graph_transforms/transform_graph
INFO: Elapsed time: 0.578s, Critical Path: 0.01s
INFO: Running command line: bazel-bin/TensorFlow/tools/graph_transforms/transform_graph '--in_graph=/Users/gulli/graphs/inception5h/TensorFlow_inception_graph.pb' '--out_graph=/tmp/optimized_inception_graph.pb' '--transforms=strip_unused_nodes fold_constants(ignore_errors=true) fold_batch_norms fold_old_batch_norms'
2017-10-15 22:26:59.357129: I TensorFlow/tools/graph_transforms/transform_graph.cc:264] Applying strip_unused_nodes
2017-10-15 22:26:59.367997: I TensorFlow/tools/graph_transforms/transform_graph.cc:264] Applying fold_constants
2017-10-15 22:26:59.387800: I TensorFlow/core/platform/cpu_feature_guard.cc:137] Your CPU supports instructions that this TensorFlow binary was not compiled to use: SSE4.2 AVX AVX2 FMA
2017-10-15 22:26:59.388676: E TensorFlow/tools/graph_transforms/transform_graph.cc:279] fold_constants: Ignoring error Must specify at least one target to fetch or execute.
2017-10-15 22:26:59.388695: I TensorFlow/tools/graph_transforms/transform_graph.cc:264] Applying fold_batch_norms
2017-10-15 22:26:59.388721: I TensorFlow/tools/graph_transforms/transform_graph.cc:264] Applying fold_old_batch_norms

工作原理

为了创建可以在设备上加载的更轻的模型,我们使用了图变换工具应用的strip_unused_nodes规则删除了所有不需要的节点。 该操作将删除用于学习的所有操作,并保留用于推理的操作。

十一、生成模型和 CapsNet

在本章中,我们将介绍一些用于以下方面的方法:

  • 学习使用简单 GAN 伪造 MNIST 图像
  • 学习使用 DCGAN 伪造 MNIST 图像
  • 学习使用 DCGAN 伪造名人面孔和其他数据集
  • 实现变分自编码器
  • 通过胶囊网络学习击败 MNIST 的最新结果

介绍

在本章中,我们将讨论如何将生成对抗网络GAN)用于深度学习领域,其中关键方法是训练图像生成器来挑战鉴别器,并同时训练鉴别器来改进生成器。 可以将相同的方法应用于不同于图像领域。 另外,我们将讨论变分自编码器。

GAN 已被深度学习之父之一 Yann LeCun 定义为“这是深度学习的突破”。 GAN 能够学习如何再现看起来真实的合成数据。 例如,计算机可以学习如何绘制和创建逼真的图像。 这个想法最初是由与蒙特利尔大学 Google Brain 合作的 Ian Goodfellow 提出的,最近由 OpenAI 提出。

那么,GAN 是什么?

通过将其视为类似于艺术伪造的方式,可以很容易地理解 GAN 的关键过程,这是创作被误认为其他人(通常是更著名的艺术家)的艺术品的过程。 GAN 同时训练两个神经网络。

生成器G(Z)是生成赝品,而鉴别器D(Y)可以根据对真实艺术品和复制品的观察来判断复制品的逼真度。 D(Y)接受输入 Y(例如图像),并投票决定输入的真实程度。 通常,接近零的值表示真实,而接近一的值表示伪造G(Z)从随机噪声Z中获取输入,并训练自己以欺骗 D 认为G(Z)产生的任何东西都是真实的。 因此,训练鉴别器D(Y)的目标是,从为每个来自真实数据分布的图像最大化D(Y),并为每个不来自真实数组的图像最小化D(Y),而不是来自真实数据分布的每个图像。 因此,G 和 D 扮演相反的游戏:因此称为对抗训练。 请注意,我们以交替的方式训练 G 和 D,其中它们的每个目标都表示为通过梯度下降优化的损失函数。 生成模型学习如何越来越好地进行伪造,而鉴别模型学习如何越来越好地识别伪造。

鉴别器网络(通常是标准卷积神经网络)试图对输入图像是真实的还是生成的进行分类。 一个重要的新思想是反向传播鉴别器和生成器,以调整生成器的参数,以使生成器可以学习如何在越来越多的情况下欺骗鉴别器。 最后,生成器将学习如何生成与真实图像无法区分的图像:

生成器(伪造)- 鉴别器(判断)模型的示例。 鉴别器接收伪造的真实图像

当然,GAN 可以在有两名玩家的游戏中找到平衡点。 为了有效学习,如果一个玩家在下一轮更新中成功下坡,那么相同的更新也必须使另一个玩家也下坡。 想想看! 如果伪造者每次都学会如何愚弄法官,那么伪造者本人就没什么可学的了。 有时,两个玩家最终会达到平衡,但这并不总是可以保证的,因此两个玩家可以长时间继续比赛。 下图提供了双方的示例:

生成器和鉴别器的收敛示例

一些很酷的 GAN 应用

我们已经确定生成器学习如何伪造数据。 这意味着它将学习如何创建由网络创建的新合成数据,并且看起来是真实的并且由人类创建。 在讨论有关 GAN 代码的详细信息之前,我想分享使用 GAN 的最新论文(代码可在线获得)的结果。 从文本描述开始合成伪造的图像。 结果令人印象深刻。 第一列是测试集中的真实图像,其他所有列都是从 StackGAN 的 Stage-I 和 Stage-II 中相同的文本描述生成的图像。 YouTube 上有更多示例

现在,让我们看看 GAN 如何学习伪造 MNIST 数据集。 在这种情况下,它是用于生成器和鉴别器网络的 GAN 和卷积网络的组合。 最初,生成器不会产生任何可理解的东西,但是经过几次迭代,合成的伪造数字变得越来越清晰。 在下图中,通过增加训练时期来对面板进行排序,您可以看到面板之间的质量改进:

改进后的图像如下:

我们可以在下图中看到进一步的改进:

GAN 最酷的用途之一是对生成器向量Z的面部进行算术。换句话说,如果我们停留在合成伪造图像的空间中,则可能会看到类似以下内容:[微笑的女人]-[中性的女人] + [中性的男人] = [微笑的男人],或类似这样:[戴眼镜的男人] - [戴眼镜的男人] + [戴眼镜的女人] = [戴眼镜的女人]。 下图取自:《深度卷积生成对抗网络的无监督表示学习》(Alec Radford,Luke Metz,Soumith Chintala,2016)

这个链接提供了 GAN 的其他出色示例。 本文中所有图像均由神经网络生成。 他们不是真实的。 全文可在此处找到

卧室:经过五个时期的训练后生成的卧室:

生成卧室的示例

专辑封面:这些图像不是真实的,而是由 GAN 生成的。 专辑封面看起来很真实:

生成专辑封面的示例

学习使用简单的 GAN 伪造 MNIST 图像

Ian J.Goodfellow,Jean Pouget-Abadie,Mehdi Mirza,Bing Xu,David Warde-Farley,Sherjil Ozair,Aaron Courville,Yoshua Bengio 等人撰写的 Generative Adversarial Networks(2014)是更好地理解 GAN 的好论文。 在本秘籍中,我们将学习如何使用以 Generator-Discriminator 架构组织的全连接层网络来伪造 MNIST 手写数字。

准备

此秘籍基于这个页面上可用的代码。

操作步骤

我们按以下步骤进行:

  1. 从 github 克隆代码:
git clone https://github.com/TengdaHan/GAN-TensorFlow
  1. 定义 Xavier 初始化器,如论文Understanding the difficulty of training deep feedforward neural networks (2009) by Xavier Glorot, Yoshua Bengio, http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.207.2059&rep=rep1&type=pdf中所述。事实证明,初始化器可让 GAN 更好地收敛:
def xavier_init(size):
  in_dim = size[0]
  xavier_stddev = 1\. / tf.sqrt(in_dim / 2.)
  return xavier_stddev
  1. 定义输入X的生成器。首先,我们定义尺寸为[100, K = 128]的矩阵W1,并根据正态分布对其进行初始化。 注意 100 是Z的任意值,Z是我们的生成器使用的初始噪声。 然后,我们定义尺寸为[K = 256]的偏差B1。 类似地,我们定义尺寸为[K=128, L=784]的矩阵W2和尺寸为[L = 784]的偏置B2。 使用步骤 1 中定义的xavier_init初始化两个矩阵W1W2,而使用tf.constant_initializer()初始化B1B2。 之后,我们计算矩阵X * W1之间的乘法,求和B1的偏差,然后将其传递给 RELU 激活函数以获得fc1。 然后将该密集层与下一个密集层连接,该密集层是通过将矩阵fc1W2相乘并求和B2的偏差而创建的。 然后将结果通过 Sigmoid 函数传递。 这些步骤用于定义用于生成器的两层神经网络:
def generator(X):
  with tf.variable_scope('generator'): 
    K = 128
    L = 784
    W1 = tf.get_variable('G_W1', [100, K],
  initializer=tf.random_normal_initializer(stddev=xavier_init([100,   K])))
    B1 = tf.get_variable('G_B1', [K],       initializer=tf.constant_initializer())
    W2 = tf.get_variable('G_W2', [K, L],
    initializer=tf.random_normal_initializer(stddev=xavier_init([K,   L])))
    B2 = tf.get_variable('G_B2', [L],    initializer=tf.constant_initializer())
    # summary
    tf.summary.histogram('weight1', W1)
    tf.summary.histogram('weight2', W2)
    tf.summary.histogram('biases1', B1)
    tf.summary.histogram('biases2', B2)
    fc1 = tf.nn.relu((tf.matmul(X, W1) + B1))
    fc2 = tf.matmul(fc1, W2) + B2
    prob = tf.nn.sigmoid(fc2)
    return prob
  1. 定义输入X的鉴别器。原则上,这与生成器非常相似。 主要区别在于,如果参数重用为true,则调用scope.reuse_variables()触发重用。 然后我们定义两个密集层。 第一层使用尺寸为[J=784, K=128]的矩阵W1,尺寸为[K=128]的偏差B1,并且它基于XW1的标准乘积。 将该结果添加到B1并传递给 RELU 激活函数以获取结果fc1。 第二个矩阵使用尺寸为[K=128, L=1]的矩阵W2和尺寸为[L=1]的偏差B2,它基于fc1W2的标准乘积。 将此结果添加到B2并传递给 Sigmoid 函数:
def discriminator(X, reuse=False):
  with tf.variable_scope('discriminator'):
    if reuse:
      tf.get_variable_scope().reuse_variables()
    J = 784
    K = 128
    L = 1
    W1 = tf.get_variable('D_W1', [J, K],
    initializer=tf.random_normal_initializer(stddev=xavier_init([J,  K])))
    B1 = tf.get_variable('D_B1', [K],    initializer=tf.constant_initializer())
    W2 = tf.get_variable('D_W2', [K, L],
initializer=tf.random_normal_initializer(stddev=xavier_init([K, L])))
    B2 = tf.get_variable('D_B2', [L],  initializer=tf.constant_initializer())
    # summary
    tf.summary.histogram('weight1', W1)
    tf.summary.histogram('weight2', W2)
    tf.summary.histogram('biases1', B1)
    tf.summary.histogram('biases2', B2)
    fc1 = tf.nn.relu((tf.matmul(X, W1) + B1))
    logits = tf.matmul(fc1, W2) + B2
    prob = tf.nn.sigmoid(logits)
    return prob, logits
  1. 现在让我们定义一些有用的附加函数。 首先,我们导入一堆标准模块:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import os
import argparse
  1. 然后,我们从 MNIST 数据集中读取数据,并定义了用于绘制样本的辅助函数:
def read_data():
  from tensorflow.examples.tutorials.mnist import input_data
  mnist = input_data.read_data_sets("../MNIST_data/", one_hot=True)
  return mnist

def plot(samples):
  fig = plt.figure(figsize=(8, 8))
  gs = gridspec.GridSpec(8, 8)
  gs.update(wspace=0.05, hspace=0.05)
  for i, sample in enumerate(samples):
    ax = plt.subplot(gs[i])
    plt.axis('off')
    ax.set_xticklabels([])
    ax.set_yticklabels([])
    ax.set_aspect('equal')
    plt.imshow(sample.reshape(28, 28), cmap='Greys_r')
  return fig
  1. 现在让我们定义训练函数。 首先,让我们读取 MNIST 数据,然后定义一个具有一个用于标准 MNIST 手写字符的通道的28 x 28形状的矩阵X。 然后,让我们定义大小为 100 的z噪声向量,这是 GAN 论文中提出的一个常见选择。 下一步是在z上调用生成器,然后将结果分配给G。之后,我们将X传递给鉴别器,而无需重用。 然后,我们将伪造/伪造的G结果传递给鉴别器,从而重用学习到的权重。 这方面的一个重要方面是我们如何选择鉴别器的损失函数,该函数是两个交叉熵的和:一个交叉熵,一个用于实字符,其中所有真实 MNIST 字符的标签都设置为一个,另一个用于伪造的字符,其中所有伪造的字符的标签都设置为零。 鉴别器和生成器以交替顺序运行 100,000 步。 每 500 步,会从学习到的分布中抽取一个样本,以打印该生成器到目前为止所学的内容。 这就是定义新周期的条件,结果将在下一节中显示。 让我们看看实现我们刚刚描述的代码片段。
def train(logdir, batch_size):
  from model_fc import discriminator, generator
  mnist = read_data()
  with tf.variable_scope('placeholder'):
    # Raw image
    X = tf.placeholder(tf.float32, [None, 784])
    tf.summary.image('raw image', tf.reshape(X, [-1, 28, 28, 1]), 3)
   # Noise
   z = tf.placeholder(tf.float32, [None, 100]) # noise
   tf.summary.histogram('Noise', z)

  with tf.variable_scope('GAN'):
    G = generator(z)
    D_real, D_real_logits = discriminator(X, reuse=False)
    D_fake, D_fake_logits = discriminator(G, reuse=True)
    tf.summary.image('generated image', tf.reshape(G, [-1, 28, 28, 1]), 3)

  with tf.variable_scope('Prediction'):
    tf.summary.histogram('real', D_real) 
    tf.summary.histogram('fake', D_fake)

  with tf.variable_scope('D_loss'):
    d_loss_real = tf.reduce_mean(
    tf.nn.sigmoid_cross_entropy_with_logits(
    logits=D_real_logits, labels=tf.ones_like(D_real_logits)))
    d_loss_fake = tf.reduce_mean(
    tf.nn.sigmoid_cross_entropy_with_logits(
     logits=D_fake_logits, labels=tf.zeros_like(D_fake_logits)))
    d_loss = d_loss_real + d_loss_fake

  tf.summary.scalar('d_loss_real', d_loss_real)
  tf.summary.scalar('d_loss_fake', d_loss_fake)
  tf.summary.scalar('d_loss', d_loss)

  with tf.name_scope('G_loss'):
    g_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits
   (logits=D_fake_logits, labels=tf.ones_like(D_fake_logits)))
    tf.summary.scalar('g_loss', g_loss)
    tvar = tf.trainable_variables()
    dvar = [var for var in tvar if 'discriminator' in var.name]
    gvar = [var for var in tvar if 'generator' in var.name]

  with tf.name_scope('train'):
    d_train_step = tf.train.AdamOptimizer().minimize(d_loss,   var_list=dvar)
    g_train_step = tf.train.AdamOptimizer().minimize(g_loss,  var_list=gvar)

  sess = tf.Session()
  init = tf.global_variables_initializer()
  sess.run(init)
  merged_summary = tf.summary.merge_all()
  writer = tf.summary.FileWriter('tmp/mnist/'+logdir)
  writer.add_graph(sess.graph)
  num_img = 0
  if not os.path.exists('output/'):
    os.makedirs('output/')

  for i in range(100000):
    batch_X, _ = mnist.train.next_batch(batch_size)
    batch_noise = np.random.uniform(-1., 1., [batch_size, 100])
    if i % 500 == 0:
     samples = sess.run(G, feed_dict={z: np.random.uniform(-1., 1., [64, 100])})
     fig = plot(samples)
     plt.savefig('output/%s.png' % str(num_img).zfill(3),    bbox_inches='tight')
     num_img += 1
    plt.close(fig)

  _, d_loss_print = sess.run([d_train_step, d_loss],
feed_dict={X: batch_X, z: batch_noise})
  _, g_loss_print = sess.run([g_train_step, g_loss],
  feed_dict={z: batch_noise})
  if i % 100 == 0:
    s = sess.run(merged_summary, feed_dict={X: batch_X, z: batch_noise})
    writer.add_summary(s, i)
    print('epoch:%d g_loss:%f d_loss:%f' % (i, g_loss_print, d_loss_print))

  if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Train vanila GAN using fully-connected layers networks')
    parser.add_argument('--logdir', type=str, default='1', help='logdir for Tensorboard, give a string')
    parser.add_argument('--batch_size', type=int, default=64, help='batch size: give a int')
    args = parser.parse_args()
    train(logdir=args.logdir, batch_size=args.batch_size)

工作原理

在每个时期,生成器都会进行许多预测(它会生成伪造的 MNIST 图像),鉴别器会在将预测与实际 MNIST 图像混合后尝试学习如何生成伪造的图像。 在 32 个周期之后,生成器学习伪造这组手写数字。 没有人对机器进行编程来编写,但是它学会了如何编写与人类所写的数字没有区别的数字。 请注意,训练 GAN 可能非常困难,因为有必要在两个参与者之间找到平衡。 如果您对该主题感兴趣,我建议您看看从业者收集的一系列技巧

让我们看一下不同时期的许多实际示例,以了解机器将如何学习以改善其编写过程:

周期 0 周期 2 周期 4
周期 8 周期 16 周期 32
周期 64 周期 128 周期 200

Example of forged MNIST-like characters with a GAN

学习使用 DCGAN 伪造 MNIST 图像

在本秘籍中,我们将使用一个简单的 GAN,它使用 CNN 来学习如何伪造 MNIST 图像并创建不属于原始数据集的新图像。 这个想法是 CNN 与 GAN 一起使用将提高处理图像数据集的能力。 请注意,先前的方法是将 GAN 与完全连接的网络一起使用,而在此我们重点介绍 CNN。

准备

此秘籍基于这个页面上可用的代码。

操作步骤

我们按以下步骤进行:

  1. 从 github 克隆代码:
git clone https://github.com/TengdaHan/GAN-TensorFlow
  1. 定义 Xavier 初始化器,如论文Understanding the difficulty of training deep feedforward neural networks (2009) by Xavier Glorot, Yoshua Bengio, http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.207.2059&rep=rep1&type=pdf中所述。事实证明,初始化器可让 GAN 更好地收敛:
def xavier_init(size):
  in_dim = size[0]
  xavier_stddev = 1\. / tf.sqrt(in_dim / 2.)
  # return tf.random_normal(shape=size, stddev=xavier_stddev)
  return xavier_stddev
  1. 为给定输入x,权重w,偏差b和给定步幅定义卷积运算。 我们的代码使用标准的tf.nn.conv2d(...)模块。 请注意,我们使用第 4 章中定义的SAME填充:
def conv(x, w, b, stride, name):
  with tf.variable_scope('conv'):
    tf.summary.histogram('weight', w)
    tf.summary.histogram('biases', b)
    return tf.nn.conv2d(x,
      filter=w,
      strides=[1, stride, stride, 1],
      padding='SAME',
      name=name) + b
  1. 为给定输入x,权重w,偏差b和给定步幅定义反卷积运算。 我们的代码使用标准的tf.nn.conv2d_transpose(...)模块。 同样,我们使用'SAME'填充。
def deconv(x, w, b, shape, stride, name):
  with tf.variable_scope('deconv'):
    tf.summary.histogram('weight', w)
    tf.summary.histogram('biases', b)
    return tf.nn.conv2d_transpose(x,
      filter=w,
      output_shape=shape,
      strides=[1, stride, stride, 1],
      padding='SAME',
      name=name) + b
  1. 定义一个标准LeakyReLU,这对于 GAN 是非常有效的激活函数:
def lrelu(x, alpha=0.2):
  with tf.variable_scope('leakyReLU'):
    return tf.maximum(x, alpha * x)
  1. 定义生成器。 首先,我们定义输入大小为 100(Z 的任意大小,即生成器使用的初始噪声)的完全连接层。 全连接层由尺寸为[100, 7 * 7 * 256]且根据正态分布初始化的矩阵W1和尺寸为[7 * 7 * 256]的偏置B1组成。 该层使用 ReLu 作为激活函数。 在完全连接的层之后,生成器将应用两个反卷积运算 deconv1 和 deconv2,两者的步幅均为 2。 完成第一个 deconv1 操作后,将结果批量标准化。 请注意,第二次反卷积运算之前会出现丢弃,概率为 40%。 最后一个阶段是一个 Sigmoid,用作非线性激活,如下面的代码片段所示:
def generator(X, batch_size=64):
  with tf.variable_scope('generator'):
    K = 256
    L = 128
    M = 64
    W1 = tf.get_variable('G_W1', [100, 7`7`K],    initializer=tf.random_normal_initializer(stddev=0.1))
    B1 = tf.get_variable('G_B1', [7`7`K], initializer=tf.constant_initializer())
    W2 = tf.get_variable('G_W2', [4, 4, M, K], initializer=tf.random_normal_initializer(stddev=0.1))
    B2 = tf.get_variable('G_B2', [M], initializer=tf.constant_initializer())
    W3 = tf.get_variable('G_W3', [4, 4, 1, M], initializer=tf.random_normal_initializer(stddev=0.1))
    B3 = tf.get_variable('G_B3', [1], initializer=tf.constant_initializer())
    X = lrelu(tf.matmul(X, W1) + B1)
    X = tf.reshape(X, [batch_size, 7, 7, K])
    deconv1 = deconv(X, W2, B2, shape=[batch_size, 14, 14, M], stride=2, name='deconv1')
    bn1 = tf.contrib.layers.batch_norm(deconv1)
    deconv2 = deconv(tf.nn.dropout(lrelu(bn1), 0.4), W3, B3, shape=[batch_size, 28, 28, 1], stride=2, name='deconv2')
    XX = tf.reshape(deconv2, [-1, 28*28], 'reshape')
    return tf.nn.sigmoid(XX)
  1. 定义鉴别器。 与前面的秘籍一样,如果参数重用为true,则调用scope.reuse_variables()触发重用。 鉴别器使用两个卷积层。 第一个是批量归一化,而第二个是概率为 40% 的丢弃,然后是批量归一化步骤。 之后,我们得到了一个具有激活函数 ReLU 的致密层,然后是另一个具有基于 Sigmoid 激活函数的致密层:
def discriminator(X, reuse=False):
  with tf.variable_scope('discriminator'):
    if reuse:
      tf.get_variable_scope().reuse_variables()
    K = 64
    M = 128
    N = 256
    W1 = tf.get_variable('D_W1', [4, 4, 1, K],   initializer=tf.random_normal_initializer(stddev=0.1))
    B1 = tf.get_variable('D_B1', [K], initializer=tf.constant_initializer())
    W2 = tf.get_variable('D_W2', [4, 4, K, M], initializer=tf.random_normal_initializer(stddev=0.1))
    B2 = tf.get_variable('D_B2', [M], initializer=tf.constant_initializer())
    W3 = tf.get_variable('D_W3', [7`7`M, N], initializer=tf.random_normal_initializer(stddev=0.1))
    B3 = tf.get_variable('D_B3', [N], initializer=tf.constant_initializer())
    W4 = tf.get_variable('D_W4', [N, 1], initializer=tf.random_normal_initializer(stddev=0.1))
    B4 = tf.get_variable('D_B4', [1], initializer=tf.constant_initializer())
    X = tf.reshape(X, [-1, 28, 28, 1], 'reshape')
    conv1 = conv(X, W1, B1, stride=2, name='conv1')
    bn1 = tf.contrib.layers.batch_norm(conv1)
    conv2 = conv(tf.nn.dropout(lrelu(bn1), 0.4), W2, B2, stride=2, name='conv2')
    bn2 = tf.contrib.layers.batch_norm(conv2)
    flat = tf.reshape(tf.nn.dropout(lrelu(bn2), 0.4), [-1, 7`7`M], name='flat')
    dense = lrelu(tf.matmul(flat, W3) + B3)
    logits = tf.matmul(dense, W4) + B4
    prob = tf.nn.sigmoid(logits)
    return prob, logits
  1. 然后,我们从 MNIST 数据集中读取数据,并定义用于绘制样本的辅助函数:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import os
import argparse

def read_data():
  from tensorflow.examples.tutorials.mnist import input_data
  mnist = input_data.read_data_sets("../MNIST_data/", one_hot=True)
  return mnist

def plot(samples):
  fig = plt.figure(figsize=(8, 8))
  gs = gridspec.GridSpec(8, 8)
  gs.update(wspace=0.05, hspace=0.05)
  for i, sample in enumerate(samples):
    ax = plt.subplot(gs[i])
    plt.axis('off')
    ax.set_xticklabels([])
    ax.set_yticklabels([])
    ax.set_aspect('equal')
    plt.imshow(sample.reshape(28, 28), cmap='Greys_r')
    return fig
  1. 现在让我们定义训练函数。 首先,让我们读取 MNIST 数据,然后定义一个具有一个用于标准 MNIST 手写字符的通道的28 x 28形状的矩阵X。 然后,让我们定义大小为 100 的z噪声向量,这是 GAN 论文中提出的一个常见选择。 下一步是在z上调用生成器,然后将结果分配给G。之后,我们将X传递给鉴别器,而无需重用。 然后,我们将伪造/伪造的G结果传递给鉴别器,从而重用学习到的权重。 这方面的一个重要方面是我们如何选择鉴别函数的损失函数,该函数是两个交叉熵的和:一个用于实字符,其中所有真实 MNIST 字符的标号都设置为 1,一个用于遗忘字符,其中所有伪造的字符的标签设置为零。 鉴别器和生成器以交替顺序运行 100,000 步。 每 500 步,会从学习到的分布中抽取一个样本,以打印该生成器到目前为止所学的内容。 这就是定义新周期的条件,结果将在下一部分中显示。 训练函数代码段报告如下
def train(logdir, batch_size):
  from model_conv import discriminator, generator
  mnist = read_data()

  with tf.variable_scope('placeholder'):
    # Raw image
    X = tf.placeholder(tf.float32, [None, 784])
    tf.summary.image('raw image', tf.reshape(X, [-1, 28, 28, 1]), 3)
    # Noise
    z = tf.placeholder(tf.float32, [None, 100]) # noise
    tf.summary.histogram('Noise', z)

  with tf.variable_scope('GAN'):
    G = generator(z, batch_size)
    D_real, D_real_logits = discriminator(X, reuse=False)
    D_fake, D_fake_logits = discriminator(G, reuse=True)
    tf.summary.image('generated image', tf.reshape(G, [-1, 28, 28, 1]), 3)

  with tf.variable_scope('Prediction'):
    tf.summary.histogram('real', D_real)
    tf.summary.histogram('fake', D_fake)

  with tf.variable_scope('D_loss'):
    d_loss_real = tf.reduce_mean(
    tf.nn.sigmoid_cross_entropy_with_logits(
logits=D_real_logits, labels=tf.ones_like(D_real_logits)))

    d_loss_fake = tf.reduce_mean(
tf.nn.sigmoid_cross_entropy_with_logits(
logits=D_fake_logits, labels=tf.zeros_like(D_fake_logits)))
    d_loss = d_loss_real + d_loss_fake
    tf.summary.scalar('d_loss_real', d_loss_real)
    tf.summary.scalar('d_loss_fake', d_loss_fake)
    tf.summary.scalar('d_loss', d_loss)

  with tf.name_scope('G_loss'):
    g_loss =   tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits
(logits=D_fake_logits, labels=tf.ones_like(D_fake_logits)))
    tf.summary.scalar('g_loss', g_loss)
    tvar = tf.trainable_variables()
    dvar = [var for var in tvar if 'discriminator' in var.name]
    gvar = [var for var in tvar if 'generator' in var.name]

  with tf.name_scope('train'):
    d_train_step = tf.train.AdamOptimizer().minimize(d_loss, var_list=dvar)
    g_train_step = tf.train.AdamOptimizer().minimize(g_loss, var_list=gvar)

  sess = tf.Session()
  init = tf.global_variables_initializer()

  sess.run(init)
  merged_summary = tf.summary.merge_all()
  writer = tf.summary.FileWriter('tmp/'+'gan_conv_'+logdir)
  writer.add_graph(sess.graph)
  num_img = 0

  if not os.path.exists('output/'):
    os.makedirs('output/')
  for i in range(100000):
    batch_X, _ = mnist.train.next_batch(batch_size)
    batch_noise = np.random.uniform(-1., 1., [batch_size, 100])
    if i % 500 == 0:
      samples = sess.run(G, feed_dict={z: np.random.uniform(-1., 1., [64, 100])})
    fig = plot(samples)
    plt.savefig('output/%s.png' % str(num_img).zfill(3), bbox_inches='tight')
    num_img += 1
    plt.close(fig)

  _, d_loss_print = sess.run([d_train_step, d_loss],
feed_dict={X: batch_X, z: batch_noise})
  _, g_loss_print = sess.run([g_train_step, g_loss],
feed_dict={z: batch_noise})

  if i % 100 == 0:
    s = sess.run(merged_summary, feed_dict={X: batch_X, z: batch_noise})
    writer.add_summary(s, i)
    print('epoch:%d g_loss:%f d_loss:%f' % (i, g_loss_print, d_loss_print))

  if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Train vanila GAN using convolutional networks')
    parser.add_argument('--logdir', type=str, default='1', help='logdir for Tensorboard, give a string')
    parser.add_argument('--batch_size', type=int, default=64, help='batch size: give a int')
    args = parser.parse_args()
    train(logdir=args.logdir, batch_size=args.batch_size)

工作原理

将 CNN 与 GAN 一起使用可提高学习能力。 让我们看一下不同时期的许多实际示例,以了解机器将如何学习以改善其编写过程。 例如,将以下秘籍中的四次迭代后获得的结果与先前秘籍中的四次迭代后获得的结果进行比较。 你看得到差别吗? 我希望自己可以学习这种艺术!

周期 0 周期 2 周期 4
周期 8 周期 16 周期 32
周期 64 周期 128 周期 200

Example of forged MNIST-like with DCGAN

学习使用 DCGAN 伪造名人人脸和其他数据集

用于伪造 MNIST 图像的相同思想可以应用于其他图像域。 在本秘籍中,您将学习如何使用位于这个链接的包在不同的数据集上训练 DCGAN 模型。 这项工作基于论文《深度卷积生成对抗网络的无监督表示学习》(Alec Radford,Luke Metz,Soumith Chintal,2015 年)。引用摘要:

近年来,通过卷积网络(CNN)进行监督学习已在计算机视觉应用中得到了广泛采用。 相比之下,CNN 的无监督学习受到的关注较少。 在这项工作中,我们希望帮助弥合CNN在有监督学习的成功与无监督学习之间的差距。 我们介绍了一种称为深度卷积生成对抗网络(DCGAN)的 CNN,它们具有一定的架构约束,并证明它们是无监督学习的强大候选者。 在各种图像数据集上进行训练,我们显示出令人信服的证据,即我们深厚的卷积对抗对在生成器和鉴别器中学习了从对象部分到场景的表示层次。 此外,我们将学习到的特征用于新颖的任务-展示了它们作为一般图像表示形式的适用性。

请注意,生成器具有下图所示的架构:

请注意,在包装中,相对于原始纸张进行了更改,以避免D(鉴别器)网络快速收敛,G(生成器)网络每次D网络更新都会更新两次。

准备

此秘籍基于这个页面上提供的代码。

操作步骤

我们按以下步骤进行:

  1. 从 Github 克隆代码:
git clone https://github.com/carpedm20/DCGAN-tensorflow
  1. 使用以下命令下载数据集:
python download.py mnist celebA
  1. 要使用下载的数据集训练模型,请使用以下命令:
python main.py --dataset celebA --input_height=108 --train --crop
  1. 要使用现有模型对其进行测试,请使用以下命令:
python main.py --dataset celebA --input_height=108 --crop
  1. 另外,您可以通过执行以下操作来使用自己的数据集:
$ mkdir data/DATASET_NAME
 ... add images to data/DATASET_NAME ...
 $ python main.py --dataset DATASET_NAME --train
 $ python main.py --dataset DATASET_NAME
 $ # example
 $ python main.py --dataset=eyes --input_fname_pattern="*_cropped.png" --train

工作原理

生成器学习如何生成名人的伪造图像,鉴别器学习如何将伪造的图像与真实的图像区分开。 两个网络中的每个周期都在竞争以改善和减少损失。 下表报告了前五个时期:

周期 0 周期 1
周期 2 周期 3
周期 4 周期 5

Example of forged celebrities with a DCGAN

更多

内容感知填充是摄影师使用的一种工具,用于填充不需要的或丢失的图像部分。论文《具有感知和上下文损失的语义图像修复》使用 DCGAN 进行图像补全,并学习如何填充部分图像。

实现变分自编码器

变分自编码器VAE)是神经网络和贝叶斯推理两者的最佳结合。 它们是最酷的神经网络,并已成为无监督学习的流行方法之一。 它们是自编码器。 与传统的编码器和自编码器的解码器网络(请参阅第 8 章“自编码器”)一起,它们还具有其他随机层。 编码器网络之后的随机层使用高斯分布对数据进行采样,解码器网络之后的随机层使用伯努利分布对数据进行采样。 像 GAN 一样,可以使用变分自编码器根据经过训练的分布来生成图像和图形。 VAE 允许人们设置潜在的复杂先验,从而学习强大的潜在表示。

下图描述了 VAE。 编码器网络qᵩ(z | x)逼近真实但棘手的后验分布p(z | x),其中x是 VAE 的输入,z是潜在表示。 解码器网络p[ϴ](x | z)d维潜在变量(也称为潜在空间)作为其输入,并且分布与P(x)相同。 从z | x ~ N(μ[z|x], Σ[z|x])中采样潜在表示z,解码器网络的输出从x | z ~ N(μ[x|z], Σ[x|z])中采样x | z

自动编码器的编码器-解码器示例。

准备

既然我们已经掌握了 VAE 的基本架构,那么就出现了一个问题,即如何对它们进行训练,因为训练数据的最大可能性和后验密度是很难解决的? 通过最大化日志数据可能性的下限来训练网络。 因此,损耗项包括两个部分:生成损耗,它是通过解码器网络通过采样获得的;以及 KL 发散项,也称为潜在损耗。

生成损失确保解码器生成的图像和用于训练网络的图像相同,而潜在损失确保后验分布qᵩ(z | x)接近先前的p[ϴ](z)。 由于编码器使用高斯分布进行采样,因此潜在损失可以衡量潜在变量与单位高斯的匹配程度。

对 VAE 进行训练后,我们只能使用解码器网络来生成新图像。

操作步骤

此处的代码基于 Kingma 和 Welling 的论文自动编码变分贝叶斯,并改编自 GitHub

  1. 第一步是始终导入必要的模块。 对于此秘籍,我们将需要 Numpy,Matplolib 和 TensorFlow:
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
%matplotlib inline
  1. 接下来,我们定义VariationalAutoencoder类。 class __init__ method定义了超参数,例如学习率,批量大小,输入的占位符以及编码器和解码器网络的权重和偏差变量。 它还根据 VAE 的网络架构构建计算图。 在此秘籍中,我们使用 Xavier 初始化来初始化权重。 我们没有定义自己的 Xavier 初始化方法,而是使用tf.contrib.layers.xavier_initializer() TensorFlow 来完成任务。 最后,我们定义损失(生成和潜在)和优化器操作:
class VariationalAutoencoder(object):
  def __init__(self, network_architecture,   transfer_fct=tf.nn.softplus,
learning_rate=0.001, batch_size=100):
      self.network_architecture = network_architecture
      self.transfer_fct = transfer_fct
      self.learning_rate = learning_rate
      self.batch_size = batch_size
      # Place holder for the input
      self.x = tf.placeholder(tf.float32, [None,    network_architecture["n_input"]])
      # Define weights and biases
      network_weights = self._initialize_weights(**self.network_architecture)
      # Create autoencoder network
      # Use Encoder Network to determine mean and
      # (log) variance of Gaussian distribution in latent
      # space
      self.z_mean, self.z_log_sigma_sq = \
      self._encoder_network(network_weights["weights_encoder"],
    network_weights["biases_encoder"])
      # Draw one sample z from Gaussian distribution
      n_z = self.network_architecture["n_z"]
      eps = tf.random_normal((self.batch_size, n_z), 0, 1, dtype=tf.float32)
      # z = mu + sigma*epsilon
      self.z =       tf.add(self.z_mean,tf.multiply(tf.sqrt(tf.exp(self.z_log_sigma_sq)), eps))

      # Use Decoder network to determine mean of
      # Bernoulli distribution of reconstructed input
      self.x_reconstr_mean = \
      self._decoder_network(network_weights["weights_decoder"],
   network_weights["biases_decoder"])
      # Define loss function based variational upper-bound and 
      # corresponding optimizer
      # define generation loss
      generation_loss = \
  -tf.reduce_sum(self.x * tf.log(1e-10 + self.x_reconstr_mean)
+ (1-self.x) * tf.log(1e-10 + 1 - self.x_reconstr_mean), 1)
      latent_loss = -0.5 * tf.reduce_sum(1 + self.z_log_sigma_sq
- tf.square(self.z_mean)- tf.exp(self.z_log_sigma_sq), 1)
      self.cost = tf.reduce_mean(generation_loss + latent_loss)       #    average over batch
      # Define the optimizer
      self.optimizer = \
 tf.train.AdamOptimizer(learning_rate=self.learning_rate).minimize(self.cost)
      # Initializing the tensor flow variables
      init = tf.global_variables_initializer()
  # Launch the session
      self.sess = tf.InteractiveSession()
      self.sess.run(init)

def _initialize_weights(self, n_hidden_recog_1, n_hidden_recog_2,
n_hidden_gener_1, n_hidden_gener_2,
n_input, n_z):
   initializer = tf.contrib.layers.xavier_initializer()
   all_weights = dict()
   all_weights['weights_encoder'] = {
   'h1': tf.Variable(initializer(shape=(n_input, n_hidden_recog_1))),
   'h2': tf.Variable(initializer(shape=(n_hidden_recog_1, n_hidden_recog_2))),
   'out_mean': tf.Variable(initializer(shape=(n_hidden_recog_2, n_z))),
   'out_log_sigma': tf.Variable(initializer(shape=(n_hidden_recog_2, n_z)))}
   all_weights['biases_encoder'] = {
   'b1': tf.Variable(tf.zeros([n_hidden_recog_1], dtype=tf.float32)),
   'b2': tf.Variable(tf.zeros([n_hidden_recog_2], dtype=tf.float32)),
   'out_mean': tf.Variable(tf.zeros([n_z], dtype=tf.float32)),
   'out_log_sigma': tf.Variable(tf.zeros([n_z], dtype=tf.float32))}

   all_weights['weights_decoder'] = {
   'h1': tf.Variable(initializer(shape=(n_z, n_hidden_gener_1))),
   'h2': tf.Variable(initializer(shape=(n_hidden_gener_1, n_hidden_gener_2))),
   'out_mean': tf.Variable(initializer(shape=(n_hidden_gener_2, n_input))),
   'out_log_sigma': tf.Variable(initializer(shape=(n_hidden_gener_2, n_input)))}

    all_weights['biases_decoder'] = {
   'b1': tf.Variable(tf.zeros([n_hidden_gener_1],    dtype=tf.float32)),
   'b2': tf.Variable(tf.zeros([n_hidden_gener_2], dtype=tf.float32)),'out_mean': tf.Variable(tf.zeros([n_input], dtype=tf.float32)),
   'out_log_sigma': tf.Variable(tf.zeros([n_input], dtype=tf.float32))}
   return all_weights
  1. 我们建立编码器网络和解码器网络。 编码器网络的第一层正在获取输入并生成输入的简化的潜在表示。 第二层将输入映射到高斯分布。 网络学习了以下转换:
def _encoder_network(self, weights, biases):
  # Generate probabilistic encoder (recognition network), which
  # maps inputs onto a normal distribution in latent space.
  # The transformation is parametrized and can be learned.
  layer_1 = self.transfer_fct(tf.add(tf.matmul(self.x,     weights['h1']),
biases['b1']))
  layer_2 = self.transfer_fct(tf.add(tf.matmul(layer_1,   weights['h2']),
biases['b2']))
  z_mean = tf.add(tf.matmul(layer_2, weights['out_mean']),
biases['out_mean'])
  z_log_sigma_sq = \
tf.add(tf.matmul(layer_2, weights['out_log_sigma']),
biases['out_log_sigma'])
  return (z_mean, z_log_sigma_sq)

def _decoder_network(self, weights, biases):
  # Generate probabilistic decoder (decoder network), which
  # maps points in latent space onto a Bernoulli distribution in data space.
  # The transformation is parametrized and can be learned.
  layer_1 = self.transfer_fct(tf.add(tf.matmul(self.z, weights['h1']),
biases['b1']))
  layer_2 = self.transfer_fct(tf.add(tf.matmul(layer_1, weights['h2']),
biases['b2']))
  x_reconstr_mean = \
tf.nn.sigmoid(tf.add(tf.matmul(layer_2, weights['out_mean']),
  biases['out_mean']))
  return x_reconstr_mean
  1. VariationalAutoencoder类还包含一些辅助函数,用于生成和重建数据并适合 VAE:
def fit(self, X):
  opt, cost = self.sess.run((self.optimizer, self.cost),
  feed_dict={self.x: X})
  return cost

def generate(self, z_mu=None):
""" Generate data by sampling from latent space.
If z_mu is not None, data for this point in latent space is
generated. Otherwise, z_mu is drawn from prior in latent
space.
"""
  if z_mu is None:
    z_mu = np.random.normal(size=self.network_architecture["n_z"])
# Note: This maps to mean of distribution, we could alternatively
# sample from Gaussian distribution
  return self.sess.run(self.x_reconstr_mean,
      feed_dict={self.z: z_mu})

def reconstruct(self, X):
""" Use VAE to reconstruct given data. """
  return self.sess.run(self.x_reconstr_mean,
    feed_dict={self.x: X})
  1. 一旦完成了 VAE 类,我们就定义了一个训练函数,它使用 VAE 类对象并为给定数据训练它。
def train(network_architecture, learning_rate=0.001,
batch_size=100, training_epochs=10, display_step=5):
  vae = VariationalAutoencoder(network_architecture,
  learning_rate=learning_rate,
  batch_size=batch_size)
  # Training cycle
  for epoch in range(training_epochs):
    avg_cost = 0.
    total_batch = int(n_samples / batch_size)
    # Loop over all batches
    for i in range(total_batch):
      batch_xs, _ = mnist.train.next_batch(batch_size)
      # Fit training using batch data
      cost = vae.fit(batch_xs)
      # Compute average loss
      avg_cost += cost / n_samples * batch_size
      # Display logs per epoch step
     if epoch % display_step == 0:
       print("Epoch:", '%04d' % (epoch+1), 
           "cost=", "{:.9f}".format(avg_cost))
  return vae
  1. 现在让我们使用 VAE 类和训练函数。 我们将 VAE 用于我们最喜欢的 MNIST 数据集:
# Load MNIST data in a format suited for tensorflow.
# The script input_data is available under this URL:
#https://raw.githubusercontent.com/tensorflow/tensorflow/master/tensorflow/examples/tutorials/mnist/input_data.py

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
n_samples = mnist.train.num_examples

  1. 我们定义网络架构,并在 MNIST 数据集上进行 VAE 训练。 在这种情况下,为简单起见,我们保留潜在尺寸 2。
network_architecture = \
dict(n_hidden_recog_1=500, # 1st layer encoder neurons
n_hidden_recog_2=500, # 2nd layer encoder neurons
n_hidden_gener_1=500, # 1st layer decoder neurons
n_hidden_gener_2=500, # 2nd layer decoder neurons
n_input=784, # MNIST data input (img shape: 28*28)
n_z=2) # dimensionality of latent space
vae = train(network_architecture, training_epochs=75)
  1. 现在让我们看看 VAE 是否真正重建了输入。 输出结果表明确实可以重建数字,并且由于我们使用了 2D 潜在空间,因此图像明显模糊:
x_sample = mnist.test.next_batch(100)[0]
x_reconstruct = vae.reconstruct(x_sample)
plt.figure(figsize=(8, 12))
for i in range(5):
  plt.subplot(5, 2, 2*i + 1)
  plt.imshow(x_sample[i].reshape(28, 28),  vmin=0, vmax=1, cmap="gray")
  plt.title("Test input")
  plt.colorbar()
  plt.subplot(5, 2, 2*i + 2)
  plt.imshow(x_reconstruct[i].reshape(28, 28), vmin=0, vmax=1, cmap="gray")
  plt.title("Reconstruction")
  plt.colorbar()
  plt.tight_layout()

以下是上述代码的输出:

MNIST 重建字符的示例

  1. 以下是使用经过训练的 VAE 生成的手写数字示例:
nx = ny = 20
x_values = np.linspace(-3, 3, nx)
y_values = np.linspace(-3, 3, ny)
canvas = np.empty((28*ny, 28*nx))
for i, yi in enumerate(x_values):
  for j, xi in enumerate(y_values):
    z_mu = np.array([[xi, yi]]*vae.batch_size)
    x_mean = vae.generate(z_mu)
    canvas[(nx-i-1)*28:(nx-i)*28, j*28:(j+1)*28] = x_mean[0].reshape(28, 28)
plt.figure(figsize=(8, 10))
Xi, Yi = np.meshgrid(x_values, y_values)
plt.imshow(canvas, origin="upper", cmap="gray")
plt.tight_layout()

以下是自编码器生成的 MNIST 类字符的范围:

由自动编码器生成的一系列 MNIST 字符

工作原理

VAE 学会重建并同时生成新图像。 生成的图像取决于潜在空间。 生成的图像与训练的数据集具有相同的分布。

我们还可以通过在VariationalAutoencoder类中定义一个转换函数来查看潜在空间中的数据:

def transform(self, X):
    """Transform data by mapping it into the latent space."""
    # Note: This maps to mean of distribution, we could alternatively sample from Gaussian distribution
    return self.sess.run(self.z_mean,   feed_dict={self.x: X})

使用转换函数的 MNIST 数据集的潜在表示如下:

更多

VAE 的生成图像取决于潜在空间尺寸。 模糊减少了潜在空间的尺寸,增加了。 分别针对 5 维,10 维和 20 维潜在维度的重构图像如下:

另见

Kingma 和 Welling 的论文是该领域的开创性论文。 他们会经历完整的架构思维过程以及优雅的数学运算。 对于对 VAE 感兴趣的任何人,必须阅读。

另一个有趣的读物是 Carl Doersch 的论文,变分编码器教程

Github 链接包含 VAE 的另一种实现,以及来自 Kingma 和 Welling 论文的图像再现

通过胶囊网络学习击败 MNIST 的最新结果

胶囊网络(或 CapsNets)是一种非常新颖的深度学习网络。 这项技术是在 2017 年 10 月底由 Sara Sabour,Nicholas Frost 和 Geoffrey Hinton 发表的名为《胶囊之间的动态路由》的开创性论文中引入的。 欣顿(Hinton)是深度学习之父之一,因此,整个深度学习社区很高兴看到胶囊技术取得的进步。 确实,CapsNets 已经在 MNIST 分类中击败了最好的 CNN,这真是……令人印象深刻!

那么 CNN 有什么问题? 在 CNN 中,每一层都会以渐进的粒度理解图像。 正如我们在多种秘籍中讨论的那样,第一层将最有可能识别直线或简单的曲线和边缘,而随后的层将开始理解更复杂的形状(例如矩形)和复杂的形式(例如人脸)。

现在,用于 CNN 的一项关键操作是池化。 池化旨在创建位置不变性,通常在每个 CNN 层之后使用它来使任何问题在计算上易于处理。 但是,合并会带来一个严重的问题,因为它迫使我们丢失所有位置数据。 不是很好。 考虑一下脸:它由两只眼睛,一张嘴和一只鼻子组成,重要的是这些部分之间存在空间关系(嘴在鼻子下方,通常在眼睛下方)。 确实,欣顿说:

卷积神经网络中使用的池化操作是一个很大的错误,它运行良好的事实是一场灾难。

从技术上讲,我们不需要位置不变。 相反,我们需要等方差。 等方差是一个奇特的术语,表示我们想了解图像中的旋转或比例变化,并且我们要相应地调整网络。 这样,图像中不同成分的空间定位不会丢失。

那么胶囊网络有什么新功能? 据作者说,我们的大脑有称为胶囊的模块,每个胶囊专门处理特定类型的信息。 尤其是,有些胶囊对于理解位置的概念,尺寸的概念,方向的概念,变形的概念,纹理等非常有用。 除此之外,这组作者还建议我们的大脑具有特别有效的机制,可以将每条信息动态路由到胶囊,这被认为最适合处理特定类型的信息。

因此,CNN 和 CapsNets 之间的主要区别在于,使用 CNN 时,您会不断添加用于创建深度网络的层,而使用 CapsNet 时,您会在另一个内部嵌套神经层。 胶囊是一组神经元,可在网络中引入更多结构。 它产生一个向量来表示图像中实体的存在。 尤其是,欣顿使用活动向量的长度来表示实体存在的概率,并使用其方向来表示实例化参数。 当多个预测结果一致时,更高级别的胶囊就会生效。 对于每个可能的父项,胶囊产生一个额外的预测向量。

现在有了第二项创新:我们将使用跨胶囊的动态路由,并且不再使用池化的原始思想。 较低级别的容器倾向于将其输出发送到较高级别的容器,并且活动向量的标量积很大,而预测来自较低级别的容器。 标量预测向量乘积最大的亲本会增加胶囊键。 所有其他父项都减少了联系。 换句话说,这种想法是,如果较高级别的胶囊同意较低级别的胶囊,则它将要求发送更多该类型的信息。 如果没有协议,它将要求发送更少的协议。 使用协定方法的这种动态路由优于当前的机制(例如最大池),并且根据 Hinton 的说法,路由最终是解析图像的一种方法。 实际上,最大池化忽略了除最大值以外的任何东西,而动态路由根据较低层和较高层之间的协议选择性地传播信息。

第三个差异是引入了新的非线性激活函数。 CapsNet 并未像在 CNN 中那样向每个层添加挤压函数,而是向嵌套的一组层添加了挤压函数。 下图表示了非线性激活函数,它被称为挤压函数(方程式 1):

如欣顿的开创性论文中所示的压缩函数

此外,Hinton 等人表明,经过判别训练的多层胶囊系统在 MNIST 上达到了最先进的表现,并且在识别高度重叠的数字方面比卷积网络要好得多。

论文《胶囊之间的动态路由》向我们展示了简单的 CapsNet 架构:

简单的 CapsNet 架构

该架构很浅,只有两个卷积层和一个完全连接的层。 Conv1 具有 256 个9×9卷积核,步幅为 1,并具有 ReLU 激活函数。 该层的作用是将像素强度转换为局部特征检测器的活动,然后将其用作主胶囊的输入。 PrimaryCapsules 是具有 32 个通道的卷积胶囊层。 每个主胶囊包含 8 个卷积单元,其内核为9×9,步幅为 2。 总计,PrimaryCapsules 具有[32, 6, 6]胶囊输出(每个输出是 8D 向量),并且[6, 6]网格中的每个胶囊彼此共享权重。 最后一层(DigitCaps)每位数字类具有一个 16D 胶囊,这些胶囊中的每个胶囊都接收来自下一层中所有其他胶囊的输入。 路由仅发生在两个连续的胶囊层之间(例如 PrimaryCapsules 和 DigitCaps)。

准备

此秘籍基于这个页面上提供的代码,而该代码又基于这个页面

操作步骤

这是我们如何进行秘籍的方法:

  1. 在 Apache Licence 下从 github 克隆代码:
git clone https://github.com/naturomics/CapsNet-Tensorflow.git
 $ cd CapsNet-Tensorflow
  1. 下载 MNIST 并创建适当的结构:
mkdir -p data/mnist
wget -c -P data/mnist \\
http://yann.lecun.com/exdb/mnist/{train-images-idx3-ubyte.gz,train-labels-idx1-ubyte.gz,t10k-images-idx3-ubyte.gz,t10k-labels-idx1-ubyte.gz}
gunzip data/mnist/*.gz
  1. 开始训练过程:
python main.py
  1. 让我们看看用于定义胶囊的代码。 每个胶囊将 4D 张量作为输入并返回 4D 张量。 可以将胶囊定义为完全连接的网络(DigiCaps)或卷积网络(主胶囊)。 请注意,Primary 是卷积网络的集合,在它们之后应用了非线性压缩函数。 主胶囊将通过动态路由与 DigiCaps 通信:
# capsLayer.py
#
import numpy as np
import tensorflow as tf
from config import cfg
epsilon = 1e-9
class CapsLayer(object):
''' Capsule layer.
Args:
input: A 4-D tensor.
num_outputs: the number of capsule in this layer.
vec_len: integer, the length of the output vector of a capsule.
layer_type: string, one of 'FC' or "CONV", the type of this layer,
fully connected or convolution, for the future expansion capability
with_routing: boolean, this capsule is routing with the
lower-level layer capsule.
Returns:
A 4-D tensor.
'''
def __init__(self, num_outputs, vec_len, with_routing=True, layer_type='FC'):
  self.num_outputs = num_outputs
  self.vec_len = vec_len
  self.with_routing = with_routing
  self.layer_type = layer_type

def __call__(self, input, kernel_size=None, stride=None):
'''
The parameters 'kernel_size' and 'stride' will be used while 'layer_type' equal 'CONV'
'''
  if self.layer_type == 'CONV':
    self.kernel_size = kernel_size
    self.stride = stride

    if not self.with_routing:
    # the PrimaryCaps layer, a convolutional layer
    # input: [batch_size, 20, 20, 256]
      assert input.get_shape() ==  [cfg.batch_size, 20, 20, 256]
      capsules = []
      for i in range(self.vec_len):
        # each capsule i: [batch_size, 6, 6, 32]
        with tf.variable_scope('ConvUnit_' + str(i)):
          caps_i = tf.contrib.layers.conv2d(input,      self.num_outputs,
self.kernel_size, self.stride,
padding="VALID")
          caps_i = tf.reshape(caps_i, shape=(cfg.batch_size, -1, 1, 1))
          capsules.append(caps_i)

      assert capsules[0].get_shape() == [cfg.batch_size, 1152, 1, 1]
# [batch_size, 1152, 8, 1]
      capsules = tf.concat(capsules, axis=2)
      capsules = squash(capsules)
      assert capsules.get_shape() == [cfg.batch_size, 1152, 8, 1]
      return(capsules)

  if self.layer_type == 'FC':
    if self.with_routing:
      # the DigitCaps layer, a fully connected layer
      # Reshape the input into [batch_size, 1152, 1, 8, 1]
      self.input = tf.reshape(input, shape=(cfg.batch_size, -1, 1, input.shape[-2].value, 1))
    with tf.variable_scope('routing'):
      # b_IJ: [1, num_caps_l, num_caps_l_plus_1, 1, 1]
      b_IJ = tf.constant(np.zeros([1, input.shape[1].value,    self.num_outputs, 1, 1], dtype=np.float32))
      capsules = routing(self.input, b_IJ)
      capsules = tf.squeeze(capsules, axis=1)
    return(capsules)
  1. 论文《胶囊之间的动态路由》介绍了路由算法,相关章节中定义了等式 2 和等式 3。该路由算法的目标是将信息从较低层的胶囊传递到较高层的胶囊,并且了解哪里有一致性。 通过简单地使用上层中每个胶囊j的当前输出vⱼ,和胶囊i得出的预测u_hat[j|i]的标量乘积,即可计算出一致性:

以下方法实现了前面图像中过程 1 中描述的步骤。 注意,输入是来自l层中 1,152 个胶囊的 4D 张量。 输出是形状为[batch_size, 1, length(v_j)=16, 1]的张量,表示层l + 1中胶囊j的向量输出vⱼ

def routing(input, b_IJ):
''' The routing algorithm.
Args:
input: A Tensor with [batch_size, num_caps_l=1152, 1, length(u_i)=8, 1]
shape, num_caps_l meaning the number of capsule in the layer l.
Returns:
A Tensor of shape [batch_size, num_caps_l_plus_1, length(v_j)=16, 1]
representing the vector output `v_j` in the layer l+1
Notes:
u_i represents the vector output of capsule i in the layer l, and
v_j the vector output of capsule j in the layer l+1.
'''
  # W: [num_caps_j, num_caps_i, len_u_i, len_v_j]
  W = tf.get_variable('Weight', shape=(1, 1152, 10, 8, 16), dtype=tf.float32,
  initializer=tf.random_normal_initializer(stddev=cfg.stddev))
  # Eq.2, calc u_hat
  # do tiling for input and W before matmul
  # input => [batch_size, 1152, 10, 8, 1]
  # W => [batch_size, 1152, 10, 8, 16]
  input = tf.tile(input, [1, 1, 10, 1, 1])
  W = tf.tile(W, [cfg.batch_size, 1, 1, 1, 1])
  assert input.get_shape() == [cfg.batch_size, 1152, 10, 8, 1]

  # in last 2 dims:
  # [8, 16].T x [8, 1] => [16, 1] => [batch_size, 1152, 10, 16, 1]
  u_hat = tf.matmul(W, input, transpose_a=True)
  assert u_hat.get_shape() == [cfg.batch_size, 1152, 10, 16, 1]

  # line 3,for r iterations do
  for r_iter in range(cfg.iter_routing):
    with tf.variable_scope('iter_' + str(r_iter)):
      # line 4:
      # => [1, 1152, 10, 1, 1]
      c_IJ = tf.nn.softmax(b_IJ, dim=2)
      c_IJ = tf.tile(c_IJ, [cfg.batch_size, 1, 1, 1, 1])
      assert c_IJ.get_shape() == [cfg.batch_size, 1152, 10, 1, 1]
      # line 5:
      # weighting u_hat with c_IJ, element-wise in the last two dims
      # => [batch_size, 1152, 10, 16, 1]
      s_J = tf.multiply(c_IJ, u_hat)
      # then sum in the second dim, resulting in [batch_size, 1, 10, 16, 1]
      s_J = tf.reduce_sum(s_J, axis=1, keep_dims=True)
      assert s_J.get_shape() == [cfg.batch_size, 1, 10, 16, 16
      # line 6:
      # squash using Eq.1,
      v_J = squash(s_J)
      assert v_J.get_shape() == [cfg.batch_size, 1, 10, 16, 1]
      # line 7:
      # reshape & tile v_j from [batch_size ,1, 10, 16, 1] to [batch_size, 10, 1152, 16, 1]
      # then matmul in the last tow dim: [16, 1].T x [16, 1] => [1, 1], reduce mean in the
      # batch_size dim, resulting in [1, 1152, 10, 1, 1]
      v_J_tiled = tf.tile(v_J, [1, 1152, 1, 1, 1])
      u_produce_v = tf.matmul(u_hat, v_J_tiled, transpose_a=True)
      assert u_produce_v.get_shape() == [cfg.batch_size, 1152, 10, 1, 1]
      b_IJ += tf.reduce_sum(u_produce_v, axis=0, keep_dims=True)
  return(v_J)
  1. 现在让我们回顾一下非线性激活压缩函数。 输入是具有[batch_size, num_caps, vec_len, 1]形状的 4D 向量,输出是具有与向量相同形状但被压缩在第三维和第四维中的 4-D 张量。 给定一个向量输入,目标是计算公式 1 中表示的值,如下所示:

def squash(vector):
'''Squashing function corresponding to Eq. 1
Args:
vector: A 5-D tensor with shape [batch_size, 1, num_caps, vec_len, 1],
Returns:
A 5-D tensor with the same shape as vector but squashed in 4rd and 5th dimensions.
'''
  vec_squared_norm = tf.reduce_sum(tf.square(vector), -2, keep_dims=True)
  scalar_factor = vec_squared_norm / (1 + vec_squared_norm) / tf.sqrt(vec_squared_norm + epsilon)
  vec_squashed = scalar_factor * vector # element-wise
return(vec_squashed)
  1. 在前面的步骤中,我们定义了什么是胶囊,胶囊之间的动态路由算法,以及非线性压缩函数。 现在我们可以定义适当的 CapsNet。 构建损失函数以进行训练,并选择了 Adam 优化器。 方法build_arch(...)定义了 CapsNet,如下图所示:

请注意,本文将重构技术描述为一种正则化方法。 从本文:

我们使用额外的重建损失来鼓励数字囊对输入数字的实例化参数进行编码。 在训练过程中,我们会掩盖除正确数字胶囊外的所有活动向量。

然后,我们使用此活动向量进行重构。

数字胶囊的输出被馈送到解码器,该解码器由三个完全连接的层组成,这些层对像素强度进行建模,如图 2 所示。我们将逻辑单元的输出与像素强度之间的平方差之和最小化。 我们将这种重建损失降低了 0.0005,以使其在训练过程中不会控制保证金损失。 如下实现的方法build_arch(..)也用于创建解码器:

#capsNet.py
#
import tensorflow as tf
from config import cfg
from utils import get_batch_data
from capsLayer import CapsLayer
epsilon = 1e-9

class CapsNet(object):
  def __init__(self, is_training=True):
    self.graph = tf.Graph()
    with self.graph.as_default():
      if is_training:
        self.X, self.labels = get_batch_data()
        self.Y = tf.one_hot(self.labels, depth=10, axis=1, dtype=tf.float32)
        self.build_arch()
        self.loss()
        self._summary()

        # t_vars = tf.trainable_variables()
        self.global_step = tf.Variable(0, name='global_step', trainable=False)
        self.optimizer = tf.train.AdamOptimizer()
        self.train_op =    self.optimizer.minimize(self.total_loss, global_step=self.global_step) # var_list=t_vars)

      elif cfg.mask_with_y:
        self.X = tf.placeholder(tf.float32,
          shape=(cfg.batch_size, 28, 28, 1))
        self.Y = tf.placeholder(tf.float32, shape=(cfg.batch_size, 10, 1))
        self.build_arch()
      else:
        self.X = tf.placeholder(tf.float32,
        shape=(cfg.batch_size, 28, 28, 1))
        self.build_arch()
      tf.logging.info('Setting up the main structure')

def build_arch(self):

  with tf.variable_scope('Conv1_layer'):
    # Conv1, [batch_size, 20, 20, 256]
    conv1 = tf.contrib.layers.conv2d(self.X, num_outputs=256,
       kernel_size=9, stride=1, 
       padding='VALID')
    assert conv1.get_shape() == [cfg.batch_size, 20, 20, 256]# Primary Capsules layer, return [batch_size, 1152, 8, 1]

  with tf.variable_scope('PrimaryCaps_layer'):
    primaryCaps = CapsLayer(num_outputs=32, vec_len=8,   with_routing=False, layer_type='CONV')
    caps1 = primaryCaps(conv1, kernel_size=9, stride=2)
    assert caps1.get_shape() == [cfg.batch_size, 1152, 8, 1]

  # DigitCaps layer, return [batch_size, 10, 16, 1]
  with tf.variable_scope('DigitCaps_layer'):
    digitCaps = CapsLayer(num_outputs=10, vec_len=16,   with_routing=True, layer_type='FC')
    self.caps2 = digitCaps(caps1)

  # Decoder structure in Fig. 2
  # 1\. Do masking, how:
  with tf.variable_scope('Masking'):
    # a). calc ||v_c||, then do softmax(||v_c||)
    # [batch_size, 10, 16, 1] => [batch_size, 10, 1, 1]
    self.v_length = tf.sqrt(tf.reduce_sum(tf.square(self.caps2),
axis=2, keep_dims=True) + epsilon)
    self.softmax_v = tf.nn.softmax(self.v_length, dim=1)
    assert self.softmax_v.get_shape() == [cfg.batch_size, 10, 1, 1]
    # b). pick out the index of max softmax val of the 10 caps
    # [batch_size, 10, 1, 1] => [batch_size] (index)
    self.argmax_idx = tf.to_int32(tf.argmax(self.softmax_v, axis=1))
    assert self.argmax_idx.get_shape() == [cfg.batch_size, 1, 1]
    self.argmax_idx = tf.reshape(self.argmax_idx, shape=(cfg.batch_size, )) .  
    # Method 1.
   if not cfg.mask_with_y:
     # c). indexing
     # It's not easy to understand the indexing process with  argmax_idx
     # as we are 3-dim animal
     masked_v = []
     for batch_size in range(cfg.batch_size):
       v = self.caps2[batch_size][self.argmax_idx[batch_size], :]
       masked_v.append(tf.reshape(v, shape=(1, 1, 16, 1)))
       self.masked_v = tf.concat(masked_v, axis=0)
       assert self.masked_v.get_shape() == [cfg.batch_size, 1, 16, 1]

   # Method 2\. masking with true label, default mode
   else:
     self.masked_v = tf.matmul(tf.squeeze(self.caps2), tf.reshape(self.Y, (-1, 10, 1)), transpose_a=True)
     self.v_length = tf.sqrt(tf.reduce_sum(tf.square(self.caps2), axis=2, keep_dims=True) + epsilon)

  # 2\. Reconstruct the MNIST images with 3 FC layers
  # [batch_size, 1, 16, 1] => [batch_size, 16] => [batch_size, 512] 
  with tf.variable_scope('Decoder'):
    vector_j = tf.reshape(self.masked_v, shape=(cfg.batch_size, -1))
    fc1 = tf.contrib.layers.fully_connected(vector_j, num_outputs=512)
    assert fc1.get_shape() == [cfg.batch_size, 512]
    fc2 = tf.contrib.layers.fully_connected(fc1, num_outputs=1024)
    assert fc2.get_shape() == [cfg.batch_size, 1024]
    self.decoded = tf.contrib.layers.fully_connected(fc2, num_outputs=784, activation_fn=tf.sigmoid)
  1. 本文中定义的另一个重要部分是保证金损失函数。 这在下面的论文(等式 4)的摘录引用中进行了说明,并在loss(..)方法中实现,该方法包括三个损失,即边际损失,重建损失和总损失:

def loss(self):
  # 1\. The margin loss
  # [batch_size, 10, 1, 1]
  # max_l = max(0, m_plus-||v_c||)^2
  max_l = tf.square(tf.maximum(0., cfg.m_plus - self.v_length))
  # max_r = max(0, ||v_c||-m_minus)^2
  max_r = tf.square(tf.maximum(0., self.v_length - cfg.m_minus))
  assert max_l.get_shape() == [cfg.batch_size, 10, 1, 1]

  # reshape: [batch_size, 10, 1, 1] => [batch_size, 10]
  max_l = tf.reshape(max_l, shape=(cfg.batch_size, -1))
  max_r = tf.reshape(max_r, shape=(cfg.batch_size, -1))
  # calc T_c: [batch_size, 10]
  T_c = self.Y
  # [batch_size, 10], element-wise multiply
  L_c = T_c * max_l + cfg.lambda_val * (1 - T_c) * max_r

  self.margin_loss = tf.reduce_mean(tf.reduce_sum(L_c, axis=1))

  # 2\. The reconstruction loss
  orgin = tf.reshape(self.X, shape=(cfg.batch_size, -1))
  squared = tf.square(self.decoded - orgin)
  self.reconstruction_err = tf.reduce_mean(squared)

  # 3\. Total loss
  # The paper uses sum of squared error as reconstruction   error, but we
  # have used reduce_mean in `# 2 The reconstruction loss` to calculate
  # mean squared error. In order to keep in line with the paper,the
  # regularization scale should be 0.0005*784=0.392
  self.total_loss = self.margin_loss + cfg.regularization_scale * self.reconstruction_err
  1. 另外,定义a _summary(...)方法来报告损失和准确率可能会很方便:
#Summary
def _summary(self):
  train_summary = []
  train_summary.append(tf.summary.scalar('train/margin_loss', self.margin_loss))train_summary.append(tf.summary.scalar('train/reconstruction_loss', self.reconstruction_err))
  train_summary.append(tf.summary.scalar('train/total_loss', self.total_loss))
  recon_img = tf.reshape(self.decoded, shape=(cfg.batch_size, 28, 28, 1))
  train_summary.append(tf.summary.image('reconstruction_img', recon_img))
  correct_prediction = tf.equal(tf.to_int32(self.labels), self.argmax_idx)
  self.batch_accuracy = tf.reduce_sum(tf.cast(correct_prediction, tf.float32))
  self.test_acc = tf.placeholder_with_default(tf.constant(0.), shape=[])
  test_summary = []
  test_summary.append(tf.summary.scalar('test/accuracy', self.test_acc))
  self.train_summary = tf.summary.merge(train_summary)
  self.test_summary = tf.summary.merge(test_summary)

工作原理

CapsNet 与最先进的深度学习网络有很大的不同。 CapsNet 并没有添加更多的层并使网络更深,而是使用了浅层网络,其中,胶囊层嵌套在其他层内。 每个胶囊专门用于检测图像中的特定实体,并且使用动态路由机制将检测到的实体发送给父层。 使用 CNN,您必须从许多不同角度考虑成千上万张图像,以便从不同角度识别物体。 Hinton 认为,这些层中的冗余将使胶囊网络能够从多个角度和在不同情况下以 CNN 通常使用的较少数据识别对象。 让我们检查一下 tensorboad 所示的网络:

代码中定义并由 tensorboard 显示的 CapsNet 示例

如下图所示,其结果令人印象深刻。 CapsNet 在以前仅在更深层的网络中才能实现的三层网络上具有较低的测试误差(0.25%)。 基线是具有256, 256-128个通道的三个卷积层的标准 CNN。 每个都有5 x 5个内核,步幅为 1。最后一个卷积层后面是两个大小为328, 192 的完全连接的层。 最后一个完全连接的层通过压降连接到具有交叉熵损失的 10 类 softmax 层:

让我们检查保证金损失,重建损失和总损失的减少:

我们还要检查准确率的提高; 经过 500 次迭代,它在 3500 次迭代中分别达到 92% 和 98.46% :

迭代 精度
500 0.922776442308
1000 0.959735576923
1500 0.971955128205
2000 0.978365384615
2500 0.981770833333
3000 0.983473557692
3500 0.984675480769

CapsNet 提高准确率的示例

更多

CapsNets 在 MNIST 上可以很好地工作,但是在理解是否可以在其他数据集(例如 CIFAR)或更通用的图像集合上获得相同的令人印象深刻的结果方面,还有很多研究工作要做。 如果您有兴趣了解更多信息,请查看以下内容:

Google 的 AI 向导在神经网络上带来了新的变化

Google 研究人员可以替代传统神经网络

Keras-CapsNet 是可在这个页面上使用的 Keras 实现。

杰弗里·欣顿(Geoffrey Hinton)讨论了卷积神经网络的问题

十二、分布式 TensorFlow 和云深度学习

在本章中,我们将讨论使用分布式 TensorFlow 和云深度学习。 我们将为您提供一些秘籍:

  • 使用 TensorFlow 和 GPU
  • 使用分布式 TensorFlow:多个 GPU 和一个 CPU
  • 使用分布式 TensorFlow:多台服务器
  • 训练分布式 TensorFlow MNIST 分类器
  • 使用 TensorFlow Serving 和 Docker
  • 使用 Compute Engine 在 Google Cloud(GCP)上运行分布式 TensorFlow
  • 在 Google CloudML 上运行分布式 TensorFlow
  • 在 Microsoft Azure 上运行分布式 TensorFlow
  • 在 Amazon AWS 上运行分布式 TensorFlow

介绍

每个 TensorFlow 计算均以图描述。 这允许在结构和操作的放置方面具有自然的灵活性,可以在计算的分布式节点之间拆分操作。 该图可以拆分为多个子图,这些子图分配给服务器集群中的不同节点。

我强烈建议读者看一看大型分布式深度网络(Jeffrey Dean,Greg S.Corrado,Rajat Monga,Kai Chen,Matthieu Devin,Quoc V.Le,Mark Z.Mao,Marc'Aurelio Ranzato,Andrew Senior ,Paul Tucker,Ke Yang 和 Andrew Y. Ng。 NIPS,2012 年)

本文的一项关键结果是证明可以运行分布式随机梯度下降SGD),其中多个节点在数据碎片上并行工作,并且通过将更新发送到参数服务器,来实现梯度的独立和异步更新。 引用论文摘要:

我们的实验揭示了关于大规模非凸优化的几个令人惊讶的结果。 首先,异步 SGD 很少用于非凸问题,对于训练深度网络非常有效,特别是与 Adagrad 自适应学习率结合使用时。

下图取自论文本身,很好地说明了这一点:

来自参数服务器的分布式梯度下降示例

您应该阅读的另一份文档是白皮书《TensorFlow:异构分布式系统上的大规模机器学习》(MartínAbadi 等人,2015 年 11 月)

考虑其中包含的一些示例,我们可以在下面的图片的左侧看到 TensorFlow 代码的片段,然后将其表示为右侧的图形:

TensorFlow 图的示例

通过进行本地计算并在需要时透明地将远程通信节点添加到图,可以在多个节点之间划分图。 下图中对此作了很好的解释,该图仍取自前面提到的论文:

摘自以下内容的分布式 TensorFlow 图计算示例

可以通过集中方式(下图的左侧)或分布式方式(右侧)来计算梯度下降和所有主要的优化器算法。 后者涉及一个主进程,该主进程与调配 GPU 和 CPU 的多个工作器进行对话:

摘自以下内容的单机和分布式系统结构示例和分布式 TensorFlow 图计算示例

分布式计算既可以是同步的(所有工作程序都在同时更新分片数据上的梯度),也可以是异步的(更新没有同时发生)。 后者通常可以实现更高的可伸缩性,并且在收敛到最佳解决方案方面,较大的图计算仍然可以很好地工作。 同样,这些图片均来自 TensorFlow 白皮书,如果您想了解更多信息,我强烈建议有兴趣的读者阅读本文:

同步和异步数据并行训练的示例

使用 TensorFlow 和 GPU

在本秘籍中,我们将学习如何在 GPU 上使用 TensorFlow:执行的操作是在 CPU 或 GPU 上的简单矩阵乘法。

准备

第一步是安装支持 GPU 的 TensorFlow 版本。 正式的 TensorFlow 安装说明是您的起点。 请记住,您需要一个通过 CUDA 或 CuDNN 支持 GPU 的环境。

操作步骤

我们按以下步骤进行:

  1. 首先导入一些模块
import sys
import numpy as np
import tensorflow as tf
from datetime import datetime
  1. 从命令行获取要使用的处理单元的类型("gpu""cpu"
device_name = sys.argv[1] # Choose device from cmd line. Options: gpu or cpu
shape = (int(sys.argv[2]), int(sys.argv[2]))
if device_name == "gpu":
  device_name = "/gpu:0"
else:
  device_name = "/cpu:0"
  1. 在 GPU 或 CPU 上执行矩阵乘法。 按键说明与tf.device(device_name)一起使用。 它创建一个新的上下文管理器,告诉 TensorFlow 在 GPU 或 CPU 上执行这些操作
with tf.device(device_name):
  random_matrix = tf.random_uniform(shape=shape, minval=0, maxval=1)
  dot_operation = tf.matmul(random_matrix, tf.transpose(random_matrix))
  sum_operation = tf.reduce_sum(dot_operation)

startTime = datetime.now()
with tf.Session(config=tf.ConfigProto(log_device_placement=True)) as session:
  result = session.run(sum_operation)
  print(result)

4.打印一些调试时间,只是为了验证 CPU 和 GPU 有什么区别

print("Shape:", shape, "Device:", device_name)
print("Time taken:", datetime.now() - startTime)

工作原理

本秘籍说明了如何将 TensorFlow 计算分配给 CPU 或 GPU。 该代码非常简单,它将用作下一个秘籍的基础。

使用分布式 TensorFlow:多个 GPU 和一个 CPU

我们将展示一个数据并行化的示例,其中数据被拆分到多个 GPU 中

准备

此秘籍的灵感来自尼尔·特内霍尔茨(Neil Tenenholtz)撰写的一篇不错的博客文章,该文章可在线获取

操作步骤

我们按以下步骤进行:

  1. 考虑这段在单个 GPU 上运行矩阵乘法的代码。
# single GPU (baseline)
import tensorflow as tf
# place the initial data on the cpu
with tf.device('/cpu:0'):
  input_data = tf.Variable([[1., 2., 3.],
    [4., 5., 6.],
    [7., 8., 9.],
    [10., 11., 12.]])
b = tf.Variable([[1.], [1.], [2.]])

# compute the result on the 0th gpu
with tf.device('/gpu:0'):
  output = tf.matmul(input_data, b)

# create a session and run
with tf.Session() as sess:
  sess.run(tf.global_variables_initializer())
  print sess.run(output)
  1. 如图 2 所示,在 2 个不同的 GPU 之间使用代码复制对代码进行分区。 请注意,CPU 充当主节点来分发图并收集最终结果。
# in-graph replication
import tensorflow as tf
num_gpus = 2
# place the initial data on the cpu
with tf.device('/cpu:0'):
  input_data = tf.Variable([[1., 2., 3.],
   [4., 5., 6.],
   [7., 8., 9.],
   [10., 11., 12.]])
   b = tf.Variable([[1.], [1.], [2.]])

# split the data into chunks for each gpu
inputs = tf.split(input_data, num_gpus)
outputs = []

# loop over available gpus and pass input data
for i in range(num_gpus):
  with tf.device('/gpu:'+str(i)):
    outputs.append(tf.matmul(inputs[i], b))

# merge the results of the devices
with tf.device('/cpu:0'):
  output = tf.concat(outputs, axis=0)

# create a session and run
with tf.Session() as sess:
  sess.run(tf.global_variables_initializer())
  print sess.run(output)

工作原理

这是一个非常简单的方法,其中通过将 CPU 用作主对象并将其分配到两个用作分布式工作程序的 GPU,将图分为两部分。 计算结果被收集回 CPU。

使用分布式 TensorFlow:多台服务器

在本秘籍中,我们将学习如何在多个服务器之间分配 TensorFlow 计算。 关键假设是工作程序和参数服务器的代码都相同。 因此,每个计算节点的角色都传递给命令行参数。

准备

同样,此秘籍的灵感来自尼尔·特纳霍尔茨(Neil Tenenholtz)撰写的一篇不错的博客文章,该文章可在线获取

操作步骤

我们按以下步骤进行:

  1. 考虑这段代码,在此我们指定集群架构,其中一个主服务器运行在192.168.1.1:1111上,两个工作服务器分别运行在192.168.1.2:1111192.168.1.3:1111上。
import sys
import tensorflow as tf

# specify the cluster's architecture
cluster = tf.train.ClusterSpec({'ps': ['192.168.1.1:1111'],
     'worker': ['192.168.1.2:1111',
     '192.168.1.3:1111']
})
  1. 请注意,代码是在多台机器上复制的,因此了解当前执行节点的作用很重要。 我们从命令行获得此信息。 机器可以是工作程序,也可以是参数服务器(ps)。
# parse command-line to specify machine
job_type = sys.argv[1] # job type: "worker" or "ps"
task_idx = sys.argv[2] # index job in the worker or ps list
# as defined in the ClusterSpec
  1. 在给定群集的情况下运行训练服务器,我们为每个计算赋予一个角色(工作者或ps)和一个 ID。
# create TensorFlow Server. This is how the machines communicate.
server = tf.train.Server(cluster, job_name=job_type, task_index=task_idx)
  1. 根据特定计算节点的角色,计算是不同的:
    • 如果角色是参数服务器,则条件是要加入服务器。 请注意,在这种情况下,没有代码要执行,因为工作进程将不断推送更新,并且参数服务器唯一要做的就是等待。
    • 否则,工作程序代码将在集群中的特定设备上执行。 这部分代码类似于在一台机器上执行的代码,在该机器上我们首先构建模型,然后在本地对其进行训练。 请注意,所有工作分配和更新结果的收集都是由 Tensoflow 透明完成的。 请注意,TensorFlow 提供了方便的tf.train.replica_device_setter,可自动将操作分配给设备。
# parameter server is updated by remote clients.
# will not proceed beyond this if statement.
if job_type == 'ps':
  server.join()
else:
  # workers only
  with tf.device(tf.train.replica_device_setter(
    worker_device='/job:worker/task:'+task_idx,
    cluster=cluster)):
# build your model here as if you only were using a single machine

with tf.Session(server.target):
  # train your model here

工作原理

在本秘籍中,我们已经看到了如何创建具有多个计算节点的集群。 节点既可以扮演参数服务器的角色,也可以扮演工作者的角色。

在这两种情况下,根据从命令行收集的参数,执行的代码相同,但是代码的执行不同。 参数服务器仅需要等待,直到工作器发送更新。 请注意,tf.train.replica_device_setter(..)的作用是将操作自动分配给可用设备,而tf.train.ClusterSpec(..)则用于集群设置。

更多

可以在这个页面在线获取针对 MNIST 的分布式训练的示例。

此外,请注意,出于效率考虑,您可以决定拥有多个参数服务器。 使用参数,服务器可以提供更好的网络利用率,并且可以将模型扩展到更多并行计算机。 可以分配多个参数服务器。 有兴趣的读者可以看看这里

训练分布式 TensorFlow MNIST 分类器

此秘籍用于以分布式方式训练完​​整的 MNIST 分类器。 该秘籍的灵感来自这个页面中的博客文章,此处提供了在 TensorFlow 1.2 上运行的代码

准备

此秘籍基于上一个秘籍。 因此,按顺序阅读它们可能会很方便。

操作步骤

我们按以下步骤进行:

  1. 导入一些标准模块并定义运行计算的 TensorFlow 集群。 然后为特定任务启动服务器
import tensorflow as tf
import sys
import time
# cluster specification
parameter_servers = ["pc-01:2222"]
workers = [ "pc-02:2222",
"pc-03:2222",
"pc-04:2222"]
cluster = tf.train.ClusterSpec({"ps":parameter_servers, "worker":workers})
# input flags
tf.app.flags.DEFINE_string("job_name", "", "Either 'ps' or 'worker'")
tf.app.flags.DEFINE_integer("task_index", 0, "Index of task within the job")FLAGS = tf.app.flags.FLAGS
# start a server for a specific task
server = tf.train.Server(
  cluster,
  job_name=FLAGS.job_name,
  task_index=FLAGS.task_index)
  1. 读取 MNIST 数据并定义用于训练的超参数
# config
batch_size = 100
learning_rate = 0.0005
training_epochs = 20
logs_path = "/tmp/mnist/1"
# load mnist data set
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
  1. 检查您的角色是参数服务器还是工作器。 如果工作器随后定义了一个简单的密集神经网络,则定义一个优化器以及用于评估分类器的度量(例如准确率)。
if FLAGS.job_name == "ps":
  server.join()
elif FLAGS.job_name == "worker":
# Between-graph replication
with tf.device(tf.train.replica_device_setter(
  worker_device="/job:worker/task:%d" % FLAGS.task_index,
  cluster=cluster)):
# count the number of updates
  global_step = tf.get_variable( 'global_step', [], initializer =      tf.constant_initializer(0),
trainable = False)

  # input images
  with tf.name_scope('input'):
    # None -> batch size can be any size, 784 -> flattened mnist image
    x = tf.placeholder(tf.float32, shape=[None, 784], name="x-input")
    # target 10 output classes
    y_ = tf.placeholder(tf.float32, shape=[None, 10], name="y-input")

  # model parameters will change during training so we use tf.Variable
  tf.set_random_seed(1)
  with tf.name_scope("weights"):
    W1 = tf.Variable(tf.random_normal([784, 100]))
    W2 = tf.Variable(tf.random_normal([100, 10]))

  # bias
  with tf.name_scope("biases"):
    b1 = tf.Variable(tf.zeros([100]))
    b2 = tf.Variable(tf.zeros([10]))

  # implement model
  with tf.name_scope("softmax"):
    # y is our prediction
    z2 = tf.add(tf.matmul(x,W1),b1)
    a2 = tf.nn.sigmoid(z2)
    z3 = tf.add(tf.matmul(a2,W2),b2)
    y = tf.nn.softmax(z3)

  # specify cost function
  with tf.name_scope('cross_entropy'):
    # this is our cost
    cross_entropy = tf.reduce_mean(
-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1]))

  # specify optimizer
  with tf.name_scope('train'):
    # optimizer is an "operation" which we can execute in a session
    grad_op = tf.train.GradientDescentOptimizer(learning_rate)
    train_op = grad_op.minimize(cross_entropy, global_step=global_step)

  with tf.name_scope('Accuracy'):
    # accuracy
    correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

  # create a summary for our cost and accuracy
  tf.summary.scalar("cost", cross_entropy)
  tf.summary.scalar("accuracy", accuracy)
  # merge all summaries into a single "operation" which we can execute in a session
  summary_op = tf.summary.merge_all()
  init_op = tf.global_variables_initializer()
  print("Variables initialized ...")
  1. 启动一个主管,该主管充当分布式设置的首席计算机。 首要的是负责管理集群其余所有部分的工作机。 会话由负责人维护,关键指令为sv = tf.train.Supervisor(is_chief=(FLAGS.task_index == 0))。 同样,使用prepare_or_wait_for_session(server.target),主管将等待模型准备就绪可以使用。 请注意,每个工作器将处理不同的批量模型,然后最终模型可用于主管。
sv = tf.train.Supervisor(is_chief=(FLAGS.task_index == 0),
begin_time = time.time()
frequency = 100
with sv.prepare_or_wait_for_session(server.target) as sess:
  # create log writer object (this will log on every machine)
  writer = tf.summary.FileWriter(logs_path, graph=tf.get_default_graph())
  # perform training cycles
  start_time = time.time()
  for epoch in range(training_epochs):
    # number of batches in one epoch
    batch_count = int(mnist.train.num_examples/batch_size)
    count = 0
    for i in range(batch_count):
      batch_x, batch_y = mnist.train.next_batch(batch_size)
      # perform the operations we defined earlier on batch
      _, cost, summary, step = sess.run(
     [train_op, cross_entropy, summary_op, global_step],
     feed_dict={x: batch_x, y_: batch_y})
     writer.add_summary(summary, step)
     count += 1
     if count % frequency == 0 or i+1 == batch_count:
       elapsed_time = time.time() - start_time
       start_time = time.time()
       print("Step: %d," % (step+1),
        " Epoch: %2d," % (epoch+1), " Batch: %3d of %3d," % (i+1, batch_count),
       " Cost: %.4f," % cost,
       "AvgTime:%3.2fms" % float(elapsed_time*1000/frequency))
       count = 0
    print("Test-Accuracy: %2.2f" % sess.run(accuracy, feed_dict={x:  mnist.test.images, y_: mnist.test.labels}))
    print("Total Time: %3.2fs" % float(time.time() - begin_time))
    print("Final Cost: %.4f" % cost)
  sv.stop()
  print("done")

工作原理

本秘籍描述了分布式 MNIST 分类器的示例。 在此示例中,TensorFlow 允许我们定义三台机器的集群。 一台充当参数服务器,另外两台机器用作工作器,负责处理不同批次的训练数据。

使用 TensorFlow Serving 和 Docker

在本秘籍中,我们将展示如何为 TensorFlow Serving 运行 Docker 容器,这是一组组件以导出经过训练的 TensorFlow 模型并使用标准tensorflow_model_server为其提供服务。 TensorFlow Serving 服务器发现新导出的模型并运行 gRPC 服务来为其服务。

准备

我们将使用 Docker,并假设您熟悉该系统。 如果没有,请确保查看并安装。 我们要做的是构建 TF 服务版本。

操作步骤

这是我们进行秘籍的方法:

  1. 这里下载Dockerfile.devel
  2. 通过运行以下来构建容器
docker build --pull -t $USER/tensorflow-serving-devel -f Dockerfile.devel
  1. 运行容器
docker run -it $USER/tensorflow-serving-devel
  1. 克隆 TensorFlow 服务,配置和测试服务器
git clone --recurse-submodules https://github.com/tensorflow/serving
cd serving/tensorflow
./configure
cd ..
bazel test tensorflow_serving/...
  1. 现在,让我们看一个保存模型的示例,以便服务器可以保存它。 此步骤的灵感来自用于构建 MNIST 训练器和服务模型的示例。 第一步是将构建器导入为saved_model_builder。 然后,大部分工作由SavedModelBuilder()完成,该工作将已训练模型的快照保存到可靠的存储中。 请注意,此处export_path/tmp/mnist_model/
from tensorflow.python.saved_model import builder as saved_model_builder
...
export_path_base = sys.argv[-1]
export_path = os.path.join(
  compat.as_bytes(export_path_base),
  compat.as_bytes(str(FLAGS.model_version)))
print 'Exporting trained model to', export_path
builder = saved_model_builder.SavedModelBuilder(export_path)
builder.add_meta_graph_and_variables(
  sess, [tag_constants.SERVING],
  signature_def_map={
    'predict_images':
    prediction_signature,
    signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY:
    classification_signature,
  },
  legacy_init_op=legacy_init_op)
builder.save()
  1. 然后可以通过简单的命令为模型提供服务
tensorflow_model_server --port=9000 --model_name=mnist --model_base_path=/tmp/mnist_model/

工作原理

Google 在 2016 年 2 月发布了 TensorFlow Serving,这是一种针对机器学习模型的高性能服务系统,专为生产环境而设计。 截至 2017 年 8 月,谷歌内部有 800 多个使用 TensorFlow Serving 的项目投入生产。

更多

TensoFlow Serving 是一款非常通用的软件,在本秘籍中,我们只是初步探究了潜在用途。 如果您有兴趣进一步了解高级功能。 例如大批量运行或动态加载模型,则应查看这个页面

使用 Compute Engine 在 Google Cloud(GCP)上运行分布式 TensorFlow

在本秘籍中,我们将学习如何在 Google Cloud(GCP)上使用 Google Tensorflow。 我们将要审查的示例是经典 MNIST。

准备

看看 GCP 在 GoogleCloud 中的工作方式会很好。 请注意,GCP 提供 300 美元的免费赠金,以开始使用任何 GCP 产品。 此外,在免费试用期间和之后,某些产品针对符合条件的客户可以免费使用。 (优惠如有更改,请参见这个页面

操作步骤

这是我们进行秘籍的方法:

  1. 通过网络控制台创建一个新的 Google Cloud 项目。

单击创建项目时,将显示以下屏幕:

  1. 通过选择控制台左侧栏上的相关语音,为该项目启用计费。 然后为该项目启用 Compute Engine 和 Cloud Machine Learning API:

  1. 登录到网络 cloudshell

  1. 从控制台运行以下命令,以配置将在其中执行计算的区域,下载示例代码以及创建用于运行代码的 VM。 最后连接到机器:
gcloud config set compute/zone us-east1-c
gcloud config set project [YOUR_PROJECT_ID]
git clone https://github.com/GoogleCloudPlatform/cloudml-dist-mnist-example
cd cloudml-dist-mnist-example
gcloud compute instances create template-instance \
 --image-project ubuntu-os-cloud \
 --image-family ubuntu-1604-lts \
 --boot-disk-size 10GB \
 --machine-type n1-standard-1
gcloud compute ssh template-instance
  1. 现在,登录到机器后,我们需要通过使用这些命令安装 PIP 和 TensorFlow 来设置环境。
sudo apt-get update
sudo apt-get -y upgrade \
 && sudo apt-get install -y python-pip python-dev
sudo pip install tensorflow
sudo pip install --upgrade tensorflow
  1. 我们将有多个工作器对 MNIST 数据进行操作,因此最好的方法是创建一个在所有工作器之间共享的存储桶,并在该存储桶中复制 MNIST 数据
BUCKET="mnist-$RANDOM-$RANDOM"
gsutil mb -c regional -l us-east1 gs://${BUCKET}
sudo ./scripts/create_records.py
gsutil cp /tmp/data/train.tfrecords gs://${BUCKET}/data/
gsutil cp /tmp/data/test.tfrecords gs://${BUCKET}/data/
  1. 现在,我们将创建多个工作程序(worker-0worker-1),它们是初始模板实例计算机的克隆。 我们不希望计算机在关闭时删除磁盘,因此这就是我们拥有第一个命令的原因。
gcloud compute instances set-disk-auto-delete template-instance \
 --disk template-instance --no-auto-delete
gcloud compute instances delete template-instance
gcloud compute images create template-image \
 --source-disk template-instance
gcloud compute instances create \
 master-0 worker-0 worker-1 ps-0 \
 --image template-image \
 --machine-type n1-standard-4 \
 --scopes=default,storage-rw
  1. 最后一步是运行用于分布式训练的计算。
./scripts/start-training.sh gs://${BUCKET}

工作原理

演示脚本将代码移动到每个 VM 并开始分布式计算。 这两个工作程序在公共存储桶上共享的相同 MNIST 数据上并行运行。 计算结束后,脚本将打印训练后的模型的位置。

更多

如果我们不想管理 TensorFlow,那么我们可以决定使用 Google 代表您运行的托管版本。 这是在下一个秘籍中描述的 CloudML 服务。 此外,如果您决定不将 GPU 与 CloudML 一起使用,那么此 URL 是一个不错的起点

在 Google CloudML 上运行分布式 TensorFlow

CloudML 是 Google 运行的 Tensorflow 的托管版本。 无需自己运行 TF,您只需使用 CloudML 即可,而无需考虑与基础架构和可伸缩性相关的所有问题。

准备

在这里,我们假设您已经创建了一个 Cloud Platform Project,为您的项目启用计费,并启用了 Google Compute Engine 和 Cloud Machine Learning API。 这些步骤类似于先前秘籍中描述的步骤。 此秘籍的灵感来自这个页面中提供的 MNIST 训练代码。

操作步骤

我们继续在 Google CloudML 上运行分布式 TensorFlow:

  1. 第一步只是下载示例代码
git clonehttps://github.com/GoogleCloudPlatform/cloudml-dist-mnist-example
cd cloudml-dist-mnist-example
  1. 然后,我们下载数据并将其保存在 GCP 存储桶中
PROJECT_ID=$(gcloud config list project --format "value(core.project)")
BUCKET="${PROJECT_ID}-ml"
gsutil mb -c regional -l us-central1 gs://${BUCKET}
./scripts/create_records.py
gsutil cp /tmp/data/train.tfrecords gs://${BUCKET}/data/
gsutil cp /tmp/data/test.tfrecords gs://${BUCKET}/data/
  1. 提交训练工作非常简单:我们可以使用 CloudML 引擎简单地调用训练步骤。 在此示例中,训练人员代码在us-central1区域中运行 1000 次迭代。 输入数据是从存储桶中提取的,而输出桶将被提交到其他存储桶中。
JOB_NAME="job_$(date +%Y%m%d_%H%M%S)"
gcloud ml-engine jobs submit training ${JOB_NAME} \
 --package-path trainer \
 --module-name trainer.task \
 --staging-bucket gs://${BUCKET} \
 --job-dir gs://${BUCKET}/${JOB_NAME} \
 --runtime-version 1.2 \
 --region us-central1 \
 --config config/config.yaml \
 -- \
 --data_dir gs://${BUCKET}/data \
 --output_dir gs://${BUCKET}/${JOB_NAME} \
 --train_steps 10000
  1. 如果需要,您可以通过访问这个页面中的 CloudML 控制台来控制训练过程。
  2. 训练结束后,就可以直接从 CloudML 服务模型了
MODEL_NAME=MNIST
gcloud ml-engine models create --regions us-central1 ${MODEL_NAME}
VERSION_NAME=v1
ORIGIN=$(gsutil ls gs://${BUCKET}/${JOB_NAME}/export/Servo | tail -1)
gcloud ml-engine versions create \
 --origin ${ORIGIN} \
 --model ${MODEL_NAME} \
${VERSION_NAME}
gcloud ml-engine versions set-default --model ${MODEL_NAME} ${VERSION_NAME}
  1. 在线提供模型后,便可以访问服务器并做出预测。 通过使用脚本make_request.py创建request.json,该脚本从 MNIST 读取数据,执行一次热编码,然后使用格式正确的 json 模式编写功能。
gcloud ml-engine predict --model ${MODEL_NAME} --json-instances request.json

工作原理

CloudML 是使用 Google 运行的托管版本的 TensorFlow 的便捷解决方案。 除了直接照顾基础设施和运营,还可以直接专注于开发机器学习模型。

更多

CloudML 的一项非常酷的功能是能够通过并行运行多个试验来自动调整模型中包含的超参数的能力。 这为您提供了超参数的优化值,从而最大化了模型的预测精度。 如果您有兴趣了解更多信息,那么看看这里

在 Microsoft Azure 上运行分布式 TensorFlow

Microsoft Azure 提供了一项名为 Batch AI 的服务,该服务使我们能够在 Azure 虚拟机的群集上运行机器学习模型。

准备

第一步,我们需要一个 Azure 帐户:如果您还没有 Azure 帐户,则可以在此处免费创建一个帐户 。 Azure 向新用户提供为期 30 天的 200 美元信用额度。 此秘籍将遵循 Microsoft Azure 提供的示例,以使用 TensorFlow 分布式在两个 GPU 上运行 MNIST,相关代码可在 Github 上获得

操作步骤

我们按以下步骤进行:

  1. 第一步是安装 Azure CLI。 可在此处获得在不同 OS 平台上安装 Azure CLI 的详细信息。
  2. 在创建群集之前,您需要使用命令az login登录 Azure。 它将生成一个代码,并为您提供一个网站地址,将要求您在其中验证您的凭据,一旦网站上的所有步骤都结束,将要求您关闭并验证您的 az 凭据。
  3. 配置默认位置,创建和配置资源组。
az group create --name myResourceGroup --location eastus
az configure --defaults group=myResourceGroup
az configure --defaults location=eastus
  1. 接下来,我们将需要使用az命令创建存储,并根据您的操作系统设置环境变量,有关环境变量及其值的详细信息,请参见这里

  2. 下载并提取预处理的 MNIST 数据库

wget "https://batchaisamples.blob.core.windows.net/samples/mnist_dataset_original.zip?st=2017-09-29T18%3A29%3A00Z&se=2099-12-31T08%3A00%3A00Z&sp=rl&sv=2016-05-31&sr=b&sig=Qc1RA3zsXIP4oeioXutkL1PXIrHJO0pHJlppS2rID3I%3D" -O mnist_dataset_original.zip
unzip mnist_dataset_original.zip
  1. 下载mnist_replica
wget "https://raw.githubusercontent.com/Azure/BatchAI/master/recipes/TensorFlow/TensorFlow-GPU-Distributed/mnist_replica.py?token=AcZzrcpJGDHCUzsCyjlWiKVNfBuDdkqwks5Z4dPrwA%3D%3D" -O mnist_replica.py
  1. 接下来,创建一个 Azure 文件共享,在其中上传下载的 MNIST 数据集和mnist_replica.py文件。
az storage share create --name batchaisample
az storage directory create --share-name batchaisample --name mnist_dataset
az storage file upload --share-name batchaisample --source t10k-images-idx3-ubyte.gz --path mnist_dataset
az storage file upload --share-name batchaisample --source t10k-labels-idx1-ubyte.gz --path mnist_dataset
az storage file upload --share-name batchaisample --source train-images-idx3-ubyte.gz --path mnist_dataset
az storage file upload --share-name batchaisample --source train-labels-idx1-ubyte.gz --path mnist_dataset
az storage directory create --share-name batchaisample --name tensorflow_samples
az storage file upload --share-name batchaisample --source mnist_replica.py --path tensorflow_samples
  1. 现在,我们为该秘籍创建一个集群,该集群由具有 standard_NC6 大小的两个 GPU 节点组成,具有标准的 Ubuntu LTS 或 Ubuntu DVSM。 可以使用 Azure CLI 命令创建群集:

对于 Linux:

az batchai cluster create -n nc6 -i UbuntuDSVM -s Standard_NC6 --min 2 --max 2 --afs-name batchaisample --afs-mount-path external -u $USER -k ~/.ssh/id_rsa.pub

对于 Windows:

az batchai cluster create -n nc6 -i UbuntuDSVM -s Standard_NC6 --min 2 --max 2 --afs-name batchaisample --afs-mount-path external -u <user_name> -p <password>
  1. 下一步是在job.json文件中创建作业创建参数:
{
  "properties": {
    "nodeCount": 2,
    "tensorFlowSettings": {
      "parameterServerCount": 1,
      "workerCount": 2,
      "pythonScriptFilePath": "$AZ_BATCHAI_INPUT_SCRIPT/mnist_replica.py",
      "masterCommandLineArgs": "--job_name=worker --num_gpus=1 --ps_hosts=$AZ_BATCHAI_PS_HOSTS --worker_hosts=$AZ_BATCHAI_WORKER_HOSTS --task_index=$AZ_BATCHAI_TASK_INDEX --data_dir=$AZ_BATCHAI_INPUT_DATASET --output_dir=$AZ_BATCHAI_OUTPUT_MODEL",
      "workerCommandLineArgs": "--job_name=worker --num_gpus=1 --ps_hosts=$AZ_BATCHAI_PS_HOSTS --worker_hosts=$AZ_BATCHAI_WORKER_HOSTS --task_index=$AZ_BATCHAI_TASK_INDEX --data_dir=$AZ_BATCHAI_INPUT_DATASET --output_dir=$AZ_BATCHAI_OUTPUT_MODEL",
      "parameterServerCommandLineArgs": "--job_name=ps --num_gpus=0 --ps_hosts=$AZ_BATCHAI_PS_HOSTS --worker_hosts=$AZ_BATCHAI_WORKER_HOSTS --task_index=$AZ_BATCHAI_TASK_INDEX --data_dir=$AZ_BATCHAI_INPUT_DATASET --output_dir=$AZ_BATCHAI_OUTPUT_MODEL"
},
     "stdOutErrPathPrefix": "$AZ_BATCHAI_MOUNT_ROOT/external",
     "inputDirectories": [{
     "id": "DATASET",
     "path": "$AZ_BATCHAI_MOUNT_ROOT/external/mnist_dataset"
    }, {
    "id": "SCRIPT",
    "path": "$AZ_BATCHAI_MOUNT_ROOT/external/tensorflow_samples"
    }],
     "outputDirectories": [{
       "id": "MODEL",
       "pathPrefix": "$AZ_BATCHAI_MOUNT_ROOT/external",
     "pathSuffix": "Models"
   }],
    "containerSettings": {
      "imageSourceRegistry": {
      "image": "tensorflow/tensorflow:1.1.0-gpu"
    }
  }
 }
}
  1. 最后,使用以下命令创建 Batch AI 作业:
az batchai job create -n distibuted_tensorflow --cluster-name nc6 -c job.json

工作原理

Batch AI 自行管理资源,您只需要指定作业,输入的位置以及存储输出的位置即可。 如果在执行作业期间想要查看结果,则可以使用以下命令:

az batchai job stream-file --job-name myjob --output-directory-id stdouterr --name stderr.txt

作业结束后,您可以使用az batchai job deleteaz batchai cluster delete命令删除作业和群集。

更多

上面我们学习了如何使用 Azure 命令行工具将 Microsoft Azure Batch AI 用于分布式 TensorFlow。 我们也可以使用 Jupyter 笔记本做同样的事情。 这将涉及设置 Azure Active Directory 并进行新的 App 注册。 可以在以下链接上获得详细信息

Azure BatchAI 也可以与其他 AI 深度学习库一起使用,我们建议您仔细阅读 BatchAI Github 以获取更多详细信息

在 Amazon AWS 上运行分布式 TensorFlow

Amazon AWS 提供了配备 NVIDIA K8 GPU 的 P2.x 机器。 为了能够使用,第一步再次涉及创建一个 Amazon AWS 账户。 如果还没有,可以使用以下链接创建它。 登录帐户后,仪表板将如下所示:

您可以看到 Amazon AWS 提供了许多服务,但是在这里,我们担心使用 Amazon AWS 进行深度学习。

GPU 仅在 P2 实例创建中可用,并且默认情况下不可用,要获得此服务,您必须通过 AWS 支持筹集更多资源,一旦获得支持,该支持就会显示在右上角, 您将看到一个按钮,创建案例,选择按钮并进行以下选择:

您可以选择提供 p2.xlarge 实例的任何区域。 新的限制值决定了您可以使用的实例的最大数量,请谨慎选择数量,因为该服务不是免费的,并且每小时的费用约为 1 美元。 AWS 大约需要 24 小时才能响应请求。

准备

现在您已经可以使用 AWS 账户和 p2.xlarge 实例,现在就可以从 EC2 仪表板创建自己的实例了。 第一步是选择机器映像,到目前为止,Amazon 提供了预装有深度学习库的特殊机器映像,您可以选择 Ubuntu 或 Linux 版本。 接下来,选择 GPU 以计算实例类型。

您可以使用默认参数查看并启动实例,也可以配置设置,选择存储并配置安全组。 配置安全组很重要,默认情况下已设置 SSH 安全组,但是如果要使用 Jupyter 笔记本,则需要为端口 8888 添加自定义安全组,可以选择从中登录的源。 对于实例,有三种可能性:“自定义”,“随处可见”或“我的 IP”:

最后,在启动实例时,将要求您创建一个密钥对,以允许您登录到特定实例,您将需要创建一个密钥对并下载相应的.pem文件,您将使用文件以便后续记录。

操作步骤

  1. 第一步是连接到实例,您可以使用命令行通过 SSH 或浏览器来实现。 我们使用 CLI。

  2. 要进行连接,首先更改.pem文件的模式。

chmod 400 <security-keypair-filename>.pem
  1. 接下来,使用以下命令对实例进行 SSH。 选择连接时,确切的地址将在仪表板上可用:
ssh -i " <security-keypair-filename>.pem" ubuntu@ec2-XXXXXXXXXXXXX.compute-1.amazonaws.com
  1. 我们选择的机器实例已经包含了所有深度学习库,包括 TensorFlow,因此我们不需要安装任何东西:

  1. 每个文件夹都包含一个自述文件,该文件描述了如何使用相应的库:

工作原理

您可以运行在我们创建的实例上学到的代码。 工作结束后,不要忘记退出,并从仪表板停止实例。 有关价格和使用情况的更多详细信息,请参见这里

更多

AWS 市场上提供了带有预配置库和 API 的大量 Docker 映像和机器映像。 要启动 jupyter 笔记本,请在命令行中使用<jupyter notebook --ip=0.0.0.0 --no-browser>。 这将导致输出,如下所示:

首次连接以使用令牌登录时,将此 URL 复制/粘贴到浏览器中:

http://0.0.0.0:8888/?token=3156e...

您将 URL 复制并粘贴到浏览器中,就可以开始使用了。

此外,可以通过查看 AWS CloudFormation 简化整个过程。 它使用模板创建和配置 Amazon Web Services 资源。 这样可以简化设置分布式深度学习集群的过程。 有兴趣的读者可以看看这里

十三、AutoML 和学习如何学习(元学习)

深度学习的成功极大地促进了特征工程的工作。 确实,传统的机器学习很大程度上取决于对正确的特征集的选择,而且与选择特定的学习算法相比,这一步骤通常更为重要。 深度学习改变了这种情况。 创建正确的模型仍然非常重要,但是现今的网络对特定特征集的选择已不那么敏感,并且能够自动选择真正重要的特征。

取而代之的是,深度学习的引入使人们更加关注正确的神经网络架构的选择。 这意味着研究人员的兴趣正逐渐从特征工程转向网络工程。 AutoML元学习)是一个新兴的研究主题,旨在针对给定的学习任务自动选择最有效的神经网络。 换句话说,AutoML 代表学习如何有效学习的一组方法。 考虑例如机器翻译,图像识别或游戏的任务。 通常,模型是由工程师,数据科学家和领域专家组成的团队手动设计的。 如果您认为一个典型的 10 层网络可以具有约10^10个候选网络,那么您就会知道该过程可能很昂贵,容易出错并且最终不是最佳选择。

使用循环网络和强化学习的元学习

解决此问题的关键思想是拥有一个控制器网络,该控制器网络会在给定特定网络输入的情况下,以概率p提出模型架构。 对子项进行训练和评估,以解决要解决的特定任务(例如,子项获得的准确率R)。 该评估R被传递回控制器,该控制器进而使用R来改善下一个候选架构。 在此框架下,可以将候选子项到控制器的反馈建模为计算p的梯度的任务,然后通过R缩放此梯度。 控制器可以实现为循环神经网络(请参见下图)。 这样做,控制器将倾向于在迭代之后获得更好的R的架构候选区域,并倾向于给得分不那么高的候选区域分配较低的概率。

例如,控制器循环神经网络可以对卷积网络进行采样。 控制器可以预测许多超参数,例如过滤器高度,过滤器宽度,步幅高度,步幅宽度和一层过滤器的数量,然后可以重复。 每个预测可以由 softmax 分类器执行,然后输入到下一个 RNN 时间步中。 以下图片来自《使用增强学习的神经架构搜索》(Barret Zoph,Quoc V. Le):

预测超参数是不够的,因为最好定义一组动作以在网络中创建新层。 这特别困难,因为描述新层的奖励函数极有可能是不可区分的,因此无法通过标准技术(例如 SGD)对其进行优化。 解决方案来自强化学习,它包括采用类似于我们的第 9 章,“强化学习”中所述的策略梯度网络。

除此之外,并行性可用于优化控制器 RNN 的参数。 Quoc Le&Barret Zoph 建议采用参数服务器方案,其中我们有一个 S 碎片的参数服务器,用于存储 K 个控制器副本的共享参数。 每个控制器副本都采样了如下图所示的并行训练的不同子架构,如下图所示,取自《使用增强学习的神经架构搜索》(Barret Zoph,Quoc V. Le):

Quoc 和 Barret 将 AutoML 技术用于神经架构搜索应用于 Penn Treebank 数据集,这是语言建模的著名基准。 他们的结果改进了目前被认为是最先进的手动设计网络。 特别是,他们在 Penn Treebank 上实现了 62.4 的测试集困惑,这比以前的最新模型好 3.6 困惑。 同样,在 CIFAR-10 数据集上,该方法可以设计一种新颖的网络架构,与测试集准确率方面最好的人类发明架构旗鼓相当。 提出的 CIFAR-10 模型实现了 3.65 的测试错误率,比使用类似架构方案的最新技术模型好 0.09%,并且快 1.05 倍。

元学习块

《用于可伸缩图像识别的学习可传输架构》(Barret Zoph,Vijay Vasudevan,Jonathon Shlens,Quoc V. Le,2017)中,建议在小型数据集上学习架构构造块,然后将其传输到大型数据集。 作者建议在 CIFAR-10 数据集上搜索最佳的卷积层(或单元),然后通过堆叠该单元的更多副本(每个都有其自己的参数),将此学习到的单元应用于 ImageNet 数据集。 准确地说,所有卷积网络都是由结构相同但权重不同的卷积层(或单元)组成的。 因此,将搜索最佳卷积架构简化为搜索最佳单元结构,这样可以更快地将其推广到其他问题。 尽管无法在 ImageNet 上直接学习该单元,但是在已发表的工作中,由学得最好的单元构成的架构可实现 ImageNet 的最新精度为 82.7% top-1 和 96.2% top-5。 该模型的 top-1 准确率比人类发明的最佳架构高 1.2%,而 FLOPS 则减少了 90 亿个,与之前的最新模型相比降低了 28%。 还需要注意的重要一点是,使用 RNN + RL(循环神经网络+强化学习)学习的模型正在击败随机搜索(RL)代表的基线,如本文中所取的图所示。 在 RL 与 RS 中确定的前 5 名和前 25 名模型的平均表现中,RL 始终是赢家:

新任务的元学习

可以对元学习系统进行训练以完成大量任务,然后对其元学习新任务的能力进行测试。 这种元学习的一个著名例子是在高级 CNN 章节中讨论的所谓迁移学习,其中网络可以从相对较小的数据集中成功学习基于图像的新任务。 但是,对于诸如语音,语言和文本之类的非视觉领域,没有类似的预训练方案。

《用于快速适应深度网络的模型不可知元学习》(切尔西·芬恩(Chelsea Finn),彼得·阿比尔(Siety Levine),2017 年)提出了一个模型 -不可知论方法的名称为 MAML,它与经过梯度下降训练的任何模型兼容,并且适用于各种不同的学习问题,包括分类,回归和强化学习。 元学习的目标是针对各种学习任务训练模型,以便仅使用少量训练样本即可解决新的学习任务。 元学习器旨在找到一种可以快速(以很少的步骤)并有效地(仅使用几个示例)快速适应各种问题的初始化。 用参数为θ的参数化函数f₀表示的模型。 当适应新任务Tᵢ时,模型参数θ变为θ[i']。 在 MAML 中,使用对任务Tᵢ的一个或多个梯度下降更新来计算更新的参数向量θ[i']

例如,当使用一个梯度更新时,θ[i'] = θ - α∇[0]L[Ti](f[θ]),其中L[Ti]是任务T的损失函数,而α是元学习参数。 该图报告了 MAML 算法:

MAML 能够在流行的少拍图像分类基准上大大胜过许多现有方法。 旨在从一个或几个概念中学习新概念的镜头很少有挑战性。 例如,《通过概率性程序归纳进行人级概念学习》(Brenden M. Lake,Ruslan Salakhutdinov,Joshua B. Tenenbaum,2015 年),建议人们可以学会从单个图片中识别新颖的两轮车,例如红色框中包含的图片,如下所示:

在 2017 年底,AutoML(或元学习)主题是一个活跃的研究主题,旨在针对给定的学习任务自动选择最高效的神经网络。 目标是学习如何高效,自动地设计网络,从而可以学习特定任务或适应新任务。 主要问题是不能简单地用可微分的损失函数描述网络的设计,因此传统的优化技术不能简单地用于元学习。 因此,已经提出了一些解决方案,包括具有控制器循环网络(RNN)和基于强化学习的奖励策略的思想,以及具有与模型无关的元学习的思想。 两种方法都非常有前途,但是肯定还有很多研究空间。

因此,如果您对一个热门话题感兴趣,那么学习深度学习当然是一个可以考虑作为下一份工作的空间。

  • Google 提出了采用 RNN 作为控制器的方法,该方法使用机器学习来探索神经网络架构。
  • 《带有增强学习的神经架构搜索》(Barret Zoph,Quoc V. Le)是一篇开创性的论文,它证明了有关 Google 方法的更多细节。 但是,RNN 不是唯一的选择。
  • 《图像分类器的大规模发展》(Esteban Real,Sherry Moore,Andrew Selle,Saurabh Saxena,Yutaka Leon Suematsu,Jie Tan,Quoc Le,Alex Kurakin,2017 年)提出在进化遗传算法中使用遗传计算来探索新的候选网络。
  • 《学习用于可伸缩图像识别的可迁移架构》(Barret Zoph,Vijay Vasudevan,Jonathon Shlens,Quoc V. Le)提出了在 CIFAR 上学习的单元,用于改善 ImageNet 分类。
  • 《构建可以建立人工智能的 AI》:谷歌和其他公司为争夺一小部分研究人员而奋斗,他们正在寻找自动方法来应对人工智能专家的短缺。
  • 《用于快速适应深度网络的模型不可知元学习》(切尔西·芬恩(Chelsea Finn),彼得·阿比耶尔(Pieter Abbeel),谢尔盖·莱文(Sergey Levine),2017 年)。
  • 《通过梯度下降学习》(Marcin Andrychowicz,Misha Denil,Sergio Gomez,Matthew W. Hoffman,David Pfau,Tom Schaul,Brendan Shillingford,Nando de Freitas)展示了如何将优化算法的设计转换为学习问题,从而使该算法能够自动学习感兴趣的问题中的结构。 LSMT 学习的算法在训练任务上胜过手工设计的竞争对手,并且可以很好地推广到结构相似的新任务。 该算法的代码可在 GitHub 上获得。

连体网络

连体网络是 Yann LeCun 及其同事在 NIPS 1994 中提出的一种特殊类型的神经网络。 它们背后的基本思想是,像“连体双胞胎”一样,该网络由两个不同的神经网络组成,它们共享相同的架构和权重。

在这里,您可以看到连体架构:

在训练阶段,该对网络会显示一个训练对(X[1], X[2]),其中两个输入不同但相似,例如, X[1] = 他很聪明,而X[2] = 他是一个聪明人。 这两个神经网络将产生两个不同的结果。 可以将组合网络视为测量训练对(X[1], X[2])之间相似度的标量能量函数 , 定义为:

连体网络的目标是训练对(X[1], X[2])之间的能量应小于其他赝品对之间的能量(X[1], X'[2])

这可以通过使用对比损失函数进行训练来实现。

在训练阶段,网络被输入一个训练对和一个与之相关的标签,告诉它是一个真正的对或赝品对的网络:(X[1], X[2], Y[i])i个训练样本。 对比损失函数计算如下:

其中,

L[G]是真正对的部分损失, Lᵢ是赝品的部分损失对和P训练样本数。 当偶对是正品时,标签Y的值为 0;当偶对是赝品时,标签Y的值为 1。 设计局部损失L[G]Lᵢ的方式应使对比损耗L(W)将减少真正对的能量并增加赝品的能量。 这是通过选择部分损失L(G)单调增加而选择部分损失L(I)单调减少f来实现的。 一种可能的选择是使用余弦相似度来计算部分损耗。

使用反向传播算法调整权重。

连体网络的应用

近年来,连体网络已用于许多应用。 他们在 LeCun 论文中首次使用的是签名验证。 从那时起,出现了许多应用,我们将介绍一些最近的应用:

除此之外,还对连体网络进行了脸部验证/识别它们已用于问题解答

一个有效的例子 -- MNIST

工作示例基于 Github 页面。 此处的代码使用连体网络将手写的 MNIST 数字嵌入到 2D 空间中,属于同一类的数字被嵌入在一起。 该代码包含三个主要文件:

  • run.py:它包含执行训练的基本包装。 它使用“梯度下降”算法将对比度损失降至最低。

  • inference.py:包含定义 3 层全连接网络的连体类。 代码中两个网络的输出之间的相似性是欧几里得。 然后,使用部分生成损失和部分赝品损失来计算对比损失。

  • visualize.py:这只是用于可视化结果的包装。

经过前 100,000 个训练步骤,结果是:

您可以看到相同(标记)的数字一起嵌入 2D 空间。

还有另一个有趣的示例](https://github.com/dhwajraj/deep-siamese-text-similarity) 。

在这里,使用 Tensorflow,训练了深层的连体 LSTM 网络以使用字符嵌入来捕获短语/句子相似性。

十四、TensorFlow 处理单元

Google 服务(例如 Google 搜索(RankBrain),街景,Google 照片和 Google 翻译)有一个共同点:它们都使用 Google 的 Tensor 处理单元或 TPU 进行计算。

您可能在想什么是 TPU,这些服务有什么好处? 所有这些服务都在后台使用最新的机器学习算法,并且这些算法涉及大量计算。 TPU 有助于加速所涉及的神经网络计算。 甚至 AlphaGo,一种在 Go 游戏中击败 Lee Sedol 的深度学习程序,都由 TPU 推动。 因此,让我们看看 TPU 到底是什么。

TPU 是 Google 专门为机器学习而定制的定制专用集成电路(ASIC),是针对 Tensorflow 量身定制的。 它基于 28 纳米工艺构建,运行频率为 700 MHz,运行时消耗 40 W 的能量。 它包装为外部加速卡,可以插入现有的 SATA 硬盘插槽中。 TPU 通过 PCIe Gen 3×16 总线连接到主机 CPU,该总线提供 12.5 GB/s 的有效带宽。

到目前为止,第一代 TPU 的目标是推理,即使用已经训练好的模型。 DNN 的训练通常需要更多时间,但仍在 CPU 和 GPU 上进行。 在 2017 年 5 月的博客文章中宣布的第二代 TPU 都可以训练和推断机器学习模型。

TPU 的组件

在本书涵盖的所有深度学习模型中,无论学习范例如何,都需要进行三个基本计算:乘法,加法和激活函数的应用。

前两个成分是矩阵乘法的一部分:权重矩阵W需要与输入矩阵X相乘Wᐪ · X; 矩阵乘法在 CPU 上的计算量很大,尽管 GPU 使操作并行化,但仍有改进的余地。

TPU 具有 65,536 个 8 位整数矩阵乘法器单元(MXU),峰值吞吐量为 92 TOPS。 GPU 和 TPU 乘法之间的主要区别在于 GPU 包含浮点乘法器,而 TPU 包含 8 位整数乘法器。 TPU 还包含一个统一缓冲区(UB),用作寄存器的 24 MB SRAM 和一个包含硬接线激活函数的激活单元(AU)。

MXU 是使用脉动数组架构实现的。 它包含一个数组算术逻辑单元(ALU),该数组连接到网状拓扑中的少量最近邻居。 每个数据值仅读取一次,但在流过 ALU 数组时会多次用于不同的操作,而无需将其存储回寄存器。 TPU 中的 ALU 仅以固定模式执行乘法和加法。 MXU 已针对矩阵乘法进行了优化,不适用于通用计算。

每个 TPU 还具有一个片外 8GiB DRAM 池,称为加权存储器。 它具有四个阶段的流水线,并执行 CISC 指令。 到目前为止,TPU 由六个神经网络组成:两个 MLP,两个 CNN 和两个 LSTM。

在高级指令的帮助下对 TPU 进行编程; 下面是一些用于对 TPU 进行编程的指令:

  • Read_Weights:从内存读取权重
  • Read_Host_Memory:从内存中读取数据
  • MatrixMultiply/Convolve:与数据相乘或卷积并累加结果
  • Activate:应用激活函数
  • Write_Host_Memory:将结果写入存储器

Google 创建了一个 API 栈,以方便 TPU 编程; 它将来自 Tensorflow 图的 API 调用转换为 TPU 指令。

TPU 的优势

TPU 提供的优于 GPU 和 CPU 的首要优势是性能。 Google 将 TPU 的性能与运行基准代码(代表 95% 的推理工作量)的服务器级 Intel Haswell CPU 和 NVIDIA K80 GPU 进行了比较。 它发现 TPU 的速度比 NVIDIA GPU 和 Intel CPU 快 15-30 倍。

第二个重要参数是功耗。 降低功耗非常重要,因为它具有双重能源优势:它不仅减少了功耗,而且还通过降低散热成本来散热,从而节省了功耗,从而消除了加工过程中产生的热量。 TPU / CPU 每瓦性能比其他 CPU 和 GPU 配置提高了 30-80 倍。

TPU 的另一个优点是其最小化和确定性的设计,因为它们一次只能执行一个任务。

与 CPU 和 GPU 相比,单线程 TPU 没有任何复杂的微架构功能会消耗晶体管和能量来改善平均情况,但不会消耗 99% 的情况:没有缓存,分支预测,乱序执行, 多处理,推测性预取,地址合并,多线程,上下文切换等。 极简主义是特定领域处理器的优点。

访问 TPU

Google 已决定不直接将 TPU 出售给他人; 取而代之的是,将通过 Google 云平台提供 TPU:Cloud TPU Alpha。 Cloud TPU Alpha 将提供高达 180 teraflops 的计算性能和 64 GB 的超高带宽内存。 用户将能够从自定义虚拟机连接到这些 Cloud TPU。

Google 还决定向全球的机器学习研究人员免费提供 1000 个云 TPU 集群,以加快开放式机器学习研究的步伐。 在有限的计算时间内,将授予选定的个人访问权限; 个人可以使用以下链接进行注册。 根据 Google Blog:

“由于 TensorFlow 研究云的主要目标是使整个开放式机器学习研究社区受益,因此,成功的申请人有望做到以下几点:

通过同行评审的出版物,开源代码,博客文章或其他开放媒体与世界分享其 TFRC 支持的研究

与 Google 分享具体的建设性反馈,以帮助我们随着时间的推移改进 TFRC 计划和基础的 Cloud TPU 平台。

想象一下 ML 加速丰富的未来,并基于这种未来开发新的机器学习模型。”

TPU 上的资源

posted @ 2026-03-25 10:34  布客飞龙II  阅读(1)  评论(0)    收藏  举报