Keras-深度学习秘籍-全-
Keras 深度学习秘籍(全)
原文:
annas-archive.org/md5/1db02a22ae55dea1c646433e37acffb2译者:飞龙
前言
如果你对机器学习、人工智能或深度学习等术语有所了解,你可能知道什么是神经网络。你是否曾想过它们是如何高效地帮助解决复杂的计算问题,或者如何训练高效的神经网络?本书将教你这两方面的内容,以及更多。
你将首先快速了解流行的 TensorFlow 库,并了解它是如何用于训练不同的神经网络的。你将深入理解神经网络的基本原理和数学基础,并了解为什么 TensorFlow 是一个流行的选择。接下来,你将实现一个简单的前馈神经网络。然后,你将掌握使用 TensorFlow 进行神经网络优化的技巧和算法。之后,你将学习如何实现一些更复杂的神经网络类型,例如卷积神经网络(CNNs)、递归神经网络(RNNs)和深度置信网络(DBNs)。在本书的过程中,你将使用真实世界的数据集来动手理解神经网络编程。你还将学习如何训练生成模型,并了解自动编码器的应用。
到本书结尾时,你将较为清楚地了解如何利用 TensorFlow 的强大功能,轻松训练不同复杂度的神经网络。
本书所需内容
本书将引导你安装所有必要的工具,以便你能跟随示例进行操作:
-
Python 3.4 或以上版本
-
TensorFlow 1.14 或以上版本
本书适合的人群
本书适合具有统计学背景的开发人员,他们希望使用神经网络。虽然我们将使用 TensorFlow 作为神经网络的底层库,但本书也可以作为一个通用资源,帮助填补数学与深度学习实现之间的空白。如果你对 TensorFlow 和 Python 有一定了解,并希望学习更深入的内容,而不仅仅是 API 语法层面的知识,那么这本书适合你。
约定
在本书中,你会看到几种文本样式,用来区分不同种类的信息。以下是这些样式的示例及其含义。代码词汇、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 账号等以以下方式显示:“下一行代码读取链接并将其分配给BeautifulSoup函数。”代码块的显示方式如下:
#import packages into the project
from bs4 import BeautifulSoup
from urllib.request import urlopen
import pandas as pd
当我们希望引起你注意某个代码块的特定部分时,相关的行或项会以粗体显示:
[default] exten
=> s,1,Dial(Zap/1|30) exten
=> s,2,Voicemail(u100) exten
=> s,102,Voicemail(b100) exten
=> i,1,Voicemail(s0)
任何命令行输入或输出都以如下方式书写:
C:\Python34\Scripts> pip install -upgrade pip
C:\Python34\Scripts> pip install pandas
新术语和重要词汇以粗体显示。屏幕上出现的单词,例如在菜单或对话框中,通常会以如下方式显示:“为了下载新模块,我们将进入文件 | 设置 | 项目名称 | 项目解释器。”
警告或重要提示将以如下方式显示。
提示和技巧以如下方式显示。
读者反馈
我们始终欢迎读者的反馈。请告诉我们你对本书的看法——你喜欢或不喜欢的内容。读者反馈对我们非常重要,因为它帮助我们开发出你真正能够最大化利用的书籍。如果你有任何建议或反馈,请通过邮件发送至feedback@packtpub.com,并在邮件主题中注明书名。如果你在某个领域有专业知识,并且有兴趣写作或参与书籍的编写,欢迎查看我们的作者指南:www.packtpub.com/authors。
客户支持
既然你已经成为了《Packt》书籍的骄傲拥有者,我们为你提供了一些资源,帮助你充分利用这次购买。
下载示例代码
你可以从你的账户中下载本书的示例代码文件,网址为www.packtpub.com。如果你是在其他地方购买的本书,可以访问www.packtpub.com/support并注册以便直接将文件通过电子邮件发送给你。你可以通过以下步骤下载代码文件:
-
使用你的电子邮件地址和密码登录或注册我们的网站。
-
将鼠标指针悬停在顶部的“支持”标签上。
-
点击“代码下载 & 勘误”。
-
在搜索框中输入书名。
-
选择你想下载代码文件的书籍。
-
从下拉菜单中选择你购买本书的地点。
-
点击“代码下载”。
下载完文件后,请确保使用以下最新版本的工具来解压或提取文件夹:
-
WinRAR / 7-Zip for Windows
-
Zipeg / iZip / UnRarX for Mac
-
7-Zip / PeaZip for Linux
本书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Neural-Network-Programming-with-TensorFlow。我们还提供了来自我们丰富书籍和视频目录中的其他代码包,地址为github.com/PacktPublishing/。赶紧去看看吧!
下载本书的彩色图片
我们还为你提供了一份包含本书中截图/图表的彩色图片的 PDF 文件。这些彩色图片将帮助你更好地理解输出结果的变化。你可以从www.packtpub.com/sites/default/files/downloads/NeuralNetworkProgrammingwithTensorFlow_ColorImages.pdf下载此文件。
勘误
虽然我们已尽力确保内容的准确性,但难免会出现错误。如果您在我们的书籍中发现任何错误——可能是文本或代码中的错误——我们将非常感激您能报告此问题。这样,您可以帮助其他读者避免困扰,并帮助我们改进该书的后续版本。如果您发现任何勘误,请访问www.packtpub.com/submit-errata,选择您的书籍,点击“勘误提交表单”链接,并输入勘误的详细信息。一旦您的勘误被验证,您的提交将被接受,勘误将上传至我们的网站或添加到该书的勘误列表中。要查看已提交的勘误,请访问www.packtpub.com/books/content/support,并在搜索框中输入书名。所需信息将显示在勘误部分。
盗版
网络上侵犯版权的行为在所有媒体中都是一个持续存在的问题。在 Packt,我们非常重视版权和许可证的保护。如果您在互联网上遇到任何形式的我们作品的非法复制,请立即向我们提供其所在的地址或网站名称,以便我们采取相应的措施。请通过copyright@packtpub.com与我们联系,并提供涉嫌盗版材料的链接。感谢您在保护我们的作者和我们提供有价值内容的能力方面给予的帮助。
问题
如果您对本书的任何方面有问题,可以通过questions@packtpub.com与我们联系,我们将尽力解决问题。
第一章:神经网络的数学
神经网络用户需要对神经网络的概念、算法及其背后的数学有一定的了解。良好的数学直觉和对多种技术的理解对于深入掌握算法的内部运作并获得良好的结果是必要的。理解这些技术所需的数学量及数学水平是多维度的,并且也取决于兴趣。在本章中,你将通过理解用于解决复杂计算问题的数学,来学习神经网络。本章涵盖了神经网络所需的线性代数、微积分和优化的基础知识。
本章的主要目的是为接下来的章节设置数学基础。
本章将涵盖以下主题:
-
理解线性代数
-
理解微积分
-
优化
理解线性代数
线性代数是数学的一个重要分支。理解线性代数对于深度学习(即神经网络)至关重要。在本章中,我们将介绍线性代数的关键基础知识。线性代数处理线性方程组。我们不再只处理标量,而是开始处理矩阵和向量。通过线性代数,我们可以描述深度学习中的复杂运算。
环境设置
在我们深入研究数学及其性质之前,设置开发环境至关重要,因为它将为我们提供执行所学概念所需的设置,意味着安装编译器、依赖项以及IDE(集成开发环境)来运行我们的代码库。
在 Pycharm 中设置 Python 环境
最好使用像 Pycharm 这样的集成开发环境(IDE)来编辑 Python 代码,因为它提供了开发工具和内置的代码辅助功能。代码检查使编码和调试变得更快、更简便,确保你能专注于学习神经网络所需的数学知识这一最终目标。
以下步骤展示了如何在 Pycharm 中设置本地 Python 环境:
- 转到首选项,验证 TensorFlow 库是否已安装。如果没有,请按照
www.tensorflow.org/install/上的说明安装 TensorFlow:

-
保持 TensorFlow 的默认选项并点击确定。
-
最后,右键点击源文件并点击运行 'matrices':

线性代数结构
在接下来的部分,我们将描述线性代数的基本结构。
标量、向量和矩阵
标量、向量和矩阵是数学中的基本对象。基本定义如下:
-
标量是通过一个称为大小的单一数字或数值来表示的。
-
向量是按顺序排列的数字数组。每个数字都有一个唯一的索引来标识。向量表示空间中的一个点,每个元素给出了沿不同轴的坐标。
-
矩阵是一个二维数字数组,其中每个数字通过两个索引(i、j)来标识。
张量
拥有可变数量轴的数字数组称为张量。例如,对于三个轴,它通过三个索引(i、j、k)来标识。
以下图像总结了一个张量,描述了一个二阶张量对象。在三维笛卡尔坐标系中,张量的分量将形成矩阵:

图片参考来自张量维基 en.wikipedia.org/wiki/Tensor
操作
以下主题将描述线性代数的各种操作。
向量
Norm函数用于获取向量的大小;向量x的范数衡量了从原点到点x的距离。它也被称为
范数,其中p=2 被称为欧几里得范数。
以下示例展示了如何计算给定向量的
范数:
import tensorflow as tf
vector = tf.constant([[4,5,6]], dtype=tf.float32)
eucNorm = tf.norm(vector, ord="euclidean")
with tf.Session() as sess:
print(sess.run(eucNorm))
列表的输出为 8.77496。
矩阵
矩阵是一个二维的数字数组,其中每个元素由两个索引标识,而不仅仅是一个。如果一个实数矩阵X的高度为m,宽度为n,则我们说X ∈ Rm × n。这里的R是实数集。
以下示例展示了如何将不同的矩阵转换为张量对象:
# convert matrices to tensor objects
import numpy as np
import tensorflow as tf
# create a 2x2 matrix in various forms
matrix1 = [[1.0, 2.0], [3.0, 40]]
matrix2 = np.array([[1.0, 2.0], [3.0, 40]], dtype=np.float32)
matrix3 = tf.constant([[1.0, 2.0], [3.0, 40]])
print(type(matrix1))
print(type(matrix2))
print(type(matrix3))
tensorForM1 = tf.convert_to_tensor(matrix1, dtype=tf.float32)
tensorForM2 = tf.convert_to_tensor(matrix2, dtype=tf.float32)
tensorForM3 = tf.convert_to_tensor(matrix3, dtype=tf.float32)
print(type(tensorForM1))
print(type(tensorForM2))
print(type(tensorForM3))
列表的输出如下所示:
<class 'list'>
<class 'numpy.ndarray'>
<class 'tensorflow.python.framework.ops.Tensor'>
<class 'tensorflow.python.framework.ops.Tensor'>
<class 'tensorflow.python.framework.ops.Tensor'>
<class 'tensorflow.python.framework.ops.Tensor'>
矩阵乘法
矩阵A和矩阵B的矩阵乘法是第三个矩阵C:
C = AB
矩阵的逐元素积称为哈达玛积,表示为A.B。
两个维度相同的向量x和y的点积是矩阵乘积x转置y。矩阵乘积C = AB就像计算C[i,j],即矩阵A的第i行与矩阵B的第j列的点积:

以下示例展示了使用张量对象的哈达玛积和点积:
import tensorflow as tf
mat1 = tf.constant([[4, 5, 6],[3,2,1]])
mat2 = tf.constant([[7, 8, 9],[10, 11, 12]])
*# hadamard product (element wise)* mult = tf.multiply(mat1, mat2)
*# dot product (no. of rows = no. of columns)* dotprod = tf.matmul(mat1, tf.transpose(mat2))
with tf.Session() as sess:
print(sess.run(mult))
print(sess.run(dotprod))
列表的输出如下所示:
[[28 40 54][30 22 12]]
[[122 167][ 46 64]]
迹操作符
矩阵A的迹操作符Tr(A)给出矩阵所有对角线元素的和。以下示例展示了如何在张量对象上使用迹操作符:
import tensorflow as tf
mat = tf.constant([
[0, 1, 2],
[3, 4, 5],
[6, 7, 8]
], dtype=tf.float32)
*# get trace ('sum of diagonal elements') of the matrix* mat = tf.trace(mat)
with tf.Session() as sess:
print(sess.run(mat))
列表的输出为12.0。
矩阵转置
矩阵的转置是矩阵沿主对角线的镜像。对称矩阵是任何等于其自身转置的矩阵:

以下示例展示了如何在张量对象上使用转置操作符:
import tensorflow as tf
x = [[1,2,3],[4,5,6]]
x = tf.convert_to_tensor(x)
xtrans = tf.transpose(x)
y=([[[1,2,3],[6,5,4]],[[4,5,6],[3,6,3]]])
y = tf.convert_to_tensor(y)
ytrans = tf.transpose(y, perm=[0, 2, 1])
with tf.Session() as sess:
print(sess.run(xtrans))
print(sess.run(ytrans))
列表的输出如下所示:
[[1 4] [2 5] [3 6]]
矩阵对角线
对角矩阵通常由零组成,只有主对角线上的元素非零。并非所有对角矩阵都必须是方阵。
使用对角部分操作,我们可以获得给定矩阵的对角线,为了创建一个具有给定对角线的矩阵,我们可以使用 tensorflow 的 diag 操作。以下示例展示了如何在张量对象上使用对角操作:
import tensorflow as tf
mat = tf.constant([
[0, 1, 2],
[3, 4, 5],
[6, 7, 8]
], dtype=tf.float32)
*# get diagonal of the matrix* diag_mat = tf.diag_part(mat)
*# create matrix with given diagonal* mat = tf.diag([1,2,3,4])
with tf.Session() as sess:
print(sess.run(diag_mat))
print(sess.run(mat))
输出结果如下所示:
[ 0\. 4\. 8.]
[[1 0 0 0][0 2 0 0] [0 0 3 0] [0 0 0 4]]
单位矩阵
单位矩阵是一个矩阵 I,它在与任何向量相乘时不会改变该向量,例如 V,当与 I 相乘时。
以下示例展示了如何为给定大小计算单位矩阵:
import tensorflow as tf
identity = tf.eye(3, 3)
with tf.Session() as sess:
print(sess.run(identity))
输出结果如下所示:
[[ 1\. 0\. 0.] [ 0\. 1\. 0.] [ 0\. 0\. 1.]]
逆矩阵
I 的矩阵逆表示为
。考虑以下方程式;使用逆矩阵和不同的 b 值来求解时,x 可以有多个解。请注意以下性质:


以下示例展示了如何使用 matrix_inverse 操作计算矩阵的逆:
import tensorflow as tf
mat = tf.constant([[2, 3, 4], [5, 6, 7], [8, 9, 10]], dtype=tf.float32)
print(mat)
inv_mat = tf.matrix_inverse(tf.transpose(mat))
with tf.Session() as sess:
print(sess.run(inv_mat))
求解线性方程
TensorFlow 可以使用 solve 操作求解一系列线性方程。我们首先解释不使用库的情况,然后再使用 solve 函数。
一个线性方程的表示如下:
ax + b = yy - ax = b
y - ax = b
y/b - a/b(x) = 1
我们的任务是根据观察到的点来求解前述方程中的 a 和 b 值。首先,创建矩阵点。第一列表示 x 值,第二列表示 y 值。
假设 X 是输入矩阵,A 是我们需要学习的参数;我们设立一个类似于 AX=B 的系统,因此,
。
以下示例通过代码展示了如何求解线性方程:
3x+2y = 15
4x−y = 10
import tensorflow as tf
# equation 1
x1 = tf.constant(3, dtype=tf.float32)
y1 = tf.constant(2, dtype=tf.float32)
point1 = tf.stack([x1, y1])
# equation 2
x2 = tf.constant(4, dtype=tf.float32)
y2 = tf.constant(-1, dtype=tf.float32)
point2 = tf.stack([x2, y2])
# solve for AX=C
X = tf.transpose(tf.stack([point1, point2]))
C = tf.ones((1,2), dtype=tf.float32)
A = tf.matmul(C, tf.matrix_inverse(X))
with tf.Session() as sess:
X = sess.run(X)
print(X)
A = sess.run(A)
print(A)
b = 1 / A[0][1]
a = -b * A[0][0]
print("Hence Linear Equation is: y = {a}x + {b}".format(a=a, b=b))
列表的输出结果如下所示:
[[ 3\. 4.][ 2\. -1.]]
[[ 0.27272728 0.09090909]]
Hence Linear Equation is: y = -2.9999999999999996x + 10.999999672174463
圆的标准方程是 x2+y2+dx+ey+f=0;为了求解其中的参数 d、e 和 f,我们使用 TensorFlow 的 solve 操作,如下所示:
# canonical circle equation
# x2+y2+dx+ey+f = 0
# dx+ey+f=−(x2+y2) ==> AX = B
# we have to solve for d, e, f
points = tf.constant([[2,1], [0,5], [-1,2]], dtype=tf.float64)
X = tf.constant([[2,1,1], [0,5,1], [-1,2,1]], dtype=tf.float64)
B = -tf.constant([[5], [25], [5]], dtype=tf.float64)
A = tf.matrix_solve(X,B)
with tf.Session() as sess:
result = sess.run(A)
D, E, F = result.flatten()
print("Hence Circle Equation is: x**2 + y**2 + {D}x + {E}y + {F} = 0".format(**locals()))
列表的输出结果如下所示:
Hence Circle Equation is: x**2 + y**2 + -2.0x + -6.0y + 5.0 = 0
奇异值分解
当我们将一个整数分解为它的质因数时,我们可以了解该整数的一些有用性质。同样,当我们分解一个矩阵时,我们也能理解许多并非直接显现的功能性属性。矩阵有两种分解方式,分别是特征值分解和奇异值分解。
所有实数矩阵都有奇异值分解,但特征值分解则并非所有矩阵都适用。例如,如果一个矩阵不是方阵,那么特征值分解就不可定义,我们必须使用奇异值分解。
奇异值分解 (SVD) 在数学形式上是三个矩阵 U、S 和 V 的乘积,其中 U 是 mr,S* 是 rr,V* 是 rn*:

以下示例展示了使用 TensorFlow svd操作对文本数据进行 SVD:
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plts
path = "/neuralnetwork-programming/ch01/plots"
text = ["I", "like", "enjoy",
"deep", "learning", "NLP", "flying", "."]
xMatrix = np.array([[0,2,1,0,0,0,0,0],
[2,0,0,1,0,1,0,0],
[1,0,0,0,0,0,1,0],
[0,1,0,0,1,0,0,0],
[0,0,0,1,0,0,0,1],
[0,1,0,0,0,0,0,1],
[0,0,1,0,0,0,0,1],
[0,0,0,0,1,1,1,0]], dtype=np.float32)
X_tensor = tf.convert_to_tensor(xMatrix, dtype=tf.float32)
# tensorflow svd
with tf.Session() as sess:
s, U, Vh = sess.run(tf.svd(X_tensor, full_matrices=False))
for i in range(len(text)):
plts.text(U[i,0], U[i,1], text[i])
plts.ylim(-0.8,0.8)
plts.xlim(-0.8,2.0)
plts.savefig(path + '/svd_tf.png')
# numpy svd
la = np.linalg
U, s, Vh = la.svd(xMatrix, full_matrices=False)
print(U)
print(s)
print(Vh)
# write matrices to file (understand concepts)
file = open(path + "/matx.txt", 'w')
file.write(str(U))
file.write("\n")
file.write("=============")
file.write("\n")
file.write(str(s))
file.close()
for i in range(len(text)):
plts.text(U[i,0], U[i,1], text[i])
plts.ylim(-0.8,0.8)
plts.xlim(-0.8,2.0)
plts.savefig(path + '/svd_np.png')
该输出如下所示:
[[ -5.24124920e-01 -5.72859168e-01 9.54463035e-02 3.83228481e-01 -1.76963374e-01 -1.76092178e-01 -4.19185609e-01 -5.57702743e-02]
[ -5.94438076e-01 6.30120635e-01 -1.70207784e-01 3.10038358e-0
1.84062332e-01 -2.34777853e-01 1.29535481e-01 1.36813134e-01]
[ -2.56274015e-01 2.74017543e-01 1.59810841e-01 3.73903001e-16
-5.78984618e-01 6.36550903e-01 -3.32297325e-16 -3.05414885e-01]
[ -2.85637408e-01 -2.47912124e-01 3.54610324e-01 -7.31901303e-02
4.45784479e-01 8.36141407e-02 5.48721075e-01 -4.68012422e-01]
[ -1.93139315e-01 3.38495038e-02 -5.00790417e-01 -4.28462476e-01
3.47110212e-01 1.55483231e-01 -4.68663752e-01 -4.03576553e-01]
[ -3.05134684e-01 -2.93989003e-01 -2.23433599e-01 -1.91614240e-01
1.27460942e-01 4.91219401e-01 2.09592804e-01 6.57535374e-01]
[ -1.82489842e-01 -1.61027774e-01 -3.97842437e-01 -3.83228481e-01
-5.12923241e-01 -4.27574426e-01 4.19185609e-01 -1.18313827e-01]
[ -2.46898428e-01 1.57254755e-01 5.92991650e-01 -6.20076716e-01
-3.21868137e-02 -2.31065080e-01 -2.59070963e-01 2.37976909e-01]]
[ 2.75726271 2.67824793 1.89221275 1.61803401 1.19154561 0.94833982
0.61803401 0.56999218]
[[ -5.24124920e-01 -5.94438076e-01 -2.56274015e-01 -2.85637408e-01
-1.93139315e-01 -3.05134684e-01 -1.82489842e-01 -2.46898428e-01]
[ 5.72859168e-01 -6.30120635e-01 -2.74017543e-01 2.47912124e-01
-3.38495038e-02 2.93989003e-01 1.61027774e-01 -1.57254755e-01]
[ -9.54463035e-02 1.70207784e-01 -1.59810841e-01 -3.54610324e-01
5.00790417e-01 2.23433599e-01 3.97842437e-01 -5.92991650e-01]
[ 3.83228481e-01 3.10038358e-01 -2.22044605e-16 -7.31901303e-02
-4.28462476e-01 -1.91614240e-01 -3.83228481e-01 -6.20076716e-01]
[ -1.76963374e-01 1.84062332e-01 -5.78984618e-01 4.45784479e-01
3.47110212e-01 1.27460942e-01 -5.12923241e-01 -3.21868137e-02]
[ 1.76092178e-01 2.34777853e-01 -6.36550903e-01 -8.36141407e-02
-1.55483231e-01 -4.91219401e-01 4.27574426e-01 2.31065080e-01]
[ 4.19185609e-01 -1.29535481e-01 -3.33066907e-16 -5.48721075e-01
4.68663752e-01 -2.09592804e-01 -4.19185609e-01 2.59070963e-01]
[ -5.57702743e-02 1.36813134e-01 -3.05414885e-01 -4.68012422e-01
-4.03576553e-01 6.57535374e-01 -1.18313827e-01 2.37976909e-01]]
下面是前述数据集的 SVD 图示:

特征值分解
特征值分解是最著名的分解技术之一,我们将矩阵分解为一组特征向量和特征值。
对于方阵,特征向量* v 是一个向量,使得乘以 A 后只改变 v *的尺度:
Av = λv
标量* λ *称为与此特征向量对应的特征值。
矩阵* A *的特征值分解如下所示:

矩阵的特征值分解描述了许多关于矩阵的有用细节。例如,当且仅当任何特征值为零时,矩阵是奇异的。
主成分分析
主成分分析(PCA)将给定的数据集投影到一个低维线性空间,以使得投影数据的方差最大化。PCA 需要协方差矩阵的特征值和特征向量,协方差矩阵是* X 的乘积,其中 X *是数据矩阵。
对数据矩阵* X *进行的 SVD 如下所示:

以下示例展示了使用 SVD 进行 PCA:
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
import plotly.plotly as py
import plotly.graph_objs as go
import plotly.figure_factory as FF
import pandas as pd
path = "/neuralnetwork-programming/ch01/plots"
logs = "/neuralnetwork-programming/ch01/logs"
xMatrix = np.array([[0,2,1,0,0,0,0,0],
[2,0,0,1,0,1,0,0],
[1,0,0,0,0,0,1,0],
[0,1,0,0,1,0,0,0],
[0,0,0,1,0,0,0,1],
[0,1,0,0,0,0,0,1],
[0,0,1,0,0,0,0,1],
[0,0,0,0,1,1,1,0]], dtype=np.float32)
def pca(mat):
mat = tf.constant(mat, dtype=tf.float32)
mean = tf.reduce_mean(mat, 0)
less = mat - mean
s, u, v = tf.svd(less, full_matrices=True, compute_uv=True)
s2 = s ** 2
variance_ratio = s2 / tf.reduce_sum(s2)
with tf.Session() as session:
run = session.run([variance_ratio])
return run
if __name__ == '__main__':
print(pca(xMatrix))
列表的输出如下所示:
[array([ 4.15949494e-01, 2.08390564e-01, 1.90929279e-01,
8.36438537e-02, 5.55494241e-02, 2.46047471e-02,
2.09326427e-02, 3.57540098e-16], dtype=float32)]
微积分
前几节中的主题涵盖了标准线性代数的内容;但未涉及的是基础微积分。尽管我们使用的微积分相对简单,但其数学形式可能看起来非常复杂。本节将介绍一些矩阵微积分的基本形式,并附有几个例子。
梯度
函数关于实值矩阵* A 的梯度定义为 A *的偏导数矩阵,表示如下:


TensorFlow 不进行数值微分,而是支持自动微分。通过在 TensorFlow 图中指定操作,它可以自动在图中运行链式法则,并且由于它知道我们指定的每个操作的导数,因此能够自动将它们组合起来。
以下示例展示了使用 MNIST 数据训练网络,MNIST 数据库由手写数字组成。它有一个包含 60,000 个样本的训练集和一个包含 10,000 个样本的测试集。数字已进行尺寸归一化。
这里执行了反向传播,没有使用任何 API,并且导数是手动计算的。我们在 1,000 次测试中获得了 913 个正确结果。这个概念将在下一章介绍。
mnist dataset and initialize weights and biases:
import tensorflow as tf
# get mnist dataset
from tensorflow.examples.tutorials.mnist import input_data
data = input_data.read_data_sets("MNIST_data/", one_hot=True)
# x represents image with 784 values as columns (28*28), y represents output digit
x = tf.placeholder(tf.float32, [None, 784])
y = tf.placeholder(tf.float32, [None, 10])
# initialize weights and biases [w1,b1][w2,b2]
numNeuronsInDeepLayer = 30
w1 = tf.Variable(tf.truncated_normal([784, numNeuronsInDeepLayer]))
b1 = tf.Variable(tf.truncated_normal([1, numNeuronsInDeepLayer]))
w2 = tf.Variable(tf.truncated_normal([numNeuronsInDeepLayer, 10]))
b2 = tf.Variable(tf.truncated_normal([1, 10]))
现在我们定义一个具有非线性sigmoid函数的两层网络;应用平方损失函数并使用反向传播算法进行优化,如下所示:
# non-linear sigmoid function at each neuron
def sigmoid(x):
sigma = tf.div(tf.constant(1.0), tf.add(tf.constant(1.0), tf.exp(tf.negative(x))))
return sigma
# starting from first layer with wx+b, then apply sigmoid to add non-linearity
z1 = tf.add(tf.matmul(x, w1), b1)
a1 = sigmoid(z1)
z2 = tf.add(tf.matmul(a1, w2), b2)
a2 = sigmoid(z2)
# calculate the loss (delta)
loss = tf.subtract(a2, y)
# derivative of the sigmoid function der(sigmoid)=sigmoid*(1-sigmoid)
def sigmaprime(x):
return tf.multiply(sigmoid(x), tf.subtract(tf.constant(1.0), sigmoid(x)))
# backward propagation
dz2 = tf.multiply(loss, sigmaprime(z2))
db2 = dz2
dw2 = tf.matmul(tf.transpose(a1), dz2)
da1 = tf.matmul(dz2, tf.transpose(w2))
dz1 = tf.multiply(da1, sigmaprime(z1))
db1 = dz1
dw1 = tf.matmul(tf.transpose(x), dz1)
# finally update the network
eta = tf.constant(0.5)
step = [
tf.assign(w1,
tf.subtract(w1, tf.multiply(eta, dw1)))
, tf.assign(b1,
tf.subtract(b1, tf.multiply(eta,
tf.reduce_mean(db1, axis=[0]))))
, tf.assign(w2,
tf.subtract(w2, tf.multiply(eta, dw2)))
, tf.assign(b2,
tf.subtract(b2, tf.multiply(eta,
tf.reduce_mean(db2, axis=[0]))))
]
acct_mat = tf.equal(tf.argmax(a2, 1), tf.argmax(y, 1))
acct_res = tf.reduce_sum(tf.cast(acct_mat, tf.float32))
sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())
for i in range(10000):
batch_xs, batch_ys = data.train.next_batch(10)
sess.run(step, feed_dict={x: batch_xs,
y: batch_ys})
if i % 1000 == 0:
res = sess.run(acct_res, feed_dict=
{x: data.test.images[:1000],
y: data.test.labels[:1000]})
print(res)
该输出如下所示:
Extracting MNIST_data
125.0
814.0
870.0
874.0
889.0
897.0
906.0
903.0
922.0
913.0
现在,让我们使用 TensorFlow 进行自动微分。以下示例展示了使用 GradientDescentOptimizer。我们在 1,000 次测试中得到了 924 次正确的结果。
import tensorflow as tf
# get mnist dataset
from tensorflow.examples.tutorials.mnist import input_data
data = input_data.read_data_sets("MNIST_data/", one_hot=True)
# x represents image with 784 values as columns (28*28), y represents output digit
x = tf.placeholder(tf.float32, [None, 784])
y = tf.placeholder(tf.float32, [None, 10])
# initialize weights and biases [w1,b1][w2,b2]
numNeuronsInDeepLayer = 30
w1 = tf.Variable(tf.truncated_normal([784, numNeuronsInDeepLayer]))
b1 = tf.Variable(tf.truncated_normal([1, numNeuronsInDeepLayer]))
w2 = tf.Variable(tf.truncated_normal([numNeuronsInDeepLayer, 10]))
b2 = tf.Variable(tf.truncated_normal([1, 10]))
# non-linear sigmoid function at each neuron
def sigmoid(x):
sigma = tf.div(tf.constant(1.0), tf.add(tf.constant(1.0), tf.exp(tf.negative(x))))
return sigma
# starting from first layer with wx+b, then apply sigmoid to add non-linearity
z1 = tf.add(tf.matmul(x, w1), b1)
a1 = sigmoid(z1)
z2 = tf.add(tf.matmul(a1, w2), b2)
a2 = sigmoid(z2)
# calculate the loss (delta)
loss = tf.subtract(a2, y)
# derivative of the sigmoid function der(sigmoid)=sigmoid*(1-sigmoid)
def sigmaprime(x):
return tf.multiply(sigmoid(x), tf.subtract(tf.constant(1.0), sigmoid(x)))
# automatic differentiation
cost = tf.multiply(loss, loss)
step = tf.train.GradientDescentOptimizer(0.1).minimize(cost)
acct_mat = tf.equal(tf.argmax(a2, 1), tf.argmax(y, 1))
acct_res = tf.reduce_sum(tf.cast(acct_mat, tf.float32))
sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())
for i in range(10000):
batch_xs, batch_ys = data.train.next_batch(10)
sess.run(step, feed_dict={x: batch_xs,
y: batch_ys})
if i % 1000 == 0:
res = sess.run(acct_res, feed_dict=
{x: data.test.images[:1000],
y: data.test.labels[:1000]})
print(res)
其输出如下所示:
96.0
777.0
862.0
870.0
889.0
901.0
911.0
905.0
914.0
924.0
以下示例展示了使用梯度下降的线性回归:
import tensorflow as tf
import numpy
import matplotlib.pyplot as plt
rndm = numpy.random
# config parameters
learningRate = 0.01
trainingEpochs = 1000
displayStep = 50
# create the training data
trainX = numpy.asarray([3.3,4.4,5.5,6.71,6.93,4.168,9.779,6.182,7.59,2.167,
7.042,10.791,5.313,7.997,5.654,9.27,3.12])
trainY = numpy.asarray([1.7,2.76,2.09,3.19,1.694,1.573,3.366,2.596,2.53,1.221,
2.827,3.465,1.65,2.904,2.42,2.94,1.34])
nSamples = trainX.shape[0]
# tf inputs
X = tf.placeholder("float")
Y = tf.placeholder("float")
# initialize weights and bias
W = tf.Variable(rndm.randn(), name="weight")
b = tf.Variable(rndm.randn(), name="bias")
# linear model
linearModel = tf.add(tf.multiply(X, W), b)
# mean squared error
loss = tf.reduce_sum(tf.pow(linearModel-Y, 2))/(2*nSamples)
# Gradient descent
opt = tf.train.GradientDescentOptimizer(learningRate).minimize(loss)
# initializing variables
init = tf.global_variables_initializer()
# run
with tf.Session() as sess:
sess.run(init)
# fitting the training data
for epoch in range(trainingEpochs):
for (x, y) in zip(trainX, trainY):
sess.run(opt, feed_dict={X: x, Y: y})
# print logs
if (epoch+1) % displayStep == 0:
c = sess.run(loss, feed_dict={X: trainX, Y:trainY})
print("Epoch is:", '%04d' % (epoch+1), "loss=", "{:.9f}".format(c), "W=", sess.run(W), "b=", sess.run(b))
print("optimization done...")
trainingLoss = sess.run(loss, feed_dict={X: trainX, Y: trainY})
print("Training loss=", trainingLoss, "W=", sess.run(W), "b=", sess.run(b), '\n')
# display the plot
plt.plot(trainX, trainY, 'ro', label='Original data')
plt.plot(trainX, sess.run(W) * trainX + sess.run(b), label='Fitted line')
plt.legend()
plt.show()
# Testing example, as requested (Issue #2)
testX = numpy.asarray([6.83, 4.668, 8.9, 7.91, 5.7, 8.7, 3.1, 2.1])
testY = numpy.asarray([1.84, 2.273, 3.2, 2.831, 2.92, 3.24, 1.35, 1.03])
print("Testing... (Mean square loss Comparison)")
testing_cost = sess.run(
tf.reduce_sum(tf.pow(linearModel - Y, 2)) / (2 * testX.shape[0]),
feed_dict={X: testX, Y: testY})
print("Testing cost=", testing_cost)
print("Absolute mean square loss difference:", abs(trainingLoss - testing_cost))
plt.plot(testX, testY, 'bo', label='Testing data')
plt.plot(trainX, sess.run(W) * trainX + sess.run(b), label='Fitted line')
plt.legend()
plt.show()
其输出如下所示:
Epoch is: 0050 loss= 0.141912043 W= 0.10565 b= 1.8382
Epoch is: 0100 loss= 0.134377643 W= 0.11413 b= 1.7772
Epoch is: 0150 loss= 0.127711013 W= 0.122106 b= 1.71982
Epoch is: 0200 loss= 0.121811897 W= 0.129609 b= 1.66585
Epoch is: 0250 loss= 0.116592340 W= 0.136666 b= 1.61508
Epoch is: 0300 loss= 0.111973859 W= 0.143304 b= 1.56733
Epoch is: 0350 loss= 0.107887231 W= 0.149547 b= 1.52241
Epoch is: 0400 loss= 0.104270980 W= 0.15542 b= 1.48017
Epoch is: 0450 loss= 0.101070963 W= 0.160945 b= 1.44043
Epoch is: 0500 loss= 0.098239250 W= 0.166141 b= 1.40305
Epoch is: 0550 loss= 0.095733419 W= 0.171029 b= 1.36789
Epoch is: 0600 loss= 0.093516059 W= 0.175626 b= 1.33481
Epoch is: 0650 loss= 0.091553882 W= 0.179951 b= 1.3037
Epoch is: 0700 loss= 0.089817807 W= 0.184018 b= 1.27445
Epoch is: 0750 loss= 0.088281371 W= 0.187843 b= 1.24692
Epoch is: 0800 loss= 0.086921677 W= 0.191442 b= 1.22104
Epoch is: 0850 loss= 0.085718453 W= 0.194827 b= 1.19669
Epoch is: 0900 loss= 0.084653646 W= 0.198011 b= 1.17378
Epoch is: 0950 loss= 0.083711281 W= 0.201005 b= 1.15224
Epoch is: 1000 loss= 0.082877308 W= 0.203822 b= 1.13198
optimization done...
Training loss= 0.0828773 W= 0.203822 b= 1.13198
Testing... (Mean square loss Comparison)
Testing cost= 0.0957726
Absolute mean square loss difference: 0.0128952
图表如下:

下图展示了使用模型在测试数据上的拟合曲线:

Hessian
梯度是向量函数的第一导数,而 Hessian 是第二导数。我们现在来讲解一下符号表示:

与梯度类似,Hessian 仅在 f(x) 是实值时才定义。
使用的代数函数是
。
以下示例展示了使用 TensorFlow 实现 Hessian:
import tensorflow as tf
import numpy as np
X = tf.Variable(np.random.random_sample(), dtype=tf.float32)
y = tf.Variable(np.random.random_sample(), dtype=tf.float32)
def createCons(x):
return tf.constant(x, dtype=tf.float32)
function = tf.pow(X, createCons(2)) + createCons(2) * X * y + createCons(3) * tf.pow(y, createCons(2)) + createCons(4) * X + createCons(5) * y + createCons(6)
# compute hessian
def hessian(func, varbles):
matrix = []
for v_1 in varbles:
tmp = []
for v_2 in varbles:
# calculate derivative twice, first w.r.t v2 and then w.r.t v1
tmp.append(tf.gradients(tf.gradients(func, v_2)[0], v_1)[0])
tmp = [createCons(0) if t == None else t for t in tmp]
tmp = tf.stack(tmp)
matrix.append(tmp)
matrix = tf.stack(matrix)
return matrix
hessian = hessian(function, [X, y])
sess = tf.Session()
sess.run(tf.initialize_all_variables())
print(sess.run(hessian))
其输出如下所示:
[[ 2\. 2.] [ 2\. 6.]]
行列式
行列式为我们提供了关于矩阵的信息,这在求解线性方程和寻找矩阵的逆矩阵时非常有用。
对于给定的矩阵 X,行列式如下所示:


以下示例展示了如何使用 TensorFlow 获取行列式:
import tensorflow as tf
import numpy as np
x = np.array([[10.0, 15.0, 20.0], [0.0, 1.0, 5.0], [3.0, 5.0, 7.0]], dtype=np.float32)
det = tf.matrix_determinant(x)
with tf.Session() as sess:
print(sess.run(det))
其输出如下所示:
-15.0
优化
作为深度学习的一部分,我们通常希望优化一个函数的值,使其最小化或最大化 f(x) 相对于 x。一些优化问题的例子包括最小二乘法、逻辑回归和支持向量机。许多这些技术将在后续章节中详细讨论。
优化器
我们将在这里研究 AdamOptimizer;TensorFlow AdamOptimizer 使用 Kingma 和 Ba 的 Adam 算法来管理学习率。Adam 相较于简单的 GradientDescentOptimizer 有许多优点。首先,它使用参数的移动平均,这使得 Adam 可以使用较大的步长,并且能够在没有精细调整的情况下收敛到该步长。
Adam 的缺点是它需要为每个训练步骤中的每个参数执行更多的计算。GradientDescentOptimizer 也可以使用,但它需要更多的超参数调优,才能更快地收敛。
以下示例展示了如何使用 AdamOptimizer:
-
tf.train.Optimizer创建一个优化器 -
tf.train.Optimizer.minimize(loss, var_list)将优化操作添加到计算图中
这里,自动微分计算梯度时不需要用户输入:
import numpy as np
import seaborn
import matplotlib.pyplot as plt
import tensorflow as tf
# input dataset
xData = np.arange(100, step=.1)
yData = xData + 20 * np.sin(xData/10)
# scatter plot for input data
plt.scatter(xData, yData)
plt.show()
# defining data size and batch size
nSamples = 1000
batchSize = 100
# resize
xData = np.reshape(xData, (nSamples,1))
yData = np.reshape(yData, (nSamples,1))
# input placeholders
x = tf.placeholder(tf.float32, shape=(batchSize, 1))
y = tf.placeholder(tf.float32, shape=(batchSize, 1))
# init weight and bias
with tf.variable_scope("linearRegression"):
W = tf.get_variable("weights", (1, 1), initializer=tf.random_normal_initializer())
b = tf.get_variable("bias", (1,), initializer=tf.constant_initializer(0.0))
y_pred = tf.matmul(x, W) + b
loss = tf.reduce_sum((y - y_pred)**2/nSamples)
# optimizer
opt = tf.train.AdamOptimizer().minimize(loss)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
# gradient descent loop for 500 steps
for _ in range(500):
# random minibatch
indices = np.random.choice(nSamples, batchSize)
X_batch, y_batch = xData[indices], yData[indices]
# gradient descent step
_, loss_val = sess.run([opt, loss], feed_dict={x: X_batch, y: y_batch})
这是数据集的散点图:

这是在数据上学习到的模型的图示:

总结
在本章中,我们介绍了理解神经网络所需的数学概念,并回顾了与张量相关的一些数学内容。我们还展示了如何在 TensorFlow 中执行数学运算。在接下来的章节中,我们将反复应用这些概念。
第二章:深度前馈网络
在第一章中,你学习了驱动各种神经网络背后逻辑的数学知识。在本章中,我们将重点讨论最基本的神经网络——前馈神经网络。我们还将研究具有多个隐藏层的深度前馈网络,以提高模型的准确性。
我们将涵盖以下主题:
-
定义前馈网络
-
理解反向传播
-
在 TensorFlow 中实现前馈网络
-
分析鸢尾花数据集
-
创建用于图像分类的前馈网络
定义前馈网络
深度前馈网络,也称为前馈神经网络,有时也被称为多层感知机(MLPs)。前馈网络的目标是逼近 f∗ 的函数。例如,对于一个分类器,y = f∗(x) 将输入 x 映射到标签 y。前馈网络定义了从输入到标签的映射 y = f(x;θ)。它学习参数 θ 的值,以得到最佳的函数逼近。
我们在第五章中讨论了 RNN,递归神经网络。前馈网络是递归网络的概念性基石,后者驱动了许多自然语言应用。前馈神经网络之所以被称为网络,是因为它们由许多不同的函数组合而成,这些函数在有向无环图中被组合在一起。
该模型与一个有向无环图相关联,描述了函数如何组合在一起。例如,有三个函数 f(1)、f(2) 和 f(3),它们组合成 f(x) = f(3)(f(2)(f(1)(x)))。这些链式结构是神经网络中最常用的结构。在这种情况下,f(1) 被称为第一层,f(2) 被称为第二层,以此类推。链的整体长度给出了模型的深度。正是这个术语产生了“深度学习”这个名字。前馈网络的最后一层被称为输出层。

图示展示了在输入 x 上激活的各种函数,形成一个神经网络
这些网络被称为神经网络,因为它们受到神经科学的启发。每一层隐藏层是一个向量,这些隐藏层的维度决定了模型的宽度。
理解反向传播
当使用前馈神经网络接受输入 x 并生成输出 yˆ 时,信息通过网络元素向前流动。输入 x 提供信息,接着传播到每一层的隐藏单元,并产生 yˆ。这叫做 前向传播。在训练过程中,前向传播一直持续,直到产生一个标量成本 J(θ)。反向传播算法,通常称为反向传播,允许成本的信息然后向后流经网络,从而计算梯度。
计算梯度的解析表达式很简单,但数值计算该表达式可能非常耗费计算资源。反向传播算法通过一种简单且廉价的过程实现这一点。
反向传播仅指计算梯度的方法,而另一种算法,如随机梯度下降,指的是实际的机制。
使用 TensorFlow 实现前馈网络
使用 TensorFlow 实现前馈网络非常简单,通过为隐藏层定义占位符,计算激活值,并利用它们来计算预测。我们以一个使用前馈网络进行分类的示例为例:
X = tf.placeholder("float", shape=[None, x_size])
y = tf.placeholder("float", shape=[None, y_size])
weights_1 = initialize_weights((x_size, hidden_size), stddev)
weights_2 = initialize_weights((hidden_size, y_size), stddev)
sigmoid = tf.nn.sigmoid(tf.matmul(X, weights_1))
y = tf.matmul(sigmoid, weights_2)
一旦定义了预测值张量,我们就可以计算 cost 函数:
cost = tf.reduce_mean(tf.nn.OPERATION_NAME(labels=<actual value>, logits=<predicted value>))
updates_sgd = tf.train.GradientDescentOptimizer(sgd_step).minimize(cost)
在这里,OPERATION_NAME 可能是以下之一:
tf.nn.sigmoid_cross_entropy_with_logits: 计算传入的logits和labels的 sigmoid 交叉熵:
sigmoid_cross_entropy_with_logits(
_sentinel=None,
labels=None,
logits=None,
name=None
)Formula implemented is max(x, 0) - x * z + log(1 + exp(-abs(x)))
_sentinel: 用于防止位置参数。内部使用,请勿使用。
labels: 与 logits 具有相同类型和形状的张量。
logits: 类型为 float32 或 float64 的张量。实现的公式是(x = logits,z = labels)max(x, 0) - x * z + log(1 + exp(-abs(x)))。
tf.nn.softmax: 对传入的张量执行softmax激活。这仅仅是归一化,确保张量中每一行的概率总和为 1。不能直接用于分类。
softmax = exp(logits) / reduce_sum(exp(logits), dim)
logits: 非空张量。必须是以下类型之一——half、float32 或 float64。
dim: 执行 softmax 的维度。默认值为 -1,表示最后一个维度。
name: 操作的名称(可选)。
tf.nn.log_softmax: 计算 softmax 函数的对数,并有助于归一化欠拟合。该函数也仅是一个归一化函数。
log_softmax(
logits,
dim=-1,
name=None
)
logits: 非空张量。必须是以下类型之一——half、float32 或 float64。
dim: 执行 softmax 的维度。默认值为 -1,表示最后一个维度。
name: 操作的名称(可选)。
tf.nn.softmax_cross_entropy_with_logits
softmax_cross_entropy_with_logits(
_sentinel=None,
labels=None,
logits=None,
dim=-1,
name=None
)
_sentinel: 用于防止位置参数。仅供内部使用。
labels: 每一行的labels[i]必须是有效的概率分布。
logits: 未缩放的对数概率。
dim: 类维度。默认为 -1,即最后一个维度。
name: 操作的名称(可选)。
softmax cross entropy between logits and labels. While the classes are mutually exclusive, their probabilities need not be. All that is required is that each row of labels is a valid probability distribution. For exclusive labels, use (where one and only one class is true at a time) sparse_softmax_cross_entropy_with_logits.
tf.nn.sparse_softmax_cross_entropy_with_logits
sparse_softmax_cross_entropy_with_logits(
_sentinel=None,
labels=None,
logits=None,
name=None
)
labels: 形状为 [d_0,d_1,...,d_(r-1)](其中 r 为标签和结果的秩)和 dtype 为 int32 或 int64 的张量。标签中的每个条目必须是 [0,num_classes] 中的索引。其他值将在 CPU 上运行时引发异常,并在 GPU 上返回对应的 NaN 损失和梯度行。
logits: 形状为 [d_0,d_1,...,d_(r-1),num_classes],类型为 float32 或 float64 的未缩放对数概率张量。
上面的代码计算了 logits 和 labels 之间的稀疏 softmax 交叉熵。给定标签的概率被认为是独占的。软类是不允许的,并且标签的向量必须为每一行的 logits 提供一个特定的真实类别索引。
tf.nn.weighted_cross_entropy_with_logits
weighted_cross_entropy_with_logits(
targets,
logits,
pos_weight,
name=None
)
targets: 与 logits 形状和类型相同的张量。
logits: 类型为 float32 或 float64 的张量。
pos_weight: 用于正例的系数。
这与 sigmoid_cross_entropy_with_logits() 类似,不同之处在于 pos_weight 通过加权正误差与负误差之间的成本来调节召回率和精确度。
分析 Iris 数据集
让我们来看一个使用 Iris 数据集的前馈示例。
你可以从 github.com/ml-resources/neuralnetwork-programming/blob/ed1/ch02/iris/iris.csv 下载数据集,从 github.com/ml-resources/neuralnetwork-programming/blob/ed1/ch02/iris/target.csv 下载目标标签。
在 Iris 数据集中,我们将使用 150 行数据,每种鸢尾花(Iris setosa、Iris virginica 和 Iris versicolor)各有 50 个样本。
从三种鸢尾花中比较花瓣的几何形状:
Iris Setosa、Iris Virginica 和 Iris Versicolor。

在数据集中,每行包含每个花样本的数据:萼片长度、萼片宽度、花瓣长度、花瓣宽度和花的种类。花种类以整数存储,0 表示 Iris setosa,1 表示 Iris versicolor,2 表示 Iris virginica。
首先,我们将创建一个 run() 函数,该函数接受三个参数——隐藏层大小 h_size、权重的标准差 stddev,以及随机梯度下降的步长 sgd_step:
def run(h_size, stddev, sgd_step)
输入数据加载通过 numpy 中的 genfromtxt 函数完成。加载的 Iris 数据具有形状 L: 150 和 W: 4。数据加载到 all_X 变量中。目标标签从 target.csv 加载到 all_Y 中,形状为 L: 150,W: 3:
def load_iris_data():
from numpy import genfromtxt
data = genfromtxt('iris.csv', delimiter=',')
target = genfromtxt('target.csv', delimiter=',').astype(int)
# Prepend the column of 1s for bias
L, W = data.shape
all_X = np.ones((L, W + 1))
all_X[:, 1:] = data
num_labels = len(np.unique(target))
all_y = np.eye(num_labels)[target]
return train_test_split(all_X, all_y, test_size=0.33, random_state=RANDOMSEED)
数据加载完成后,我们根据 x_size、y_size 和 h_size 初始化权重矩阵,并将标准差传递给 run() 方法:
-
x_size= 5 -
y_size= 3 -
h_size= 128(或为隐藏层中选择的任意神经元数量)
# Size of Layers
x_size = train_x.shape[1] # Input nodes: 4 features and 1 bias
y_size = train_y.shape[1] # Outcomes (3 iris flowers)
# variables
X = tf.placeholder("float", shape=[None, x_size])
y = tf.placeholder("float", shape=[None, y_size])
weights_1 = initialize_weights((x_size, h_size), stddev)
weights_2 = initialize_weights((h_size, y_size), stddev)
接下来,我们使用在forward_propagration()函数中定义的激活函数sigmoid进行预测:
def forward_propagation(X, weights_1, weights_2):
sigmoid = tf.nn.sigmoid(tf.matmul(X, weights_1))
y = tf.matmul(sigmoid, weights_2)
return y
首先,计算从输入X和weights_1得到的sigmoid输出。然后,这个输出将用于通过sigmoid和weights_2的矩阵乘法计算y:
y_pred = forward_propagation(X, weights_1, weights_2)
predict = tf.argmax(y_pred, dimension=1)
接下来,我们定义成本函数和使用梯度下降法进行优化。我们来看一下正在使用的GradientDescentOptimizer。它定义在tf.train.GradientDescentOptimizer类中,并实现了梯度下降算法。
要构建实例,我们使用以下构造函数并将sgd_step作为参数传入:
# constructor for GradientDescentOptimizer
__init__(
learning_rate,
use_locking=False,
name='GradientDescent'
)
传入的参数在这里解释:
-
learning_rate:一个张量或浮动值。用于学习率。 -
use_locking:如果为 True,则在更新操作中使用锁。 -
name:应用梯度时创建的操作的可选名称前缀。默认名称为"GradientDescent"。
以下是实现cost函数的代码列表:
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y, logits=y_pred))
updates_sgd = tf.train.GradientDescentOptimizer(sgd_step).minimize(cost)
接下来,我们将实现以下步骤:
- 初始化 TensorFlow 会话:
sess = tf.Session()
-
使用
tf.initialize_all_variables()初始化所有变量;返回的对象用于实例化会话。 -
遍历
steps(从 1 到 50)。 -
对
train_x和train_y中的每一步,执行updates_sgd。 -
计算
train_accuracy和test_accuracy。
我们将每个步骤的准确率存储在一个列表中,以便绘制图表:
init = tf.initialize_all_variables()
steps = 50
sess.run(init)
x = np.arange(steps)
test_acc = []
train_acc = []
print("Step, train accuracy, test accuracy")
for step in range(steps):
# Train with each example
for i in range(len(train_x)):
sess.run(updates_sgd, feed_dict={X: train_x[i: i + 1], y: train_y[i: i + 1]})
train_accuracy = np.mean(np.argmax(train_y, axis=1) ==
sess.run(predict, feed_dict={X: train_x, y: train_y}))
test_accuracy = np.mean(np.argmax(test_y, axis=1) ==
sess.run(predict, feed_dict={X: test_x, y: test_y}))
print("%d, %.2f%%, %.2f%%"
% (step + 1, 100\. * train_accuracy, 100\. * test_accuracy))
test_acc.append(100\. * test_accuracy)
train_acc.append(100\. * train_accuracy)
代码执行
让我们为h_size设置为128,标准差为0.1,sgd_step为0.01,运行这段代码:
def run(h_size, stddev, sgd_step):
...
def main():
run(128,0.1,0.01)
if __name__ == '__main__':
main()
上述代码输出以下图表,图表绘制了步数与测试和训练准确率的关系:

让我们比较 SGD 步骤的变化及其对训练准确率的影响。以下代码与之前的代码示例非常相似,但我们将重新运行它并使用多个 SGD 步骤,看看 SGD 步骤如何影响准确率水平。
def run(h_size, stddev, sgd_steps):
....
test_accs = []
train_accs = []
time_taken_summary = []
for sgd_step in sgd_steps:
start_time = time.time()
updates_sgd = tf.train.GradientDescentOptimizer(sgd_step).minimize(cost)
sess = tf.Session()
init = tf.initialize_all_variables()
steps = 50
sess.run(init)
x = np.arange(steps)
test_acc = []
train_acc = []
print("Step, train accuracy, test accuracy")
for step in range(steps):
# Train with each example
for i in range(len(train_x)):
sess.run(updates_sgd, feed_dict={X: train_x[i: i + 1],
y: train_y[i: i + 1]})
train_accuracy = np.mean(np.argmax(train_y, axis=1) ==
sess.run(predict,
feed_dict={X: train_x, y: train_y}))
test_accuracy = np.mean(np.argmax(test_y, axis=1) ==
sess.run(predict,
feed_dict={X: test_x, y: test_y}))
print("%d, %.2f%%, %.2f%%"
% (step + 1, 100\. * train_accuracy, 100\. * test_accuracy))
#x.append(step)
test_acc.append(100\. * test_accuracy)
train_acc.append(100\. * train_accuracy)
end_time = time.time()
diff = end_time -start_time
time_taken_summary.append((sgd_step,diff))
t = [np.array(test_acc)]
t.append(train_acc)
train_accs.append(train_acc)
上述代码的输出将是一个数组,包含每个 SGD 步长值的训练和测试准确率。在我们的示例中,我们调用了sgd_steps函数,SGD 步长值为[0.01, 0.02, 0.03]:
def main():
sgd_steps = [0.01,0.02,0.03]
run(128,0.1,sgd_steps)
if __name__ == '__main__':
main()
这是展示训练准确率如何随sgd_steps变化的图表。对于0.03的 SGD 值,由于步长较大,它能更快地达到更高的准确率。

实现带有图像的前馈神经网络
现在我们将探讨如何使用前馈神经网络对图像进行分类。我们将使用notMNIST数据集。该数据集包含九个字母的图像,从 A 到 I。
NotMNIST 数据集类似于 MNIST 数据集,但它专注于字母而非数字(yaroslavvb.blogspot.in/2011/09/notmnist-dataset.html)
我们已经将原始数据集缩小为较小的版本,以便您可以轻松入门。下载 ZIP 文件并将其解压到包含数据集的文件夹中,1drv.ms/f/s!Av6fk5nQi2j-kniw-8GtP8sdWejs。
Python 的 pickle 模块实现了一种用于序列化和反序列化 Python 对象结构的算法。Pickling 是将 Python 对象层次结构转换为字节流的过程,unpickling 是其逆操作,将字节流转换回对象层次结构。Pickling(和 unpickling)也被称为 序列化、编组,[1] 或 扁平化。
首先,我们使用 maybe_pickle(..) 方法从以下文件夹列表加载 numpy.ndarray 格式的图像:
test_folders = ['./notMNIST_small/A', './notMNIST_small/B', './notMNIST_small/C', './notMNIST_small/D',
'./notMNIST_small/E', './notMNIST_small/F', './notMNIST_small/G', './notMNIST_small/H',
'./notMNIST_small/I', './notMNIST_small/J']
train_folders = ['./notMNIST_large_v2/A', './notMNIST_large_v2/B', './notMNIST_large_v2/C', './notMNIST_large_v2/D',
'./notMNIST_large_v2/E', './notMNIST_large_v2/F', './notMNIST_large_v2/G', './notMNIST_large_v2/H',
'./notMNIST_large_v2/I', './notMNIST_large_v2/J']
maybe_pickle(data_folders, min_num_images_per_class, force=False):
maybe_pickle 使用 load_letter 方法从单个文件夹加载图像到 ndarray:
def load_letter(folder, min_num_images):
image_files = os.listdir(folder)
dataset = np.ndarray(shape=(len(image_files), image_size, image_size),
dtype=np.float32)
num_images = 0
for image in image_files:
image_file = os.path.join(folder, image)
try:
image_data = (ndimage.imread(image_file).astype(float) -
pixel_depth / 2) / pixel_depth
if image_data.shape != (image_size, image_size):
raise Exception('Unexpected image shape: %s' % str(image_data.shape))
dataset[num_images, :, :] = image_data
num_images = num_images + 1
except IOError as e:
print('Could not read:', image_file, ':', e, '- it\'s ok, skipping.')
dataset = dataset[0:num_images, :, :]
if num_images < min_num_images:
raise Exception('Fewer images than expected: %d < %d' %
(num_images, min_num_images))
print('Dataset tensor:', dataset.shape)
print('Mean:', np.mean(dataset))
print('Standard deviation:', np.std(dataset))
return dataset
maybe_pickle 方法分别对两组文件夹 train_folders 和 test_folders 进行调用:
train_datasets = maybe_pickle(train_folders, 100)
test_datasets = maybe_pickle(test_folders, 50)
输出类似于以下屏幕截图。
第一张屏幕截图显示了 dataset_names 列表变量的值:

以下屏幕截图显示了 dataset_names 变量在 notMNIST_small 数据集中的值:

接下来,调用 merge_datasets,将每个字符的 pickle 文件合并为以下 ndarray:
-
valid_dataset -
valid_labels -
train_dataset -
train_labels
train_size = 1000
valid_size = 500
test_size = 500
valid_dataset, valid_labels, train_dataset, train_labels = merge_datasets(
train_datasets, train_size, valid_size)
_, _, test_dataset, test_labels = merge_datasets(test_datasets, test_size)
前面代码的输出列出如下:
Training dataset and labels shape: (1000, 28, 28) (1000,)
Validation dataset and labels shape: (500, 28, 28) (500,)
Testing dataset and labels shape: (500, 28, 28) (500,)
最后,通过将每个 ndarray 存储为键值对来创建 noMNIST.pickle 文件,其中键包括 train_dataset、train_labels、valid_dataset、valid_labels、test_dataset 和 test_labels,值为相应的 ndarray,如以下代码所示:
try:
f = open(pickle_file, 'wb')
save = {
'train_dataset': train_dataset,
'train_labels': train_labels,
'valid_dataset': valid_dataset,
'valid_labels': valid_labels,
'test_dataset': test_dataset,
'test_labels': test_labels,
}
pickle.dump(save, f, pickle.HIGHEST_PROTOCOL)
f.close()
except Exception as e:
print('Unable to save data to', pickle_file, ':', e)
raise
这是生成 notMNIST.pickle 文件的完整代码:
from __future__ import print_function
import numpy as np
import os
from scipy import ndimage
from six.moves import cPickle as pickle
data_root = '.' # Change me to store data elsewhere
num_classes = 10
np.random.seed(133)
test_folders = ['./notMNIST_small/A', './notMNIST_small/B', './notMNIST_small/C', './notMNIST_small/D',
'./notMNIST_small/E', './notMNIST_small/F', './notMNIST_small/G', './notMNIST_small/H',
'./notMNIST_small/I', './notMNIST_small/J']
train_folders = ['./notMNIST_large_v2/A', './notMNIST_large_v2/B', './notMNIST_large_v2/C', './notMNIST_large_v2/D',
'./notMNIST_large_v2/E', './notMNIST_large_v2/F', './notMNIST_large_v2/G', './notMNIST_large_v2/H',
'./notMNIST_large_v2/I', './notMNIST_large_v2/J']
image_size = 28 # Pixel width and height.
pixel_depth = 255.0
def load_letter(folder, min_num_images):
image_files = os.listdir(folder)
dataset = np.ndarray(shape=(len(image_files), image_size, image_size),
dtype=np.float32)
num_images = 0
for image in image_files:
image_file = os.path.join(folder, image)
try:
image_data = (ndimage.imread(image_file).astype(float) -
pixel_depth / 2) / pixel_depth
if image_data.shape != (image_size, image_size):
raise Exception('Unexpected image shape: %s' % str(image_data.shape))
dataset[num_images, :, :] = image_data
num_images = num_images + 1
except IOError as e:
print('Could not read:', image_file, ':', e, '- it\'s ok, skipping.')
dataset = dataset[0:num_images, :, :]
if num_images < min_num_images:
raise Exception('Fewer images than expected: %d < %d' %
(num_images, min_num_images))
print('Dataset tensor:', dataset.shape)
print('Mean:', np.mean(dataset))
print('Standard deviation:', np.std(dataset))
return dataset
def maybe_pickle(data_folders, min_num_images_per_class, force=False):
dataset_names = []
for folder in data_folders:
set_filename = folder + '.pickle'
dataset_names.append(set_filename)
if os.path.exists(set_filename) and not force:
print('%s already present - Skipping pickling.' % set_filename)
else:
print('Pickling %s.' % set_filename)
dataset = load_letter(folder, min_num_images_per_class)
try:
with open(set_filename, 'wb') as f:
#pickle.dump(dataset, f, pickle.HIGHEST_PROTOCOL)
print(pickle.HIGHEST_PROTOCOL)
pickle.dump(dataset, f, 2)
except Exception as e:
print('Unable to save data to', set_filename, ':', e)
return dataset_names
def make_arrays(nb_rows, img_size):
if nb_rows:
dataset = np.ndarray((nb_rows, img_size, img_size), dtype=np.float32)
labels = np.ndarray(nb_rows, dtype=np.int32)
else:
dataset, labels = None, None
让我们看看之前创建的 pickle 文件如何加载数据并运行具有一个隐藏层的网络。
首先,我们将从 notMNIST.pickle 文件中加载训练、测试和验证数据集(ndarray):
with open(pickle_file, 'rb') as f:
save = pickle.load(f)
training_dataset = save['train_dataset']
training_labels = save['train_labels']
validation_dataset = save['valid_dataset']
validation_labels = save['valid_labels']
test_dataset = save['test_dataset']
test_labels = save['test_labels']
print 'Training set', training_dataset.shape, training_labels.shape
print 'Validation set', validation_dataset.shape, validation_labels.shape
print 'Test set', test_dataset.shape, test_labels.shape
您将看到类似于以下内容的输出:
Training set (1000, 28, 28) (1000,)
Validation set (500, 28, 28) (500,)
Test set (500, 28, 28) (500,)
接下来,我们将 dataset 重塑为二维数组,以便数据能更容易地通过 TensorFlow 进行处理:
def reformat(dataset, labels):
dataset = dataset.reshape((-1, image_size * image_size)).astype(np.float32)
# Map 0 to [1.0, 0.0, 0.0 ...], 1 to [0.0, 1.0, 0.0 ...]
labels = (np.arange(num_of_labels) == labels[:, None]).astype(np.float32)
return dataset, labels
train_dataset, train_labels = reformat(training_dataset, training_labels)
valid_dataset, valid_labels = reformat(validation_dataset, validation_labels)
test_dataset, test_labels = reformat(test_dataset, test_labels)
print 'Training dataset shape', train_dataset.shape, train_labels.shape
print 'Validation dataset shape', valid_dataset.shape, valid_labels.shape
print 'Test dataset shape', test_dataset.shape, test_labels.shape
您将看到以下输出:
Training dataset shape (1000, 784) (1000, 10)
Validation dataset shape (500, 784) (500, 10)
Test dataset shape (500, 784) (500, 10)
接下来,我们定义图形,返回将加载所有变量的内容。
每个权重和偏差的大小列在这里,其中 image_size = 28 和 no_of_neurons = 1024。
隐藏层中神经元的数量应该是最优的。神经元太少会导致准确度较低,而神经元太多则会导致过拟合。
| 神经网络中的层 | 权重 | 偏差 |
|---|---|---|
| 1 | 行 = 28 x 28 = 784 列 = 1024 | 1024 |
| 2 | 行 = 1024 列 = 10 | 10 |
我们将初始化 TensorFlow 图,并从训练、验证和测试数据集及标签中初始化占位符变量。
我们还将为两个层定义权重和偏置:
graph = tf.Graph()
no_of_neurons = 1024
with graph.as_default():
# Placeholder that will be fed
# at run time with a training minibatch in the session
tf_train_dataset = tf.placeholder(tf.float32,
shape=(batch_size, image_size * image_size))
tf_train_labels = tf.placeholder(tf.float32, shape=(batch_size, num_of_labels))
tf_valid_dataset = tf.constant(valid_dataset)
tf_test_dataset = tf.constant(test_dataset)
# Variables.
w1 = tf.Variable(tf.truncated_normal([image_size * image_size, no_of_neurons]))
b1 = tf.Variable(tf.zeros([no_of_neurons]))
w2 = tf.Variable(
tf.truncated_normal([no_of_neurons, num_of_labels]))
b2 = tf.Variable(tf.zeros([num_of_labels]))
接下来,我们定义隐藏层张量和计算得到的 logit:
hidden1 = tf.nn.relu(tf.matmul(tf_train_dataset, w1) + b1)
logits = tf.matmul(hidden1, w2) + b2
我们网络的损失函数将基于对交叉熵应用softmax函数后的logits:
loss = tf.reduce_mean(
tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=tf_train_labels))
# Training computation.
loss = tf.reduce_mean(
tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=tf_train_labels))
# Optimizer.
optimizer = tf.train.GradientDescentOptimizer(0.5).minimize(loss)
接下来,我们计算logits(预测值);请注意,我们使用softmax来归一化logits:
train_prediction = tf.nn.softmax(logits)
计算测试和验证预测。请注意,此处使用的激活函数是RELU,用于计算w1和b1:
tf.nn.relu(tf.matmul(tf_valid_dataset, w1) + b1)
valid_prediction = tf.nn.softmax(
tf.matmul( tf.nn.relu(tf.matmul(tf_valid_dataset, w1) + b1),
w2
) + b2)
test_prediction = tf.nn.softmax(
tf.matmul(tf.nn.relu(tf.matmul(tf_test_dataset, w1) + b1), w2) + b2)
现在我们将创建一个 TensorFlow 会话,并将通过神经网络加载的数据集传递进去:
with tf.Session(graph=graph) as session:
tf.initialize_all_variables().run()
print("Initialized")
for step in xrange(num_steps):
offset = (step * batch_size) % (train_labels.shape[0] - batch_size)
# Generate a minibatch.
batch_data = train_dataset[offset:(offset + batch_size), :]
batch_labels = train_labels[offset:(offset + batch_size), :]
feed_dict = {tf_train_dataset: batch_data, tf_train_labels: batch_labels}
_, l, predictions = session.run(
[optimizer, loss, train_prediction], feed_dict=feed_dict)
minibatch_accuracy = accuracy(predictions, batch_labels)
validation_accuracy = accuracy(
valid_prediction.eval(), valid_labels)
if (step % 10 == 0):
print("Minibatch loss at step", step, ":", l)
print("Minibatch accuracy: %.1f%%" % accuracy(predictions, batch_labels))
print("Validation accuracy: %.1f%%" % validation_accuracy)
minibatch_acc.append( minibatch_accuracy)
validation_acc.append( validation_accuracy)
t = [np.array(minibatch_acc)]
t.append(validation_acc)
完整代码可以在github.com/rajdeepd/neuralnetwork-programming/blob/ed1/ch02/nomnist/singlelayer-neural_network.py找到。
完整代码可以在前述 GitHub 链接中找到。请注意,我们正在将验证精度和小批量精度追加到一个数组中,稍后我们将绘制这个数组:
print("Test accuracy: %.1f%%" % accuracy(test_prediction.eval(), test_labels))
title = "NotMNIST DataSet - Single Hidden Layer - 1024 neurons Activation function: RELU"
label = ['Minibatch Accuracy', 'Validation Accuracy']
draw_plot(x, t, title, label)
让我们看看前面代码生成的图表:

小批量精度在第 8 次迭代时达到 100,而验证精度停留在 60。
分析激活函数对前馈网络精度的影响
在之前的示例中,我们使用了RELU作为激活函数。TensorFlow 支持多种激活函数。我们来看看这些激活函数如何影响验证精度。我们将生成一些随机值:
x_val = np.linspace(start=-10., stop=10., num=1000)
然后生成激活输出:
# ReLU activation
y_relu = session.run(tf.nn.relu(x_val))
# ReLU-6 activation
y_relu6 = session.run(tf.nn.relu6(x_val))
# Sigmoid activation
y_sigmoid = session.run(tf.nn.sigmoid(x_val))
# Hyper Tangent activation
y_tanh = session.run(tf.nn.tanh(x_val))
# Softsign activation
y_softsign = session.run(tf.nn.softsign(x_val))
# Softplus activation
y_softplus = session.run(tf.nn.softplus(x_val))
# Exponential linear activation
y_elu = session.run(tf.nn.elu(x_val))
绘制激活与x_val的关系:
plt.plot(x_val, y_softplus, 'r--', label='Softplus', linewidth=2)
plt.plot(x_val, y_relu, 'b:', label='RELU', linewidth=2)
plt.plot(x_val, y_relu6, 'g-.', label='RELU6', linewidth=2)
plt.plot(x_val, y_elu, 'k-', label='ELU', linewidth=1)
plt.ylim([-1.5,7])
plt.legend(loc='top left')
plt.title('Activation functions', y=1.05)
plt.show()
plt.plot(x_val, y_sigmoid, 'r--', label='Sigmoid', linewidth=2)
plt.plot(x_val, y_tanh, 'b:', label='tanh', linewidth=2)
plt.plot(x_val, y_softsign, 'g-.', label='Softsign', linewidth=2)
plt.ylim([-1.5,1.5])
plt.legend(loc='top left')
plt.title('Activation functions with Vanishing Gradient', y=1.05)
plt.show()
以下截图展示了绘图结果:

比较带有消失梯度的激活函数的图表如下:

现在让我们看看激活函数如何影响 NotMNIST 数据集的验证精度。
我们修改了之前的示例,以便在main()中将激活函数作为参数传递:
RELU = 'RELU'
RELU6 = 'RELU6'
CRELU = 'CRELU'
SIGMOID = 'SIGMOID'
ELU = 'ELU'
SOFTPLUS = 'SOFTPLUS'
def activation(name, features):
if name == RELU:
return tf.nn.relu(features)
if name == RELU6:
return tf.nn.relu6(features)
if name == SIGMOID:
return tf.nn.sigmoid(features)
if name == CRELU:
return tf.nn.crelu(features)
if name == ELU:
return tf.nn.elu(features)
if name == SOFTPLUS:
return tf.nn.softplus(features)
run()函数的定义包括我们之前定义的登录:
batch_size = 128
#activations = [RELU, RELU6, SIGMOID, CRELU, ELU, SOFTPLUS]
activations = [RELU, RELU6, SIGMOID, ELU, SOFTPLUS]
plot_loss = False
def run(name):
print(name)
with open(pickle_file, 'rb') as f:
save = pickle.load(f)
training_dataset = save['train_dataset']
training_labels = save['train_labels']
validation_dataset = save['valid_dataset']
validation_labels = save['valid_labels']
test_dataset = save['test_dataset']
test_labels = save['test_labels']
train_dataset, train_labels = reformat(training_dataset, training_labels)
valid_dataset, valid_labels = reformat(validation_dataset,
validation_labels)
test_dataset, test_labels = reformat(test_dataset, test_labels)
graph = tf.Graph()
no_of_neurons = 1024
with graph.as_default():
tf_train_dataset = tf.placeholder(tf.float32,
shape=(batch_size, image_size * image_size))
tf_train_labels = tf.placeholder(tf.float32, shape=(batch_size,
num_of_labels))
tf_valid_dataset = tf.constant(valid_dataset)
tf_test_dataset = tf.constant(test_dataset)
# Define Variables.
# Training computation...
# Optimizer ..
# Predictions for the training, validation, and test data.
train_prediction = tf.nn.softmax(logits)
valid_prediction = tf.nn.softmax(
tf.matmul(activation(name,tf.matmul(tf_valid_dataset, w1) + b1), w2) + b2)
test_prediction = tf.nn.softmax(
tf.matmul(activation(name,tf.matmul(tf_test_dataset, w1) + b1), w2) + b2)
num_steps = 101
minibatch_acc = []
validation_acc = []
loss_array = []
with tf.Session(graph=graph) as session:
tf.initialize_all_variables().run()
print("Initialized")
for step in xrange(num_steps):
offset = (step * batch_size) % (train_labels.shape[0] - batch_size)
# Generate a minibatch.
batch_data = train_dataset[offset:(offset + batch_size), :]
batch_labels = train_labels[offset:(offset + batch_size), :]
feed_dict = {tf_train_dataset: batch_data, tf_train_labels: batch_labels}
_, l, predictions = session.run(
[optimizer, loss, train_prediction], feed_dict=feed_dict)
minibatch_accuracy = accuracy(predictions, batch_labels)
validation_accuracy = accuracy(
valid_prediction.eval(), valid_labels)
if (step % 10 == 0):
print("Minibatch loss at step", step, ":", l)
print("Minibatch accuracy: %.1f%%" % accuracy(predictions,
batch_labels))
print("Validation accuracy: %.1f%%" % accuracy(
valid_prediction.eval(), valid_labels))
minibatch_acc.append(minibatch_accuracy)
validation_acc.append(validation_accuracy)
loss_array.append(l)
print("Test accuracy: %.1f%%" % accuracy(test_prediction.eval(),
test_labels))
return validation_acc, loss_array
前述列表中的图表展示如下截图:

不同激活函数的验证精度
如前面的图表所示,RELU 和 RELU6 提供了最大验证精度,接近 60%。现在,让我们看看不同激活函数在训练过程中损失如何变化:

不同激活函数的训练损失随步骤变化的图示
对于大多数激活函数,训练损失收敛到零,尽管 RELU 在短期内效果最差。
总结
在这一章中,我们构建了第一个神经网络,它仅为前馈神经网络,并使用它进行鸢尾花数据集的分类,后来又应用于 NotMNIST 数据集。你学习了像激活函数等如何影响预测的验证准确性。
在下一章,我们将探讨卷积神经网络,它对于图像数据集更为先进且有效。
第三章:神经网络的优化
深度学习中的许多应用需要解决优化问题。优化是指将我们所处理的对象带向其最终状态。通过优化过程解决的问题必须提供数据,提供模型常量和参数,函数中描述总体目标函数及一些约束条件。
在本章中,我们将探讨 TensorFlow 流水线和 TensorFlow 库提供的各种优化模型。涵盖的主题列表如下:
-
优化基础
-
优化器的类型
-
梯度下降
-
选择正确的优化器
什么是优化?
寻找极大值或极小值的过程基于约束条件。选择优化算法对深度学习模型的影响可能意味着在几分钟、几小时或几天内产生不同的结果。
优化是深度学习的核心。大多数学习问题都可以简化为优化问题。让我们设想,我们正在为一组数据解决一个问题。使用这些预处理过的数据,我们通过解决一个优化问题来训练模型,优化模型的权重,以使其符合所选择的损失函数和某些正则化函数。
模型的超参数在模型的高效训练中起着重要作用。因此,使用不同的优化策略和算法来测量模型超参数的合适和值是至关重要的,这些超参数会影响模型的学习过程,并最终影响模型的输出。
优化器的类型
首先,我们将查看优化算法的高级类别,然后深入研究各个优化器。
一阶优化算法使用梯度值相对于参数来最小化或最大化损失函数。常用的一阶优化算法是梯度下降。在这里,一阶导数告诉我们函数在某个特定点是增加还是减少。一阶导数给出了一条切线,与误差曲面上的某点相切。
函数的导数依赖于单一变量,而函数的梯度依赖于多个变量。
二阶优化算法使用二阶导数,也称为海森矩阵,来最小化或最大化给定的损失函数。这里,海森矩阵是二阶偏导数的矩阵。二阶导数的计算代价较高,因此不常使用。二阶导数告诉我们一阶导数是增加还是减少,从而反映了函数的曲率。二阶导数给出了一个二次曲面,它与误差曲面的形状相切。
二阶导数计算成本较高,但二阶优化方法的优势在于它不会忽略或忽视曲面的弯曲性。同时,逐步性能也更好。在选择优化方法时,关键要注意,一级优化方法计算简单且时间消耗较少,在大数据集上收敛速度较快。而二阶方法仅在已知二阶导数时更快,且在时间和内存上的计算开销较大,速度较慢且计算代价较高。
二阶优化方法有时比一阶梯度下降方法表现得更好,因为二阶方法永远不会在收敛缓慢的路径上卡住,即不会卡在鞍点附近,而梯度下降有时会卡住并无法收敛。
梯度下降
梯度下降是一种最小化函数的算法。一组参数定义了一个函数,梯度下降算法从初始参数值集开始,逐步逼近最小化该函数的参数值集。
这种迭代最小化通过微积分实现,沿函数梯度的负方向一步步推进,如下图所示:

梯度下降是最成功的优化算法。如前所述,它用于在神经网络中更新权重,从而最小化损失函数。接下来,我们将讨论一个重要的神经网络方法——反向传播。在这个过程中,我们首先进行前向传播,计算输入与相应权重的点积,然后将激活函数应用于加权和,将输入转化为输出,并向模型中添加非线性因素,使得模型能够学习几乎任何任意的功能映射。
接下来,我们在神经网络中进行反向传播,传递误差项并使用梯度下降更新权重值,如下图所示:

梯度下降的不同变种
标准梯度下降,也称为批量梯度下降,将计算整个数据集的梯度,但只进行一次更新。因此,对于那些极大且无法完全加载到内存中的数据集,它可能会非常缓慢且难以控制。接下来,让我们看看能解决这个问题的算法。
随机梯度下降(SGD)在每个训练样本上执行参数更新,而小批量梯度下降则在每个批次的 n 个训练样本上执行更新。SGD 的问题在于,由于频繁的更新和波动,最终会使收敛到准确最小值变得复杂,并且由于持续的波动,可能会不断超出目标。而小批量梯度下降则解决了这个问题,它通过减少参数更新中的方差,从而实现更好且稳定的收敛。SGD 和小批量梯度下降可以互换使用。
梯度下降的总体问题包括选择合适的学习率,以避免在较小值时出现收敛缓慢,或者在较大值时出现发散,并且对所有参数更新应用相同的学习率。如果数据稀疏,我们可能不希望对所有参数进行相同程度的更新。最后,还需要处理鞍点问题。
优化梯度下降的算法
现在,我们将探讨优化梯度下降的各种方法,以便为每个参数计算不同的学习率、计算动量并防止学习率衰减。
为了解决 SGD 高方差振荡的问题,发现了一种叫做 动量 的方法;它通过沿着正确的方向进行加速,并在无关方向上减缓振荡,来加速 SGD。基本上,它会将上一步更新向量的一部分加到当前更新向量中。动量值通常设置为 0.9。动量可以加速收敛并减少振荡,使收敛更稳定。
Nesterov 加速梯度 解释了当我们达到最小值时,也就是曲线的最低点,动量相当大,且由于动量过大,它并不会在该点减速,这可能会导致错过最小值并继续上升。Nesterov 提出了我们首先基于之前的动量进行一次较大的跳跃,然后计算梯度,接着进行修正,从而更新参数。这个更新可以防止我们过快地前进,从而错过最小值,并且使模型对变化更敏感。
Adagrad 允许学习率根据参数自适应调整。因此,对于不常见的参数,它执行较大的更新,而对于常见的参数,它执行较小的更新。因此,Adagrad 非常适合处理稀疏数据。其主要缺点是学习率总是在逐渐减小和衰减。使用 AdaDelta 可以解决学习率衰减的问题。
AdaDelta 解决了 AdaGrad 中学习率逐渐减小的问题。在 AdaGrad 中,学习率是通过将 1 除以平方根和计算的。在每个阶段,我们都会将另一个平方根加入总和,这会导致分母不断减小。现在,AdaDelta 不再对所有以前的平方根求和,而是使用滑动窗口,这允许总和减小。
自适应矩估计(Adam)为每个参数计算自适应学习率。与 AdaDelta 类似,Adam 不仅存储过去平方梯度的衰减平均值,还存储每个参数的动量变化。Adam 在实践中表现良好,是目前最常用的优化方法之一。
以下两张图(图片来源:Alec Radford)展示了前面描述的优化算法的优化行为。我们看到它们在损失曲面的等高线上的行为随时间变化。Adagrad、RMsprop 和 Adadelta 几乎迅速朝着正确的方向前进并快速收敛,而 momentum 和 NAG 则偏离轨道。由于 NAG 通过提前查看并向最小值移动,从而提高响应能力,很快就能修正其方向。

第二张图展示了算法在鞍点的行为。SGD、Momentum 和 NAG 在打破对称性方面遇到挑战,但它们最终能够慢慢摆脱鞍点,而 Adagrad、Adadelta 和 RMsprop 则沿着负斜率下坡,从下图可以看到:

选择哪个优化器
如果输入数据稀疏,或者我们希望在训练复杂的神经网络时实现快速收敛,使用自适应学习率方法能得到最佳结果。我们还不需要调整学习率。对于大多数情况,Adam 通常是一个不错的选择。
通过示例进行优化
让我们以线性回归为例,我们通过最小化从直线到每个数据点的距离的平方,来找到最佳拟合的直线。这就是我们称其为最小二乘回归的原因。本质上,我们将问题表述为一个优化问题,其中我们尝试最小化损失函数。
让我们设置输入数据并查看散点图:
# input data
xData = np.arange(100, step=.1)
yData = xData + 20 * np.sin(xData/10)

定义数据大小和批次大小:
# define the data size and batch sizenSamples = 1000
batchSize = 100
我们将需要调整数据大小以符合 TensorFlow 输入格式,如下所示:
# resize input for tensorflowxData = np.reshape(xData, (nSamples, 1))
yData = np.reshape(yData, (nSamples, 1))
以下范围初始化了weights和bias,并描述了线性模型和损失函数:
with tf.variable_scope("linear-regression-pipeline"):
W = tf.get_variable("weights", (1,1), initializer=tf.random_normal_initializer())
b = tf.get_variable("bias", (1, ), initializer=tf.constant_initializer(0.0))
*#* model
yPred = tf.matmul(X, W) + b
*#* loss function
loss = tf.reduce_sum((y - yPred)**2/nSamples)
然后我们设置优化器来最小化损失:
# set the optimizer
#optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001).minimize(loss)
#optimizer = tf.train.AdamOptimizer(learning_rate=.001).minimize(loss)
#optimizer = tf.train.AdadeltaOptimizer(learning_rate=.001).minimize(loss)
#optimizer = tf.train.AdagradOptimizer(learning_rate=.001).minimize(loss)
#optimizer = tf.train.MomentumOptimizer(learning_rate=.001, momentum=0.9).minimize(loss)
#optimizer = tf.train.FtrlOptimizer(learning_rate=.001).minimize(loss)
optimizer = tf.train.RMSPropOptimizer(learning_rate=.001).minimize(loss) We then select the mini batch and run the optimizers errors = []
with tf.Session() as sess:
# init variables
sess.run(tf.global_variables_initializer())
for _ in range(1000):
# select mini batchindices = np.random.choice(nSamples, batchSize)
xBatch, yBatch = xData[indices], yData[indices]
# run optimizer_, lossVal = sess.run([optimizer, loss], feed_dict={X: xBatch, y: yBatch})
errors.append(lossVal)
plt.plot([np.mean(errors[i-50:i]) for i in range(len(errors))])
plt.show()
plt.savefig("errors.png")
上述代码的输出如下:

我们还得到一个滑动曲线,如下所示:

总结
在本章中,我们学习了优化技术的基本原理和不同类型。优化是一个复杂的主题,很多因素取决于我们数据的性质和大小。此外,优化还取决于权重矩阵。这些优化器大多针对图像分类或预测任务进行了训练和调整。然而,对于定制或新的使用案例,我们需要通过反复试验来确定最佳解决方案。
第四章:卷积神经网络
卷积网络(参考 LeCun[1],2013),也被称为卷积神经网络
或CNNs,是一种特定类型的神经网络,它使用网格状拓扑处理数据。举例来说,时间序列数据可以看作是一个 1D 网格,按规律时间间隔采样,或者图像数据是一个二维像素网格。卷积神经网络的名字意味着该网络采用了一种叫做卷积的数学运算。卷积是一种特定的线性运算。卷积网络是使用卷积(一个数学运算)代替至少一层中的常规矩阵乘法的神经网络。
首先,我们将描述卷积的数学运算。然后我们将讨论池化的概念以及它如何帮助 CNN。我们还将探讨在 TensorFlow 中实现卷积网络。
在本章的末尾,我们将使用 TensorFlow 的 CNN 实现来对斯坦福数据集中的狗和猫进行分类。
Lecun[1] : yann.lecun.com/exdb/lenet/
在本章中,我们将涵盖以下主题:
-
CNN 概述与直觉
-
卷积运算
-
池化
-
使用卷积网络进行图像分类
CNN 概述与直觉
CNN 由多个卷积层、池化层和最终的全连接层组成。这比我们在第二章中讨论的纯前馈网络更高效,深度前馈网络。

上述图示通过卷积层 | 最大池化 | 卷积 | 最大池化 | 全连接层进行,这就是一个 CNN 架构
单一卷积层计算
首先让我们直观地讨论卷积层的计算方式。卷积层的参数包括一组可学习的滤波器(也称为张量)。每个滤波器在空间上很小(深度、宽度和高度),但它会延伸到输入体积(图像)的完整深度。卷积神经网络的第一层滤波器通常大小为 5 x 5 x 3(即宽度和高度为五个像素,深度为三个,因为图像有三个颜色通道的深度)。在前向传播过程中,滤波器会在输入体积的宽度和高度上滑动(或卷积),并在任何点上计算滤波器与输入的点积。当滤波器在输入体积的宽度和高度上滑动时,它会产生一个二维激活图,显示该滤波器在每个空间位置的响应。网络将学习到,当它们看到某种视觉特征时,滤波器会激活,例如在第一层看到某种方向的边缘或某种颜色的斑点,或者在网络的更高层检测到整个蜂窝状或轮状的模式。一旦我们在每个卷积层中拥有一整套滤波器(例如,12 个滤波器),每个滤波器都会产生一个独立的二维激活图。我们将这些激活图沿深度维度堆叠在一起,生成输出体积。

32 x 32 像素的图像被 5 x 5 滤波器卷积处理
上面的图像展示了一个 32 x 32 x 3 的图像,在其上应用了一个 5 x 5 x 3 的滤波器。

每个滤波器与图像块之间的点积结果生成一个单一的数值
接下来,让我们对上面创建的滤波器进行卷积处理,遍历整个图像,每次移动一个像素。最终的输出将是 28 x 28 x 1。这被称为激活图。

通过对图像应用滤波器生成的激活图
考虑将两个滤波器依次使用,这将产生两个大小为 28 x 28 x 1 的激活图。

对单个图像应用两个滤波器会生成两个激活图
如果我们使用六个这样的滤波器,最终得到的图像大小将是 28 x 28 x 3。卷积神经网络(ConvNet)是由这样的卷积层组成,并交替使用激活函数,如Relu。

将六个 5 x 5 x 3 的滤波器应用到图像上,结果生成 28 x 28 x 6 的激活图
让我们按照 TensorFlow 的术语正式定义卷积神经网络(CNN)。
定义:卷积神经网络(CNN)是一种至少包含一层(tf.nn.conv2d)的神经网络,该层对输入和可配置的卷积核进行卷积运算,生成该层的输出。卷积通过将卷积核(滤波器)应用于输入层(张量)中的每一个点来进行。它通过滑动卷积核在输入张量上生成一个过滤后的输出。
使用案例:以下示例是一个边缘检测滤波器,应用于输入图像,使用卷积

通过在输入图像上应用卷积核进行边缘检测
CNN 遵循一个过程,匹配的信息结构类似于猫的条纹皮层中的细胞布局结构。当信号通过猫的条纹皮层时,某些层会在视觉模式被突出时发出信号。例如,当水平线穿过某一层时,该层的细胞会激活(增加其输出信号)。CNN 会表现出类似的行为,其中神经元簇会根据训练中学习到的模式进行激活。基于预标记数据进行训练后,CNN 会有某些层在水平/垂直线通过时激活。
匹配水平/垂直线将是一个有用的神经网络架构,但 CNN 通过将多个简单模式层叠来匹配复杂模式。这些模式被称为滤波器或卷积核。训练的目标是调整这些卷积核权重,以最小化损失函数。训练这些滤波器是通过结合多个层并使用梯度下降或其他优化技术来学习权重。
TensorFlow 中的 CNN
CNN 由卷积层(通过 tf.nn.conv2d 定义)、非线性层(tf.nn.relu)、最大池化层(tf.nn.max_pool)和全连接层(tf.matmul)组成。以下图像显示了典型的 CNN 层及其在 TensorFlow 中的对应实现:

将 CNN 层映射到 TensorFlow 函数
TensorFlow 中的图像加载
现在让我们来看一下 TensorFlow 如何加载图像。我们定义一个包含三个小图像的常量数组,并将它们加载到会话中:
sess = tf.InteractiveSession()
image_batch = tf.constant([
[ # First Image
[[255, 0, 0], [255, 0, 0], [0, 255, 0]],
[[255, 0, 0], [255, 0, 0], [0, 255, 0]]
],
[ # Second Image
[[0, 0, 0], [0, 255, 0], [0, 255, 0]],
[[0, 0, 0], [0, 255, 0], [0, 255, 0]]
],
[ # Third Image
[[0, 0, 255], [0, 0, 255], [0, 0, 255]],
[[0, 0, 255], [0, 0, 255], [0, 0, 255]]
]
])
print(image_batch.get_shape())
print(sess.run(image_batch)[1][0][0])
上述代码的输出显示了张量的形状以及第一张图像的第一个像素。在这个示例代码中,创建了一个包含三张图像的图像数组。每张图像的高度为两像素,宽度为三像素,并采用 RGB 色彩空间。示例代码的输出显示图像的数量作为第一个维度的大小,Dimension(1)。每张图像的高度是第二个维度的大小,Dimension(2),每张图像的宽度是第三个维度,Dimension(3),而颜色通道的数组大小是最后一个维度,Dimension(3):
(3, 2, 3, 3)
[255 0 0]
卷积操作
卷积操作是卷积神经网络(CNN)的关键组成部分;这些操作使用输入张量和滤波器来计算输出。关键在于决定可用的参数来调整它们。
假设我们在追踪一个物体的位置。它的输出是一个单一的 x(t),表示物体在时间 t 的位置。x 和 t 都是实数值,也就是说,我们可以在任何时刻获得不同的读数。假设我们的测量有噪声。为了获得一个较少噪声的物体位置估计,我们希望将多个测量值进行平均。最近的测量对我们来说更为相关;我们希望这成为一个加权平均,赋予最近的测量更高的权重。我们可以使用加权函数 w(a) 来计算,其中 a 是测量的时间(测量发生的时刻)。
如果我们在每个时刻应用加权平均操作,我们将得到一个新的函数,提供物体位置的平滑估计:

这个操作称为卷积。卷积操作用星号表示:

这里,
-
w 是卷积核
-
x 是输入
-
s 是输出,也称为特征图
图像上的卷积
如果我们使用一个二维图像 I 作为输入,我们可能还想使用一个二维卷积核 K。前面的方程将如下所示:

由于卷积函数是可交换的,我们可以将前面的方程写成如下形式:

将 i - m 和 j - n 改为加法被称为互相关,因为这正是 TensorFlow 实现的:

让我们定义一个简单的输入和卷积核,并在 TensorFlow 中运行 conv2d 操作。我们来看一下简单的图像输入和卷积核输入。下图展示了一个基本的图像、卷积核和应用卷积操作后的期望输出:

基本图像及应用于其上的卷积核示例
现在我们来看一下当步幅为 1, 1, 1, 1 时如何得到输出:

通过将卷积核应用于输入来计算输出
接下来,我们将在 TensorFlow 中实现相同的操作:
i = tf.constant([
[1.0, 1.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 1.0, 1.0],
[0.0, 0.0, 1.0, 1.0, 0.0],
[0.0, 0.0, 1.0, 0.0, 0.0]], dtype=tf.float32)
k = tf.constant([
[1.0, 0.0, 1.0],
[0.0, 1.0, 0.0],
[1.0, 0.0, 1.0]
], dtype=tf.float32),
kernel = tf.reshape(k, [3, 3, 1, 1], name='kernel')
image = tf.reshape(i, [1, 4, 5, 1], name='image')
res = tf.squeeze(tf.nn.conv2d(image, kernel, strides=[1, 1, 1, 1], padding="VALID"))
# VALID means no padding
with tf.Session() as sess:
print sess.run(res)
上述代码段的输出如下——这与我们手动计算的结果相同:
[[ 3\. 3\. 3.]
[ 2\. 2\. 4.]]
步幅
卷积的主要目的是减少图像的维度(宽度、高度和通道数)。图像越大,所需的处理时间就越长。
strides 参数使卷积核在图像中跳过一些像素,并且不将这些像素包含在输出中。strides 参数决定了当使用更大的图像和更复杂的卷积核时,卷积操作如何与卷积核一起工作。由于卷积是将卷积核滑动输入,因此它使用 strides 参数来确定它如何遍历输入,而不是遍历输入的每一个元素。
让我们看一下下面的示例,我们将一个 3 x 3 x 1 的卷积核以步幅 1, 3, 3, 1 移动到一个 6 x 6 x 1 的图像上:

第一步,卷积核以步幅 1,3,3,1 滑动
卷积核在步骤 3 和 4 中跨越以下元素:

卷积核在输入上滑动的步骤 3 和 4
让我们在 TensorFlow 中实现这个,输出将是一个 4 x 4 x 1 的张量:
import tensorflow as tf
def main():
session = tf.InteractiveSession()
input_batch = tf.constant([
[ # First Input (6x6x1)
[[0.0], [1.0], [2.0], [3.0], [4.0], [5.0]],
[[0.1], [1.1], [2.1], [3.1], [4.1], [5.1]],
[[0.2], [1.2], [2.2], [3.2], [4.2], [5.2]],
[[0.3], [1.3], [2.3], [3.3], [4.3], [5.3]],
[[0.4], [1.4], [2.4], [3.4], [4.4], [5.4]],
[[0.5], [1.5], [2.5], [3.5], [4.5], [5.5]],
],
])
kernel = tf.constant([ # Kernel (3x3x1)
[[[0.0]], [[0.5]], [[0.0]]],
[[[0.0]], [[0.5]], [[0.0]]],
[[[0.0]], [[0.5]], [[0.0]]]
])
# NOTE: the change in the size of the strides parameter.
conv2d = tf.nn.conv2d(input_batch, kernel, strides=[1, 3, 3, 1], padding='SAME')
conv2d_output = session.run(conv2d)
print(conv2d_output)
if __name__ == '__main__':
main()
输出类似于下面的列表,其中 1、3、3、1 的步幅在前面的图像中形成了四个红色框,并与卷积核相乘:
[[[[ 1.64999998][ 6.1500001 ]]
[[ 2.0999999 ][ 6.60000038]]]]
池化
池化层通过减少输入张量的大小来帮助防止过拟合并提高性能。通常,它们用于缩小输入,保留重要信息。与 tf.nn.conv2d 相比,池化是一种更快速的输入大小缩减机制。
TensorFlow 支持以下池化机制:
-
平均值
-
最大池化
-
最大值与索引
每个池化操作使用大小为 ksize 的矩形窗口,窗口之间的间隔由 strides 决定。如果 strides 全部为 1(1, 1, 1, 1),则每个窗口都会被使用;如果 strides 全部为 2(1, 2, 2, 1),则每个维度中会跳过一个窗口,以此类推。
最大池化
以下定义的函数提供了对输入 4D 张量的最大池化 tf.nn.max_pool:
max_pool(
value, ksize, strides, padding, data_format='NHWC', name=None
)
上述参数的解释如下:
-
value:这是一个形状为 [batch, height, width, channels] 的 4D 张量,类型为tf.float32,需要进行最大池化操作。 -
ksize:这是一个整数列表,length >= 4。输入张量每个维度的窗口大小。 -
strides:这是一个整数列表,length >= 4。每个维度的滑动窗口步幅。 -
padding:这是一个字符串,可以是VALID或SAME。填充算法。以下部分解释了VALID和SAME填充。

-
data_format:这是一个字符串,支持NHWC和NCHW格式。 -
name:这是操作的可选名称。
示例代码
以下代码演示了使用 VALID 填充模式对张量进行最大池化:
import tensorflow as tf
batch_size=1
input_height = 3
input_width = 3
input_channels = 1
def main():
sess = tf.InteractiveSession()
layer_input = tf.constant([
[
[[1.0], [0.2], [2.0]],
[[0.1], [1.2], [1.4]],
[[1.1], [0.4], [0.4]]
]
])
# The strides will look at the entire input by using the image_height and image_width
kernel = [batch_size, input_height, input_width, input_channels]
max_pool = tf.nn.max_pool(layer_input, kernel, [1, 1, 1, 1], "VALID")
print(sess.run(max_pool))
if __name__ == '__main__':
main()
上述代码的输出将在 3 x 3 x 1 的窗口中给出最大值:
[[[[ 2.]]]]
以下图表说明了最大池化的逻辑:

如图所示,最大池化根据步幅为 1, 1, 1 的窗口选择了最大值。
平均池化
它对输入张量执行平均池化。输出中的每个条目是相应大小的 ksize 窗口中的值的平均值。它通过 tf.nn.avg_pool 方法定义:
avg_pool( value, ksize, strides, padding, data_format='NHWC', name=None)
我们来看看使用avg_pool的简单 2D 张量的代码示例:
import tensorflow as tf
batch_size=1
input_height = 3
input_width = 3
input_channels = 1
def main():
sess = tf.InteractiveSession()
layer_input = tf.constant([
[
[[1.0], [0.2], [2.0]],
[[0.1], [1.2], [1.4]],
[[1.1], [0.4], [0.4]]
]
])
# The strides will look at the entire input by using the image_height and image_width
kernel = [batch_size, input_height, input_width, input_channels]
avg_pool = tf.nn.avg_pool(layer_input, kernel, [1, 1, 1, 1], "VALID")
print(sess.run(avg_pool))
if __name__ == '__main__':
main()
上述代码的输出是张量中所有值的平均值。
平均值 = (1.0 + 0.2 + 2.0 + 0.1 + 1.2 + 1.4 + 1.1 + 0.4 + 0.4) / 9 = 0.86666
[[[[ 0.86666667]]]]
使用卷积网络进行图像分类
让我们看一个更实际的 CNN 使用案例;我们将使用斯坦福狗与猫数据集。该数据集包含 100 多张狗和猫的图片。
你可以从以下位置下载该数据集(每类 100 张图片):s3.amazonaws.com/neural-networking-book/ch04/dogs_vs_cats.tar.gz
- 导入相关的函数和 Python 类:
import matplotlib.pyplot as plt
import tensorflow as tf
import pandas as pd
import numpy as np
from sklearn.metrics import confusion_matrix
import time
from datetime import timedelta
import math
import dataset
import random
- 我们将定义卷积层的参数。共有三个卷积层,参数如下:
| 层编号 | 层类型 | 滤波器/神经元数量 |
|---|---|---|
| 1 | 卷积 | 32 个滤波器 |
| 2 | 卷积 | 32 个滤波器 |
| 3 | 卷积 | 64 个滤波器 |
| 4 | 全连接层 | 128 个神经元 |
网络拓扑可以通过以下图示表示:

以下代码有助于理解参数:
# Convolutional Layer 1.
filter_size1 = 3
num_filters1 = 32
# Convolutional Layer 2.
filter_size2 = 3
num_filters2 = 32
# Convolutional Layer 3.
filter_size3 = 3
num_filters3 = 64
# Fully-connected layer.
# Number of neurons in fully-connected layer.
fc_size = 128
# Number of color channels for the images: 1 channel for gray-scale.
num_channels = 3
# image dimensions (only squares for now)
img_size = 128
# Size of image when flattened to a single dimension
img_size_flat = img_size * img_size * num_channels
# Tuple with height and width of images used to reshape arrays.
img_shape = (img_size, img_size)
- 定义类别数量(此处为两个类别)和其他变量。我们使用了斯坦福数据集,并将其缩减为每类 100 张狗和猫的图片,以便于处理:
# class info
classes = ['dogs', 'cats']
num_classes = len(classes)
# batch size
batch_size = 2
# validation split
validation_size = .2
total_iterations = 0
early_stopping = None # use None if you don't want to implement early stoping
home = '/home/ubuntu/Downloads/dogs_vs_cats'
train_path = home + '/train-cat-dog-100/'
test_path = home + '/test-cat-dog-100/'
checkpoint_dir = home + "/models/"
首先让我们将数据集读入张量。读取逻辑在dataset类中定义:
data = dataset.read_train_sets(train_path, img_size, classes, validation_size=validation_size)
这里,定义了train_path、image_size、classes和validation_size。让我们看看read_train_sets(..)的实现:
def read_train_sets(train_path, image_size, classes, validation_size=0):
class DataSets(object):
pass
data_sets = DataSets()
images, labels, ids, cls = load_train(train_path, image_size, classes)
images, labels, ids, cls = shuffle(images, labels, ids, cls) # shuffle the data
if isinstance(validation_size, float):
validation_size = int(validation_size * images.shape[0])
validation_images = images[:validation_size]
validation_labels = labels[:validation_size]
validation_ids = ids[:validation_size]
validation_cls = cls[:validation_size]
train_images = images[validation_size:]
train_labels = labels[validation_size:]
train_ids = ids[validation_size:]
train_cls = cls[validation_size:]
data_sets.train = DataSet(train_images, train_labels, train_ids, train_cls)
data_sets.valid = DataSet(validation_images, validation_labels, validation_ids,
validation_cls)
return data_sets
该方法进一步调用load_train(...),返回一个数据类型为numpy.array的数据:
def load_train(train_path, image_size, classes) :
images = labels = []
ids = cls = []
# load data into arrays
images = np.array(images)
labels = np.array(labels)
ids = np.array(ids)
cls = np.array(cls)
return images, labels, ids, cls
加载到训练中的数据是validation_set的一个函数;它是根据图像数组的第一维计算得到的:

我们计算validation_size,如下代码所示:
validation_size = int(validation_size * images.shape[0])
由于我们将验证集大小设置为0.2,计算结果为58.2,四舍五入为58:

同样,我们创建测试数据集,test_images和test_ids:
test_images, test_ids = dataset.read_test_set(test_path, img_size)
这里,read_test_set(...)是一个内部调用的函数:
def read_test_set(test_path, image_size):
images, ids = load_test(test_path, image_size)
return images, ids
read_test_set(test_path, image_size)又会调用load_test(test_path, image_size),其代码如下:
def load_test(test_path, image_size):
path = os.path.join(test_path, '*g')
files = sorted(glob.glob(path))
X_test = []
X_test_id = []
print("Reading test images")
for fl in files:
flbase = os.path.basename(fl)
img = cv2.imread(fl)
img = cv2.resize(img, (image_size, image_size), fx=0.5, fy=0.5,
interpolation=cv2.INTER_LINEAR)
#img = cv2.resize(img, (image_size, image_size), cv2.INTER_LINEAR)
X_test.append(img)
X_test_id.append(flbase)
### because we're not creating a DataSet object for the test images,
### normalization happens here
X_test = np.array(X_test, dtype=np.uint8)
X_test = X_test.astype('float32')
X_test = X_test / 255
return X_test, X_test_id
- 我们来看看创建的各种
numpy数组的尺寸:
print("Size of:")
print("- Training-set:\t\t{}".format(len(data.train.labels)))
print("- Test-set:\t\t{}".format(len(test_images)))
print("- Validation-set:\t{}".format(len(data.valid.labels)))
Size of:Size of:
- Training-set: 233
- Test-set: 100
- Validation-set: 58
- 在 3 x 3 网格中绘制九张随机图片,并标注相应的类别:
images, cls_true = data.train.images, data.train.cls
plot_images(images=images, cls_true=cls_true
这里,plot_images函数在以下代码块中定义:
def plot_images(images, cls_true, cls_pred=None):
if len(images) == 0:
print("no images to show")
return
else:
random_indices = random.sample(range(len(images)), min(len(images), 9))
images, cls_true = zip(*[(images[i], cls_true[i]) for i in random_indices])
# Create figure with 3x3 sub-plots.
fig, axes = plt.subplots(3, 3)
fig.subplots_adjust(hspace=0.3, wspace=0.3)
for i, ax in enumerate(axes.flat):
# Plot image.
print(images[i])
ax.imshow(images[i].reshape(img_size, img_size, num_channels))
print(images[i].size)
print(img_size)
print(num_channels)
# Show true and predicted classes.
if cls_pred is None:
xlabel = "True: {0}".format(cls_true[i])
else:
xlabel = "True: {0}, Pred: {1}".format(cls_true[i], cls_pred[i])
# Show the classes as the label on the x-axis.
ax.set_xlabel(xlabel)
# Remove ticks from the plot.
ax.set_xticks([])
ax.set_yticks([])
# Ensure the plot is shown correctly with multiple plots
# in a single Notebook cell.
plt.show()
以下是我们代码的输出:

数据集中的九张随机图片
定义输入图像的张量和第一个卷积层
接下来,我们将为输入图像和第一个卷积层定义张量。
输入张量
创建一个占位符,形状为shape[None, img_size_flat],并将其重塑为[-1, img_size, img_size, num_channels]:
x = tf.placeholder(tf.float32, shape=[None, img_size_flat], name='x')
x_image = tf.reshape(x, [-1, img_size, img_size, num_channels])
这里,参数img_size和num_channels的值如下:
-
img_size= 128 -
num_channels= 3
第一个卷积层
在将输入张量重塑为x_image后,我们将创建第一个卷积层:
layer_conv1, weights_conv1 = new_conv_layer(input=x_image,
num_input_channels=num_channels,
filter_size=filter_size1,
num_filters=num_filters1,
use_pooling=True)
print(layer_conv1)
new_conv_layer(...)函数在这里定义。让我们来看一下传递给该函数的每个变量的值:

def new_conv_layer(input, # The previous layer.
num_input_channels, # Num. channels in prev. layer.
filter_size, # Width and height of each filter.
num_filters, # Number of filters.
use_pooling=True): # Use 2x2 max-pooling.
# Shape of the filter-weights for the convolution.
# This format is determined by the TensorFlow API.
shape = [filter_size, filter_size, num_input_channels, num_filters]
# Create new weights aka. filters with the given shape.
weights = new_weights(shape=shape)
# Create new biases, one for each filter.
biases = new_biases(length=num_filters)
# Create the TensorFlow operation for convolution.
# Note the strides are set to 1 in all dimensions.
# The first and last stride must always be 1,
# because the first is for the image-number and
# the last is for the input-channel.
# But e.g. strides=[1, 2, 2, 1] would mean that the filter
# is moved 2 pixels across the x- and y-axis of the image.
# The padding is set to 'SAME' which means the input image
# is padded with zeroes so the size of the output is the same.
layer = tf.nn.conv2d(input=input,
filter=weights,
strides=[1, 1, 1, 1],
padding='SAME')
# Add the biases to the results of the convolution.
# A bias-value is added to each filter-channel.
layer += biases
# Use pooling to down-sample the image resolution?
if use_pooling:
# This is 2x2 max-pooling, which means that we
# consider 2x2 windows and select the largest value
# in each window. Then we move 2 pixels to the next window.
layer = tf.nn.max_pool(value=layer,
ksize=[1, 2, 2, 1],
strides=[1, 2, 2, 1],
padding='SAME')
# Rectified Linear Unit (ReLU).
# It calculates max(x, 0) for each input pixel x.
# This adds some non-linearity to the formula and allows us
# to learn more complicated functions.
layer = tf.nn.relu(layer)
# Note that ReLU is normally executed before the pooling,
# but since relu(max_pool(x)) == max_pool(relu(x)) we can
# save 75% of the relu-operations by max-pooling first.
# We return both the resulting layer and the filter-weights
# because we will plot the weights later.
return layer, weights
运行时,变量的值如下:


如果我们运行此代码,print(..)语句的输出将如下所示:
Tensor("Relu:0", shape=(?, 64, 64, 32), dtype=float32)
输出显示了从输入层 1 传出的输出张量的形状。
第二个卷积层
在第二个卷积层中,我们将第一个层的输出作为输入,并使用以下参数构建一个新层:

首先,我们为真实的y和真实的y的类别(类别标签)定义一个占位符:
y_true = tf.placeholder(tf.float32, shape=[None, num_classes], name='y_true')
y_true_cls = tf.argmax(y_true, dimension=1)
这两个变量的形状如下:

layer_conv2, weights_conv2 = new_conv_layer(input=layer_conv1,
num_input_channels=num_filters1,filter_size=filter_size2,num_filters=num_filters2,use_pooling=True)
其中,以下是各个值:
-
num_input_channels= 3 -
filter_size= 3 -
num_filters= 32
这是打印输出的结果:
Tensor("Relu_1:0", shape=(?, 32, 32, 32), dtype=float32)
第三个卷积层
该层将第二层的输出作为输入。让我们看看输入是如何进入该层的:

shape = [filter_size, filter_size, num_input_channels, num_filters] weights = new_weights(shape=shape)
layer_conv3, weights_conv3 = new_conv_layer(input=layer_conv2,
num_input_channels=num_filters2,filter_size=filter_size3,num_filters=num_filters3,use_pooling=True)
print(layer_conv3)
layer_conv3的形状如下:
Tensor("Relu_2:0", shape=(?, 16, 16, 64), dtype=float32)
扁平化该层
接下来,我们将该层扁平化为num图像和num特征,在本例中为 16,384。如果你注意到最后一层的输出,我们已经使用以下逻辑进行了扁平化:16 x 16 x 64 = 16,384:
layer_flat, num_features = flatten_layer(layer_conv3)
如果我们打印这些值,你将看到以下输出:
Tensor("Reshape_1:0", shape=(?, 16384), dtype=float32)
16384
全连接层
在第四和第五层中,我们定义了全连接层:
layer_fc1 = new_fc_layer(input=layer_flat,
num_inputs=num_features,
num_outputs=fc_size,
use_relu=True)
其中
-
layer_flat:最后一层的扁平化 -
num_features:特征数量 -
fc_size:输出数量
下图显示了传递给new_fc_layer()的值:

print(layer_fc1)
打印的值如下:
Tensor("Relu_3:0", shape=(?, 128), dtype=float32)
接下来是第二个全连接层,其中该函数接受以下参数:
-
layer_fc1:来自第一个全连接层的输出 -
num_inputs:128 -
num_inputs:num_classes,此处为 2 -
use_relu:一个布尔函数,用于指定是否使用relu;此处为False
layer_fc2 = new_fc_layer(input=layer_fc1,
num_inputs=fc_size,
num_outputs=num_classes,
use_relu=False)
让我们看看第二个全连接层的输出:
print(layer_fc2)
Tensor("add_4:0", shape=(?, 2), dtype=float32)
定义代价函数和优化器
对来自layer_fc2(第二个全连接层)的输出应用 Softmax。
在数学中,softmax 函数或归一化指数函数([[1]](https://en.wikipedia.org/wiki/Softmax_function#cite_note-bishop-1))(:198) 是逻辑斯蒂函数的推广,它将任意实值的 K 维向量 Z 压缩为一个 K 维实值向量 σ(z),该向量的取值范围为 [0,1],并且所有值的和为 1。该函数的公式如下:

y_pred = tf.nn.softmax(layer_fc2)
y_pred_cls = tf.argmax(y_pred, dimension=1)
计算交叉熵:
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(
logits=layer_fc2,
labels=y_true)
cost = tf.reduce_mean(cross_entropy)
优化器
接下来,我们定义基于 Adam 优化器的优化器。
Adam 与随机梯度下降算法有所不同。随机梯度下降为所有权重更新维持一个单一的学习率(称为alpha),并且学习率在训练过程中不会改变。
该算法为每个网络权重(参数)维持一个学习率,并随着学习的展开进行单独调整。它通过估计梯度的一阶和二阶矩,计算不同参数的自适应学习率。
Adam 结合了随机梯度下降的两种扩展的优点。
自适应梯度算法(AdaGrad)为每个参数维持一个单独的学习率,它在稀疏梯度的机器学习问题(例如自然语言处理和计算机视觉问题)中提高性能。均方根传播(RMSProp)为每个参数维持学习率;这些学习率根据最近的梯度平均值进行调整(即权重变化的速度)。
optimizer = tf.train.AdamOptimizer(learning_rate=1e-4).minimize(cost)
我们还计算了 correct_prediction 和 accuracy 的变量:
correct_prediction = tf.equal(y_pred_cls, y_true_cls)
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
第一轮
初始化会话,并对 num_iterations=1 调用 optimize() 函数:
session = tf.Session()
session.run(tf.global_variables_initializer())
batch_size = 2
train_batch_size = batch_size
optimize(num_iterations = 1, data=data, train_batch_size=train_batch_size, x=x, y_true=y_true,
session=session, optimizer=optimizer, cost=cost, accuracy=accuracy)
这里,optimize() 函数在以下代码块中定义:
def optimize(num_iterations, data, train_batch_size, x, y_true, session, optimizer, cost, accuracy):
# Ensure we update the global variable rather than a local copy.
global total_iterations
# Start-time used for printing time-usage below.
start_time = time.time()
best_val_loss = float("inf")
patience = 0
for i in range(total_iterations,
total_iterations + num_iterations):
# Get a batch of training examples.
# x_batch now holds a batch of images and
# y_true_batch are the true labels for those images.
x_batch, y_true_batch, _, cls_batch = data.train.next_batch(train_batch_size)
x_valid_batch, y_valid_batch, _, valid_cls_batch = data.valid.next_batch(train_batch_size)
# Convert shape from [num examples, rows, columns, depth]
# to [num examples, flattened image shape]
x_batch = x_batch.reshape(train_batch_size, img_size_flat)
x_valid_batch = x_valid_batch.reshape(train_batch_size, img_size_flat)
# Put the batch into a dict with the proper names
# for placeholder variables in the TensorFlow graph.
feed_dict_train = {x: x_batch,
y_true: y_true_batch}
feed_dict_validate = {x: x_valid_batch,
y_true: y_valid_batch}
# Run the optimizer using this batch of training data.
# TensorFlow assigns the variables in feed_dict_train
# to the placeholder variables and then runs the optimizer.
session.run(optimizer, feed_dict=feed_dict_train)
# Print status at end of each epoch (defined as full pass through
# training dataset).
if i % int(data.train.num_examples/batch_size) == 0:
val_loss = session.run(cost, feed_dict=feed_dict_validate)
epoch = int(i / int(data.train.num_examples/batch_size))
#print_progress(epoch, feed_dict_train, feed_dict_validate, val_loss)
print_progress(session, accuracy, epoch, feed_dict_train, feed_dict_validate,
val_loss)
if early_stopping:
if val_loss < best_val_loss:
best_val_loss = val_loss
patience = 0
else:
patience += 1
if patience == early_stopping:
break
# Update the total number of iterations performed.
total_iterations += num_iterations
# Ending time.
end_time = time.time()
# Difference between start and end-times.
time_dif = end_time - start_time
# Print the time-usage.
print("Time elapsed: " + str(timedelta(seconds=int(round(time_dif)))))
输出打印了训练、验证准确率和验证损失,内容如下:
Epoch 1 --- Training Accuracy: 100.0%, Validation Accuracy: 50.0%, Validation Loss: 0.705
打印 Test-Set 的准确率:
print_validation_accuracy(x, y_true, y_pred_cls, session, data, show_example_errors=True, show_confusion_matrix=False)
Epoch 2 --- Training Accuracy: 50.0%, Validation Accuracy: 100.0%, Validation Loss: 0.320
Accuracy on Test-Set: 43.1% (25 / 58)
接下来,让我们对模型进行 100 次迭代优化:
optimize(num_iterations=100, data=data, train_batch_size=train_batch_size, x=x, y_true=y_true,session=session, optimizer=optimizer, cost=cost, accuracy=accuracy)
print_validation_accuracy(x, y_true, y_pred_cls, session, data, show_example_errors=True,
show_confusion_matrix=False)
Accuracy on Test-Set: 62.1% (36 / 58)
输出中也显示了假阳性:

显示假阳性的输出
绘制过滤器及其对图像的影响
让我们对两张测试图像应用两层过滤器,看看它们的变化:
image1 = test_images[0]
plot_image(image1)
plot_image(image1) 函数的输出如下面的图像所示:

image2 = test_images[13]
plot_image(image2)
应用过滤器后的 image2 输出如下:

卷积层 1:以下是层 1 权重的图示:

层 1 的过滤器应用于图像 1:
plot_conv_layer(layer=layer_conv1, image=image1, session=session, x=x)

层 1 的过滤器应用于图像 2:
plot_conv_layer(layer=layer_conv1, image=image2, session=session, x=x)

卷积层 2:现在绘制第二个卷积层的滤波器权重。第一卷积层有 16 个输出通道,意味着第二个卷积层有 16 个输入通道。第二个卷积层每个输入通道都有一组滤波器权重。我们首先绘制第一个通道的滤波器权重。
Layer 2 权重:
plot_conv_weights(weights=weights_conv1, session=session)

Conv2 的权重,输入通道 0。正权重为红色,负权重为蓝色
第二个卷积层有 16 个输入通道,因此我们可以制作另外 15 个类似这样的滤波器权重图。我们只需再制作一个,用于第二个通道的滤波器权重:
plot_conv_weights(weights=weights_conv2, session=session, input_channel=1)

正权重为红色,负权重为蓝色
使用第二个卷积层的滤波器绘制图像 1 和图像 2:
plot_conv_layer(layer=layer_conv2, image=image1, session=session, x=x)
plot_conv_layer(layer=layer_conv2, image=image2, session=session, x=x)

Conv2 的权重,输入通道 1。图像 1 经第 2 层滤波器过滤

显示经第 2 层滤波器过滤的图像 2
卷积层 3:让我们打印第 3 层的权重;这一层有 64 个滤波器。这是图像 1 和图像 2 通过每个滤波器后的效果:
plot_conv_weights(weights=weights_conv3, session=session, input_channel=0)

Conv2 的权重,输入通道 0。正权重为红色,负权重为蓝色
plot_conv_weights(weights=weights_conv3, session=session, input_channel=1)

Conv2 的权重,输入通道 1。正权重为红色,负权重为蓝色。
通过第 3 层滤波器绘制通过的图像:执行以下语句绘制通过第 3 层 64 个卷积滤波器的图像 1 和图像 2:
plot_conv_layer(layer=layer_conv3, image=image1, session=session, x=x)
plot_conv_layer(layer=layer_conv3, image=image2, session=session, x=x)

图像 1,使用第 2 层滤波器绘制的图像
下面是来自 conv3 的卷积滤波器图像:

图像 2,使用第 3 层滤波器绘制
通过这些,我们完成了猫与狗数据集的分析,在这里我们使用了一个五层 CNN,包括三个隐藏层和两个全连接层来构建我们的模型。
摘要
在本章中,您学习了卷积的基础知识,以及为什么它是图像标签预测的有效机制。您学习了诸如 strides 和填充等基本概念。接着以斯坦福数据集中猫与狗的示例展开,我们使用了三个卷积层来构建神经网络,并使用两个全连接层展示如何用于图像分类。我们还绘制了三层的权重,并看到滤波器如何修改图像。我们还讨论了图像池化等概念,以及它如何帮助使 CNN 更高效。
在下一章,我们将介绍一种不同类型的神经网络,叫做循环神经网络(RNN),它处理时间序列数据或用于自然语言处理(NLP),以预测序列中的下一个词。
第五章:递归神经网络
递归神经网络(RNN)利用序列或时间序列数据。在常规神经网络中,我们认为所有的输入和输出是相互独立的。对于预测给定句子中下一个单词的任务,知道它前面的单词更为重要。RNN 是递归的,因为对序列中的每个元素执行相同的任务,输出依赖于之前的计算。RNN 可以被看作具有 记忆,捕捉到迄今为止计算的信息。
从前馈神经网络到递归神经网络,我们将使用跨模型各部分共享参数的概念。参数共享使得模型能够扩展并应用于不同形式(这里是不同长度)的示例,并且能够在这些示例之间进行泛化。
RNN 简介
要理解 RNN,我们必须了解前馈神经网络的基本原理。关于前馈网络的详细信息,请参阅 第三章,神经网络优化。前馈和递归神经网络的区别在于它们如何通过一系列数学操作处理信息或特征,操作发生在网络的各个节点上。一个是将信息直接传递(每个节点仅处理一次),另一个则是将信息循环处理。
前馈神经网络对图像数据进行训练,直到它在预测或分类图像类型时最小化损失或误差。通过训练后的超参数集或权重,神经网络可以对从未见过的数据进行分类。训练好的前馈神经网络可以展示任何随机的图像集合,它对第一张图像的分类不会改变它对其他图像的分类。
简而言之,这些网络没有时间或时间模式的顺序概念,它们仅考虑它们被要求分类的当前示例。
RNN 考虑到输入数据的时间性。RNN 单元的输入既来自当前时刻,又来自前一个时刻。详情请见下图:

RNN 本质上是递归的,因为它们对序列中的每个元素执行相同的计算,输出依赖于之前的计算。另一种看待 RNN 的方式是,它们具有可以捕捉到迄今为止已计算信息的“记忆”。RNN 可以利用长序列中的信息或知识,但实际上它们只限于回顾几步之前的信息。
一个典型的 RNN 如下所示:

下图展示了未展开版本的 RNN;展开的意思是我们将神经网络写出以适应整个序列。考虑一个包含五个单词的序列;该网络将展开成一个五层的神经网络,每一层对应一个单词:

RNN 中的计算过程如下:

-
表示时间步
的输入。 -
表示时间步
的隐藏状态。隐藏状态是网络的记忆。
是基于先前的隐藏状态和当前步骤的输入计算得出的,
。 -
函数 f 表示非线性,例如 tanh 或 ReLU。第一个隐藏状态通常初始化为全零。
-
表示步骤
的输出。为了预测给定句子中的下一个单词,它将是一个遍历词汇表的概率向量,
。
RNN 实现
以下程序处理一系列数字,目标是预测下一个值,并提供先前的值。在每个时间步,RNN 网络的输入是当前值和一个状态向量,该向量表示或存储了神经网络在之前时间步看到的内容。这个状态向量是 RNN 的编码记忆,最初设置为零。
训练数据基本上是一个随机的二进制向量。输出向右偏移。
from __future__ import print_function, division
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
"""
define all the constants
"""
numEpochs = 10
seriesLength = 50000
backpropagationLength = 15
stateSize = 4
numClasses = 2
echoStep = 3
batchSize = 5
num_batches = seriesLength // batchSize // backpropagationLength
"""
generate data
"""
def generateData():
x = np.array(np.random.choice(2, seriesLength, p=[0.5, 0.5]))
y = np.roll(x, echoStep)
y[0:echoStep] = 0
x = x.reshape((batchSize, -1))
y = y.reshape((batchSize, -1))
return (x, y)
"""
start computational graph
"""
batchXHolder = tf.placeholder(tf.float32, [batchSize, backpropagationLength], name="x_input")
batchYHolder = tf.placeholder(tf.int32, [batchSize, backpropagationLength], name="y_input")
initState = tf.placeholder(tf.float32, [batchSize, stateSize], "rnn_init_state")
W = tf.Variable(np.random.rand(stateSize+1, stateSize), dtype=tf.float32, name="weight1")
bias1 = tf.Variable(np.zeros((1,stateSize)), dtype=tf.float32)
W2 = tf.Variable(np.random.rand(stateSize, numClasses),dtype=tf.float32, name="weight2")
bias2 = tf.Variable(np.zeros((1,numClasses)), dtype=tf.float32)
tf.summary.histogram(name="weights", values=W)
# Unpack columns
inputsSeries = tf.unstack(batchXHolder, axis=1, name="input_series")
labelsSeries = tf.unstack(batchYHolder, axis=1, name="labels_series")
# Forward pass
currentState = initState
statesSeries = []
for currentInput in inputsSeries:
currentInput = tf.reshape(currentInput, [batchSize, 1], name="current_input")
inputAndStateConcatenated = tf.concat([currentInput, currentState], 1, name="input_state_concat")
nextState = tf.tanh(tf.matmul(inputAndStateConcatenated, W) + bias1, name="next_state")
statesSeries.append(nextState)
currentState = nextState
# calculate loss
logits_series = [tf.matmul(state, W2) + bias2 for state in statesSeries]
predictions_series = [tf.nn.softmax(logits) for logits in logits_series]
losses = [tf.nn.sparse_softmax_cross_entropy_with_logits(labels=labels, logits=logits) for logits, labels in zip(logits_series,labelsSeries)]
total_loss = tf.reduce_mean(losses, name="total_loss")
train_step = tf.train.AdagradOptimizer(0.3).minimize(total_loss, name="training")
"""
plot computation
"""
def plot(loss_list, predictions_series, batchX, batchY):
plt.subplot(2, 3, 1)
plt.cla()
plt.plot(loss_list)
for batchSeriesIdx in range(5):
oneHotOutputSeries = np.array(predictions_series)[:, batchSeriesIdx, :]
singleOutputSeries = np.array([(1 if out[0] < 0.5 else 0) for out in oneHotOutputSeries])
plt.subplot(2, 3, batchSeriesIdx + 2)
plt.cla()
plt.axis([0, backpropagationLength, 0, 2])
left_offset = range(backpropagationLength)
plt.bar(left_offset, batchX[batchSeriesIdx, :], width=1, color="blue")
plt.bar(left_offset, batchY[batchSeriesIdx, :] * 0.5, width=1, color="red")
plt.bar(left_offset, singleOutputSeries * 0.3, width=1, color="green")
plt.draw()
plt.pause(0.0001)
"""
run the graph
"""
with tf.Session() as sess:
writer = tf.summary.FileWriter("logs", graph=tf.get_default_graph())
sess.run(tf.global_variables_initializer())
plt.ion()
plt.figure()
plt.show()
loss_list = []
for epoch_idx in range(numEpochs):
x,y = generateData()
_current_state = np.zeros((batchSize, stateSize))
print("New data, epoch", epoch_idx)
for batch_idx in range(num_batches):
start_idx = batch_idx * backpropagationLength
end_idx = start_idx + backpropagationLength
batchX = x[:,start_idx:end_idx]
batchY = y[:,start_idx:end_idx]
_total_loss, _train_step, _current_state, _predictions_series = sess.run(
[total_loss, train_step, currentState, predictions_series],
feed_dict={
batchXHolder:batchX,
batchYHolder:batchY,
initState:_current_state
})
loss_list.append(_total_loss)
# fix the cost summary later
tf.summary.scalar(name="totalloss", tensor=_total_loss)
if batch_idx%100 == 0:
print("Step",batch_idx, "Loss", _total_loss)
plot(loss_list, _predictions_series, batchX, batchY)
plt.ioff()
plt.show()
计算图
计算图如下所示:

以下是列表的输出结果:
New data, epoch 0
Step 0 Loss 0.777418
Step 600 Loss 0.693907
New data, epoch 1
Step 0 Loss 0.690996
Step 600 Loss 0.691115
New data, epoch 2
Step 0 Loss 0.69259
Step 600 Loss 0.685826
New data, epoch 3
Step 0 Loss 0.684189
Step 600 Loss 0.690608
New data, epoch 4
Step 0 Loss 0.691302
Step 600 Loss 0.691309
New data, epoch 5
Step 0 Loss 0.69172
Step 600 Loss 0.694034
New data, epoch 6
Step 0 Loss 0.692927
Step 600 Loss 0.42796
New data, epoch 7
Step 0 Loss 0.42423
Step 600 Loss 0.00845207
New data, epoch 8
Step 0 Loss 0.188478
Step 500 Loss 0.00427217
使用 TensorFlow 实现的 RNN
我们现在将使用 TensorFlow API;RNN 的内部工作机制隐藏在幕后。TensorFlow 的 rnn 包展开了 RNN 并自动创建图形,这样我们就可以去掉 for 循环:
from __future__ import print_function, division
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
"""
define all the constants
"""
numEpochs = 10
seriesLength = 50000
backpropagationLength = 15
stateSize = 4
numClasses = 2
echoStep = 3
batchSize = 5
num_batches = seriesLength // batchSize // backpropagationLength
"""
generate data
"""
def generateData():
x = np.array(np.random.choice(2, seriesLength, p=[0.5, 0.5]))
y = np.roll(x, echoStep)
y[0:echoStep] = 0
x = x.reshape((batchSize, -1))
y = y.reshape((batchSize, -1))
return (x, y)
"""
start computational graph
"""
batchXHolder = tf.placeholder(tf.float32, [batchSize, backpropagationLength], name="x_input")
batchYHolder = tf.placeholder(tf.int32, [batchSize, backpropagationLength], name="y_input")
initState = tf.placeholder(tf.float32, [batchSize, stateSize], "rnn_init_state")
W = tf.Variable(np.random.rand(stateSize+1, stateSize), dtype=tf.float32, name="weight1")
bias1 = tf.Variable(np.zeros((1,stateSize)), dtype=tf.float32)
W2 = tf.Variable(np.random.rand(stateSize, numClasses),dtype=tf.float32, name="weight2")
bias2 = tf.Variable(np.zeros((1,numClasses)), dtype=tf.float32)
tf.summary.histogram(name="weights", values=W)
# Unpack columns
inputsSeries = tf.split(axis=1, num_or_size_splits=backpropagationLength, value=batchXHolder)
labelsSeries = tf.unstack(batchYHolder, axis=1)
# Forward passes
from tensorflow.contrib import rnn
cell = rnn.BasicRNNCell(stateSize)
statesSeries, currentState = rnn.static_rnn(cell, inputsSeries, initState)
# calculate loss
logits_series = [tf.matmul(state, W2) + bias2 for state in statesSeries]
predictions_series = [tf.nn.softmax(logits) for logits in logits_series]
losses = [tf.nn.sparse_softmax_cross_entropy_with_logits(labels=labels, logits=logits) for logits, labels in zip(logits_series,labelsSeries)]
total_loss = tf.reduce_mean(losses, name="total_loss")
train_step = tf.train.AdagradOptimizer(0.3).minimize(total_loss, name="training")
"""
plot computation
"""
def plot(loss_list, predictions_series, batchX, batchY):
plt.subplot(2, 3, 1)
plt.cla()
plt.plot(loss_list)
for batchSeriesIdx in range(5):
oneHotOutputSeries = np.array(predictions_series)[:, batchSeriesIdx, :]
singleOutputSeries = np.array([(1 if out[0] < 0.5 else 0) for out in oneHotOutputSeries])
plt.subplot(2, 3, batchSeriesIdx + 2)
plt.cla()
plt.axis([0, backpropagationLength, 0, 2])
left_offset = range(backpropagationLength)
plt.bar(left_offset, batchX[batchSeriesIdx, :], width=1, color="blue")
plt.bar(left_offset, batchY[batchSeriesIdx, :] * 0.5, width=1, color="red")
plt.bar(left_offset, singleOutputSeries * 0.3, width=1, color="green")
plt.draw()
plt.pause(0.0001)
"""
run the graph
"""
with tf.Session() as sess:
writer = tf.summary.FileWriter("logs", graph=tf.get_default_graph())
sess.run(tf.global_variables_initializer())
plt.ion()
plt.figure()
plt.show()
loss_list = []
for epoch_idx in range(numEpochs):
x,y = generateData()
_current_state = np.zeros((batchSize, stateSize))
print("New data, epoch", epoch_idx)
for batch_idx in range(num_batches):
start_idx = batch_idx * backpropagationLength
end_idx = start_idx + backpropagationLength
batchX = x[:,start_idx:end_idx]
batchY = y[:,start_idx:end_idx]
_total_loss, _train_step, _current_state, _predictions_series = sess.run(
[total_loss, train_step, currentState, predictions_series],
feed_dict={
batchXHolder:batchX,
batchYHolder:batchY,
initState:_current_state
})
loss_list.append(_total_loss)
# fix the cost summary later
tf.summary.scalar(name="totalloss", tensor=_total_loss)
if batch_idx%100 == 0:
print("Step",batch_idx, "Loss", _total_loss)
plot(loss_list, _predictions_series, batchX, batchY)
plt.ioff()
plt.show()
计算图
以下是计算图的图像:

以下是列表的输出结果:
New data, epoch 0
Step 0 Loss 0.688437
Step 600 Loss 0.00107078
New data, epoch 1
Step 0 Loss 0.214923
Step 600 Loss 0.00111716
New data, epoch 2
Step 0 Loss 0.214962
Step 600 Loss 0.000730697
New data, epoch 3
Step 0 Loss 0.276177
Step 600 Loss 0.000362316
New data, epoch 4
Step 0 Loss 0.1641
Step 600 Loss 0.00025342
New data, epoch 5
Step 0 Loss 0.0947087
Step 600 Loss 0.000276762
长短期记忆网络简介
梯度消失问题已成为循环网络最大的障碍。
随着直线沿 x 轴变化,y 轴的变化幅度较小,梯度显示了所有权重随误差变化的变化。如果我们不知道梯度,就无法调整权重以减少损失或误差,我们的神经网络将停止学习。
长短期记忆(LSTM)旨在克服梯度消失问题。保留信息更长时间实际上是它们的隐性行为。
在标准的 RNN 中,重复单元将具有一个基础结构,例如单一的 tanh 层:

如前图所示,LSTM 也具有链式结构,但其递归单元具有不同的结构:

LSTM 的生命周期
LSTM 的关键是单元状态,它像一个传送带。它通过轻微的线性交互沿着流动,数据可以轻松流动而不改变:

LSTM 网络能够有选择地从单元状态中删除或添加信息,而这些信息的操作由称为门的结构精心调控。
- LSTM 网络的第一步是确定我们将从单元状态中丢弃哪些信息。这个决策是通过一个称为忘记门层的 sigmoid 层来做出的。该层查看前一个状态 h(t-1) 和当前输入 x(t),并为单元状态 C(t−1) 中的每个数字输出一个介于 0 和 1 之间的数值,其中 1 表示绝对保留此信息,而 0 表示完全丢弃此信息:

- 下一步是确定我们将要在单元状态中保持哪些新信息。首先,一个称为输入门层的 sigmoid 层决定哪些值会被更新。其次,一个 tanh 层生成一个新的候选值向量 C̃,这些值可能会被添加到状态中。

- 现在我们将更新旧的单元状态 C(t−1) 为新的单元状态 C(t)。我们将旧状态乘以 f(t),忘记我们之前决定要忘记的内容。然后我们添加 i(t) ∗ C̃;这些是新的候选值,按我们决定更新每个状态值的比例进行缩放。

- 最后,我们决定输出什么,这将基于我们的单元状态,但将是一个经过滤波或修改的版本。首先,我们执行一个 sigmoid 层来决定我们将输出单元状态的哪些部分。然后,我们将单元状态通过 tanh 层,以便将值压缩到 −1 和 1 之间,并将其与 sigmoid 门的输出相乘,这样我们只输出我们决定的部分。

LSTM 实现
LSTM 会记住、忘记,并根据当前的状态和输入选择要传递和输出的内容。一个 LSTM 具有更多的活动组件,但使用原生的 TensorFlow API,操作起来会非常直接:
from __future__ import print_function, division
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.contrib import rnn
"""
define all the constants
"""
numEpochs = 10
seriesLength = 50000
backpropagationLength = 15
stateSize = 4
numClasses = 2
echoStep = 3
batchSize = 5
num_batches = seriesLength // batchSize // backpropagationLength
"""
generate data
"""
def generateData():
x = np.array(np.random.choice(2, seriesLength, p=[0.5, 0.5]))
y = np.roll(x, echoStep)
y[0:echoStep] = 0
x = x.reshape((batchSize, -1))
y = y.reshape((batchSize, -1))
return (x, y)
"""
start computational graph
"""
batchXHolder = tf.placeholder(tf.float32, [batchSize, backpropagationLength], name="x_input")
batchYHolder = tf.placeholder(tf.int32, [batchSize, backpropagationLength], name="y_input")
# rnn replace
#initState = tf.placeholder(tf.float32, [batchSize, stateSize], "rnn_init_state")
cellState = tf.placeholder(tf.float32, [batchSize, stateSize])
hiddenState = tf.placeholder(tf.float32, [batchSize, stateSize])
initState = rnn.LSTMStateTuple(cellState, hiddenState)
W = tf.Variable(np.random.rand(stateSize+1, stateSize), dtype=tf.float32, name="weight1")
bias1 = tf.Variable(np.zeros((1,stateSize)), dtype=tf.float32)
W2 = tf.Variable(np.random.rand(stateSize, numClasses),dtype=tf.float32, name="weight2")
bias2 = tf.Variable(np.zeros((1,numClasses)), dtype=tf.float32)
tf.summary.histogram(name="weights", values=W)
# Unpack columns
inputsSeries = tf.split(axis=1, num_or_size_splits=backpropagationLength, value=batchXHolder)
labelsSeries = tf.unstack(batchYHolder, axis=1)
# Forward passes
# rnn replace
# cell = rnn.BasicRNNCell(stateSize)
# statesSeries, currentState = rnn.static_rnn(cell, inputsSeries, initState)
cell = rnn.BasicLSTMCell(stateSize, state_is_tuple=True)
statesSeries, currentState = rnn.static_rnn(cell, inputsSeries, initState)
# calculate loss
logits_series = [tf.matmul(state, W2) + bias2 for state in statesSeries]
predictions_series = [tf.nn.softmax(logits) for logits in logits_series]
losses = [tf.nn.sparse_softmax_cross_entropy_with_logits(labels=labels, logits=logits) for logits, labels in zip(logits_series,labelsSeries)]
total_loss = tf.reduce_mean(losses, name="total_loss")
train_step = tf.train.AdagradOptimizer(0.3).minimize(total_loss, name="training")
"""
plot computation
"""
def plot(loss_list, predictions_series, batchX, batchY):
plt.subplot(2, 3, 1)
plt.cla()
plt.plot(loss_list)
for batchSeriesIdx in range(5):
oneHotOutputSeries = np.array(predictions_series)[:, batchSeriesIdx, :]
singleOutputSeries = np.array([(1 if out[0] < 0.5 else 0) for out in oneHotOutputSeries])
plt.subplot(2, 3, batchSeriesIdx + 2)
plt.cla()
plt.axis([0, backpropagationLength, 0, 2])
left_offset = range(backpropagationLength)
plt.bar(left_offset, batchX[batchSeriesIdx, :], width=1, color="blue")
plt.bar(left_offset, batchY[batchSeriesIdx, :] * 0.5, width=1, color="red")
plt.bar(left_offset, singleOutputSeries * 0.3, width=1, color="green")
plt.draw()
plt.pause(0.0001)
"""
run the graph
"""
with tf.Session() as sess:
writer = tf.summary.FileWriter("logs", graph=tf.get_default_graph())
sess.run(tf.global_variables_initializer())
plt.ion()
plt.figure()
plt.show()
loss_list = []
for epoch_idx in range(numEpochs):
x,y = generateData()
# rnn remove
# _current_state = np.zeros((batchSize, stateSize))
_current_cell_state = np.zeros((batchSize, stateSize))
_current_hidden_state = np.zeros((batchSize, stateSize))
print("New data, epoch", epoch_idx)
for batch_idx in range(num_batches):
start_idx = batch_idx * backpropagationLength
end_idx = start_idx + backpropagationLength
batchX = x[:,start_idx:end_idx]
batchY = y[:,start_idx:end_idx]
_total_loss, _train_step, _current_state, _predictions_series = sess.run(
[total_loss, train_step, currentState, predictions_series],
feed_dict={
batchXHolder:batchX,
batchYHolder:batchY,
cellState: _current_cell_state,
hiddenState: _current_hidden_state
})
_current_cell_state, _current_hidden_state = _current_state
loss_list.append(_total_loss)
# fix the cost summary later
tf.summary.scalar(name="totalloss", tensor=_total_loss)
if batch_idx%100 == 0:
print("Step",batch_idx, "Loss", _total_loss)
plot(loss_list, _predictions_series, batchX, batchY)
plt.ioff()
plt.show()
计算图
以下是来自 TensorBoard 的计算图,描述了 LSTM 网络的工作原理:

列表的输出如下所示:
New data, epoch 0
Step 0 Loss 0.696803
Step 600 Loss 0.00743465
New data, epoch 1
Step 0 Loss 0.404039
Step 600 Loss 0.00243205
New data, epoch 2
Step 0 Loss 1.11536
Step 600 Loss 0.00140995
New data, epoch 3
Step 0 Loss 0.858743
Step 600 Loss 0.00141037
情感分析
现在我们将编写一个应用程序来预测电影评论的情感。评论由一系列单词组成,单词的顺序编码了预测情感的非常有用的信息。第一步是将单词映射到词嵌入。第二步是 RNN,它接收一个向量序列作为输入,并考虑向量的顺序来生成预测。
词嵌入
现在我们将训练一个神经网络来进行词到向量的表示。给定句子中心的特定单词,即输入词,我们查看附近的单词。网络将告诉我们,在我们的词汇表中,每个单词作为选择的邻近单词的概率。

import time
import tensorflow as tf
import numpy as np
import utility
from tqdm import tqdm
from urllib.request import urlretrieve
from os.path import isfile, isdir
import zipfile
from collections import Counter
import random
dataDir = 'data'
dataFile = 'text8.zip'
datasetName = 'text 8 data set'
'''
track progress of file download
'''
class DownloadProgress(tqdm):
lastBlock = 0
def hook(self, blockNum=1, blockSize=1, totalSize=None):
self.total = totalSize
self.update((blockNum - self.lastBlock) * blockSize)
self.lastBlock = blockNum
if not isfile(dataFile):
with DownloadProgress(unit='B', unit_scale=True, miniters=1, desc=datasetName) as progressBar:
urlretrieve('http://mattmahoney.net/dc/text8.zip', dataFile, progressBar.hook)
if not isdir(dataDir):
with zipfile.ZipFile(dataFile) as zipRef:
zipRef.extractall(dataDir)
with open('data/text8') as f:
text = f.read()
'''
pre process the downloaded wiki text
'''
words = utility.preProcess(text)
print(words[:30])
print('Total words: {}'.format(len(words)))
print('Unique words: {}'.format(len(set(words))))
'''
convert words to integers
'''
int2vocab, vocab2int = utility.lookupTable(words)
intWords = [vocab2int[word] for word in words]
print('test')
'''
sub sampling (***think of words as int's***)
'''
threshold = 1e-5
wordCounts = Counter(intWords)
totalCount = len(intWords)
frequency = {word: count / totalCount for word, count in wordCounts.items()}
probOfWords = {word: 1 - np.sqrt(threshold / frequency[word]) for word in wordCounts}
trainWords = [word for word in intWords if random.random() < (1 - probOfWords[word])]
'''
get window batches
'''
def getTarget(words, index, windowSize=5):
rNum = np.random.randint(1, windowSize + 1)
start = index - rNum if (index - rNum) > 0 else 0
stop = index + rNum
targetWords = set(words[start:index] + words[index + 1:stop + 1])
return list(targetWords)
'''
Create a generator of word batches as a tuple (inputs, targets)
'''
def getBatches(words, batchSize, windowSize=5):
nBatches = len(words) // batchSize
print('no. of batches {}'.format(nBatches))
# only full batches
words = words[:nBatches * batchSize]
start = 0
for index in range(0, len(words), batchSize):
x = []
y = []
stop = start + batchSize
batchWords = words[start:stop]
for idx in range(0, len(batchWords), 1):
yBatch = getTarget(batchWords, idx, windowSize)
y.extend(yBatch)
x.extend([batchWords[idx]] * len(yBatch))
start = stop + 1
yield x, y
'''
start computational graph
'''
train_graph = tf.Graph()
with train_graph.as_default():
netInputs = tf.placeholder(tf.int32, [None], name='inputS')
netLabels = tf.placeholder(tf.int32, [None, None], name='labelS')
'''
create embedding layer
'''
nVocab = len(int2vocab)
nEmbedding = 300
with train_graph.as_default():
embedding = tf.Variable(tf.random_uniform((nVocab, nEmbedding), -1, 1))
embed = tf.nn.embedding_lookup(embedding, netInputs)
'''
Below, create weights and biases for the softmax layer. Then, use tf.nn.sampled_softmax_loss to calculate the loss
'''
n_sampled = 100
with train_graph.as_default():
soft_W = tf.Variable(tf.truncated_normal((nVocab, nEmbedding)))
soft_b = tf.Variable(tf.zeros(nVocab), name="softmax_bias")
# Calculate the loss using negative sampling
loss = tf.nn.sampled_softmax_loss(
weights=soft_W,
biases=soft_b,
labels=netLabels,
inputs=embed,
num_sampled=n_sampled,
num_classes=nVocab)
cost = tf.reduce_mean(loss)
optimizer = tf.train.AdamOptimizer().minimize(cost)
'''
Here we're going to choose a few common words and few uncommon words. Then, we'll print out the closest words to them.
It's a nice way to check that our embedding table is grouping together words with similar semantic meanings.
'''
with train_graph.as_default():
validSize = 16
validWindow = 100
validExamples = np.array(random.sample(range(validWindow), validSize // 2))
validExamples = np.append(validExamples,
random.sample(range(1000, 1000 + validWindow), validSize // 2))
validDataset = tf.constant(validExamples, dtype=tf.int32)
norm = tf.sqrt(tf.reduce_sum(tf.square(embedding), 1, keep_dims=True))
normalizedEmbedding = embedding / norm
valid_embedding = tf.nn.embedding_lookup(normalizedEmbedding, validDataset)
similarity = tf.matmul(valid_embedding, tf.transpose(normalizedEmbedding))
'''
Train the network. Every 100 batches it reports the training loss. Every 1000 batches, it'll print out the validation
words.
'''
epochs = 10
batch_size = 1000
window_size = 10
with train_graph.as_default():
saver = tf.train.Saver()
with tf.Session(graph=train_graph) as sess:
iteration = 1
loss = 0
sess.run(tf.global_variables_initializer())
for e in range(1, epochs + 1):
batches = getBatches(trainWords, batch_size, window_size)
start = time.time()
for x, y in batches:
feed = {netInputs: x,
netLabels: np.array(y)[:, None]}
trainLoss, _ = sess.run([cost, optimizer], feed_dict=feed)
loss += trainLoss
if iteration % 100 == 0:
end = time.time()
print("Epoch {}/{}".format(e, epochs),
"Iteration: {}".format(iteration),
"Avg. Training loss: {:.4f}".format(loss / 100),
"{:.4f} sec/batch".format((end - start) / 100))
loss = 0
start = time.time()
if iteration % 1000 == 0:
sim = similarity.eval()
for i in range(validSize):
validWord = int2vocab[validExamples[i]]
topK = 8
nearest = (-sim[i, :]).argsort()[1:topK + 1]
log = 'Nearest to %s:' % validWord
for k in range(topK):
closeWord = int2vocab[nearest[k]]
logStatement = '%s %s,' % (log, closeWord)
print(logStatement)
iteration += 1
save_path = saver.save(sess, "checkpoints/text8.ckpt")
embed_mat = sess.run(normalizedEmbedding)
'''
Restore the trained network if you need to
'''
with train_graph.as_default():
saver = tf.train.Saver()
with tf.Session(graph=train_graph) as sess:
saver.restore(sess, tf.train.latest_checkpoint('checkpoints'))
embed_mat = sess.run(embedding)
'''
Below we'll use T-SNE to visualize how our high-dimensional word vectors cluster together. T-SNE is used to project
these vectors into two dimensions while preserving local structure.
'''
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE
vizWords = 500
tsne = TSNE()
embedTSNE = tsne.fit_transform(embed_mat[:vizWords, :])
fig, ax = plt.subplots(figsize=(14, 14))
for idx in range(vizWords):
plt.scatter(*embedTSNE[idx, :], color='steelblue')
plt.annotate(int2vocab[idx], (embedTSNE[idx, 0], embedTSNE[idx, 1]), alpha=0.7)
列表的输出如下:
Total words: 16680599
Unique words: 63641
no. of batches 4626
Epoch 1/10 Iteration: 100 Avg. Training loss: 21.7284 0.3363 sec/batch
Epoch 1/10 Iteration: 1000 Avg. Training loss: 20.2269 0.3668 sec/batch
Nearest to but: universities, hungry, kyu, grandiose, edema, patty, stores, psychometrics,
Nearest to three: sulla, monuc, conjuring, ontological, auf, grimoire, unpredictably, frenetic,
Nearest to world: turkle, spectroscopic, jules, servicio, sportswriter, kamikazes, act, earns,
Epoch 1/10 Iteration: 1100 Avg. Training loss: 20.1983 0.3650 sec/batch
Epoch 1/10 Iteration: 2000 Avg. Training loss: 19.1581 0.3767 sec/batch
Nearest to but: universities, hungry, edema, kyu, grandiose, stores, patty, psychometrics,
Nearest to three: monuc, sulla, unpredictably, grimoire, hickey, ontological, conjuring, rays,
Nearest to world: turkle, spectroscopic, jules, sportswriter, kamikazes, alfons, servicio, act,
......
使用 RNN 进行情感分析
以下示例展示了使用 RNN 实现情感分析。它包含固定长度的电影评论,这些评论被编码为整数值,然后转换为词嵌入(嵌入向量),并以递归方式传递给 LSTM 层,最后选取最后的预测作为输出情感:
import numpy as np
import tensorflow as tf
from string import punctuation
from collections import Counter
'''
movie review dataset for sentiment analysis
'''
with open('data/reviews.txt', 'r') as f:
movieReviews = f.read()
with open('data/labels.txt', 'r') as f:
labels = f.read()
'''
data cleansing - remove punctuations
'''
text = ''.join([c for c in movieReviews if c not in punctuation])
movieReviews = text.split('\n')
text = ' '.join(movieReviews)
words = text.split()
print(text[:500])
print(words[:100])
'''
build a dictionary that maps words to integers
'''
counts = Counter(words)
vocabulary = sorted(counts, key=counts.get, reverse=True)
vocab2int = {word: i for i, word in enumerate(vocabulary, 1)}
reviewsInts = []
for review in movieReviews:
reviewsInts.append([vocab2int[word] for word in review.split()])
'''
convert labels from positive and negative to 1 and 0 respectively
'''
labels = labels.split('\n')
labels = np.array([1 if label == 'positive' else 0 for label in labels])
reviewLengths = Counter([len(x) for x in reviewsInts])
print("Min review length are: {}".format(reviewLengths[0]))
print("Maximum review length are: {}".format(max(reviewLengths)))
'''
remove the review with zero length from the reviewsInts list
'''
nonZeroIndex = [i for i, review in enumerate(reviewsInts) if len(review) != 0]
print(len(nonZeroIndex))
'''
turns out its the final review that has zero length. But that might not always be the case, so let's make it more
general.
'''
reviewsInts = [reviewsInts[i] for i in nonZeroIndex]
labels = np.array([labels[i] for i in nonZeroIndex])
'''
create an array features that contains the data we'll pass to the network. The data should come from reviewInts, since
we want to feed integers to the network. Each row should be 200 elements long. For reviews shorter than 200 words,
left pad with 0s. That is, if the review is ['best', 'movie', 'renaira'], [100, 40, 20] as integers, the row will look
like [0, 0, 0, ..., 0, 100, 40, 20]. For reviews longer than 200, use on the first 200 words as the feature vector.
'''
seqLen = 200
features = np.zeros((len(reviewsInts), seqLen), dtype=int)
for i, row in enumerate(reviewsInts):
features[i, -len(row):] = np.array(row)[:seqLen]
print(features[:10,:100])
'''
lets create training, validation and test data sets. trainX and trainY for example.
also define a split percentage function 'splitPerc' as the percentage of data to keep in the training
set. usually this is 0.8 or 0.9.
'''
splitPrec = 0.8
splitIndex = int(len(features)*0.8)
trainX, valX = features[:splitIndex], features[splitIndex:]
trainY, valY = labels[:splitIndex], labels[splitIndex:]
testIndex = int(len(valX)*0.5)
valX, testX = valX[:testIndex], valX[testIndex:]
valY, testY = valY[:testIndex], valY[testIndex:]
print("Train set: {}".format(trainX.shape), "\nValidation set: {}".format(valX.shape), "\nTest set: {}".format(testX.shape))
print("label set: {}".format(trainY.shape), "\nValidation label set: {}".format(valY.shape), "\nTest label set: {}".format(testY.shape))
'''
tensor-flow computational graph
'''
lstmSize = 256
lstmLayers = 1
batchSize = 500
learningRate = 0.001
nWords = len(vocab2int) + 1
# create graph object and add nodes to the graph
graph = tf.Graph()
with graph.as_default():
inputData = tf.placeholder(tf.int32, [None, None], name='inputData')
labels = tf.placeholder(tf.int32, [None, None], name='labels')
keepProb = tf.placeholder(tf.float32, name='keepProb')
'''
let us create the embedding layer (word2vec)
'''
# number of neurons in hidden or embedding layer
embedSize = 300
with graph.as_default():
embedding = tf.Variable(tf.random_uniform((nWords, embedSize), -1, 1))
embed = tf.nn.embedding_lookup(embedding, inputData)
'''
lets use tf.contrib.rnn.BasicLSTMCell to create an LSTM cell, later add drop out to it with
tf.contrib.rnn.DropoutWrapper. and finally create multiple LSTM layers with tf.contrib.rnn.MultiRNNCell.
'''
with graph.as_default():
with tf.name_scope("RNNLayers"):
def createLSTMCell():
lstm = tf.contrib.rnn.BasicLSTMCell(lstmSize, reuse=tf.get_variable_scope().reuse)
return tf.contrib.rnn.DropoutWrapper(lstm, output_keep_prob=keepProb)
cell = tf.contrib.rnn.MultiRNNCell([createLSTMCell() for _ in range(lstmLayers)])
initialState = cell.zero_state(batchSize, tf.float32)
'''
set tf.nn.dynamic_rnn to add the forward pass through the RNN. here we're actually passing in vectors from the
embedding layer 'embed'.
'''
with graph.as_default():
outputs, finalState = tf.nn.dynamic_rnn(cell, embed, initial_state=initialState)
'''
final output will carry the sentiment prediction, therefore lets get the last output with outputs[:, -1],
the we calculate the cost from that and labels.
'''
with graph.as_default():
predictions = tf.contrib.layers.fully_connected(outputs[:, -1], 1, activation_fn=tf.sigmoid)
cost = tf.losses.mean_squared_error(labels, predictions)
optimizer = tf.train.AdamOptimizer(learningRate).minimize(cost)
'''
now we can add a few nodes to calculate the accuracy which we'll use in the validation pass.
'''
with graph.as_default():
correctPred = tf.equal(tf.cast(tf.round(predictions), tf.int32), labels)
accuracy = tf.reduce_mean(tf.cast(correctPred, tf.float32))
'''
get batches
'''
def getBatches(x, y, batchSize=100):
nBatches = len(x) // batchSize
x, y = x[:nBatches * batchSize], y[:nBatches * batchSize]
for i in range(0, len(x), batchSize):
yield x[i:i + batchSize], y[i:i + batchSize]
'''
training phase
'''
epochs = 1
with graph.as_default():
saver = tf.train.Saver()
with tf.Session(graph=graph) as sess:
writer = tf.summary.FileWriter("logs", graph=tf.get_default_graph())
sess.run(tf.global_variables_initializer())
iteration = 1
for e in range(epochs):
state = sess.run(initialState)
for i, (x, y) in enumerate(getBatches(trainX, trainY, batchSize), 1):
feed = {inputData: x, labels: y[:, None], keepProb: 0.5, initialState: state}
loss, state, _ = sess.run([cost, finalState, optimizer], feed_dict=feed)
if iteration % 5 == 0:
print("Epoch are: {}/{}".format(e, epochs), "Iteration is: {}".format(iteration), "Train loss is: {:.3f}".format(loss))
if iteration % 25 == 0:
valAcc = []
valState = sess.run(cell.zero_state(batchSize, tf.float32))
for x, y in getBatches(valX, valY, batchSize):
feed = {inputData: x, labels: y[:, None], keepProb: 1, initialState: valState}
batchAcc, valState = sess.run([accuracy, finalState], feed_dict=feed)
valAcc.append(batchAcc)
print("Val acc: {:.3f}".format(np.mean(valAcc)))
iteration += 1
saver.save(sess, "checkpoints/sentimentanalysis.ckpt")
saver.save(sess, "checkpoints/sentimentanalysis.ckpt")
'''
testing phase
'''
testAcc = []
with tf.Session(graph=graph) as sess:
saver.restore(sess, "checkpoints/sentiment.ckpt")
testState = sess.run(cell.zero_state(batchSize, tf.float32))
for i, (x, y) in enumerate(getBatches(testY, testY, batchSize), 1):
feed = {inputData: x,
labels: y[:, None],
keepProb: 1,
initialState: testState}
batchAcc, testState = sess.run([accuracy, finalState], feed_dict=feed)
testAcc.append(batchAcc)
print("Test accuracy is: {:.3f}".format(np.mean(testAcc)))
计算图

列表的输出如下所示:
Train set: (20000, 200)
Validation set: (2500, 200)
Test set: (2500, 200)
label set: (20000,)
Validation label set: (2500,)
Test label set: (2500,)
Val acc: 0.682
Val acc: 0.692
Val acc: 0.714
Val acc: 0.808
Val acc: 0.763
Val acc: 0.826
Val acc: 0.854
Val acc: 0.872
摘要
在本章中,你学习了递归神经网络的基础知识,以及它为何是处理时间序列数据的有用机制。你学习了基本概念,如状态、词嵌入和长期记忆。接着是一个开发情感分析系统的示例。我们还使用 TensorFlow 实现了递归神经网络。
在下一章,我们将介绍一种不同类型的神经网络,称为生成模型。
第六章:生成模型
生成模型是机器学习模型的一类,用于描述数据是如何生成的。为了训练一个生成模型,我们首先收集大量的领域数据,然后训练一个模型来生成类似的数据。
换句话说,这些模型可以学习生成与我们提供的数据相似的数据。一个这样的方式是使用生成对抗网络(GANs),本章将详细讨论这一部分内容。
本章将涵盖以下主题:
-
生成模型简介
-
GANs
生成模型
机器学习模型有两种类型:生成模型和判别模型。我们来看以下分类器的列表:决策树、神经网络、随机森林、广义增强模型、逻辑回归、朴素贝叶斯和支持向量机(SVM)。其中大多数是分类器和集成模型,唯一的例外是朴素贝叶斯,它是列表中的唯一生成模型。其他的则是判别模型的例子。
生成模型和判别模型的根本区别在于它们的概率推理结构。在本章中,我们将学习生成模型的关键概念,如类型和 GANs,但在此之前,让我们先了解一下生成模型和判别模型之间的一些关键区别。
判别模型与生成模型的区别
判别模型学习 P(Y|X),即目标变量 Y 和特征 X 之间的条件关系。这就是最小二乘回归的工作原理,也是使用的推理模式。它是一种用于解决变量之间关系的推理方法。
生成模型旨在对数据集进行完整的概率描述。使用生成模型时,目标是开发联合概率分布 P(X, Y),可以直接计算 P(Y | X) 和 P(X),然后推断分类新数据所需的条件概率。与回归模型相比,这种方法需要更为扎实的概率思维,但它提供了数据概率结构的完整模型。了解联合分布后,你可以生成数据;因此,朴素贝叶斯就是一个生成模型。
假设我们有一个监督学习任务,其中 x[i] 是数据点的给定特征,y[i] 是对应的标签。预测未来 x 上的 y 的一种方法是学习一个函数 f(),它从 (x[i], y[i]) 中获取 x 并输出最可能的 y。这样的模型属于判别模型,因为你正在学习如何区分来自不同类别的 x。像 SVM 和神经网络这样的算法就属于这一类。即使你能够非常准确地分类数据,你也无法知道数据是如何生成的。
第二种方法是模拟数据可能是如何生成的,并学习一个函数 f(x,y),为由 x 和 y 共同决定的配置打分。然后,你可以通过找到使得 f(x,y) 得分最大的 y 来预测新的 x 的 y。高斯混合模型是这一方法的典型例子。
另一个例子是:你可以将 x 想象成一张图像,将 y 想象成一个对象种类,如狗,即在图像中。写作 p(y|x) 的概率告诉我们,在给定输入图像的情况下,模型认为其中有一只狗的程度,相对于它所知道的所有可能性。尝试直接建模这个概率映射的算法被称为判别模型。
另一方面,生成模型试图学习一个称为联合概率 p(y, x) 的函数。我们可以将其理解为模型在多大程度上相信 x 是一张图像并且其中有一只狗 y。这两个概率是相关的,可以写作 p(y, x) = p(x) p(y|x),其中 p(x) 是输入 x 为图像的可能性。p(x) 概率通常在文献中称为密度函数。
之所以最终称这些模型为生成模型,主要是因为模型能够同时访问输入和输出的概率。利用这一点,我们可以通过从 p(y, x) 中采样动物种类 y 和新的图像 x 来生成动物的图像。
我们主要可以学习密度函数 p(x),它仅依赖于输入空间。
两种模型都有用;然而,相对而言,生成模型相较于判别模型有一个有趣的优势,即它们有潜力理解和解释输入数据的潜在结构,即使在没有标签的情况下也能做到。这在实际工作中是非常需要的。
生成模型的类型
判别模型一直是最近在机器学习领域取得成功的前沿。模型根据给定输入进行预测,尽管它们不能生成新的样本或数据。
最近生成模型进展背后的想法是将生成问题转换为预测问题,并使用深度学习算法来学习这种问题。
自编码器
将生成问题转化为判别问题的一种方式是通过学习从输入空间本身的映射。例如,我们希望学习一个恒等映射,对于每个图像 x,理想情况下预测相同的图像,即 x = f(x),其中 f 是预测模型。
该模型在当前形式下可能没有什么用处,但我们可以从中创建一个生成模型。
在这里,我们创建一个由两个主要组件组成的模型:一个编码器模型 q(h|x),它将输入映射到另一个空间,这个空间被称为隐藏空间或潜在空间,用 h 表示,另一个是解码器模型 q(x|h),它学习从隐藏输入空间到原输入空间的反向映射。
这些组件——编码器和解码器——连接在一起,形成一个端到端可训练的模型。编码器和解码器模型都是具有不同架构的神经网络,例如 RNN 和注意力网络,以获得期望的结果。
随着模型的学习,我们可以从编码器中移除解码器,然后单独使用它们。为了生成新的数据样本,我们可以首先从潜在空间生成一个样本,然后将其输入解码器,从输出空间创建一个新的样本。
自编码器在第八章中有更详细的介绍,自编码器。
GAN
如自编码器所示,我们可以将创建可以在关系中共同工作的网络的概念作为一般思路,训练这些网络将帮助我们学习潜在空间,从而生成新的数据样本。
另一种生成网络是 GAN,其中我们有一个生成模型q(x|h),它将小维度的潜在空间h(通常表示为来自简单分布的噪声样本)映射到输入空间x。这与自编码器中解码器的作用非常相似。
现在的任务是引入一个判别模型p(y| x),它试图将输入实例x与一个二元答案y关联起来,判断该输入是由生成器模型生成的,还是来自我们训练数据集中的真实样本。
让我们使用之前做的图像示例。假设生成器模型创建了一张新图像,我们也有来自实际数据集的真实图像。如果生成器模型做得正确,判别模型将难以区分这两张图像。如果生成器模型做得不好,判断哪张是假图像、哪张是真图像将变得非常简单。
当这两个模型结合在一起时,我们可以通过确保生成模型随着时间的推移变得更强,从而欺骗判别模型,同时训练判别模型去处理更难的任务——检测欺诈。最终,我们希望生成器模型的输出与我们用来训练的真实数据无法区分。
在训练的初期,判别模型可以轻松区分实际数据集中的样本与生成模型刚开始学习时所生成的样本。随着生成器在建模数据集方面的能力逐渐提升,我们开始看到越来越多与数据集相似的生成样本。下面的例子展示了一个 GAN 模型随着时间推移学习的生成图像:

在接下来的部分,我们将详细讨论 GAN。
序列模型
如果数据具有时间性,我们可以使用专门的算法,称为序列模型。这些模型可以学习形式为p(y|x_n, x_1)的概率,其中i是表示序列中位置的索引,x_i是第i个输入样本。
作为一个例子,我们可以将每个单词看作是一系列字符,每个句子看作是一系列单词,每个段落看作是一系列句子。输出y可以是句子的情感。
使用自编码器中的类似技巧,我们可以将y替换为序列或序列中的下一个项,即y = x_n + 1,从而让模型学习。
GAN
GAN 是由蒙特利尔大学的一组研究人员提出的,该团队由Ian Goodfellow领导。GAN 模型的核心思想是有两个相互竞争的神经网络模型。一个网络以噪声为输入并生成样本(因此被称为生成器)。第二个模型(称为判别器)从生成器和实际的训练数据中获取样本,并应能区分这两种来源。生成模型和判别模型在不断地进行博弈,生成器学习生成更真实的样本,而判别器则学习更好地区分生成的数据和真实数据。这两个网络同时训练,目标是通过这种竞争,使得生成的样本与真实数据无法区分:

用来描述 GAN 的类比是:生成器就像一个伪造者,试图制造一些伪造的物品,而判别器模型则像警察,试图检测伪造的物品。这可能看起来与强化学习有些相似,在强化学习中,生成器从判别器那里获得奖励,帮助它知道生成的数据是否准确。GAN 的关键区别在于,我们可以将来自判别器网络的梯度信息反向传播到生成器网络,从而让生成器知道如何调整其参数,生成能够欺骗判别器的输出数据。
截至今天,GAN 主要被应用于建模自然图像。它们在图像生成任务中提供了最佳的结果,且在生成比其他基于最大似然训练目标的生成方法更清晰的图像方面也表现出色。
这里是一些由 GAN 生成的图像示例:

GAN 示例
为了更深入理解 GAN 是如何工作的,我们将使用一个 GAN 在 TensorFlow 中解决一个简单的问题,即学习近似一维高斯分布。
首先,我们将创建实际数据分布,这是一个简单的高斯分布,均值为 4,标准差为 0.5。它有一个样本函数,返回来自该分布的指定数量的样本(按值排序)。我们学习的数据分布将类似于以下图示:

生成器输入的噪声分布也通过类似的样本函数定义,用于实际数据。
生成器和判别器网络都非常简单。生成器是一个线性变换,通过非线性函数(softplus函数)进行处理,随后是另一个线性变换。
我们保持判别器比生成器更强,否则它将没有足够的能力准确区分生成的样本和真实样本。因此,我们将判别器设计为一个更深的神经网络,拥有更高的维度。除最后一层外,我们在所有层使用tanh非线性函数,最后一层使用的是 sigmoid(其输出可以描述为概率)。
我们将这些网络连接到 TensorFlow 图中,并为每个网络定义损失函数,以便生成器网络能轻松欺骗判别器网络。我们使用 TensorFlow 的梯度下降优化器,并配合指数学习率衰减作为优化器。
为了训练模型,我们从数据分布和噪声分布中抽取样本,并交替优化判别器和生成器的参数。
我们会看到,在训练方法开始时,生成器生成的分布与真实数据非常不同。网络逐渐学习逼近真实数据,最终收敛到一个聚焦于输入分布均值的更窄分布。在训练完网络之后,两个分布大致如下图所示:

生成器网络陷入一种参数设置,导致它生成一个非常狭窄的分布或点模式,是 GANs 的主要失败之一。解决方案是允许判别器一次查看多个样本,这种技术称为小批量判别。小批量判别是一种方法,判别器可以查看一个完整的批次样本,判断它们是来自生成器网络还是来自真实数据。
该方法的总结如下:
-
获取判别器网络任何中间层的输出。
-
将该输出乘以一个 3D 张量,生成一个大小为*numOfKernels ** kernelDim的矩阵。
-
计算该矩阵中每一行之间的 L1 距离,并对批次中的所有样本应用负指数。
-
样本的小批量特征或属性是这些指数距离的总和。
-
将实际输入与小批量层连接,即将前一个判别器层的输出与创建的小批量特征进行合并,然后将其作为输入传递给判别器网络的下一个层。
小批量判别使得批量大小变得和超参数一样重要:
import argparse
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
from matplotlib import animation
import seaborn as sns
from tensorflow.python.training.gradient_descent import GradientDescentOptimizer
sns.set(color_codes=True)
seed = 42
np.random.seed(seed)
tf.set_random_seed(seed)
# gaussian data distribution
class DataDist(object):
def __init__(self):
self.mue = 4
self.sigma = 0.5
def sample(self, N):
samples = np.random.normal(self.mue, self.sigma, N)
samples.sort()
return samples
# data distribution with noise
class GeneratorDist(object):
def __init__(self, rnge):
self.rnge = rnge
def sample(self, N):
return np.linspace(-self.rnge, self.rnge, N) + \
np.random.random(N) * 0.01
# linear method
def linearUnit(input, output_dim, scope=None, stddev=1.0):
with tf.variable_scope(scope or 'linearUnit'):
weight = tf.get_variable(
'weight',
[input.get_shape()[1], output_dim],
initializer=tf.random_normal_initializer(stddev=stddev)
)
bias = tf.get_variable(
'bias',
[output_dim],
initializer=tf.constant_initializer(0.0)
)
return tf.matmul(input, weight) + bias
# generator network
def generatorNetwork(input, hidden_size):
hidd0 = tf.nn.softplus(linearUnit(input, hidden_size, 'g0'))
hidd1 = linearUnit(hidd0, 1, 'g1')
return hidd1
# discriminator network
def discriminatorNetwork(input, h_dim, minibatch_layer=True):
hidd0 = tf.nn.relu(linearUnit(input, h_dim * 2, 'd0'))
hidd1 = tf.nn.relu(linearUnit(hidd0, h_dim * 2, 'd1'))
if minibatch_layer:
hidd2 = miniBatch(hidd1)
else:
hidd2 = tf.nn.relu(linearUnit(hidd1, h_dim * 2, scope='d2'))
hidd3 = tf.sigmoid(linearUnit(hidd2, 1, scope='d3'))
return hidd3
# minibatch
def miniBatch(input, numKernels=5, kernelDim=3):
x = linearUnit(input, numKernels * kernelDim, scope='minibatch', stddev=0.02)
act = tf.reshape(x, (-1, numKernels, kernelDim))
differences = tf.expand_dims(act, 3) - \
tf.expand_dims(tf.transpose(act, [1, 2, 0]), 0)
absDiffs = tf.reduce_sum(tf.abs(differences), 2)
minibatchFeatures = tf.reduce_sum(tf.exp(-absDiffs), 2)
return tf.concat([input, minibatchFeatures], 1)
# optimizer
def optimizer(loss, var_list):
learning_rate = 0.001
step = tf.Variable(0, trainable=False)
optimizer = tf.train.AdamOptimizer(learning_rate).minimize(
loss,
global_step=step,
var_list=var_list
)
return optimizer
# log
def log(x):
return tf.log(tf.maximum(x, 1e-5))
class GAN(object):
def __init__(self, params):
with tf.variable_scope('Generator'):
self.zee = tf.placeholder(tf.float32, shape=(params.batchSize, 1))
self.Gee = generatorNetwork(self.zee, params.hidden_size)
self.xVal = tf.placeholder(tf.float32, shape=(params.batchSize, 1))
with tf.variable_scope('Discriminator'):
self.Dis1 = discriminatorNetwork(
self.xVal,
params.hidden_size,
params.minibatch
)
with tf.variable_scope('D', reuse=True):
self.Dis2 = discriminatorNetwork(
self.Gee,
params.hidden_size,
params.minibatch
)
self.lossD = tf.reduce_mean(-log(self.Dis1) - log(1 - self.Dis2))
self.lossG = tf.reduce_mean(-log(self.Dis2))
vars = tf.trainable_variables()
self.dParams = [v for v in vars if v.name.startswith('D/')]
self.gParams = [v for v in vars if v.name.startswith('G/')]
self.optD = optimizer(self.lossD, self.dParams)
self.optG = optimizer(self.lossG, self.gParams)
'''
Train GAN model
'''
def trainGan(model, data, gen, params):
animFrames = []
with tf.Session() as session:
tf.local_variables_initializer().run()
tf.global_variables_initializer().run()
for step in range(params.numSteps + 1):
x = data.sample(params.batchSize)
z = gen.sample(params.batchSize)
lossD, _, = session.run([model.lossD, model.optD], {
model.x: np.reshape(x, (params.batchSize, 1)),
model.z: np.reshape(z, (params.batchSize, 1))
})
z = gen.sample(params.batchSize)
lossG, _ = session.run([model.lossG, model.optG], {
model.z: np.reshape(z, (params.batchSize, 1))
})
if step % params.log_every == 0:
print('{}: {:.4f}\t{:.4f}'.format(step, lossD, lossG))
if params.animPath and (step % params.animEvery == 0):
animFrames.append(
getSamples(model, session, data, gen.range, params.batchSize)
)
if params.animPath:
saveAnimation(animFrames, params.animPath, gen.range)
else:
samps = getSamples(model, session, data, gen.range, params.batchSize)
plotDistributions(samps, gen.range)
def getSamples(
model,
session,
data,
sampleRange,
batchSize,
numPoints=10000,
numBins=100
):
xs = np.linspace(-sampleRange, sampleRange, numPoints)
binss = np.linspace(-sampleRange, sampleRange, numBins)
# decision boundary
db = np.zeros((numPoints, 1))
for i in range(numPoints // batchSize):
db[batchSize * i:batchSize * (i + 1)] = session.run(
model.D1,
{
model.x: np.reshape(
xs[batchSize * i:batchSize * (i + 1)],
(batchSize, 1)
)
}
)
# data distribution
d = data.sample(numPoints)
pds, _ = np.histogram(d, bins=binss, density=True)
zs = np.linspace(-sampleRange, sampleRange, numPoints)
g = np.zeros((numPoints, 1))
for i in range(numPoints // batchSize):
g[batchSize * i:batchSize * (i + 1)] = session.run(
model.G,
{
model.z: np.reshape(
zs[batchSize * i:batchSize * (i + 1)],
(batchSize, 1)
)
}
)
pgs, _ = np.histogram(g, bins=binss, density=True)
return db, pds, pgs
def plotDistributions(samps, sampleRange):
db, pd, pg = samps
dbX = np.linspace(-sampleRange, sampleRange, len(db))
pX = np.linspace(-sampleRange, sampleRange, len(pd))
f, ax = plt.subplots(1)
ax.plot(dbX, db, label='Decision Boundary')
ax.set_ylim(0, 1)
plt.plot(pX, pd, label='Real Data')
plt.plot(pX, pg, label='Generated Data')
plt.title('1D Generative Adversarial Network')
plt.xlabel('Data Values')
plt.ylabel('Probability Density')
plt.legend()
plt.show()
def saveAnimation(animFrames, animPath, sampleRange):
f, ax = plt.subplots(figsize=(6, 4))
f.suptitle('1D GAN', fontsize=15)
plt.xlabel('dataValues')
plt.ylabel('probabilityDensity')
ax.set_xlim(-6, 6)
ax.set_ylim(0, 1.4)
lineDb, = ax.plot([], [], label='decision boundary')
linePd, = ax.plot([], [], label='real data')
linePg, = ax.plot([], [], label='generated data')
frameNumber = ax.text(
0.02,
0.95,
'',
horizontalalignment='left',
verticalalignment='top',
transform=ax.transAxes
)
ax.legend()
db, pd, _ = animFrames[0]
dbX = np.linspace(-sampleRange, sampleRange, len(db))
pX = np.linspace(-sampleRange, sampleRange, len(pd))
def init():
lineDb.set_data([], [])
linePd.set_data([], [])
linePg.set_data([], [])
frameNumber.set_text('')
return (lineDb, linePd, linePg, frameNumber)
def animate(i):
frameNumber.set_text(
'Frame: {}/{}'.format(i, len(animFrames))
)
db, pd, pg = animFrames[i]
lineDb.set_data(dbX, db)
linePd.set_data(pX, pd)
linePg.set_data(pX, pg)
return (lineDb, linePd, linePg, frameNumber)
anim = animation.FuncAnimation(
f,
animate,
init_func=init,
frames=len(animFrames),
blit=True
)
anim.save(animPath, fps=30, extra_args=['-vcodec', 'libx264'])
# start gan modeling
def gan(args):
model = GAN(args)
trainGan(model, DataDist(), GeneratorDist(range=8), args)
# input arguments
def parseArguments():
argParser = argparse.ArgumentParser()
argParser.add_argument('--num-steps', type=int, default=5000,
help='the number of training steps to take')
argParser.add_argument('--hidden-size', type=int, default=4,
help='MLP hidden size')
argParser.add_argument('--batch-size', type=int, default=8,
help='the batch size')
argParser.add_argument('--minibatch', action='store_true',
help='use minibatch discrimination')
argParser.add_argument('--log-every', type=int, default=10,
help='print loss after this many steps')
argParser.add_argument('--anim-path', type=str, default=None,
help='path to the output animation file')
argParser.add_argument('--anim-every', type=int, default=1,
help='save every Nth frame for animation')
return argParser.parse_args()
# start the gan app
if __name__ == '__main__':
gan(parseArguments())
输出列表:
0: 6.6300 0.1449
10: 6.5390 0.1655
20: 6.4552 0.1866
30: 6.3789 0.2106
40: 6.3190 0.2372
50: 6.2814 0.2645
60: 6.2614 0.2884
70: 6.2556 0.3036
80: 6.2814 0.3104
90: 6.2796 0.3113
100: 6.3008 0.3106
110: 6.2923 0.3112
120: 6.2792 0.3153
130: 6.3299 0.3196
140: 6.3512 0.3205
150: 6.2999 0.3197
160: 6.3513 0.3236
170: 6.3521 0.3291
180: 6.3377 0.3292
GAN 的类型
以下部分展示了不同类型的 GAN,例如普通 GAN、条件 GAN 等。欲了解更多论文信息,请参考arxiv.org。以下每种 GAN 网络的描述均来自相应的论文,论文地址为arxiv.org。
普通 GAN
普通 GAN(Vanilla GAN)有两个网络,分别是生成器网络和判别器网络。两个网络同时训练,并且相互竞争或对抗,进行极小极大博弈。生成器网络被训练为能够通过根据输入生成真实图像来欺骗判别器网络,而判别器网络则被训练为不被生成器网络所欺骗。
如需进一步了解普通 GAN,请参考arxiv.org/abs/1406.2661。
条件 GAN
GAN(生成对抗网络)最初是作为一种新颖的生成训练模型方式出现的。这些是利用额外标签数据的 GAN 网络。它能够生成高质量的图像,并且在一定程度上控制生成图像的外观。该模型可以用于学习多模态模型。
如需进一步了解条件 GAN,请参考arxiv.org/abs/1411.1784.
Info GAN
GAN 可以以无监督的方式编码或学习重要的图像特征或解耦表示。一个例子是编码数字的旋转。Info GAN 还最大化潜在变量的小子集与观测值之间的互信息。
如需进一步了解 Info GAN,请参考arxiv.org/abs/1606.03657
Wasserstein GAN
WGAN(Wasserstein 生成对抗网络)是对常规 GAN 训练的一种选择。WGAN 的损失函数与图像质量相关。此外,WGAN 的训练稳定性提高,不那么依赖于架构,并且提供了有助于调试的显著学习曲线。
如需进一步了解 Wasserstein GAN,请参考arxiv.org/abs/1701.07875
联合 GAN
联合 GAN(Coupled GANs)用于在两个独立领域中生成相似图像的集合。它由一组 GAN 组成,每个 GAN 负责在单一领域内生成图像。联合 GAN 学习两个领域中图像的联合分布,这些图像分别从各自领域的边缘分布中抽取。
如需进一步了解联合 GAN,请参考arxiv.org/abs/1606.07536
总结
生成模型是一个快速发展的研究领域。随着我们推进这些模型并扩展训练和数据集,我们可以期待最终生成出完全可信的图像数据示例。这可以应用于多种场景,如图像去噪、绘画、结构化预测以及强化学习中的探索。
这项努力的更深层次承诺是,在构建生成模型的过程中,我们将赋予计算机对世界及其组成元素的理解。
第七章:深度置信网络
深度置信网络(DBN)是一种深度神经网络,由多层隐藏单元组成,各层之间存在连接;DBN 的不同之处在于这些隐藏单元在每层内不会与其他单元交互。DBN 可以在没有监督的情况下,使用训练数据集进行训练,学习概率性地重建其输入。它是对大量相互作用的随机变量的联合(多变量)分布。这些表示位于统计学和计算机科学的交集处,依赖于概率论、图算法、机器学习等概念。
各层充当特征检测器。训练步骤完成后,DBN 可以通过监督训练来进行分类。
我们将在这一章节中涵盖以下内容:
-
理解深度置信网络
-
模型训练
-
预测标签
-
查找模型的准确率
-
MNIST 数据集的 DBN 实现
-
RBM 层中神经元数量对 DBN 的影响
-
带有两个 RBM 层的 DBN
-
使用 DBN 对 NotMNIST 数据集进行分类
理解深度置信网络
DBN 可以看作是简单无监督网络的组合,如 限制玻尔兹曼机(RBM)或自编码器;在这些网络中,每个子网络的隐藏层作为下一个子网络的可见层。RBM 是一种无向生成模型,具有一个输入层(可见)和一个隐藏层,层之间存在连接,但层内没有连接。这种拓扑结构使得逐层的无监督训练过程变得快速。对每个子网络应用对比散度,从最低层对开始(最低可见层为训练集)。
DBN 是逐层训练(贪婪式)进行的,这使其成为最早有效的深度学习算法之一。DBN 在现实应用和场景中有许多实现和用途;我们将研究如何使用 DBN 对 MNIST 和 NotMNIST 数据集进行分类。
DBN 实现
该类实例化了限制玻尔兹曼机(RBN)层和成本函数。DeepBeliefNetwork 类本身是 Model 类的子类:

类初始化
在 DBN 初始化时,调用 Model 类的初始化方法 __init__(self, name)。Model 类引用了以下内容:
-
输入数据:
self.input_data -
输入标签:
self.input_labels -
成本:
`self.cost` -
最终层中的节点数量:
self.layer_nodes -
TensorFlow 会话:
self.tf_session -
TensorFlow 图:
self.tf_graph = tf.graph
class Model(object):
"""Class representing an abstract Model."""
def __init__(self, name):
"""Constructor.
:param name: name of the model, used as filename.
string, default 'dae'
"""
self.name = name
self.model_path = os.path.join(Config().models_dir, self.name)
self.input_data = None
self.input_labels = None
> self.keep_prob = None
self.layer_nodes = [] # list of layers of the final network
self.train_step = None
self.cost = None
# tensorflow objects
self.tf_graph = tf.Graph()
self.tf_session = None
self.tf_saver = None
self.tf_merged_summaries = None
self.tf_summary_writer = None
定义的其他变量是损失函数,应为以下之一:
["cross_entropy", "softmax_cross_entropy", "mse"]
DeepBeliefNetwork 类的代码列表如下所示。__init__()函数在下面的代码中显示。在这里,所有变量(例如每个 RBM 层的参数数组)都已指定。我们还调用了SupervisedModel的__init__()函数,SupervisedModel是DeepBeliefNetwork类的父类。
初始化时有两个重要的参数:
-
self.rbms = []:RBM类实例的数组 -
self.rbm_graphs = []:每个 RBM 的tf.Graph数组
class DeepBeliefNetwork(SupervisedModel):
"""Implementation of Deep Belief Network for Supervised Learning.
The interface of the class is sklearn-like.
"""
def __init__(
self, rbm_layers, name='dbn', do_pretrain=False,
rbm_num_epochs=[10], rbm_gibbs_k=[1],
rbm_gauss_visible=False, rbm_stddev=0.1, rbm_batch_size=[10],
rbm_learning_rate=[0.01], finetune_dropout=1,
finetune_loss_func='softmax_cross_entropy',
finetune_act_func=tf.nn.sigmoid, finetune_opt='sgd',
finetune_learning_rate=0.001, finetune_num_epochs=10,
finetune_batch_size=20, momentum=0.5):
SupervisedModel.__init__(self, name)
self.loss_func = finetune_loss_func
self.learning_rate = finetune_learning_rate
self.opt = finetune_opt
self.num_epochs = finetune_num_epochs
self.batch_size = finetune_batch_size
self.momentum = momentum
self.dropout = finetune_dropout
self.loss = Loss(self.loss_func)
self.trainer = Trainer(
finetune_opt, learning_rate=finetune_learning_rate,
momentum=momentum)
self.do_pretrain = do_pretrain
self.layers = rbm_layers
self.finetune_act_func = finetune_act_func
# Model parameters
self.encoding_w_ = [] # list of matrices of encoding weights per layer
self.encoding_b_ = [] # list of arrays of encoding biases per layer
self.softmax_W = None
self.softmax_b = None
rbm_params = {
'num_epochs': rbm_num_epochs, 'gibbs_k': rbm_gibbs_k,
'batch_size': rbm_batch_size, 'learning_rate': rbm_learning_rate}
for p in rbm_params:
if len(rbm_params[p]) != len(rbm_layers):
# The current parameter is not specified by the user,
# should default it for all the layers
rbm_params[p] = [rbm_params[p][0] for _ in rbm_layers]
self.rbms = []
self.rbm_graphs = []
for l, layer in enumerate(rbm_layers):
rbm_str = 'rbm-' + str(l+1)
if l == 0 and rbm_gauss_visible:
self.rbms.append(
rbm.RBM(
name=self.name + '-' + rbm_str,
num_hidden=layer,
learning_rate=rbm_params['learning_rate'][l],
num_epochs=rbm_params['num_epochs'][l],
batch_size=rbm_params['batch_size'][l],
gibbs_sampling_steps=rbm_params['gibbs_k'][l],
visible_unit_type='gauss', stddev=rbm_stddev))
else:
self.rbms.append(
rbm.RBM(
name=self.name + '-' + rbm_str,
num_hidden=layer,
learning_rate=rbm_params['learning_rate'][l],
num_epochs=rbm_params['num_epochs'][l],
batch_size=rbm_params['batch_size'][l],
gibbs_sampling_steps=rbm_params['gibbs_k'][l]))
self.rbm_graphs.append(tf.Graph())
请注意,RBM 层是如何从rbm_layers数组中构建的:
for l, layer in enumerate(rbm_layers):
rbm_str = 'rbm-' + str(l+1)
if l == 0 and rbm_gauss_visible:
self.rbms.append(
rbm.RBM(
name=self.name + '-' + rbm_str,
num_hidden=layer,
learning_rate=rbm_params['learning_rate'][l],
num_epochs=rbm_params['num_epochs'][l],
batch_size=rbm_params['batch_size'][l],
gibbs_sampling_steps=rbm_params['gibbs_k'][l],
visible_unit_type='gauss', stddev=rbm_stddev))
else:
self.rbms.append(
rbm.RBM(
name=self.name + '-' + rbm_str,
num_hidden=layer,
learning_rate=rbm_params['learning_rate'][l],
num_epochs=rbm_params['num_epochs'][l],
batch_size=rbm_params['batch_size'][l],
gibbs_sampling_steps=rbm_params['gibbs_k'][l]))
RBM 类
对于每个 RBM 层,都会初始化一个RBM类。这个类扩展了UnsupervisedModel和Model类:

RBM类的__init__(..)函数的详细信息在以下代码中指定:
class RBM(UnsupervisedModel):
"""Restricted Boltzmann Machine implementation using TensorFlow.
The interface of the class is sklearn-like.
"""
def __init__(
self, num_hidden, visible_unit_type='bin',
name='rbm', loss_func='mse', learning_rate=0.01,
regcoef=5e-4, regtype='none', gibbs_sampling_steps=1,
batch_size=10, num_epochs=10, stddev=0.1):
"""Constructor.
:param num_hidden: number of hidden units
:param loss_function: type of loss function
:param visible_unit_type: type of the visible units (bin or gauss)
:param gibbs_sampling_steps: optional, default 1
:param stddev: default 0.1\. Ignored if visible_unit_type is not 'gauss'
"""
UnsupervisedModel.__init__(self, name)
self.loss_func = loss_func
self.learning_rate = learning_rate
self.num_epochs = num_epochs
self.batch_size = batch_size
self.regtype = regtype
self.regcoef = regcoef
self.loss = Loss(self.loss_func)
self.num_hidden = num_hidden
self.visible_unit_type = visible_unit_type
self.gibbs_sampling_steps = gibbs_sampling_steps
self.stddev = stddev
self.W = None
self.bh_ = None
self.bv_ = None
self.w_upd8 = None
self.bh_upd8 = None
self.bv_upd8 = None
self.cost = None
self.input_data = None
self.hrand = None
self.vrand = None
一旦rbm图初始化完成,它们会被附加到 TensorFlow 图中:
self.rbm_graphs.append(tf.Graph())
预训练 DBN
在本节中,我们将查看如何预训练 DBN:
class RBM(UnsupervisedModel):
...
def pretrain(self, train_set, validation_set=None):
"""Perform Unsupervised pretraining of the DBN."""
self.do_pretrain = True
def set_params_func(rbmmachine, rbmgraph):
params = rbmmachine.get_parameters(graph=rbmgraph)
self.encoding_w_.append(params['W'])
self.encoding_b_.append(params['bh_'])
return SupervisedModel.pretrain_procedure(
self, self.rbms, self.rbm_graphs, set_params_func=set_params_func,
train_set=train_set, validation_set=validation_set)
这将进一步调用SupervisedModel.pretrain_procedure(..),该函数接受以下参数:
-
layer_objs:模型对象的列表(自编码器或 RBM) -
layer_graphs:模型tf.Graph对象的列表 -
set_params_func:预训练后用于设置参数的函数 -
train_set:训练集 -
validation_set:验证集
该函数返回由最后一层编码的数据:
def pretrain_procedure(self, layer_objs, layer_graphs, set_params_func,
train_set, validation_set=None):
next_train = train_set
next_valid = validation_set
for l, layer_obj in enumerate(layer_objs):
print('Training layer {}...'.format(l + 1))
next_train, next_valid = self._pretrain_layer_and_gen_feed(
layer_obj, set_params_func, next_train, next_valid,
layer_graphs[l])
return next_train, next_valid
这将进一步调用self._pretrain_layer_and_gen_feed(...):
def _pretrain_layer_and_gen_feed(self, layer_obj, set_params_func,
train_set, validation_set, graph):
layer_obj.fit(train_set, train_set,
validation_set, validation_set, graph=graph)
with graph.as_default():
set_params_func(layer_obj, graph)
next_train = layer_obj.transform(train_set, graph=graph)
if validation_set is not None:
next_valid = layer_obj.transform(validation_set, graph=graph)
else:
next_valid = None
return next_train, next_valid
在前面的函数中,每个layer_obj都会迭代调用。
模型训练
模型训练在fit(..)方法中实现。它接受以下参数:
-
train_X:array_like, shape (n_samples, n_features),训练数据 -
train_Y:array_like, shape (n_samples, n_classes),训练标签 -
val_X:array_like, shape (N, n_features) optional, (default = None),验证数据 -
val_Y:array_like, shape (N, n_classes) optional, (default = None),验证标签 -
graph:tf.Graph,可选(默认为 None),TensorFlow 图对象
接下来,我们将查看fit(...)函数的实现,其中模型会在由model_path指定的路径中训练并保存。
def fit(self, train_X, train_Y, val_X=None, val_Y=None, graph=None):
if len(train_Y.shape) != 1:
num_classes = train_Y.shape[1]
else:
raise Exception("Please convert the labels with one-hot encoding.")
g = graph if graph is not None else self.tf_graph
with g.as_default():
# Build model
self.build_model(train_X.shape[1], num_classes)
with tf.Session() as self.tf_session:
# Initialize tf stuff
summary_objs = tf_utils.init_tf_ops(self.tf_session)
self.tf_merged_summaries = summary_objs[0]
self.tf_summary_writer = summary_objs[1]
self.tf_saver = summary_objs[2]
# Train model
self._train_model(train_X, train_Y, val_X, val_Y)
# Save model
self.tf_saver.save(self.tf_session, self.model_path)
预测标签
预测标签可以通过调用以下方法来完成:
def predict(self, test_X):
with self.tf_graph.as_default():
with tf.Session() as self.tf_session:
self.tf_saver.restore(self.tf_session, self.model_path)
feed = {
self.input_data: test_X,
self.keep_prob: 1
}
return self.mod_y.eval(feed)
查找模型的准确度
模型的准确度是通过计算测试集的平均准确度来得到的。它在以下方法中实现:
def score(self, test_X, test_Y):
...
在这里,参数如下:
-
test_X:array_like, shape (n_samples, n_features),测试数据 -
test_Y:array_like, shape (n_samples, n_features),测试标签 -
return float:测试集的平均准确度
def score(self, test_X, test_Y):
with self.tf_graph.as_default():
with tf.Session() as self.tf_session:
self.tf_saver.restore(self.tf_session, self.model_path)
feed = {
self.input_data: test_X,
self.input_labels: test_Y,
self.keep_prob: 1
}
return self.accuracy.eval(feed)
在下一节中,我们将看看如何在 MNIST 数据集上使用 DBN 实现。
MNIST 数据集的 DBN 实现
让我们来看一下之前实现的 DBN 类如何用于 MNIST 数据集。
加载数据集
首先,我们将数据集从idx3和idx1格式加载到测试集、训练集和验证集中。我们需要导入在这里说明的 common 模块中定义的 TensorFlow 常用工具:
import tensorflow as tf
from common.models.boltzmann import dbn
from common.utils import datasets, utilities
trainX, trainY, validX, validY, testX, testY =
datasets.load_mnist_dataset(mode='supervised')
你可以在以下代码清单中找到有关load_mnist_dataset()的详细信息。由于设置了mode='supervised',因此会返回训练集、测试集和验证集的标签:
def load_mnist_dataset(mode='supervised', one_hot=True):
mnist = input_data.read_data_sets("MNIST_data/", one_hot=one_hot)
# Training set
trX = mnist.train.images
trY = mnist.train.labels
# Validation set
vlX = mnist.validation.images
vlY = mnist.validation.labels
# Test set
teX = mnist.test.images
teY = mnist.test.labels
if mode == 'supervised':
return trX, trY, vlX, vlY, teX, teY
elif mode == 'unsupervised':
return trX, vlX, teX
具有 256 神经元 RBM 层的 DBN 输入参数
我们将初始化 DBN 类所需的各种参数:
finetune_act_func = tf.nn.relu
rbm_layers = [256]
do_pretrain = True
name = 'dbn'
rbm_layers = [256]
finetune_act_func ='relu'
do_pretrain = True
rbm_learning_rate = [0.001]
rbm_num_epochs = [1]
rbm_gibbs_k= [1]
rbm_stddev= 0.1
rbm_gauss_visible= False
momentum= 0.5
rbm_batch_size= [32]
finetune_learning_rate = 0.01
finetune_num_epochs = 1
finetune_batch_size = 32
finetune_opt = 'momentum'
finetune_loss_func = 'softmax_cross_entropy'
finetune_dropout = 1
finetune_act_func = tf.nn.sigmoid
一旦定义了参数,让我们在 MNIST 数据集上运行 DBN 网络:
srbm = dbn.DeepBeliefNetwork(
name=name, do_pretrain=do_pretrain,
rbm_layers=rbm_layers,
finetune_act_func=finetune_act_func,
rbm_learning_rate=rbm_learning_rate,
rbm_num_epochs=rbm_num_epochs, rbm_gibbs_k = rbm_gibbs_k,
rbm_gauss_visible=rbm_gauss_visible, rbm_stddev=rbm_stddev,
momentum=momentum, rbm_batch_size=rbm_batch_size,
finetune_learning_rate=finetune_learning_rate,
finetune_num_epochs=finetune_num_epochs,
finetune_batch_size=finetune_batch_size,
finetune_opt=finetune_opt, finetune_loss_func=finetune_loss_func,
finetune_dropout=finetune_dropout
)
print(do_pretrain)
if do_pretrain:
srbm.pretrain(trainX, validX)
# finetuning
print('Start deep belief net finetuning...')
srbm.fit(trainX, trainY, validX, validY)
# Test the model
print('Test set accuracy: {}'.format(srbm.score(testX, testY)))
具有 256 神经元 RBM 层的 DBN 输出
前面的清单输出显示了测试集的准确度:
Reconstruction loss: 0.156712: 100%|██████████| 5/5 [00:49<00:00, 9.99s/it]
Start deep belief net finetuning...
Tensorboard logs dir for this run is /home/ubuntu/.yadlt/logs/run53
Accuracy: 0.0868: 100%|██████████| 1/1 [00:04<00:00, 4.09s/it]
Test set accuracy: 0.0868000015616
总体准确度和测试集准确度都相对较低。随着迭代次数的增加,它们有所提升。让我们使用 20 个 epoch 重新运行相同的示例
Reconstruction loss: 0.120337: 100%|██████████| 20/20 [03:07<00:00, 8.79s/it]
Start deep belief net finetuning...
Tensorboard logs dir for this run is /home/ubuntu/.yadlt/logs/run80
Accuracy: 0.105: 100%|██████████| 1/1 [00:04<00:00, 4.16s/it]
Test set accuracy: 0.10339999944
如图所示,重构损失下降了,测试集的准确度提高了 20%,达到了 0.10339999944
让我们将 Epoch 数量增加到 40。输出如下所示:
Reconstruction loss: 0.104798: 100%|██████████| 40/40 [06:20<00:00, 9.18s/it]
Start deep belief net finetuning...
Tensorboard logs dir for this run is /home/ubuntu/.yadlt/logs/run82
Accuracy: 0.075: 100%|██████████| 1/1 [00:04<00:00, 4.08s/it]
Test set accuracy: 0.0773999989033
As can be seen the accuracy again came down so the optimal number of iterations peaks somewhere between 20 and 40
RBM 层中神经元数量对 DBN 的影响
让我们看看更改 RBM 层中神经元数量如何影响测试集的准确度:
一个包含 512 个神经元的 RBM 层
以下是一个包含 512 个神经元的 RBM 层的 DBN 输出。重构损失下降了,测试集的准确度也下降了:
Reconstruction loss: 0.128517: 100%|██████████| 5/5 [01:32<00:00, 19.25s/it]
Start deep belief net finetuning...
Tensorboard logs dir for this run is /home/ubuntu/.yadlt/logs/run55
Accuracy: 0.0758: 100%|██████████| 1/1 [00:06<00:00, 6.40s/it]
Test set accuracy: 0.0689999982715
请注意,准确度和测试集准确度都下降了。这意味着增加神经元的数量并不一定能提高准确度。
一个包含 128 个神经元的 RBM 层
128 神经元的 RBM 层导致测试集准确度更高,但整体准确度较低:
Reconstruction loss: 0.180337: 100%|██████████| 5/5 [00:32<00:00, 6.44s/it]
Start deep belief net finetuning...
Tensorboard logs dir for this run is /home/ubuntu/.yadlt/logs/run57
Accuracy: 0.0698: 100%|██████████| 1/1 [00:03<00:00, 3.16s/it]
Test set accuracy: 0.0763999968767
比较准确度指标
由于我们已经用多个神经元数量的 RBM 层训练了神经网络,现在来比较一下这些指标:

如前图所示,重构损失随着神经元数量的增加而减少。

测试集的准确度在 256 个神经元时达到峰值,然后下降。
具有两个 RBM 层的 DBN
在本节中,我们将创建一个具有两个 RBM 层的 DBN,并在 MNIST 数据集上运行。我们将修改DeepBeliefNetwork(..)类的输入参数:
name = 'dbn'
rbm_layers = [256, 256]
finetune_act_func ='relu'
do_pretrain = True
rbm_learning_rate = [0.001, 0.001]
rbm_num_epochs = [5, 5]
rbm_gibbs_k= [1, 1]
rbm_stddev= 0.1
rbm_gauss_visible= False
momentum= 0.5
rbm_batch_size= [32, 32]
finetune_learning_rate = 0.01
finetune_num_epochs = 1
finetune_batch_size = 32
finetune_opt = 'momentum'
finetune_loss_func = 'softmax_cross_entropy'
finetune_dropout = 1
finetune_act_func = tf.nn.sigmoid
请注意,某些参数是数组形式,因此我们需要为两个层指定这些参数:
-
rbm_layers = [256, 256]:每个 RBM 层中的神经元数量 -
rbm_learning_rate = [0.001, 0001]:每个 RBM 层的学习率 -
rbm_num_epochs = [5, 5]:每层的 Epoch 数量 -
rbm_batch_size= [32, 32]:每个 RBM 层的批次大小
让我们看看 DBN 初始化和模型训练:
srbm = dbn.DeepBeliefNetwork(
name=name, do_pretrain=do_pretrain,
rbm_layers=rbm_layers,
finetune_act_func=finetune_act_func, rbm_learning_rate=rbm_learning_rate,
rbm_num_epochs=rbm_num_epochs, rbm_gibbs_k = rbm_gibbs_k,
rbm_gauss_visible=rbm_gauss_visible, rbm_stddev=rbm_stddev,
momentum=momentum, rbm_batch_size=rbm_batch_size, finetune_learning_rate=finetune_learning_rate,
finetune_num_epochs=finetune_num_epochs, finetune_batch_size=finetune_batch_size,
finetune_opt=finetune_opt, finetune_loss_func=finetune_loss_func,
finetune_dropout=finetune_dropout
)
if do_pretrain:
srbm.pretrain(trainX, validX)
#
finetuning
print('Start deep belief net finetuning...')
srbm.fit(trainX, trainY, validX, validY)
测试模型:
print('Test set accuracy: {}'.format(srbm.score(testX, testY)))
完整的代码清单可以在这里找到:
以下是前面列表的输出:
Reconstruction loss: 0.156286: 100%|██████████| 5/5 [01:03<00:00, 13.04s/it]
Training layer 2...
Tensorboard logs dir for this run is /home/ubuntu/.yadlt/logs/run73
Reconstruction loss: 0.127524: 100%|██████████| 5/5 [00:23<00:00, 4.87s/it]
Start deep belief net finetuning...
Tensorboard logs dir for this run is /home/ubuntu/.yadlt/logs/run74
Accuracy: 0.1496: 100%|██████████| 1/1 [00:05<00:00, 5.53s/it]
Test set accuracy: 0.140300005674
从前面的列表中可以看出,测试集的准确率优于单层 RBM 的 DBN。
使用 DBN 分类 NotMNIST 数据集
让我们来看看 NotMNIST 数据集,之前我们在第二章的深度前馈网络部分中已经探讨过,并附带了图像,看看我们的 DBN 如何在该数据集上工作。
我们将利用在第二章的深度前馈网络部分中创建的相同 pickle 文件,notMNIST.pickle。初始化参数和导入的库如下所示:
import tensorflow as tf
import numpy as np
import cPickle as pickle
from common.models.boltzmann import dbn
from common.utils import datasets, utilities
flags = tf.app.flags
FLAGS = flags.FLAGS
pickle_file = '../notMNIST.pickle'
image_size = 28
num_of_labels = 10
RELU = 'RELU'
RELU6 = 'RELU6'
CRELU = 'CRELU'
SIGMOID = 'SIGMOID'
ELU = 'ELU'
SOFTPLUS = 'SOFTPLUS'
实现方法与 MNIST 数据集大致相同。主要实现代码如下:
if __name__ == '__main__':
utilities.random_seed_np_tf(-1)
with open(pickle_file, 'rb') as f:
save = pickle.load(f)
training_dataset = save['train_dataset']
training_labels = save['train_labels']
validation_dataset = save['valid_dataset']
validation_labels = save['valid_labels']
test_dataset = save['test_dataset']
test_labels = save['test_labels']
del save # hint to help gc free up memory
print 'Training set', training_dataset.shape, training_labels.shape
print 'Validation set', validation_dataset.shape, validation_labels.shape
print 'Test set', test_dataset.shape, test_labels.shape
train_dataset, train_labels = reformat(training_dataset, training_labels)
valid_dataset, valid_labels = reformat(validation_dataset, validation_labels)
test_dataset, test_labels = reformat(test_dataset, test_labels)
#trainX, trainY, validX, validY, testX, testY = datasets.load_mnist_dataset(mode='supervised')
trainX = train_dataset
trainY = train_labels
validX = valid_dataset
validY = valid_labels
testX = test_dataset
testY = test_labels
finetune_act_func = tf.nn.relu
rbm_layers = [256]
do_pretrain = True
name = 'dbn'
rbm_layers = [256]
finetune_act_func ='relu'
do_pretrain = True
rbm_learning_rate = [0.001]
rbm_num_epochs = [1]
rbm_gibbs_k= [1]
rbm_stddev= 0.1
rbm_gauss_visible= False
momentum= 0.5
rbm_batch_size= [32]
finetune_learning_rate = 0.01
finetune_num_epochs = 1
finetune_batch_size = 32
finetune_opt = 'momentum'
finetune_loss_func = 'softmax_cross_entropy'
finetune_dropout = 1
finetune_act_func = tf.nn.sigmoid
srbm = dbn.DeepBeliefNetwork(
name=name, do_pretrain=do_pretrain,
rbm_layers=rbm_layers,
finetune_act_func=finetune_act_func, rbm_learning_rate=rbm_learning_rate,
rbm_num_epochs=rbm_num_epochs, rbm_gibbs_k = rbm_gibbs_k,
rbm_gauss_visible=rbm_gauss_visible, rbm_stddev=rbm_stddev,
momentum=momentum, rbm_batch_size=rbm_batch_size, finetune_learning_rate=finetune_learning_rate,
finetune_num_epochs=finetune_num_epochs, finetune_batch_size=finetune_batch_size,
finetune_opt=finetune_opt, finetune_loss_func=finetune_loss_func,
finetune_dropout=finetune_dropout
)
if do_pretrain:
srbm.pretrain(trainX, validX)
# finetuning
print('Start deep belief net finetuning...')
srbm.fit(trainX, trainY, validX, validY)
# Test the model
print('Test set accuracy: {}'.format(srbm.score(testX, testY)))
完整的代码列表可以在以下位置找到:
前面列表的输出将展示我们模型在 NotMNIST 数据集上的表现:
Reconstruction loss: 0.546223: 100%|██████████| 1/1 [00:00<00:00, 5.51it/s]
Start deep belief net finetuning...
Tensorboard logs dir for this run is /home/ubuntu/.yadlt/logs/run76
Accuracy: 0.126: 100%|██████████| 1/1 [00:00<00:00, 8.83it/s]
Test set accuracy: 0.180000007153
正如我们所看到的,该网络在 NotMNIST 数据集上的表现远超 MNIST 数据集。
摘要
在本章中,我们探讨了 DBN,并研究了如何利用这些模型通过一个或多个 RBM 层来构建分类管道。我们考察了 RBM 层中的各个参数及其对准确率、重构损失和测试集准确率的影响。我们还看了单层和多层 DBN,使用一个或多个 RBM。
在下一章中,我们将探讨生成模型,并讨论它们与判别模型的区别。
第八章:自编码器
自编码器是一种神经网络,它的训练目的是尝试将输入复制到输出。它有一个隐藏层(我们称之为h),该层描述了一种用于表示输入的编码。可以将网络视为由两个部分组成:
-
编码器函数:h = f (x)
-
生成重建的解码器:r = g(h)
下图显示了一个基本的自编码器,输入为 n,隐藏层的神经元为 m:

自编码器的基本表示
自编码器设计为不能完美地学习复制。它们受限于只能近似地复制,并且只能复制与训练数据相似的输入。由于模型被迫优先选择哪些输入方面应该被复制,它通常会学习到数据的有用属性。
本章将涵盖以下主题:
-
自编码器算法
-
欠完备自编码器
-
基本自编码器
-
加性高斯噪声自编码器
-
稀疏自编码器
自编码器算法
在以下符号中,x 是输入,y 是编码数据,z 是解码数据,σ 是非线性激活函数(通常是 Sigmoid 或双曲正切),f(x;θ) 表示由 θ 参数化的 x 的函数。
该模型可以总结如下:
输入数据被映射到隐藏层(编码)。该映射通常是一个仿射变换(允许或保持平行关系),后跟非线性处理:
y = f(x;θ) = σ(Wx+b)y = f(x;θ) =σ(Wx+b)
隐藏层被映射到输出层,这也称为解码。该映射是一个仿射变换(仿射变换是一种保持点、直线和平面的线性映射方法),后面可选择非线性化处理。以下方程式说明了这一点:
z = g(y;θ′) = g(f(x;θ);θ′) = σ(W′y+b′)
为了减小模型的大小,可以使用绑定权重,这意味着解码器权重矩阵受到约束,可以是编码器权重矩阵的转置,θ'=θ^T。
隐藏层的维度可以小于或大于输入/输出层的维度。
在低维情况下,解码器从低维表示中重建原始输入(也称为欠完备表示)。为了使整个算法有效,编码器应学习提供一个低维表示,该表示捕捉数据的本质(即分布中变化的主要因素)。它被迫找到一种有效的方式来总结数据。
参考文献:blackecho.github.io/blog/machine-learning/2016/02/29/denoising-autoencoder-tensorflow.html.
欠完备自编码器
从自编码器中获取有用特征的一种方式是通过将 h 的维度约束为比输入 x 更小。一个编码维度小于输入维度的自编码器被称为欠完备。
学习一个欠完备的表示迫使自编码器捕捉训练数据中最重要的特征。
学习过程描述为最小化损失函数 L(x, g(f(x))),
其中 L 是一个损失函数,惩罚 g(f (x)) 与 x 的不相似性,例如均方误差。
数据集
我们计划使用 idx3 格式的 MNIST 数据集作为输入来训练我们的自编码器。我们将测试自编码器在前 100 张图像上的表现。让我们首先绘制原始图像:
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
mnist = input_data.read_data_sets('MNIST_data', one_hot = True)
class OriginalImages:
def __init__(self):
pass
def main(self):
X_train, X_test = self.standard_scale(mnist.train.images, mnist.test.images)
original_imgs = X_test[:100]
plt.figure(1, figsize=(10, 10))
for i in range(0, 100):
im = original_imgs[i].reshape((28, 28))
ax = plt.subplot(10, 10, i + 1)
for label in (ax.get_xticklabels() + ax.get_yticklabels()):
label.set_fontsize(8)
plt.imshow(im, cmap="gray", clim=(0.0, 1.0))
plt.suptitle(' Original Images', fontsize=15, y=0.95)
plt.savefig('figures/original_images.png')
plt.show()
def main():
auto = OriginalImages()
auto.main()
if __name__ == '__main__':
main()
上述代码的输出如下图所示:

原始 MNIST 图像的绘图
基本自编码器
让我们来看一个基本的自编码器示例,它恰好是一个基础自编码器。首先,我们将创建一个 AutoEncoder 类,并使用以下参数初始化它:
-
num_input: 输入样本的数量 -
num_hidden: 隐藏层神经元的数量 -
transfer_function=tf.nn.softplus: 激活函数 -
optimizer = tf.train.AdamOptimizer(): 优化器
你可以传递一个自定义的 transfer_function 和 optimizer,或者使用指定的默认值。在我们的示例中,我们使用 softplus 作为默认的 transfer_function(也叫做 激活函数):f(x)=ln(1+e^x)。
自编码器初始化
首先,我们初始化类变量和权重:
self.num_input = num_input
self.num_hidden = num_hidden
self.transfer = transfer_function
network_weights = self._initialize_weights()
self.weights = network_weights
这里,_initialize_weigths() 函数是一个局部函数,用于初始化 weights 字典的值:
-
w1是一个形状为num_input X num_hidden的二维张量 -
b1是一个形状为num_hidden的一维张量 -
w2是一个形状为num_hidden X num_input的二维张量 -
b2是一个形状为num_input的二维张量
以下代码展示了如何将权重初始化为两个隐藏层的 TensorFlow 变量字典:
def _initialize_weights(self):
weights = dict()
weights['w1'] = tf.get_variable("w1", shape=[self.num_input, self.num_hidden],
initializer=tf.contrib.layers.xavier_initializer())
weights['b1'] = tf.Variable(tf.zeros([self.num_hidden], dtype=tf.float32))
weights['w2'] = tf.Variable(tf.zeros([self.num_hidden, self.num_input],
dtype=tf.float32))
weights['b2'] = tf.Variable(tf.zeros([self.num_input], dtype=tf.float32))
return weights
接下来,我们定义 x_var、hidden_layer 和 reconstruction layer:
self.x_var = tf.placeholder(tf.float32, [None, self.num_input])
self.hidden_layer = self.transfer(tf.add(tf.matmul(self.x_var,
self.weights['w1']), self.weights['b1']))
self.reconstruction = tf.add(tf.matmul(self.hidden_layer,
self.weights['w2']), self.weights['b2'])
This is followed by the cost function and the Optimizer
# cost function
self.cost = 0.5 * tf.reduce_sum(
tf.pow(tf.subtract(self.reconstruction, self.x_var), 2.0))
self.optimizer = optimizer.minimize(self.cost)

成本函数
实例化全局变量初始化器并将其传递给 TensorFlow 会话。
initializer = tf.global_variables_initializer()
self.session = tf.Session()
self.session.run(initializer)
AutoEncoder 类
以下代码展示了 AutoEncoder 类。该类将在接下来的几个部分实例化,以创建自编码器:
import tensorflow as tf
class AutoEncoder:
def __init__(self, num_input, num_hidden,
transfer_function=tf.nn.softplus,
optimizer = tf.train.AdamOptimizer()):
self.num_input = num_input
self.num_hidden = num_hidden
self.transfer = transfer_function
network_weights = self._initialize_weights()
self.weights = network_weights
# model for reconstruction of the image
self.x_var = tf.placeholder(tf.float32, [None, self.num_input])
self.hidden_layer = self.transfer(tf.add(tf.matmul(self.x_var,
self.weights['w1']), self.weights['b1']))
self.reconstruction = tf.add(tf.matmul(self.hidden_layer,
self.weights['w2']), self.weights['b2'])
# cost function
self.cost =
0.5 * tf.reduce_sum(tf.pow(tf.subtract(self.reconstruction,
self.x_var), 2.0))
self.optimizer = optimizer.minimize(self.cost)
initializer = tf.global_variables_initializer()
self.session = tf.Session()
self.session.run(initializer)
def _initialize_weights(self):
weights = dict()
weights['w1'] = tf.get_variable("w1",
shape=[self.num_input,
self.num_hidden],
initializer=
tf.contrib.layers.xavier_initializer())
weights['b1'] = tf.Variable(tf.zeros([self.num_hidden],
dtype=tf.float32))
weights['w2'] = tf.Variable(
tf.zeros([self.num_hidden, self.num_input],
dtype=tf.float32))
weights['b2'] = tf.Variable(tf.zeros(
[self.num_input], dtype=tf.float32))
return weights
def partial_fit(self, X):
cost, opt = self.session.run((self.cost, self.optimizer),
feed_dict={self.x_var: X})
return cost
def calculate_total_cost(self, X):
return self.session.run(self.cost, feed_dict = {self.x_var: X})
def transform(self, X):
return self.session.run(self.hidden_layer,
feed_dict={self.x_var: X})
def generate(self, hidden = None):
if hidden is None:
hidden = self.session.run(
tf.random_normal([1, self.num_hidden]))
return self.session.run(self.reconstruction,
feed_dict={self.hidden_layer: hidden})
def reconstruct(self, X):
return self.session.run(self.reconstruction,
feed_dict={self.x_var: X})
def get_weights(self):
return self.session.run(self.weights['w1'])
def get_biases(self):
return self.session.run(self.weights['b1'])
基本自编码器与 MNIST 数据
让我们使用 MNIST 数据集中的自编码器:mnist = input_data.read_data_sets('MNIST_data', one_hot = True)。
使用 Scikit Learn 的 sklearn.``preprocessing 模块中的 StandardScalar 来提取 testmnist.test.images 和训练图像 mnist.train.images:
X_train, X_test = self.standard_scale(mnist.train.images, mnist.test.images).
预处理模块提供了一个工具类StandardScaler,它实现了 Transformer API。它计算并转换训练集的均值和标准差,并将相同的转换应用到测试集。默认情况下,Scaler 会将均值居中,并使方差为 1。
可以通过将with_mean=False或with_std=False传递给 StandardScaler 的构造函数来禁用中心化或标准化。
接下来,我们定义之前列出的 AutoEncoder 类的实例:
n_samples = int(mnist.train.num_examples)
training_epochs = 5
batch_size = 128
display_step = 1
autoencoder = AutoEncoder(n_input = 784,
n_hidden = 200,
transfer_function = tf.nn.softplus,
optimizer = tf.train.AdamOptimizer(learning_rate = 0.001))
请注意,自编码器包括以下内容:
-
输入神经元的数量是
784 -
隐藏层的神经元数量是
200 -
激活函数是
tf.nn.softplus -
优化器是
tf.train.AdamOptimizer
接下来,我们将遍历训练数据并显示成本函数:
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 = self.get_random_block_from_data(X_train, batch_size)
# Fit training using batch data
cost = autoencoder.partial_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))
打印总成本:
print("Total cost: " + str(autoencoder.calc_total_cost(X_test)))
训练周期的输出如下所示;如预期,成本随着每次迭代而收敛:
('Epoch:', '0001', 'cost=', '20432.278386364')
('Epoch:', '0002', 'cost=', '13542.435997727')
('Epoch:', '0003', 'cost=', '10630.662196023')
('Epoch:', '0004', 'cost=', '10717.897946591')
('Epoch:', '0005', 'cost=', '9354.191921023')
Total cost: 824850.0
基本自编码器权重图示
一旦训练完成,使用 Matplotlib 库绘制权重图,代码清单如下:
print("Total cost: " + str(autoencoder.calc_total_cost(X_test)))
wts = autoencoder.getWeights()
dim = math.ceil(math.sqrt(autoencoder.n_hidden))
plt.figure(1, figsize=(dim, dim))
for i in range(0, autoencoder.n_hidden):
im = wts.flatten()[i::autoencoder.n_hidden].reshape((28, 28))
ax = plt.subplot(dim, dim, i + 1)
for label in (ax.get_xticklabels() + ax.get_yticklabels()):
label.set_fontname('Arial')
label.set_fontsize(8)
# plt.title('Feature Weights ' + str(i))
plt.imshow(im, cmap="gray", clim=(-1.0, 1.0))
plt.suptitle('Basic AutoEncoder Weights', fontsize=15, y=0.95)
#plt.title("Test Title", y=1.05)
plt.savefig('figures/basic_autoencoder_weights.png')
plt.show()

基本自编码器权重图
在下一节中,我们将研究如何使用前面图像中显示的权重重建图像。
基本自编码器重建图像图示
在重建了图像后,让我们绘制它们,以感受它们的外观。首先,我们将使用之前创建的autoencoder实例重建图像:
predicted_imgs = autoencoder.reconstruct(X_test[:100])
plt.figure(1, figsize=(10, 10))
for i in range(0, 100):
im = predicted_imgs[i].reshape((28, 28))
ax = plt.subplot(10, 10, i + 1)
for label in (ax.get_xticklabels() + ax.get_yticklabels()):
label.set_fontname('Arial')
label.set_fontsize(8)
plt.imshow(im, cmap="gray", clim=(0.0, 1.0))
plt.suptitle('Basic AutoEncoder Images', fontsize=15, y=0.95)
plt.savefig('figures/basic_autoencoder_images.png')
plt.show()
让我们来看一下神经网络创建的图像:

基本自编码器的输出图像图示
基本自编码器完整代码清单
完整代码清单可以在这里找到,也可以从 GitHub 下载--github.com/rajdeepd/neuralnetwork-programming/blob/ed1/ch07/basic_autoencoder_example.py:
import numpy as np
import sklearn.preprocessing as prep
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
from autencoder_models.auto_encoder import AutoEncoder
import math
import matplotlib.pyplot as plt
mnist = input_data.read_data_sets('MNIST_data', one_hot = True)
class BasicAutoEncoder:
def __init__(self):
pass
def standard_scale(self,X_train, X_test):
preprocessor = prep.StandardScaler().fit(X_train)
X_train = preprocessor.transform(X_train)
X_test = preprocessor.transform(X_test)
return X_train, X_test
def get_random_block_from_data(self,data, batch_size):
start_index = np.random.randint(0, len(data) - batch_size)
return data[start_index:(start_index + batch_size)]
def main(self):
X_train, X_test = self.standard_scale(mnist.train.images, mnist.test.images)
n_samples = int(mnist.train.num_examples)
training_epochs = 5
batch_size = 128
display_step = 1
autoencoder = AutoEncoder(n_input = 784,
n_hidden = 200,
transfer_function = tf.nn.softplus,
optimizer = tf.train.AdamOptimizer(
learning_rate = 0.001))
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 = self.get_random_block_from_data(X_train, batch_size)
# Fit training using batch data
cost = autoencoder.partial_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))
print("Total cost: " + str(autoencoder.calc_total_cost(X_test)))
wts = autoencoder.getWeights()
dim = math.ceil(math.sqrt(autoencoder.n_hidden))
plt.figure(1, figsize=(dim, dim))
for i in range(0, autoencoder.n_hidden):
im = wts.flatten()[i::autoencoder.n_hidden].reshape((28, 28))
ax = plt.subplot(dim, dim, i + 1)
for label in (ax.get_xticklabels() + ax.get_yticklabels()):
label.set_fontname('Arial')
label.set_fontsize(8)
# plt.title('Feature Weights ' + str(i))
plt.imshow(im, cmap="gray", clim=(-1.0, 1.0))
plt.suptitle('Basic AutoEncoder Weights', fontsize=15, y=0.95)
#plt.title("Test Title", y=1.05)
plt.savefig('figures/basic_autoencoder_weights.png')
plt.show()
predicted_imgs = autoencoder.reconstruct(X_test[:100])
plt.figure(1, figsize=(10, 10))
for i in range(0, 100):
im = predicted_imgs[i].reshape((28, 28))
ax = plt.subplot(10, 10, i + 1)
for label in (ax.get_xticklabels() + ax.get_yticklabels()):
label.set_fontname('Arial')
label.set_fontsize(8)
plt.imshow(im, cmap="gray", clim=(0.0, 1.0))
plt.suptitle('Basic AutoEncoder Images', fontsize=15, y=0.95)
plt.savefig('figures/basic_autoencoder_images.png')
plt.show()
def main():
auto = BasicAutoEncoder()
auto.main()
if __name__ == '__main__':
main()
基本自编码器总结
自编码器使用 200 个神经元的隐藏层创建了 MNSIT 图像的基本近似。下图展示了九张图像以及它们如何通过基本自编码器转换为近似:

基本自编码器输入和输出表示
在下一节中,我们将研究一种更先进的自编码器,加性高斯噪声自编码器。
加性高斯噪声自编码器
什么是去噪自编码器?它们与我们在前面章节中看到的基本模型非常相似,不同之处在于,输入在传递到网络之前会被破坏。通过在训练时将原始版本(而不是破坏后的版本)与重建版本进行匹配,这个自编码器被训练用来从破损图像中重建原始输入图像。能够从破损图像重建原始图像使自编码器变得非常智能。
加性噪声自编码器使用以下公式将噪声添加到输入数据中:
x[corr] = x + scalerandom_normal(n)*
以下是关于前面方程的详细描述:
-
x 是原始图像
-
scale 是乘以从 n 中生成的随机正态数的因子
-
n 是训练样本的数量
-
x[corr] 是损坏后的输出
自编码器类
我们通过传入以下参数来初始化在 class AdditiveGaussianNoiseAutoEncoder 中定义的自编码器:
-
num_input: 输入样本数量 -
num_hidden: 隐藏层中的神经元数量 -
transfer_function=tf.nn.sigmoid: 转换函数 -
optimizer = tf.train.AdamOptimizer(): 优化器 -
scale=0.1: 图像损坏的比例
def __init__(self, num_input, num_hidden,
transfer_function=tf.nn.sigmoid,
optimizer=tf.train.AdamOptimizer(),
scale=0.1):
将传入的参数分配给实例变量:
self.num_input = num_input
self.num_hidden = num_hidden
self.transfer = transfer_function
self.scale = tf.placeholder(tf.float32)
self.training_scale = scale
n_weights = self._initialize_weights()
self.weights = n_weights
初始化隐藏层 hidden_layer 和重建层 reconstruction:
self.x = tf.placeholder(tf.float32, [None, self.num_input])
self.hidden_layer = self.transfer(
tf.add(tf.matmul(
self.x + scale * tf.random_normal((n_input,)),
self.weights['w1']),
self.weights['b1']))
self.reconstruction = tf.add(
tf.matmul(self.hidden_layer, self.weights['w2']),
self.weights['b2'])
定义成本函数和优化器:
self.cost = 0.5 * tf.reduce_sum(tf.pow(tf.subtract(
self.reconstruction, self.x), 2.0))
self.optimizer = optimizer.minimize(self.cost)
成本函数与基本自编码器相同

加性高斯自编码器的成本函数
最后,我们初始化全局变量,创建 TensorFlow 会话,并运行它来执行 init 图:
init = tf.global_variables_initializer()
self.session = tf.Session()
self.session.run(init)
在下一节中,我们将研究如何使用该自编码器对 MNIST 数据进行编码。
加性高斯自编码器与 MNIST 数据集
首先,我们加载训练和测试数据集 X_train 和 X_test:
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
def get_random_block_from_data(data, batch_size):
start_index = np.random.randint(0, len(data) - batch_size)
return data[start_index:(start_index + batch_size)]
X_train = mnist.train.images
X_test = mnist.test.images
定义每次训练迭代的样本数量 n_samples、训练纪元 training_epoch 和批次大小 batch_size 以及显示步数 display_step:
n_samples = int(mnist.train.num_examples)
training_epochs = 2
batch_size = 128
display_step = 1
实例化自编码器和优化器。自编码器有 200 个隐藏单元,并使用 sigmoid 作为 transfer_function:
autoencoder = AdditiveGaussianNoiseAutoEncoder(n_input=784,
n_hidden=200,
transfer_function=tf.nn.sigmoid,
optimizer=tf.train.AdamOptimizer(learning_rate=0.001),
scale=0.01)
训练模型
一旦神经网络层被定义,我们通过调用方法来训练模型:
autoencoder.partial_fit(batch_xs) 每次处理一批数据:
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 = get_random_block_from_data(X_train, batch_size)
# Fit training using batch data
cost = autoencoder.partial_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=", avg_cost)
print("Total cost: " + str(autoencoder.calc_total_cost(X_test)))
每个纪元的成本已打印:
('Epoch:', '0001', 'cost=', 1759.873304261363)
('Epoch:', '0002', 'cost=', 686.85984829545475)
('Epoch:', '0003', 'cost=', 460.52834446022746)
('Epoch:', '0004', 'cost=', 355.10590241477308)
('Epoch:', '0005', 'cost=', 297.99104825994351)
训练的总成本如下:
Total cost: 21755.4
绘制权重
让我们通过 Matplotlib 可视化地绘制权重:
wts = autoencoder.get_weights()
dim = math.ceil(math.sqrt(autoencoder.num_hidden))
plt.figure(1, figsize=(dim, dim))
for i in range(0, autoencoder.num_hidden):
im = wts.flatten()[i::autoencoder.num_hidden].reshape((28, 28))
ax = plt.subplot(dim, dim, i + 1)
for label in (ax.get_xticklabels() + ax.get_yticklabels()):
label.set_fontsize(8)
#plt.title('Feature Weights ' + str(i))
plt.imshow(im, cmap="gray", clim=(-1.0, 1.0))
plt.suptitle('Additive Gaussian Noise AutoEncoder Weights', fontsize=15, y=0.95)
plt.savefig('figures/additive_gaussian_weights.png')
plt.show()

加性高斯自编码器中隐藏层神经元的权重
绘制重建的图像
最后一步是打印重建的图像,给我们提供视觉证据,说明编码器如何根据权重重建图像:
predicted_imgs = autoencoder.reconstruct(X_test[:100])
# plot the reconstructed images
plt.figure(1, figsize=(10, 10))
plt.title('Autoencoded Images')
for i in range(0, 100):
im = predicted_imgs[i].reshape((28, 28))
ax = plt.subplot(10, 10, i + 1)
for label in (ax.get_xticklabels() + ax.get_yticklabels()):
label.set_fontname('Arial')
label.set_fontsize(8)
plt.imshow(im, cmap="gray", clim=(0.0, 1.0))
plt.suptitle('Additive Gaussian Noise AutoEncoder Images', fontsize=15, y=0.95)
plt.savefig('figures/additive_gaussian_images.png')
plt.show()

使用加性高斯自编码器重建的图像
加性高斯自编码器完整代码列表
以下是加性高斯自编码器的代码:
import numpy as np
import tensorflow as tf
def xavier_init(fan_in, fan_out, constant = 1):
low = -constant * np.sqrt(6.0 / (fan_in + fan_out))
high = constant * np.sqrt(6.0 / (fan_in + fan_out))
return tf.random_uniform((fan_in, fan_out), minval = low, maxval = high, dtype = tf.float32)
class AdditiveGaussianNoiseAutoEncoder(object):
def __init__(self, num_input, num_hidden,
transfer_function=tf.nn.sigmoid,
optimizer=tf.train.AdamOptimizer(),
scale=0.1):
self.num_input = num_input
self.num_hidden = num_hidden
self.transfer = transfer_function
self.scale = tf.placeholder(tf.float32)
self.training_scale = scale
n_weights = self._initialize_weights()
self.weights = n_weights
# model
</span> self.x = tf.placeholder(tf.float32, [None, self.num_input])
self.hidden_layer = self.transfer(
tf.add(tf.matmul(
self.x + scale * tf.random_normal((n_input,)),
self.weights['w1']),
self.weights['b1']))
self.reconstruction = tf.add(
tf.matmul(self.hidden_layer, self.weights['w2']),
self.weights['b2'])
# cost
self.cost = 0.5 * tf.reduce_sum(tf.pow(tf.subtract(
self.reconstruction, self.x), 2.0))
self.optimizer = optimizer.minimize(self.cost)
init = tf.global_variables_initializer()
self.session = tf.Session()
self.session.run(init)
def _initialize_weights(self):
weights = dict()
weights['w1'] = tf.Variable(xavier_init(self.num_input, self.num_hidden))
weights['b1'] = tf.Variable(tf.zeros([self.num_hidden], dtype=tf.float32))
weights['w2'] = tf.Variable(tf.zeros([self.num_hidden, self.num_input],
dtype=tf.float32))
weights['b2'] = tf.Variable(tf.zeros([self.num_input], dtype=tf.float32))
return weights
def partial_fit(self, X):
cost, opt = self.session.run((self.cost, self.optimizer),
feed_dict={self.x: X,self.scale: self.training_scale})
return cost
def kl_divergence(self, p, p_hat):
return tf.reduce_mean(
p * tf.log(p) - p * tf.log(p_hat) + (1 - p) * tf.log(1 - p) - (1 - p) * tf.log(1 - p_hat))
def calculate_total_cost(self, X):
return self.session.run(self.cost, feed_dict={self.x: X,
self.scale: self.training_scale
})
def transform(self, X):
return self.session.run(
self.hidden_layer,
feed_dict = {self.x: X, self.scale: self.training_scale})
def generate_value(self, _hidden=None):
if _hidden is None:
_hidden = np.random.normal(size=self.weights["b1"])
return self.session.run(self.reconstruction,
feed_dict={self.hidden_layer: _hidden})
def reconstruct(self, X):
return self.session.run(self.reconstruction,
feed_dict={self.x: X,self.scale: self.training_scale })
def get_weights(self):
return self.session.run(self.weights['w1'])
def get_biases(self):
return self.session.run(self.weights['b1'])
将基本编码器的成本与加性高斯噪声自编码器进行比较
以下图表显示了两个算法在每个纪元中的成本。可以推断,基本自编码器比加性高斯噪声自编码器更昂贵:

成本比较:基本自编码器与加性高斯噪声自编码器
加性高斯噪声自编码器总结
你学习了如何创建带有高斯噪声的自编码器,与基础自编码器相比,这大大提高了模型的准确性。
稀疏自编码器
在这一节中,我们将研究如何通过向代价函数添加稀疏性来帮助降低训练代价。大多数代码保持不变,但主要的变化在于计算代价函数的方式。
KL 散度
让我们首先尝试理解 KL 散度,它用于向代价函数添加稀疏性。
我们可以将一个神经元视为处于激活状态(或发放)当其输出值接近 1 时,处于非激活状态当其输出值接近零时。我们希望大多数时间神经元保持非激活状态。这个讨论假设使用 sigmoid 激活函数。
回想一下,a^((2))[j]表示自编码器中隐藏单元j的激活值。这个符号没有明确说明导致此激活的输入x是什么。我们将写作 a^((2))j,表示当网络给定特定输入x时,隐藏单元的激活值。此外,令
为隐藏单元j的平均激活值(在训练集上平均)。我们希望(大致)强制执行约束
,其中 ![] 是一个稀疏性参数,通常是一个接近零的小值(例如,= 0.05)。我们的目标是让每个隐藏神经元j的平均激活值接近0.05(作为一个例子)。为了满足前述约束,隐藏单元的激活必须大部分时间接近零。
为了实现这一点,我们在优化目标中加入了一个额外的惩罚项,该惩罚项会惩罚与![]的偏差过大的情况:

让我们看看 KL 散度如何随平均激活值的变化而变化:

平均激活值与 KL 散度图
TensorFlow 中的 KL 散度
在我们实现的稀疏编码器中,我们在SparseEncoder类中的kl_divergence函数里定义了 KL 散度,这只是前述公式的实现:
def kl_divergence(self, p, p_hat):
return tf.reduce_mean(
p*(tf.log(p)/tf.log(p_hat)) +
(1-p)*(tf.log(1-p)/tf.log(1-p_hat)))
基于 KL 散度的稀疏自编码器代价
与本章讨论的先前编码器相比,代价函数在重新定义时引入了两个新参数,sparse_reg和kl_divergence:
self.cost = 0.5 * tf.reduce_sum(
tf.pow(tf.subtract(self.reconstruction, self.x), 2.0)) +
self.sparse_reg * self.kl_divergence(self.sparsity_level, self.hidden_layer)
稀疏自编码器的完整代码列表
作为参考,下面给出了SparseAutoEncoder的代码列表,其中包括之前讨论过的kl_divergence和cost:
class SparseAutoencoder(object):
def __init__(self, num_input, num_hidden,
transfer_function=tf.nn.softplus,
optimizer=tf.train.AdamOptimizer(),
scale=0.1):
self.num_input = num_input
self.num_hidden = num_hidden
self.transfer = transfer_function
self.scale = tf.placeholder(tf.float32)
self.training_scale = scale
network_weights = self._initialize_weights()
self.weights = network_weights
self.sparsity_level = np.repeat([0.05],
self.num_hidden).astype(np.float32)
self.sparse_reg = 0.0
# model
self.x = tf.placeholder(tf.float32, [None, self.num_input])
self.hidden_layer = self.transfer(tf.add(tf.matmul(
self.x + scale * tf.random_normal((num_input,)),
self.weights['w1']),
self.weights['b1']))
self.reconstruction = tf.add(tf.matmul(self.hidden_layer,
self.weights['w2']), self.weights['b2'])
# cost
self.cost = 0.5 * tf.reduce_sum(
tf.pow(tf.subtract(self.reconstruction, self.x), 2.0)) +
self.sparse_reg * self.kl_divergence(
self.sparsity_level, self.hidden_layer)
self.optimizer = optimizer.minimize(self.cost)
init = tf.global_variables_initializer()
self.session = tf.Session()
self.session.run(init)
def _initialize_weights(self):
all_weights = dict()
all_weights['w1'] = tf.Variable(xavier_init(self.num_input,
self.num_hidden))
all_weights['b1'] = tf.Variable(tf.zeros([self.num_hidden],
dtype = tf.float32))
all_weights['w2'] = tf.Variable(tf.zeros([self.num_hidden,
self.num_input],
dtype = tf.float32))
all_weights['b2'] = tf.Variable(tf.zeros([self.num_input],
dtype = tf.float32))
return all_weights
def partial_fit(self, X):
cost, opt = self.session.run((self.cost, self.optimizer),
feed_dict = {self.x: X,
self.scale: self.training_scale})
return cost
def kl_divergence(self, p, p_hat):
return tf.reduce_mean(p*(tf.log(p)/tf.log(p_hat)) +
(1-p)*(tf.log(1-p)/tf.log(1-p_hat)))
def calculate_total_cost(self, X):
return self.session.run(self.cost, feed_dict = {
self.x: X,
self.scale: self.training_scale
})
def transform(self, X):
return self.session.run(self.hidden_layer,
feed_dict = {self.x: X, self.scale: self.training_scale})
def generate(self, hidden = None):
if hidden is None:
hidden = np.random.normal(size = self.weights["b1"])
return self.session.run(self.reconstruction,
feed_dict = {self.hidden_layer: hidden})
def reconstruct(self, X):
return self.session.run(self.reconstruction,
feed_dict = {self.x: X, self.scale: self.training_scale})
def get_weights(self):
return self.session.run(self.weights['w1'])
def get_biases(self):
return self.session.run(self.weights['b1'])
在下一节中,我们将研究稀疏自编码器在特定数据集上的应用。
在 MNIST 数据上的稀疏自编码器
让我们在与其他示例中使用相同的数据集上运行这个编码器,并比较结果:
class SparseAutoEncoderExample:
def main(self):
mnist = input_data.read_data_sets('MNIST_data', one_hot = True)
def get_random_block_from_data(data, batch_size):
start_index = np.random.randint(0, len(data) - batch_size)
return data[start_index:(start_index + batch_size)]
X_train = mnist.train.images
X_test = mnist.test.images
n_samples = int(mnist.train.num_examples)
training_epochs = 5
batch_size = 128
display_step = 1
autoencoder =SparseAutoencoder(num_input=784,
num_hidden = 200,
transfer_function = tf.nn.sigmoid,
optimizer = tf.train.AdamOptimizer(
learning_rate = 0.001),
scale = 0.01)
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 = get_random_block_from_data(X_train, batch_size)
# Fit training using batch data
cost = autoencoder.partial_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=", avg_cost)
print("Total cost: " +
str(autoencoder.calculate_total_cost(X_test)))
# input weights
wts = autoencoder.get_weights()
dim = math.ceil(math.sqrt(autoencoder.num_hidden))
plt.figure(1, figsize=(dim, dim))
for i in range(0, autoencoder.num_hidden):
im = wts.flatten()[i::autoencoder.num_hidden].reshape((28, 28))
ax = plt.subplot(dim, dim, i + 1)
for label in (ax.get_xticklabels() + ax.get_yticklabels()):
label.set_fontsize(6)
plt.subplot(dim, dim, i+1)
plt.imshow(im, cmap="gray", clim=(-1.0, 1.0))
plt.suptitle('Sparse AutoEncoder Weights', fontsize=15, y=0.95)
plt.savefig('figures/sparse_autoencoder_weights.png')
plt.show()
predicted_imgs = autoencoder.reconstruct(X_test[:100])
# plot the reconstructed images
plt.figure(1, figsize=(10, 10))
plt.title('Sparse Autoencoded Images')
for i in range(0,100):
im = predicted_imgs[i].reshape((28,28))
ax = plt.subplot(10, 10, i + 1)
for label in (ax.get_xticklabels() + ax.get_yticklabels()):
label.set_fontsize(6)
plt.subplot(10, 10, i+1)
plt.imshow(im, cmap="gray", clim=(0.0, 1.0))
plt.suptitle('Sparse AutoEncoder Images', fontsize=15, y=0.95)
plt.savefig('figures/sparse_autoencoder_images.png')
plt.show()
def main():
auto = SparseAutoEncoderExample()
auto.main()
if __name__ == '__main__':
main()
上述代码的输出如下:
('Epoch:', '0001', 'cost=', 1697.039439488638)
('Epoch:', '0002', 'cost=', 667.23002088068188)
('Epoch:', '0003', 'cost=', 450.02947024147767)
('Epoch:', '0004', 'cost=', 351.54360497159115)
('Epoch:', '0005', 'cost=', 293.73473448153396)
Total cost: 21025.2
如图所示,成本低于其他编码器,因此 KL 散度和稀疏性确实起到了帮助作用。
比较稀疏编码器和加性高斯噪声编码器
下图显示了加性高斯噪声自编码器和稀疏自编码器的成本比较:

在 MNIST 数据集上运行五个周期的两个自编码器的成本比较
总结
在本章中,你学习了三种不同类型的自编码器:基本自编码器、加性高斯噪声自编码器和稀疏自编码器。我们理解了它们的应用场景,并在 MNIST 数据集上运行了它们,同时比较了这三种自编码器的成本。我们还绘制了权重以及近似输出。
第九章:神经网络研究
在本章中,我们将看看神经网络中的一些活跃研究领域。以下问题从基础研究领域到复杂的现实问题进行了分析:
-
神经网络中的过拟合
-
使用神经网络进行大规模视频处理
-
使用扭曲神经网络进行命名实体识别
-
双向递归神经网络
避免神经网络中的过拟合
让我们理解过拟合的构成因素,以及如何在神经网络中避免它。Nitesh Srivastava、Geoffrey Hinton 等人于 2014 年发布了一篇论文,www.cs.toronto.edu/~hinton/absps/JMLRdropout.pdf,该论文展示了如何避免过拟合的案例。
问题陈述
深度神经网络包含非线性隐藏层,这使它们成为可以学习输入和输出之间非常复杂关系的表达性模型。然而,这些复杂的关系将是采样噪声的结果。这些复杂的关系可能在测试数据中不存在,从而导致过拟合。为减少这种噪声,已经开发了许多技术和方法。包括在验证集上的性能开始变差时立即停止训练,引入 L1 和 L2 正则化等权重惩罚,以及软权重共享(Nowlan 和 Hinton,1992)。
解决方案
Dropout 是一种解决一些其他技术性能问题的技术,例如在多个模型之间进行平均。它还防止了过拟合,并提供了一种有效地组合大量不同神经网络架构的方法。Dropout 一词意味着在神经网络中删除单元(隐藏和可见)。通过删除一个单元,意味着将其从网络及其输入和输出连接中移除,如下图所示。
删除哪些单元的选择通常是随机的。在简单的情况下,每个单元以独立于其他单元的概率 p 保留。选择 p 的技巧可以是验证集,也可以设置为 0.5;这个值对于广泛的网络和任务来说接近最优。
然而,对于输入单元,保留的最优概率通常更接近 1 而非 0.5。

Dropout 神经网络模型:
-
一个标准的具有两个隐藏层的神经网络
-
一个应用 dropout 后产生的稀疏神经网络,左侧的网络中已删除的单元用交叉标记表示
在 TensorFlow 中如何应用 Dropout 的示例
cell = tf.nn.rnn_cell.LSTMCell(state_size, state_is_tuple=True)
cell = tf.nn.rnn_cell.DropoutWrapper(cell, output_keep_prob=0.5)
cell = tf.nn.rnn_cell.MultiRNNCell([cell] * num_layers, state_is_tuple=True)
如上所示,0.5 的 Dropout 已应用于LSTMCell,其中output_keep_prob:单位张量或介于 0 和 1 之间的浮动值,表示输出保持的概率;如果它是常数且为 1,则不会添加输出 dropout。
结果
让我们看看 dropout 策略如何影响模型的准确性:

如上所示,分类误差在使用 dropout 后显著下降。
使用神经网络进行大规模视频处理
在本文中,static.googleusercontent.com/media/research.google.com/en//pubs/archive/42455.pdf,作者探讨了卷积神经网络(CNN)如何应用于大规模视频分类。在这种应用场景中,神经网络不仅可以访问单一静态图像中的外观信息,还能处理图像的复杂时间演化。将 CNN 应用于这一领域面临着一些挑战。
很少有(或者根本没有)视频分类基准能够匹配现有图像数据集的规模和多样性,因为视频数据集比图像数据集更难以收集、注释和存储。为了获得足够的数据以训练我们的 CNN 架构,作者收集了一个新的 Sports-1M 数据集。该数据集包含来自 YouTube 的 100 万个视频,属于 487 个体育类别的分类体系。Sports-1M 数据集也对研究界开放,以支持该领域未来的研究工作。
在这项工作中,作者将每个视频视为一个由多个短小的固定大小片段组成的袋子。每个片段包含几个时间上连续的帧,因此网络的连接性可以在时间维度上扩展,以学习时空特征。作者描述了三种广泛的连接模式类别(早期融合、晚期融合和慢融合)。之后,我们将探讨一种多分辨率架构,以提高计算效率。
下图解释了各种融合技术:

各种融合技术用于组合时间上分离的帧
分辨率改进
作者使用了一个多分辨率架构,旨在通过在两个空间分辨率上分别处理两条流(称为视网膜流和上下文流)来实现妥协(参见下图)。178 × 178 帧的视频片段作为网络的输入:

多分辨率卷积神经网络(CNN)
上下文流接收下采样后的帧,分辨率为原始空间分辨率的一半(89 × 89 像素)。视网膜流接收中心 89 × 89 区域,分辨率保持原始分辨率。通过这种方式,总输入维度被减半。
特征直方图基准
除了比较不同 CNN 架构之间的差异外,作者还报告了基于特征的方法的准确性。使用标准的词袋管道在视频的所有帧上提取了几种类型的特征,随后通过 k-means 向量量化对其进行离散化,并通过空间金字塔编码和软量化将词语累积成直方图。
定量结果
Sports-1M 数据集测试集结果(200,000 个视频和 4,000,000 个片段)总结如下表。多网络方法始终显著超越基于特征的基线方法。基于特征的方法在视频的持续时间内密集计算视觉词,并生成基于完整视频级特征向量的预测,而作者的网络仅单独查看 20 个随机采样的片段:

在 Sports-1M 测试集的 200,000 个视频上的结果。Hit@k 值表示测试样本中
至少包含了前 k 个预测中的一个真实标签。
网络拓扑采用的方法尽管存在标签噪声,依然能够良好地学习;训练视频可能会出现一些错误注释,即使是正确标注的视频,也往往包含大量伪影,如文本、特效、剪辑和徽标,而我们并没有专门尝试过滤这些内容。
使用扭曲神经网络的命名实体识别
在本文中,www.cs.cmu.edu/~leili/pubs/lu-baylearn2015-twinet.pdf, 作者探讨了在自然语言中识别实体的问题。这通常是问答、对话及其他多个 NLP 用例的第一步。对于一段文本序列,命名实体识别器识别出属于预定义类别的实体,如人物和组织。
命名实体识别示例
IOB 标记系统是命名实体识别的一种标准。
IOB 标记 系统包含如下格式的标签:
-
B-{CHUNK_TYPE}:表示Beginning 块中的词 -
I-{CHUNK_TYPE}: 用于块内部的词Inside -
O:Outside 任何块 -
B-PERSON: 人物实体 -
B-GPE: 地缘政治实体
以下文本展示了句子中命名实体的示例:
John has lived in Britain for 14 years .
B-PERSON O O O B-GPE O B-CARDINAL I-CARDINAL O
然而,这个问题具有相当大的挑战,原因有二:
-
实体数据库往往不完整(考虑到新组织的不断成立)。
-
相同的短语可以根据上下文指代不同的实体(或没有实体)。
定义 Twinet
扭曲的 RNN(Twinet)使用两个并行分支。每个分支由一个递归网络层、一个非线性感知器层和一个反向递归网络层组成。分支是扭曲的:第二个分支中层的顺序被反转。所有递归层的输出在最后集中。
总结一下,递归神经网络(RNN)接受一系列输入向量 x1..T,并递归地计算隐藏状态(也称为输出标签):
h[t] = σ(U · x[t] + W · ht−1)
其中,
-
t 是 1..T
-
x[t] 是外部信号
-
W 是权重
-
h[t-1] 是时间步 t-1 的隐藏层权重。
-
h[t] 为时间步 t 计算的权重
-
U 是 tanh层,用于为时间步 t 创建权重。
σ(·) 是一种非线性激活函数。在作者使用的实验中,我们使用了修正线性单元(RELU)。
结果
Twinet 与斯坦福 NER 和伊利诺伊 NER 进行了比较,结果相当有利。此处 NER 代表命名实体识别器(Named Entity Recognizer)。

从前面的图中可以看出,精确度-召回率(Precision-Recall)以及 F1 分数都更高。
双向循环神经网络(Bidirectional RNNs)
在本节中,我们将介绍一种在自然语言处理(NLP)领域越来越受到关注的新型神经网络拓扑结构。
Schuster 和 Paliwal 在 1997 年提出了双向递归神经网络(BRNN)。BRNN 有助于增加可供网络使用的输入信息量。多层感知器(MLPs)和时间延迟神经网络(TDNNs)被认为在输入数据灵活性方面存在局限性。RNN 也要求其输入数据是固定的。更先进的拓扑结构如 RNN 也有其限制,因为无法从当前状态预测未来的输入信息。相反,BRNN 不需要其输入数据是固定的,其未来的输入信息可以从当前状态中获取。BRNN 的基本思想是将两个相反方向的隐藏层连接到相同的输出层。通过这种结构,输出层可以同时获取来自过去和未来状态的信息。
当输入的上下文信息非常重要时,BRNN 非常有用。例如,在手写识别中,通过了解当前字母前后的位置字母,可以提高识别性能。

这图展示的是双向循环神经网络(Bidirectional RNN)
TIMIT 数据集上的 BRNN
在本节中,我们将探讨双向 RNN(BRNN)如何在 TIMIT 数据集上为音素文本分类提供更高的准确度。
TIMIT 是一个包含美国英语发音者不同性别和方言的音素及词汇转录语音的语料库。每个转录元素都已标注时间。TIMIT 旨在推动声学-语音学知识和自动语音识别系统的发展:

从前面的图中可以看出,BRNN 比 MLP 在训练集和测试集上的准确度更高,能够正确识别更多的帧。BLSTM 的准确度更高。
总结
在本章中,我们讨论了一些已经进行研究以提高准确性并避免过拟合的领域。我们还探讨了一些新的领域,如视频分类。尽管本书的范围不涵盖所有研究领域的详细内容,但我们真诚建议你浏览谷歌、Facebook 和百度的研究网站,此外,还可以浏览顶级的 ACM 和 IEEE 会议,以便了解最新的研究进展。
第十章:入门 TensorFlow
TensorFlow 是 Google 提供的一个开源深度学习库。它提供了用于定义张量函数并自动计算其导数的原语。张量可以表示为多维数字数组。标量、向量和矩阵是张量的类型。TensorFlow 主要用于设计计算图、构建和训练深度学习模型。TensorFlow 库通过数据流图执行数值计算,其中节点代表数学操作,边代表数据点(通常是多维数组或在这些边之间传输的张量)。
环境设置
最好使用像 PyCharm 这样的 IDE 来编辑 Python 代码;它提供更快的开发工具和编码辅助。代码自动完成和检查使得编码和调试更快、更简单,确保你可以专注于编程神经网络的最终目标。
TensorFlow 提供了多种语言的 API:Python、C++、Java、Go 等。我们将下载一个 TensorFlow 版本,使我们能够用 Python 编写深度学习模型的代码。在 TensorFlow 安装网站上,我们可以找到使用 virtualenv、pip 和 Docker 安装 TensorFlow 的最常见方法和最新说明。
以下步骤描述了如何设置本地开发环境:
-
下载 Pycharm 社区版。
-
在 Pycharm 中获取最新版本的 Python。
-
进入“首选项”,设置 Python 解释器,并安装最新版本的 TensorFlow:

- TensorFlow 现在会出现在已安装的包列表中。点击“确定”。现在用如 hello world 这样的程序来测试你的安装:
import TensorFlow as tf
helloWorld = tf.constant("Hello World!")
sess = tf.Session()
print(sess.run(helloWorld))
TensorFlow 与 Numpy 的对比
TensorFlow 和 Numpy 都是 N 维数组库。TensorFlow 还允许我们创建张量函数并计算导数。TensorFlow 已成为深度学习的主要库之一,因为它非常高效,且可以在 GPU 上运行。
以下程序描述了如何使用 TensorFlow 和 numpy 执行类似操作,如创建形状为 (3,3) 的张量:
import TensorFlow as tf
import numpy as np
tf.InteractiveSession()
# TensorFlow operations
a = tf.zeros((3,3))
b = tf.ones((3,3))
print(tf.reduce_sum(b, reduction_indices=1).eval())
print(a.get_shape())
# numpy operations
a = np.zeros((3, 3))
b = np.ones((3, 3))
print(np.sum(b, axis=1))
print(a.shape)
上述代码的输出结果如下:
[ 3\. 3\. 3.]
(3, 3)
[ 3\. 3\. 3.]
(3, 3)
计算图
TensorFlow 基于构建计算图。计算图是一个节点网络,每个节点定义了执行某个函数的操作;这些操作可以是简单的加法或减法,也可以是复杂的多变量方程。TensorFlow 程序在构建阶段组装图,在执行阶段利用会话对象执行图中的操作。
一个操作被称为 op,可以返回零个或多个张量,这些张量可以在图中稍后使用。每个 op 都可以给定常量、数组或 n 维矩阵。
图
默认图会在导入 TensorFlow 库时实例化。构建图对象而非使用默认图在创建多个不依赖于彼此的模型时非常有用。常量和操作会被添加到 TensorFlow 图中。
在newGraph.as_default()之外应用的变量和操作将被添加到默认图中,默认图会在库被导入时创建:
newGraph = tf.Graph()
with newGraph.as_default():
newGraphConst = tf.constant([2., 3.])
会话对象
TensorFlow 中的会话封装了张量对象求值的环境。会话可以有它们自己的私有变量、队列和指定的读取器。我们应该在会话结束时使用 close 方法。
会话有三个参数,这些参数是可选的:
-
Target:要连接的执行引擎 -
graph:要启动的图对象 -
config:这是一个 ConfigProto 协议缓冲区
要执行 TensorFlow 计算的单步操作,需调用步骤函数并执行图中的必要依赖:
# session objects
a = tf.constant(6.0)
b = tf.constant(7.0)
c = a * b
with tf.Session() as sess:
print(sess.run(c))
print(c.eval())
当前活动会话中的sess.run(c)!
上述代码输出如下:
42.0, 42.0
tf.InteractiveSession()函数是保持默认会话在ipython中打开的简便方法。sess.run(c)是 TensorFlow 获取操作的一个示例:
session = tf.InteractiveSession()
cons1 = tf.constant(1)
cons2 = tf.constant(2)
cons3 = cons1 + cons2
# instead of sess.run(cons3)
cons3.eval()
变量
训练模型时,我们使用变量来保存和更新参数。变量就像内存缓冲区,包含张量。我们之前使用的所有张量都是常量张量,而不是变量。
变量由会话对象进行管理或维护。变量在会话之间持久存在,这非常有用,因为张量和操作对象是不可变的:
# tensor variablesW1 = tf.ones((3,3))
W2 = tf.Variable(tf.zeros((3,3)), name="weights")
with tf.Session() as sess:
print(sess.run(W1))
sess.run(tf.global_variables_initializer())
print(sess.run(W2))
上述代码输出如下:
[[ 1\. 1\. 1.] [ 1\. 1\. 1.] [ 1\. 1\. 1.]]
[[ 0\. 0\. 0.] [ 0\. 0\. 0.] [ 0\. 0\. 0.]]
TensorFlow 变量在赋值前必须初始化,这与常量张量不同:
# Variable objects can be initialized from constants or random values
W = tf.Variable(tf.zeros((2,2)), name="weights")
R = tf.Variable(tf.random_normal((2,2)), name="random_weights")
with tf.Session() as sess:
# Initializes all variables with specified values.
sess.run(tf.initialize_all_variables())
print(sess.run(W))
print(sess.run(R))
上述代码输出如下:
[[ 0\. 0.] [ 0\. 0.]]
[[ 0.65469146 -0.97390586] [-2.39198709 0.76642162]]
state = tf.Variable(0, name="counter")
new_value = tf.add(state, tf.constant(1))
update = tf.assign(state, new_value)
with tf.Session() as sess:
sess.run(tf.initialize_all_variables())
print(sess.run(state))
for _ in range(3):
sess.run(update)
print(sess.run(state))
上述代码输出如下:
0 1 2 3
获取变量状态:
input1 = tf.constant(5.0)
input2 = tf.constant(6.0)
input3 = tf.constant(7.0)
intermed = tf.add(input2, input3)
mul = tf.multiply(input1, intermed)
# Calling sess.run(var) on a tf.Session() object retrieves its value. Can retrieve multiple variables simultaneously with sess.run([var1, var2])
with tf.Session() as sess:
result = sess.run([mul, intermed])
print(result)
上述代码输出如下:
[65.0, 13.0]
范围
TensorFlow 模型可能有成百上千个变量。tf.variable_scope()提供了一个简单的命名方式。
为了管理模型的复杂性并将其分解为独立的部分,TensorFlow 提供了作用域。作用域非常简单,并且在使用 TensorBoard 时非常有用。作用域也可以嵌套在其他作用域内:
with tf.variable_scope("foo"):
with tf.variable_scope("bar"):
v = tf.get_variable("v", [1])
assert v.name == "foo/bar/v:0" with tf.variable_scope("foo"):
v = tf.get_variable("v", [1])
tf.get_variable_scope().reuse_variables()
v1 = tf.get_variable("v", [1])
assert v1 == v
以下示例展示了如何使用重用选项来理解get_variable的行为:
#reuse is falsewith tf.variable_scope("foo"):
n = tf.get_variable("n", [1])
assert v.name == "foo/n:0" *#Reuse is true* with tf.variable_scope("foo"):
n = tf.get_variable("n", [1])
with tf.variable_scope("foo", reuse=True):
v1 = tf.get_variable("n", [1])
assert v1 == n
数据输入
向 TensorFlow 对象输入外部数据:
a = np.zeros((3,3))
ta = tf.convert_to_tensor(a)
with tf.Session() as sess:
print(sess.run(ta))
上述代码输出如下:
[[ 0\. 0\. 0.] [ 0\. 0\. 0.] [ 0\. 0\. 0.]]
占位符和输入字典
使用tf.convert_to_tensor()输入数据是方便的,但它不能扩展。使用tf.placeholder变量(虚拟节点,用于为计算图提供数据的入口)。feed_dict是一个 Python 字典映射:
input1 = tf.placeholder(tf.float32)
input2 = tf.placeholder(tf.float32)
output = tf.multiply(input1, input2)
with tf.Session() as sess:
print(sess.run([output], feed_dict={input1:[5.], input2:[6.]}))
上述代码输出如下:
[array([ 30.], dtype=float32)]
自动微分
自动微分也被称为算法微分,它是一种自动计算函数导数的方法。这对于计算梯度、雅可比矩阵和海森矩阵等在数值优化等应用中非常有用。反向传播算法是自动微分反向模式的一种实现,用于计算梯度。
在以下示例中,使用mnist数据集,我们使用其中一个loss函数计算损失。问题是:我们如何将模型拟合到数据上?
我们可以使用tf.train.Optimizer来创建优化器。tf.train.Optimizer.minimize(loss, var_list)将优化操作添加到计算图中,自动微分则无需用户输入即可计算梯度:
import TensorFlow as tf
# get mnist dataset
from TensorFlow .examples.tutorials.mnist import input_data
data = input_data.read_data_sets("MNIST_data/", one_hot=True)
# x represents image with 784 values as columns (28*28), y represents output digit
x = tf.placeholder(tf.float32, [None, 784])
y = tf.placeholder(tf.float32, [None, 10])
# initialize weights and biases [w1,b1][w2,b2]
numNeuronsInDeepLayer = 30
w1 = tf.Variable(tf.truncated_normal([784, numNeuronsInDeepLayer]))
b1 = tf.Variable(tf.truncated_normal([1, numNeuronsInDeepLayer]))
w2 = tf.Variable(tf.truncated_normal([numNeuronsInDeepLayer, 10]))
b2 = tf.Variable(tf.truncated_normal([1, 10]))
# non-linear sigmoid function at each neuron
def sigmoid(x):
sigma = tf.div(tf.constant(1.0), tf.add(tf.constant(1.0), tf.exp(tf.negative(x))))
return sigma
# starting from first layer with wx+b, then apply sigmoid to add non-linearity
z1 = tf.add(tf.matmul(x, w1), b1)
a1 = sigmoid(z1)
z2 = tf.add(tf.matmul(a1, w2), b2)
a2 = sigmoid(z2)
# calculate the loss (delta)
loss = tf.subtract(a2, y)
# derivative of the sigmoid function der(sigmoid)=sigmoid*(1-sigmoid)
def sigmaprime(x):
return tf.multiply(sigmoid(x), tf.subtract(tf.constant(1.0), sigmoid(x)))
# automatic differentiation
cost = tf.multiply(loss, loss)
step = tf.train.GradientDescentOptimizer(0.1).minimize(cost)
acct_mat = tf.equal(tf.argmax(a2, 1), tf.argmax(y, 1))
acct_res = tf.reduce_sum(tf.cast(acct_mat, tf.float32))
sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())
for i in range(10000):
batch_xs, batch_ys = data.train.next_batch(10)
sess.run(step, feed_dict={x: batch_xs,
y: batch_ys})
if i % 1000 == 0:
res = sess.run(acct_res, feed_dict=
{x: data.test.images[:1000],
y: data.test.labels[:1000]})
print(res)
TensorBoard
TensorFlow 具有一个强大的内置可视化工具,叫做TensorBoard。它允许开发者解释、可视化和调试计算图。为了自动在 TensorBoard 中可视化图形和指标,TensorFlow 会将与计算图执行相关的事件写入特定文件夹。
这个示例展示了之前分析的计算图:

要查看图形,请点击 TensorBoard 顶部面板中的图形标签。如果图形中有多个节点,单独查看可能会很困难。为了让我们的可视化更易访问,我们可以使用tf.name_scope并指定名称,将相关操作组织成组。


表示时间步
的输入。
表示时间步
的隐藏状态。隐藏状态是网络的记忆。
是基于先前的隐藏状态和当前步骤的输入计算得出的,
。
表示步骤
的输出。为了预测给定句子中的下一个单词,它将是一个遍历词汇表的概率向量,
。
浙公网安备 33010602011771号