TensorFlow2-深度学习高级教程-全-
TensorFlow2 深度学习高级教程(全)
一、数学基础
深度学习是机器学习的一个分支,它使用许多层层堆叠的人工神经元来识别输入数据中的复杂特征,并解决复杂的现实世界问题。它可以用于监督和非监督的机器学习任务。深度学习目前用于计算机视觉、视频分析、模式识别、异常检测、文本处理、情感分析和推荐系统等领域。此外,它还广泛用于机器人、自动驾驶汽车机制以及一般的人工智能系统。
数学是任何机器学习算法的核心。牢牢掌握数学的核心概念有助于人们为特定的机器学习问题选择正确的算法,同时牢记最终目标。此外,它使人们能够更好地调整机器学习/深度学习模型,并理解算法没有按预期执行的可能原因。作为机器学习的一个分支,深度学习需要与其他机器学习任务一样多的数学专业知识。数学作为一门学科是广阔的,但机器学习或深度学习的专业人士和/或爱好者应该知道一些特定的主题,以便从机器学习、深度学习和人工智能的这个奇妙领域中提取最多的东西。图 1-1 展示了数学的不同分支以及它们在机器学习和深度学习领域的重要性。我们将在本章的以下每个分支中讨论相关概念:

图 1-1
Importance of mathematics topics for machine learning and data science
- 线性代数
- 概率与统计
- 结石
- 机器学习算法的优化和制定
Note
已经熟悉这些主题的读者可以选择跳过这一章或者浏览一下内容。
线性代数
线性代数是数学的一个分支,研究向量及其从一个向量空间到另一个向量空间的变换。由于在机器学习和深度学习中,我们处理多维数据及其操作,所以线性代数在几乎每个机器学习和深度学习算法中都起着至关重要的作用。图 1-2 所示为三维向量空间,其中 v 1 ,v 2 ,v 3 为向量,P 为三维向量空间内的二维平面。

图 1-2
Three-dimensional vector space with vectors and a vector plane
矢量
一组连续或离散的数字称为向量,由向量组成的空间称为向量空间。向量空间维度可以是有限的,也可以是无限的,但大多数机器学习或数据科学问题处理的是固定长度的向量;例如,在 x 和 y 方向分别具有速度 Vx 和 Vy 的平面中运动的汽车的速度(见图 1-3 )。

图 1-3
Car moving in the x-y vector plane with velocity components Vx and Vy
在机器学习中,我们处理的是多维数据,所以向量变得非常关键。假设我们试图根据房屋面积、卧室数量、浴室数量和当地人口密度来预测一个地区的房价。所有这些特征形成了房价预测问题的输入特征向量。
数量
一维向量是一个标量。高中学过,标量是只有大小没有方向的量。这是因为,由于它只有一个可以移动的方向,所以它的方向并不重要,我们只关心它的大小。
例如:孩子的身高,水果的重量等。
[数]矩阵
矩阵是按行和列排列的二维数字数组。矩阵的大小由其行长和列长决定。如果一个矩阵 A 有 m 行 n 列,可以表示为一个具有
元素的矩形对象(见图 1-4a ),可以记为
。

图 1-4a
Structure of a matrix
属于同一向量空间的几个向量构成一个矩阵。
例如,灰度图像以矩阵形式存储。图像的大小决定了图像矩阵的大小,每个矩阵单元包含一个从 0 到 255 的值,代表像素强度。图 1-4b 所示为灰度图像及其矩阵表示。

图 1-4b
Structure of a matrix
张量
张量是一个多维数组。事实上,向量和矩阵可以被视为一维和二维张量。在深度学习中,张量大多用于存储和处理数据。例如,RGB 图像存储在三维张量中,其中一个维度为横轴,另一个维度为纵轴,第三维度对应三个颜色通道,即红色、绿色和蓝色。另一个例子是在卷积神经网络中通过小批量输入图像时使用的四维张量。沿着第一个维度,我们有批中的图像编号,沿着第二个维度,我们有颜色通道,第三和第四个维度对应于水平和垂直方向上的像素位置。
矩阵运算和操作
大多数深度学习计算活动都是通过基本的矩阵运算完成的,如乘法、加法、减法、转置等。因此,回顾一下基本的矩阵运算是有意义的。
m 行 n 列的矩阵可以被认为是包含并排堆叠的 m 维 n 个列向量的矩阵。我们把矩阵表示为

两个矩阵的加法
两个矩阵 A 和 B 的相加意味着它们的逐元素相加。我们只能添加两个矩阵,前提是它们的维数匹配。如果 C 是矩阵 A 和 B 的和,那么


例子:
然后![$$ A + B = \left[\begin{array}{cc}\hfill 1+5\hfill & \hfill 2+6\hfill \ {}\hfill 3+7\hfill & \hfill 4+8\hfill \end{array}\right] = \left[\begin{array}{cc}\hfill 6\hfill & \hfill 8\hfill \ {}\hfill 10\hfill & \hfill 12\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_IEq4.gif)
两个矩阵相减
两个矩阵 A 和 B 的相减意味着它们的逐元素相减。我们只能减去两个维数匹配的矩阵。
IfC 是代表
的矩阵,那么


例子:
然后![$$ A - B = \left[\begin{array}{cc}\hfill 1-5\hfill & \hfill 2-6\hfill \ {}\hfill 3-7\hfill & \hfill 4-8\hfill \end{array}\right] = \left[\begin{array}{cc}\hfill -4\hfill & \hfill -4\hfill \ {}\hfill -4\hfill & \hfill -4\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_IEq7.gif)
两个矩阵的乘积
对于两个可相乘的矩阵
和
,n 应该等于 p。得到的矩阵是
。C 的元素可以表示为

例如,两个矩阵
的矩阵乘法可以这样计算:
![$$ A = \left[\begin{array}{cc}\hfill 1\hfill & \hfill 2\hfill \ {}\hfill 3\hfill & \hfill 4\hfill \end{array}\right]B=\left[\begin{array}{cc}\hfill 5\hfill & \hfill 6\hfill \ {}\hfill 7\hfill & \hfill 8\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_Equg.gif)
![$$ {c}_{11} = \left[1\kern0.75em 2\right]\left[\begin{array}{c}\hfill 5\hfill \ {}\hfill 7\hfill \end{array}\right]=\kern0.5em 1\times 5 + 2\times 7 = 19\kern0.75em {c}_{12} = \left[1\kern0.75em 2\right]\left[\begin{array}{c}\hfill 6\hfill \ {}\hfill 8\hfill \end{array}\right]=\kern0.5em 1\times 6 + 2\times 8 = 22 $$](A448418_1_En_1_Chapter_Equh.gif)
![$$ {c}_{21} = \left[3\kern1em 4\right]\left[\begin{array}{c}\hfill 5\hfill \ {}\hfill 7\hfill \end{array}\right]=\kern0.5em 3\times 5 + 4\times 7 = 43\kern1em {c}_{22} = \left[3\kern0.75em 4\right]\left[\begin{array}{c}\hfill 6\hfill \ {}\hfill 8\hfill \end{array}\right]=\kern0.5em 3\times 6 + 4\times 8 = 50 $$](A448418_1_En_1_Chapter_Equi.gif)
![$$ C = \left[\begin{array}{cc}\hfill {c}_{11}\hfill & \hfill {c}_{12}\hfill \ {}\hfill {c}_{21}\hfill & \hfill {c}_{22}\hfill \end{array}\right] = \left[\begin{array}{cc}\hfill 19\hfill & \hfill 22\hfill \ {}\hfill 43\hfill & \hfill 50\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_Equj.gif)
矩阵的转置
矩阵
的转置一般用
表示,通过将列向量转置为行向量来获得。
??
??
示例:![$$ \mathrm{Example}:;A=\left[\begin{array}{cc}\hfill 1\hfill & \hfill 2\hfill \ {}\hfill 3\hfill & \hfill 4\hfill \end{array}\right]\kern0.24em \mathrm{then};{A}^T=\left[\begin{array}{cc}\hfill 1\hfill & \hfill 3\hfill \ {}\hfill 2\hfill & \hfill 4\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_IEq14.gif)
两个矩阵 A 和 B 的乘积的转置是矩阵 A 和 B 的逆序转置的乘积;即
例如,如果我们取两个矩阵
和
,那么
因此![$$ {(AB)}^T = \left[\begin{array}{cc}\hfill 95\hfill & \hfill 301\hfill \ {}\hfill 132\hfill & \hfill 400\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_IEq19.gif)
现在,![$$ {A}^T = \left[\begin{array}{cc}\hfill 19\hfill & \hfill 43\hfill \ {}\hfill 22\hfill & \hfill 50\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_IEq20.gif)
![$$ {B}^T = \left[\begin{array}{cc}\hfill 5\hfill & \hfill 7\hfill \ {}\hfill 6\hfill & \hfill 8\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_IEq21.gif)
![$$ {B}T{A}T = \left[\begin{array}{cc}\hfill 5\hfill & \hfill 7\hfill \ {}\hfill 6\hfill & \hfill 8\hfill \end{array}\right]\left[\begin{array}{cc}\hfill 19\hfill & \hfill 43\hfill \ {}\hfill 22\hfill & \hfill 50\hfill \end{array}\right] = \left[\begin{array}{cc}\hfill 95\hfill & \hfill 301\hfill \ {}\hfill 132\hfill & \hfill 400\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_Equm.gif)
因此,等式
成立。
两个向量的点积
任何维数为 n 的向量都可以表示为一个矩阵
。让我们表示两个 n 维向量
和
。
![$$ {v}_1=\kern0.5em \left[\begin{array}{c}\hfill {v}_{11}\hfill \ {}\hfill {v}_{12}\hfill \ {}\hfill .\hfill \ {}\hfill .\hfill \ {}\hfill .\hfill \ {}\hfill {v}_{1n}\hfill \end{array}\right]{v}_2=\kern0.5em \left[\begin{array}{c}\hfill {v}_{21}\hfill \ {}\hfill {v}_{22}\hfill \ {}\hfill .\hfill \ {}\hfill .\hfill \ {}\hfill .\hfill \ {}\hfill {v}_{2n}\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_Equn.gif)
两个向量的点积是相应分量(即沿同一维度的分量)的乘积之和,可以表示为

![$$ {v}_1 = \left[\begin{array}{c}\hfill 1\hfill \ {}\hfill 2\hfill \ {}\hfill 3\hfill \end{array}\right]{v}_2 = \left[\begin{array}{c}\hfill 3\hfill \ {}\hfill 5\hfill \ {}\hfill -1\hfill \end{array}\right]{v}_1.{v}_2=\kern0.5em {v_1}^T{v}_2=1\times 3+2\times 5-3\times 1=10 $$](A448418_1_En_1_Chapter_Equ1.gif)
(例如:)
矩阵对向量的作用
当一个矩阵乘以一个向量时,结果是另一个向量。假设
乘以矢量
。结果会产生一个矢量
![$$ A=\left[\begin{array}{c}\hfill {c}_1{(1)}{c}_1{(2)}\dots .\ {c}_1^{(n)}\hfill \ {}\hfill {c}_2{(1)}{c}_2{(2)}\dots .\ {c}_2^{(n)}\hfill \ {}\hfill .\hfill \ {}\hfill .\hfill \ {}\hfill .\hfill \ {}\hfill {c}_m{(1)}{c}_m{(2)}\dots .\ {c}_m^{(n)}\hfill \end{array}\right]x=\kern0.5em \left[\begin{array}{c}\hfill {x}_1\hfill \ {}\hfill {x}_2\hfill \ {}\hfill .\hfill \ {}\hfill .\hfill \ {}\hfill .\hfill \ {}\hfill {x}_n\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_Equp.gif)
a 由 n 个列向量
组成。
??
??![$$ b = Ax = \kern0.5em \left[{c}{(1)}{c}{(2)}{c}^{(3)} \dots .\ {c}^{(n)}\right]\left[\begin{array}{c}\hfill {x}_1\hfill \ {}\hfill {x}_2\hfill \ {}\hfill .\hfill \ {}\hfill .\hfill \ {}\hfill .\hfill \ {}\hfill {x}_n\hfill \end{array}\right] = {x}_1{c}^{(1)} + {x}_2{c}^{(2)} + \dots .. + {x}_n{c}^{(n)} $$](A448418_1_En_1_Chapter_Equr.gif)
正如我们所看到的,乘积只不过是矩阵 A 的列向量的线性组合,向量 x 的分量是线性系数。
通过乘法形成的新向量 b 具有与 A 的列向量相同的维数,并且停留在相同的列空间中。这是如此美丽的事实;无论我们如何组合列向量,我们永远不能离开列向量所跨越的空间。
现在,让我们来看一个例子。
![$$ A = \left[\begin{array}{ccc}\hfill 1\hfill & \hfill 2\hfill & \hfill 3\hfill \ {}\hfill 4\hfill & \hfill 5\hfill & \hfill 6\hfill \end{array}\right]\kern1.25em x = \left[\begin{array}{c}\hfill 2\hfill \ {}\hfill 2\hfill \ {}\hfill 3\hfill \end{array}\right]\kern2.5em b= Ax=2\left[\begin{array}{c}\hfill 1\hfill \ {}\hfill 4\hfill \end{array}\right]+2\left[\begin{array}{c}\hfill 2\hfill \ {}\hfill 5\hfill \end{array}\right]+3\left[\begin{array}{c}\hfill 3\hfill \ {}\hfill 6\hfill \end{array}\right] = \left[\begin{array}{c}\hfill 15\hfill \ {}\hfill 36\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_Equs.gif)
正如我们所见,A 和
的列向量
向量的线性无关性
如果一个向量可以表示为其他向量的线性组合,则称该向量线性依赖于其他向量。
如果
,那么 v 1 ,v 2 和 v 3 不是线性独立的,因为它们中的至少一个可以表示为其他向量的和。一般来说,一组 n 个向量
被称为线性无关,当且仅当
暗示每个
。
如果
而不是所有的
,那么向量不是线性独立的。
给定一组向量,下面的方法可以用来检查它们是否线性无关。
= 0 可以写成
![$$ \left[{v}_1{v}_2\ ....\ {v}_n\right]\left[\begin{array}{c}\hfill {a}_1\hfill \ {}\hfill {a}_2\hfill \ {}\hfill .\hfill \ {}\hfill .\hfill \ {}\hfill {a}_n\hfill \end{array}\right]\kern1.25em = 0\ where\kern0.5em {v}_i\ \in\ {\mathrm{\mathbb{R}}}^{m\times 1}\ \forall i\ \in \left{1,2,.\ .\ .,n\right}, \kern0.5em \left[\begin{array}{c}\hfill {a}_1\hfill \ {}\hfill {a}_2\hfill \ {}\hfill .\hfill \ {}\hfill .\hfill \ {}\hfill {a}_n\hfill \end{array}\right]\ \in\ {\mathrm{\mathbb{R}}}^{n\times 1} $$](A448418_1_En_1_Chapter_Equt.gif)
求解一个 1 一个 2 ....anT,如果我们得到的唯一解是零向量,那么向量集合 v 1 ,v 2 ,…。v n 据说是线性无关的。
如果一组 n 个向量
是线性无关的,那么这些向量跨越整个 n 维空间。换句话说,通过取 n 个向量的线性组合,可以产生 n 维空间中所有可能的向量。如果 n 个向量不是线性独立的,那么它们只跨越 n 维空间中的一个子空间。
为了说明这个事实,让我们以三维空间中的向量为例,如图 1-5 所示。
如果我们有一个向量
,我们可以在三维空间中只跨越一个维度,因为可以用这个向量形成的所有向量将具有与 v 1 相同的方向,其大小由定标器乘法器确定。换句话说,每个向量都是 a 1 v 1 的形式。
现在,让我们取另一个向量
,它的方向和 v 1 的方向不一样。所以,两个向量 span 的跨度(v 1 ,v 2 )无非是 v 1 和 v 2 的线性组合。有了这两个向量,我们可以形成位于这两个向量平面内的任何形式的向量
。基本上,我们会在三维空间内跨越一个二维子空间。下图对此进行了说明。

图 1-5
A two-dimensional subspace spanned by v 1 and v 2 in a three-dimensional vector space
让我们添加另一个向量
到我们的向量集中。现在,如果我们考虑跨度(v 1 ,v 2, v 3 ),就可以在三维平面上形成任意矢量。你取任意一个三维向量,它可以表示为前面三个向量的线性组合。
这三个矢量构成了三维空间的基础。任何三个线性独立的向量将形成三维空间的基础。这同样可以推广到任何 n 维空间。
如果我们取一个向量 v 3 ,它是 v 1 和 v 2 的线性组合,那么它不可能跨越整个三维空间。我们将被限制在由 v 1 和 v 2 构成的二维子空间中。
矩阵的秩
线性代数中最重要的概念之一是矩阵的秩。矩阵的秩是线性独立的列向量或行向量的数目。对于一个矩阵,独立列向量的数量总是等于独立行向量的数量。
示例-考虑矩阵![$$ A = \left[\begin{array}{ccc}\hfill 1\hfill & \hfill 3\hfill & \hfill 4\hfill \ {}\hfill 2\hfill & \hfill 5\hfill & \hfill 7\hfill \ {}\hfill 3\hfill & \hfill 7\hfill & \hfill 10\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_IEq43.gif)
列向量
和
线性无关。然而,
不是线性独立的,因为它是另外两个列向量的线性组合;即
。因此,矩阵的秩是 2,因为它有两个线性独立的列向量。
由于矩阵的秩是 22,矩阵的列向量只能跨越三个三维向量空间内的两个二维子空间。二维子空间就是取
和
的线性组合可以形成的子空间。
一些重要注意事项:
- 如果 A 的秩为 n,则称方阵
是满秩的。秩为 n 的方阵意味着所有 n 个列向量甚至 n 个行向量都是线性独立的,因此可以通过对矩阵 A 的 n 个列向量进行线性组合来跨越整个 n 维空间 - 如果一个方阵
不是满秩的,那么它就是奇异矩阵;即,它的所有列向量或行向量不是线性独立的。奇异矩阵有一个未定义的逆矩阵和零行列式。
单位矩阵或算子
如果任何向量或矩阵乘以 I 后保持不变,则称矩阵
为单位矩阵或算子。一个
单位矩阵由
![$$ I = \left[\begin{array}{ccc}\hfill 1\hfill & \hfill 0\hfill & \hfill 0\hfill \ {}\hfill 0\hfill & \hfill 1\hfill & \hfill 0\hfill \ {}\hfill 0\hfill & \hfill 0\hfill & \hfill 1\hfill \end{array}\right]\kern0.5em \in\ {\mathrm{\mathbb{R}}}^{3\times 3} $$](A448418_1_En_1_Chapter_Equu.gif)
给出
比方说我们取矢量![$$ v = {\left[2\ 3\ 4\right]}^T $$](A448418_1_En_1_Chapter_IEq54.gif)
![$$ Iv = \left[\begin{array}{ccc}\hfill 1\hfill & \hfill 0\hfill & \hfill 0\hfill \ {}\hfill 0\hfill & \hfill 1\hfill & \hfill 0\hfill \ {}\hfill 0\hfill & \hfill 0\hfill & \hfill 1\hfill \end{array}\right]\left[\begin{array}{c}\hfill 2\hfill \ {}\hfill 3\hfill \ {}\hfill 4\hfill \end{array}\right]=\kern0.5em \left[\begin{array}{c}\hfill 2\hfill \ {}\hfill 3\hfill \ {}\hfill 4\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_Equv.gif)
类似地,假设我们有一个矩阵![$$ A = \left[\begin{array}{ccc}\hfill 1\hfill & \hfill 2\hfill & \hfill 3\hfill \ {}\hfill 4\hfill & \hfill 5\hfill & \hfill 6\hfill \ {}\hfill 7\hfill & \hfill 8\hfill & \hfill 9\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_IEq55.gif)
矩阵 AI 和 IA 都等于矩阵 a。因此,当矩阵之一是单位矩阵时,矩阵乘法是可交换的。
矩阵的行列式
方阵 A 的行列式是一个数,用 det(A)表示。它可以有几种解释。对于矩阵
,行列式表示由矩阵的 n 个 nrow 向量包围的
维体积。对于非零的行列式,a 的所有列向量或行向量应该是线性无关的。如果 n 个 nrow 向量或 column 向量不是线性独立的,那么它们不会跨越整个 n n 维空间,而是跨越维数小于 n 的子空间,因此 nn 维体积为零。对于一个矩阵
,行列式表示为
![$$ A = \left[\begin{array}{cc}\hfill {a}_{11}\hfill & \hfill {a}_{12}\hfill \ {}\hfill {a}_{21}\hfill & \hfill {a}_{22}\hfill \end{array}\right]\ \in\ {\mathrm{\mathbb{R}}}^{2\times 2} $$](A448418_1_En_1_Chapter_Equw.gif)

同样,对于一个矩阵
来说,矩阵的行列式由
![$$ B = \left[\begin{array}{ccc}\hfill {a}_{11}\hfill & \hfill {a}_{12}\hfill & \hfill {a}_{13}\hfill \ {}\hfill {a}_{21}\hfill & \hfill {a}_{22}\hfill & \hfill {a}_{23}\hfill \ {}\hfill {a}_{31}\hfill & \hfill {a}_{32}\hfill & \hfill {a}_{33}\hfill \end{array}\right]\ \in\ {\mathrm{\mathbb{R}}}^{3\times 3} $$](A448418_1_En_1_Chapter_Equy.gif)

![$$ \det \left(\left[\begin{array}{cc}\hfill {a}_{22}\hfill & \hfill {a}_{23}\hfill \ {}\hfill {a}_{32}\hfill & \hfill {a}_{33}\hfill \end{array}\right]\right) = \left|\begin{array}{cc}\hfill {a}_{22}\hfill & \hfill {a}_{23}\hfill \ {}\hfill {a}_{32}\hfill & \hfill {a}_{33}\hfill \end{array}\right| $$](A448418_1_En_1_Chapter_Equ2.gif)
(其中)
行列式计算的方法可以推广到
矩阵。把 B 看成一个 n 维矩阵,它的行列式可以表示为
![$$ det(B) = \left[\begin{array}{ccc}\hfill {a}_{11} \times \hfill & \hfill \hfill & \hfill \hfill \ {}\hfill \hfill & \hfill {a}_{22}\hfill & \hfill {a}_{23}\hfill \ {}\hfill \hfill & \hfill {a}_{32}\hfill & \hfill {a}_{33}\hfill \end{array}\right]-\left[\begin{array}{ccc}\hfill \hfill & \hfill {a}_{12\kern0.5em \times}\hfill & \hfill \hfill \ {}\hfill {a}_{21}\hfill & \hfill \hfill & \hfill {a}_{23}\hfill \ {}\hfill {a}_{31}\hfill & \hfill \hfill & \hfill {a}_{33}\hfill \end{array}\right] + \left[\begin{array}{ccc}\hfill \hfill & \hfill \hfill & \hfill {a}_{13\kern0.5em \times \kern0.75em }\hfill \ {}\hfill {a}_{21}\hfill & \hfill {a}_{22}\hfill & \hfill \hfill \ {}\hfill {a}_{31}\hfill & \hfill {a}_{32}\hfill & \hfill \hfill \end{array}\right] $$](A448418_1_En_1_Chapter_Equaa.gif)
例如,矩阵
的行列式可以计算如下:
![$$ \begin{array}{l} det(A)=\left[\begin{array}{ccc}\hfill 6 \times \hfill & \hfill \hfill & \hfill \hfill \ {}\hfill \hfill & \hfill -2\hfill & \hfill 5\hfill \ {}\hfill \hfill & \hfill 8\hfill & \hfill 7\hfill \end{array}\right]-\left[\begin{array}{ccc}\hfill \hfill & \hfill 1 \times \hfill & \hfill \hfill \ {}\hfill 4\hfill & \hfill \hfill & \hfill 5\hfill \ {}\hfill 2\hfill & \hfill \hfill & \hfill 7\hfill \end{array}\right] + \left[\begin{array}{ccc}\hfill \hfill & \hfill \hfill & \hfill 1 \times \hfill \ {}\hfill 4\hfill & \hfill -2\hfill & \hfill \hfill \ {}\hfill 2\hfill & \hfill 8\hfill & \hfill \hfill \end{array}\right]\ {}\kern5.5em =6\times \left|\begin{array}{cc}\hfill -2\hfill & \hfill 5\hfill \ {}\hfill 8\hfill & \hfill 7\hfill \end{array}\right|-1\times \left|\begin{array}{cc}\hfill 4\hfill & \hfill 5\hfill \ {}\hfill 2\hfill & \hfill 7\hfill \end{array}\right| + 1\times \left|\begin{array}{cc}\hfill 4\hfill & \hfill -2\hfill \ {}\hfill 2\hfill & \hfill \kern0.75em 8\hfill \end{array}\right|=6\left(-14-40\right)-1\left(28-10\right)+1\left(32+4\right)\ {}\kern3.25em =6\times \left(-54\right)-1(18)+36 = -306\end{array} $$](A448418_1_En_1_Chapter_Equab.gif)
行列式的解释
如前所述,矩阵行列式的绝对值决定了作为边的行向量所包围的体积。
对于矩阵
,表示以两行向量为边的平行四边形的面积。
对于矩阵
,det(A)等于以矢量
和
为边的平行四边形的面积。
平行四边形的面积= |u||v| sin θ其中θθ是 u 和 v 之间的角度(见图 1-6 )。

图 1-6
Parallelogram formed by two vectors
同样,对于一个矩阵
,行列式是以三行向量为边的平行六面体的体积。
矩阵的逆
方阵
的逆矩阵由
表示,当乘以 A.

时产生单位矩阵
并非所有方阵对 A 都有逆,计算 A 的逆的公式如下:

如果方阵
是奇异的——也就是说,如果 A 没有 n 个独立的列或行向量——那么 A 的逆就不存在。这是因为对于奇异矩阵
而言,逆矩阵变得不确定。![$$ A = \left[\begin{array}{ccc}\hfill a\hfill & \hfill b\hfill & \hfill c\hfill \ {}\hfill d\hfill & \hfill e\hfill & \hfill f\hfill \ {}\hfill g\hfill & \hfill h\hfill & \hfill i\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_Equaf.gif)
设 A 的元素由 a ij 表示,其中 I 表示元素的行号,j 表示元素的列号。
然后,
的余因子,其中 d ij 是从 a 中删除第 I 行和第 j 列形成的矩阵的行列式
元素
的余因子。
类似地,元素
的余因子。
一旦余因子矩阵形成,余因子矩阵的转置将给出伴随矩阵(A)。伴随(A)除以 det(A)得到
。
例如,
的逆矩阵可以计算如下:
的余因子矩阵
因此,
。
矩阵求逆的几个规则:

,其中是单位矩阵
向量的范数
向量的范数是其大小的度量。这样的规范有好几种。最熟悉的是欧几里德范数,定义如下。也被称为 l 2 范数。
对于一个向量
l2范数如下:

同样,l 1 范数是矢量分量的绝对值之和。
一般来说,当 1 < p < ∞:

时,向量的 l p 范数可以定义如下
当
则范数称为上确界范数,定义如下:


在图 1-7 中,绘制了 l 1 ,l 2 和上确界范数的单位范数曲线。

图 1-7
Unit l 1,l 2 and Supremum norms of vectors 
一般来说,对于机器学习,我们出于多种目的使用 l 2 和 l 1 规范。例如,我们在线性回归中使用的最小二乘成本函数是误差向量的 l 2 范数;即实际目标值向量和预测目标值向量之间的差。类似地,我们经常不得不对我们的模型使用正则化,结果是模型不能很好地适应训练数据,并且不能推广到新的数据。为了实现正则化,我们通常添加模型的参数向量的 l 2 范数或 l 1 范数的平方,作为模型的成本函数中的惩罚。当参数向量的 l 2 范数用于正则化时,通常称为脊正则化,而当使用 l 1 范数时,称为 Lasso 正则化。
矩阵的伪逆
如果我们有一个问题
,其中提供了
和
,并且要求我们求解
,如果 A 不是奇异的并且它的逆存在,我们可以求解 x 为
。
然而,如果
——也就是说,如果 A 是一个矩形矩阵,而
——那么
不存在,因此我们不能用前面的方法求解 x。在这种情况下,我们可以得到一个最优解,如
。矩阵
被称为伪逆,因为它作为逆来提供最优解。这种伪逆将出现在最小二乘技术中,例如线性回归。
特定向量方向的单位向量
特定向量方向的单位向量是向量除以其大小或范数。对于一个欧几里得空间,也叫 l 2 空间,矢量
方向的单位矢量是
![$$ \frac{x}{x_2} = \frac{x}{{\left({x}Tx\right)}{1/2}}=\kern0.75em \frac{{\left[3\ 4\right]}^T}{5} = {\left[0.6\ 0.8\right]}^T $$](A448418_1_En_1_Chapter_Equam.gif)
一个矢量在另一个矢量方向上的投影
向量 v 1 在 v 2 方向的投影是 v 1 与 v 2 方向的单位向量的点积。
,其中‖v 12 是 v 1 在 v 2 上的投影,u 2 是 v 2 方向的单位矢量。
由于
根据单位矢量的定义,投影也可以表示为
= 
比如向量[1 1] T 在向量[3 4] T 方向的投影就是[1 1] T 与单位向量[3 4] T 方向的点积;即之前计算的[0.6±0.8]T。
所需投影=
。

图 1-8
The length of the projection of the vector v 1 onto v 2
在图 1-8 中,线段 OA 的长度给出了矢量 v 1 在 v 2 上的投影长度。
特征向量
这里我们来了解线性代数中最重要的概念之一——特征向量和特征值。特征值和特征向量出现在机器学习的几个领域。例如,主成分分析中的主成分是协方差矩阵的特征向量,而特征值是沿着主成分的协方差。类似地,在 Google 的页面排名算法中,页面排名分数的向量只不过是对应于特征值 1 的页面转移概率矩阵的特征向量。
矩阵作为算子作用于向量。矩阵对向量的操作是根据矩阵维数将向量转换成另一个向量,该向量的维数可能与原始向量的维数相同,也可能不同。
当矩阵
作用于向量
时,我们再次得到向量
。通常,新向量的大小和方向与原始向量不同。如果在这种情况下,新生成的向量与原始向量方向相同或正好相反,那么在这种方向上的任何向量都称为本征向量。向量被拉伸的幅度称为特征值(见图 1-9 )。

其中 A 是通过乘法对向量 v 进行运算的矩阵算子,向量 v 也是本征向量,λ是本征值。

图 1-10
The famous Mona Lisa image has a transformation applied to the vector space of pixel location.

图 1-9
Eigen vector unaffected by the matrix transformation A
从图 1-10 可以看出,当对图像空间进行变换时,向量所代表的水平轴上的像素改变了方向,而水平方向上的像素向量没有改变方向。因此,沿水平轴的像素向量是应用于蒙娜丽莎图像的矩阵变换的特征向量。
矩阵的特征方程
矩阵特征方程的根
给出了矩阵的特征值。对于 n 阶方阵,将有对应于 n 个特征向量的 n 个特征值
对于对应于λ特征值的特征向量
,我们有


现在,作为特征向量的 v 是非零的,因此
必须是奇异的,前面的才成立。
因为
是奇异的,
= 0,这是矩阵 a 的特征方程。特征方程的根给出了特征值。将本征值代入
方程,然后求解 v,得到与本征值对应的本征向量。
例如矩阵
![$$ A = \left[\begin{array}{cc}\hfill 0\hfill & \hfill 1\hfill \ {}\hfill -2\hfill & \hfill -3\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_Equaq.gif)
的特征值和特征向量
可以如下图所示进行计算。
矩阵 A 的特征方程是
。

两个特征值是
和
。
设
的特征值对应的特征向量为
。![$$ \left[\begin{array}{cc}\hfill 0\hfill & \hfill 1\hfill \ {}\hfill -2\hfill & \hfill -3\hfill \end{array}\right]\left[\begin{array}{c}\hfill a\hfill \ {}\hfill b\hfill \end{array}\right] = -2\left[\begin{array}{c}\hfill a\hfill \ {}\hfill b\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_Equas.gif)
这给了我们以下两个等式:


两个方程是相同的;即
。
设
和
,其中 k 1 为常数。
所以特征值-2 对应的特征向量是
。
用同样的过程,对应于
特征值的特征向量 v 就是
。
需要注意的一点是,本征向量和本征值总是与在向量空间上工作的特定算子(在前面的例子中,矩阵 A 是算子)相关。特征值和特征向量不特定于任何向量空间。
函数可以被视为向量。假设我们有一个函数
。
x 的无限值中的每一个都是一个维度,在这些值上计算的 f(x)的值是沿着该维度的向量分量。所以我们会得到一个无限的向量空间。
现在,让我们看看微分算子。

这里,
是算符,e ax 是关于算符的本征函数,而 a 是相应的本征值。
如前所述,本征向量和本征值的应用在几乎任何领域都是深远的,对于机器学习也是如此。为了了解特征向量是如何影响现代应用程序的,我们将在一个简单的环境中查看 Google 页面排序算法。
让我们看看一个简单网站的页面排序算法,这个网站有三个页面——A、B 和 C——如图 1-11 所示。

图 1-11
Transition probability diagram for three pages A, B, and C
在 web 设置中,如果原始页面有到下一个页面的链接,用户可以从一个页面跳到另一个页面。此外,一个页面可以自我引用,并有一个链接到自己。因此,如果用户因为页面 A 引用页面 B 而从页面 A 转到页面 B,则事件可以用 B/A 来表示。P(B/A)可以通过从页面 A 访问页面 B 的总次数除以访问页面 A 的总次数来计算。可以类似地计算所有页面组合的转移概率。因为概率是通过归一化计数来计算的,所以页面的个体概率将承载页面重要性的本质。
在稳定状态下,每一页的概率将变成常数。我们需要根据转移概率计算每页的稳态概率。
对于任何页面在稳定状态下保持不变的概率,出去的概率质量应该等于进来的概率质量,并且它们中的每一个——当与留在页面中的概率质量相加时——应该等于该页面的概率。在这种情况下,如果我们考虑 A 页周围的平衡方程,离开 A 的概率质量是
,而进入 A 的概率质量是
。概率质量 P(A/A)P(A)保持在 A 本身。因此,在平衡状态下,来自外界的概率质量——即
——和留在 A 处的概率质量——即 P(A/A)P(A)——之和应该等于 P(A),如下所示:

【1】
类似地,如果我们考虑页 B 和 C 周围的均衡,以下成立:

(2)

(3)
现在是线性代数部分。我们可以把这三个方程排列成一个矩阵作用于一个向量,如下:
![$$ \left[\begin{array}{ccc}\hfill P\left(A/A\right)\hfill & \hfill P\left(A/B\right)\hfill & \hfill P\left(A/C\right)\hfill \ {}\hfill P\left(B/A\right)\hfill & \hfill P\left(B/B\right)\hfill & \hfill P\left(B/C\right)\hfill \ {}\hfill P\left(C/A\right)\hfill & \hfill P\left(C/B\right)\hfill & \hfill P\left(C/C\right)\hfill \end{array}\right]\left[\begin{array}{c}\hfill P(A)\hfill \ {}\hfill P(B)\hfill \ {}\hfill P(C)\hfill \end{array}\right] = \left[\begin{array}{c}\hfill P(A)\hfill \ {}\hfill P(B)\hfill \ {}\hfill P(C)\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_Equaw.gif)
转移概率矩阵作用于页面概率向量,以再次产生页面概率向量。正如我们所看到的,页面概率向量只不过是页面转移概率矩阵的一个本征向量,并且其对应的本征值是 1。
因此,计算对应于特征值 1 的特征向量将会给我们页面概率向量,该向量反过来可以用于对页面进行排序。一些知名搜索引擎的页面排序算法也是基于同样的原理。当然,搜索引擎的实际算法对这个天真的模型做了一些修改,但是基本概念是相同的。概率向量可以通过幂迭代等方法来确定,这将在下一节中讨论。
计算特征向量的幂迭代法
幂迭代法是一种迭代技术,用于计算与最大幅度特征值对应的矩阵的特征向量。
设
然后设 n 个特征值按数量级为
> λ n 对应的特征向量为
> v n 。
幂迭代从随机向量 v 开始,其在对应于最大特征值的特征向量的方向上应该具有一些分量;即 v 1 。
任意迭代中的近似特征向量由

给出
经过足够次数的迭代后,
收敛到 v 1 。在每次迭代中,我们将矩阵 A 乘以上一步得到的向量。如果我们在迭代法中去掉向量的归一化将其转换为单位向量,我们就有了
。
让初始向量 v 表示为本征向量的组合:
其中
为常数。
现在,当 k 足够大时,即(
),从

开始,除第一项之外的所有项都将消失
因此,
,它给我们最大幅度的特征值对应的特征向量。收敛速度取决于第二大特征值与最大特征值相比的大小。如果第二大特征值在量值上接近最大特征值,则该方法收敛缓慢。
Note
在这一章中,我已经谈到了线性代数的基础,以便不熟悉这一学科的读者有一个起点。然而,我建议读者在业余时间更详细地学习线性代数。著名教授 Gilbert Strang 的书《线性代数及其应用》是一个很好的开始。
结石
最简单的形式,微积分是数学的一个分支,处理函数的微分和积分。出于几个原因,很好地理解微积分对机器学习很重要:
- 不同的机器学习模型被表示为几个变量的函数。
- 为了构建机器学习模型,我们通常基于数据和模型参数来计算模型的成本函数,并且通过优化成本函数,我们导出最好地解释给定数据的模型参数。
区别
一个函数的微分通常意味着一个函数所代表的量相对于该函数所依赖的另一个量的变化率。
让我们假设一个粒子在一维平面上运动——即直线——并且它在任何特定时间的距离由函数
定义
质点在任一特定时间的速度,将由函数对时间 t 的导数给出。
函数的导数定义为
,通常根据方便的情况用以下公式表示:

或者

当我们处理一个依赖于多个变量的函数时,函数对保持其他变量不变的每个变量的导数称为偏导数,偏导数的向量称为函数的梯度。
假设房子的价格 z 取决于两个变量:房子的平方英尺面积 x 和卧室数量 y。

z 相对于 x 的偏导数用

表示
同样,z 关于 y 的偏导数是

请记住,在偏导数中,除了导数涉及的变量外,其他变量都保持不变。
函数的梯度
对于一个有两个变量
的函数,偏导数的向量
称为函数的梯度,用
表示。这同样可以推广到具有 n 个变量的函数。一个多元函数 f(x 1 ,x 2 ,..,x n 也可以表示为 f(x),其中
。多元函数 f(x)相对于 x 的梯度向量可以表示为
。
比如一个三变量
的函数的梯度由
![$$ \nabla f={\left[1\ 2y\ 3{z}²\right]}^T $$](A448418_1_En_1_Chapter_Equbf.gif)
给出
当我们试图最大化或最小化关于模型参数的成本函数时,梯度和偏导数在机器学习算法中是重要的,因为在最大值和最小值处,函数的梯度向量为零。在函数的最大值和最小值处,函数的梯度向量应该为零向量。
连续偏导数
我们可以得到一个函数对多个变量的连续偏导数。例如,对于一个函数

这是 z 首先对 x,然后对 y 的偏导数。
同样,

如果二阶导数是连续的,那么偏导数的顺序与

无关
函数的 Hessian 矩阵
多元函数的 Hessian 是二阶偏导数的矩阵。对于函数 f(x,y,z),海森定义如下:
![$$ Hf = \left[\begin{array}{ccc}\hfill \frac{\delta²f}{\delta {x}²}\hfill & \hfill \frac{\delta²f}{\delta x\delta y}\hfill & \hfill \frac{\delta²f}{\delta x\delta z}\hfill \ {}\hfill \frac{\delta²f}{\delta y\delta x}\hfill & \hfill \frac{\delta²f}{\delta {y}²}\hfill & \hfill \frac{\delta²f}{\delta y\delta z}\hfill \ {}\hfill \frac{\delta²f}{\delta z\delta x}\hfill & \hfill \frac{\delta²f}{\delta z\delta y}\hfill & \hfill \frac{\delta²f}{\delta {z}²}\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_Equbj.gif)
Hessian 在我们在机器学习领域经常遇到的优化问题中非常有用。例如,在最小化成本函数以得到一组模型参数的过程中,Hessian 被用于获得下一组参数值的更好的估计,特别是如果成本函数本质上是非线性的。非线性优化技术,如牛顿法、Broyden-Fletcher-gold farb-Shanno(BFGS)及其变体,使用 Hessian 来最小化成本函数。
函数的最大值和最小值
评估函数的最大值和最小值在机器学习中有巨大的应用。在监督和非监督学习中,建立机器学习模型依赖于最小化成本函数或最大化似然函数、熵等。
一元函数的最大值和最小值规则
- f(x)对 tox 的导数在最大值和最小值处为零。
- f(x)的二阶导数,无非是用
表示的一阶导数的导数,需要在一阶导数为零的点上考察。如果二阶导数小于零,那么它是一个最大值点,而如果它大于零,它是一个最小值点。如果二阶导数也是零,那么这个点就叫做拐点。
就拿一个很简单的函数来说,
。如果我们对函数 w . r . t xan 求导,并将其设为零,我们得到
,这就给出了
。还有,二阶导数
。因此,对于 x 的所有值,包括
,二阶导数大于零,因此
是函数 f(x)的最小值点。
让我们对
做同样的练习。
给了我们
。二阶导数
,如果我们在
求它,我们得到 0。所以,
既不是函数 g(x)的极小点,也不是最大点。二阶导数为零的点称为拐点。在拐点处,曲率的符号发生变化。
一元函数的导数为零或多元函数的梯度向量为零的点称为驻点。它们可能是也可能不是最大值点或最小值点。

图 1-12
Different types of stationary points—maxima, minima, point of inflection
图 1-12 所示为不同种类的驻点;即最大值、最小值和拐点。
多元函数的最大值和最小值有点复杂。让我们从一个例子开始,然后我们将定义规则。我们看一个有两个变量的多元函数:

为了确定驻点,梯度向量需要为零。
![$$ {\left[\begin{array}{cc}\hfill \frac{\partial f}{\partial x}\hfill & \hfill \frac{\partial f}{\partial y}\hfill \end{array}\right]}^T = \left[\begin{array}{c}\hfill 0\hfill \ {}\hfill 0\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_Equbl.gif)
将
和
设置为零,我们得到:

我们还需要计算黑森:




对于连续二阶导数的函数,
。
假设在
处梯度为零:
- 如果
那么
就是鞍点。 - 如果
那么
是极值点;即存在最大值或最小值。
- 如果
,那么 f(x,y)在
具有最大值。 - 如果
和
,那么 f(x,y)在
处最小。
- 如果
- 如果
那么需要更先进的方法来正确地分类静止点。
对于具有 n 个变量的函数,以下是检查函数的最大值、最小值和鞍点的准则:
- 计算梯度,并将其设置为零向量,这将给出驻点列表。
- 对于一个驻点
,如果函数在 x 0 处的海森矩阵既有正负特征值,那么 x 0 就是一个鞍点。如果 Hessian 矩阵的特征值都是正的,则平稳点是局部最小值,如果特征值都是负的,则平稳点是局部最大值。
局部最小值和全局最小值
函数可以有多个梯度为零的极小值,每个极小值称为局部极小点。函数具有最小值的局部极小值称为全局极小值。这同样适用于 maxima。函数的最大值和最小值是通过最优化方法得到的。由于封闭形式的解决方案并不总是可用的或者在计算上是难以处理的,所以最小值和最大值通常是通过迭代方法得到的,例如梯度下降、梯度上升等等。在推导最小值和最大值的迭代方式中,优化方法可能陷入局部最小值或最大值,而不能达到全局最小值或最大值。在迭代方法中,该算法利用函数在某一点的梯度来达到更优的点。当以这种方式遍历一系列点时,一旦遇到具有零梯度的点,算法就停止假设达到了期望的最小值或最大值。当函数存在全局最小值或最大值时,这种方法很有效。此外,优化也可能陷入鞍点。在所有这些情况下,我们都会有一个次优的模型。

图 1-13
Local and global minima/maxima
图 1-13 显示了函数的全局和局部最小值以及全局和局部最大值。
半正定和正定
方阵
是正半定的,如果对于任何非零向量
表达式
矩阵 A 是正定的,如果表达式
。半正定矩阵的所有特征值应该是非负的,而正定矩阵的特征值应该是正的。例如,如果我们将 A 视为
单位矩阵,即
,那么它就是正定的,因为它的两个特征值,即 1,1 都是正的。还有,如果我们计算 x T Ax,其中
得到
,对于非零向量 x 总是大于零,这就证实了 A 是正定矩阵。
凸集
一个点的集合称为凸的,如果给定任何两点 x 和 y 属于该集合,则连接从 x 到 y 的直线的所有点也属于该集合。在图 1-14 中,说明了一个凸集和一个非凸集。

图 1-14
Convex and non-convex set
凸函数
定义在凸集 D 上的函数 f(x ),其中
和 D 是定义域,如果连接函数中任意两点的直线位于该函数的图形之上,则称该函数是凸的。数学上,这可以表示为:
![$$ f\left(tx + \left(1\ \hbox{--}\ t\right)y\right)\ \le\ tf(x) + \left(1 - t\right)f(y)\kern0.75em \forall\ x,y\kern0.5em \in\ D,\ \forall\ t\ \in\ \left[0,1\right] $$](A448418_1_En_1_Chapter_Equbr.gif)
对于两次连续可微的凸函数,在函数的定义域 D 中每一点上取值的函数的 Hessian 矩阵应该是半正定的;即对于任意矢量
、
、
、
凸函数的全局极小值是局部极小值。请记住,可以有一个以上的全局最小值,但在凸函数的每个全局最小值处,函数值是相同的。
在图 1-15 中,说明了凸函数 f(x)。正如我们所看到的,f(x)显然服从前面所述的凸函数的性质。

图 1-15
Convex function illustration
非凸函数
一个非凸函数可以有很多局部极小值,这些极小值并不都是全局极小值。
在我们试图通过最小化成本函数来学习模型参数的任何机器学习模型构建过程中,我们更喜欢成本函数是凸的,因为通过适当的优化技术,我们肯定会达到全局最小值。对于非凸成本函数,优化技术很有可能会停留在局部最小值或鞍点,因此它可能不会达到其全局最小值。
多元凸函数和非凸函数示例
由于我们将在深度学习中处理高维函数,因此查看具有两个变量的凸函数和非凸函数是有意义的。
是在
处有最小值的凸函数,f(x,y)在
处的最小值为
。该功能绘制在图 1-16 中以供参考。

图 1-16
Plot of the convex function 
现在,让我们考虑函数
。
前面是一个非凸函数,验证这一点最简单的方法就是看函数的海森矩阵:
![$$ \mathrm{Hessian}\kern0.5em H = \left[\begin{array}{cc}\hfill -\frac{1}{x²}\hfill & \hfill 0\hfill \ {}\hfill 0\hfill & \hfill \frac{1}{y²}\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_Equbt.gif)
黑森的特征值是
和
。
对于实 x 总是负的。因此,Hessian 不是正半定的,使得函数非凸。

图 1-17
Plot of the Non-Convex function log(x/y)
我们可以在图 1-17 中看到,函数 log(x/y)的曲线看起来是非凸的。
通过最小二乘法的线性回归或通过对数损失成本函数(二元交叉熵)的逻辑回归都是凸优化问题,因此通过优化学习的模型参数是全局最小解。同样,在 SVM,我们优化的成本函数是凸的。
每当任何模型中包含隐藏层或潜在因素时,成本函数在本质上往往是非凸的。具有隐藏层的神经网络给出非凸的成本或误差表面,而不管我们是在解决回归还是分类问题。
类似地,在 K-均值聚类中,聚类的引入使得成本函数优化非凸成本函数。对于非凸成本函数,需要采用智能方法,以便在不可能达到全局最小值的情况下,我们可以达到一些足够好的局部最小值。
在处理非凸问题时,参数初始化变得非常重要。初始化参数越接近全局最小值或一些可接受的局部最小值越好。对于 k 均值,确保解不是次优的一种方法是用不同的随机初始化的模型参数运行 k 均值算法几次;即群集质心。然后,我们可以选择最大程度减少组内方差总和的一个。对于神经网络,需要使用涉及动量参数的高级梯度下降方法来摆脱局部最小值并向前移动。我们将在本书后面更详细地讨论基于梯度的神经网络优化方法。
泰勒级数
通过考虑函数及其导数在特定点的值,任何函数都可以表示为无穷和。函数的这种展开称为泰勒级数展开。一元函数围绕点 x 的泰勒级数展开可以表示为:

其中 f n (x)是函数 f(x)的 n 阶导数,n!表示数字 n 的阶乘。h 项与 x 项的维数相同,h 和 x 都是标量。
- 如果 f(x)是一个常数函数,那么所有的导数都是零并且
和 f(x)是相同的。 - 如果函数在 x 的邻域内是线性的,那么对于任何位于线性区域内的点(
,![$$ f\left(x+h\right)=f(x)+h{f}^{\prime }(x). $$]()
- 如果函数在 x 的邻域内是二次的,那么对于位于二次区域内的任意点
,
。 - 泰勒级数展开在迭代方法中变得非常重要,例如优化的梯度下降法和牛顿法,以及积分和微分的数值方法。
围绕点
的多元函数的泰勒级数展开可以表示为
高阶项
其中
是梯度向量,而
是函数 f(x)的 Hessian 矩阵。
通常,出于实际目的,我们不会在机器学习应用中超出二阶泰勒级数展开,因为在数值方法中它们很难计算。即使对于二阶展开计算,Hessian 也是成本密集型的,因此一些二阶优化方法依赖于从梯度计算近似 Hessian,而不是直接评估它们。请注意,三阶导数对象
将是一个三维张量。
可能性
在我们继续讨论概率之前,知道什么是随机实验和样本空间是很重要的。
在许多类型的工作中,无论是在研究实验室还是其他地方,在几乎相同的条件下重复实验是一种标准做法。例如,医学研究人员可能对即将上市的药物的效果感兴趣,或者农学家可能希望研究化学肥料对特定作物产量的影响。获得这些兴趣信息的唯一方法是进行实验。有时,我们可能不需要进行实验,因为实验是由自然进行的,我们只需要收集数据。
每个实验都会产生一个结果。假设实验的结果不能绝对肯定地预测。然而,在我们进行实验之前,假设我们知道所有可能的结果。如果这样的实验可以在几乎相同的条件下重复进行,那么这个实验就叫做随机实验,所有可能结果的集合就叫做样本空间。
请注意,样本空间只是我们感兴趣的一组结果。掷骰子可以有几种结果。一个是处理骰子落地面的一组结果。另一组可能的结果是骰子落地的速度。如果我们只对骰子落地的面感兴趣,那么我们的样本空间就是
;即骰子的面号。
让我们继续投掷骰子的实验,并记下结果落在哪个面上。假设我们进行了 n 次实验,第一面出现了 m 次。然后,从实验中,我们可以说骰子面为 1 的事件的概率等于骰子面为 1 的实验次数除以进行的实验总数;即
,其中 x 表示骰子正面的数字。
假设我们被告知骰子是公平的。这个数字变成 1 的概率是多少?
好吧,假设骰子是公平的,我们没有其他信息,我们大多数人会相信骰子是近似对称的,如果我们掷骰子 600 次,就会产生 100 个数字为 1 的面。这将给出概率为
。
现在,假设我们在最近的掷骰子过程中已经收集了大约 1000 个数据点。下面是统计数据:






在这种情况下,您会得出骰子面为 1 的概率为
。骰子要么不对称,要么有偏差。
并集、交集和条件概率
P(A U B) =事件 A 或事件 B 或两者的概率
=事件 A 和事件 B 的概率
=事件 A 发生的概率,假设 B 已经发生。

从现在开始,我们将把交点 B 的符号省略为
,并且为了便于表示,将把它表示为 AB。


图 1-18
Venn diagram of two events A and B showing the union and intersection of the two events
当我们看如图 1-18 所示的两个事件 A 和 B 的文氏图时,前面所有的证明都变得容易了。
假设一个实验出现了 n 次,其中 A 出现了 n 次 1 ,B 出现了 n 次 2 ,A 和 B 一起出现了 m 次。
让我们用文氏图来表示。
P(A U B)可以用三个不相关事件的概率之和来表示:(A–B),(B–A)和 AB。

P(A/B)是给定 B 已经发生的概率。鉴于 B 已经以 n 种 2 方式发生,事件 A 被限制为可以以 m 种不同方式发生的事件 AB。所以,给定 B 的概率可以表示为:

现在,
可以写成
=
。
于是,
。
类似地,如果我们考虑 P(B/A),那么关系
也成立。
事件交集的概率链规则
刚刚针对两个事件讨论的交集的乘积法则可以扩展到 n 个事件。
如果 A 1 ,A 2 ,A 3 ,....A n 是 n 个事件的集合,那么这些事件的联合概率可以表示为:

互斥事件
两个事件 A 和 B 如果不同时发生,就说它们是互斥的。换句话说,A 和 B 互斥,如果
。对于互斥事件,
。
一般来说,n 个互斥事件并集的概率可以写成它们的概率之和:

事件的独立性
如果两个事件 A 和 B 相交的概率等于它们各自概率的乘积,则称它们是独立的;即

这是可能的,因为给定 B 的条件概率与 A 的概率相同;即

这意味着 A 在所有事件的集合中发生的可能性与它在 b 的域中发生的可能性一样大。
同样,
为了让事件 A 和 B 独立。
当两个事件相互独立时,任何一个事件都不会受到另一个事件已经发生的影响。
事件的条件独立性
给定第三个事件 C 两个事件 A 和 B 条件独立如果给定 C A 和 B 共现的概率可以写成:

由因式分解性质,
。
通过结合前面的等式,我们也看到了
。
请注意,事件 A 和 B 的条件独立性并不保证 A 和 B 也是独立的。事件的条件独立性在机器学习领域有很多应用,通过条件独立性假设将似然函数分解成更简单的形式。此外,一类称为贝叶斯网络的网络模型使用条件独立性作为简化网络的几个因素之一。
贝叶斯规则
现在我们对初等概率有了基本的了解,让我们来讨论一个非常重要的定理,叫做贝叶斯法则。我们用两个事件 A 和 B 来说明这个定理,但是它可以推广到任何数量的事件。
我们从概率的乘积法则中取
。(1)
同理,
。(2)
结合(1)和(2),我们得到


前面推导出的规则被称为贝叶斯规则,它将在机器学习的许多领域中派上用场,例如从可能性计算后验分布、使用马尔可夫链模型、最大化后验算法等等。
概率质量函数
随机变量的概率质量函数(pmf)是一个函数,它给出了随机变量可以取的每个离散值的概率。概率之和必须等于 1。
例如,在一次掷骰子中,让骰子正面的数字是随机变量 x。
那么,pmf 可以定义如下:

概率密度函数
概率密度函数(pdf)给出了连续随机变量在其定义域中每个值的概率密度。因为它是一个连续变量,概率密度函数在其定义域上的积分必须等于 1。
设 X 是一个定义域为 d 的随机变量,P(x)表示它是一个概率密度函数,所以

例如,连续随机变量的概率密度函数可以取 0-11 的值,由
给出。让我们验证它是否是一个概率密度函数。
为了使 P(x)成为概率密度函数,
应该是 1。
。因此,P(x)是一个概率密度函数。
需要注意的一点是,积分计算的是曲线下的面积,由于 P(x)是概率密度函数(pdf),所以概率曲线的曲线下面积应该等于 1。
随机变量的期望
随机变量的期望就是随机变量的均值。假设随机变量 X 取 n 个离散值,x 1 ,x 2 ,.....x n ,概率 p 1 ,p 2 ,… p n 。换句话说,X 是 pmf 为
的离散随机变量。然后,随机变量 X 的期望值由
![$$ E\left[X\right] = {x}_1{p}_1 + {x}_2{p}_2 + \dots + {x}_n{p}_n\kern0.5em = {\displaystyle \sum_{i=1}^n}{x}_i{p}_i $$](A448418_1_En_1_Chapter_Equco.gif)
给出
如果 X 是一个概率密度函数为 P(x)的连续随机变量,X 的期望由
![$$ E\left[X\right]={\displaystyle {\int}_DxP(x);dx} $$](A448418_1_En_1_Chapter_Equcp.gif)
给出
其中 D 是 P(x)的定义域。
随机变量的方差
随机变量的方差衡量随机变量的可变性。它是随机变量与其均值(或期望值)的方差的均值(期望值)。
设 X 是一个均值为
的随机变量
其中![$$ \mu = E\left[X\right] $$](A448418_1_En_1_Chapter_IEq226.gif)
如果 X 是取 n 个离散值的离散随机变量,pmf 由
给定,那么 X 的方差可以表示为
![$$ Var\left[X\right] = E\left[{\left(X-\mu \right)}²\right] $$](A448418_1_En_1_Chapter_Equcq.gif)

如果 X 是具有概率密度函数 P(x)的连续随机变量,那么 Var[X]可以表示为
![$$ Var\left[X\right] = {\displaystyle \underset{D}{\int }}{\left(x - \mu \right)}²\ P(x)dx $$](A448418_1_En_1_Chapter_Equcs.gif)
其中 D 是 P(x)的定义域。
偏斜度和峰度
偏度和峰度是随机变量的高阶矩统计量。偏斜度衡量概率分布的对称性,而峰度衡量概率分布的尾部是否重。偏斜度是三阶矩,表示为
![$$ Skew(X) = \kern0.75em \frac{E\Big[\left(X\ \hbox{--}\ \mu \Big){}³\right]}{{\left(Var\left[X\right]\right)}^{3/2}} $$](A448418_1_En_1_Chapter_Equct.gif)

图 1-21
Probability distribution with negative skewness

图 1-20
Probability distribution with positive skewness

图 1-19
Symmetric probability distribution
一个完全对称的概率分布,偏度为 0,如图 1-19 。偏度的正值表示大部分数据向左,如图 1-20 所示,而偏度的负值表示大部分数据向右,如图 1-21 所示。
峰度是一个四阶统计量,对于均值为μ的随机变量 X,可以表示为
![$$ Kurt(X) = E\left[{\left[X\ \hbox{--}\ \mu \right]}⁴\right]/{\left(Var\left[X\right]\right)}² $$](A448418_1_En_1_Chapter_Equcu.gif)

图 1-23
Student’s T distribution with Kurtosis = 

图 1-22
Standard normal distribution with Kurtosis = 3
峰度越高,概率分布的尾部越重,如图 1-23 所示。正态分布的峰度(见图 1-22 )为 3。然而,为了根据正态分布来测量其他分布的峰度,通常是指过度峰度,即实际峰度减去正态分布的峰度,即 3。
协方差
两个随机变量 X 和 Y 之间的协方差是它们联合可变性的度量。如果 X 的较高值对应于 Y 的较高值,X 的较低值对应于 Y 的较低值,则协方差为正。另一方面,如果 X 的较高值对应于 Y 的较低值,X 的较低值对应于 Y 的较高值,则协方差为负。
X 和 Y 的协方差公式如下:
其中![$$ {u}_x = E\left[X\right],\ {u}_y = E\left[Y\right] $$](A448418_1_En_1_Chapter_IEq230.gif)
关于前述公式的简化,另一种方案如下:
![$$ cov\left(X,Y\right)=E\left[XY\right] - {u}_x{u}_y $$](A448418_1_En_1_Chapter_Equcv.gif)
如果两个变量是独立的,它们的协方差是零,因为![$$ E\left[XY\right]=E\left[X\right]E\left[Y\right] = {u}_x{u}_y $$](A448418_1_En_1_Chapter_IEq231.gif)
相关系数
协方差通常不提供关于两个变量之间关联程度的太多信息,因为这两个变量可能在非常不同的尺度上。获得两个变量的相关系数之间的线性相关性的度量,这是协方差的归一化版本,会有用得多。
两个变量 X 安迪之间的相关系数表示为

其中σ x 和σ y 分别为 X 和 Y 的标准差。ρ的值介于
和
之间。

图 1-24
Plot of variables with correlation coeffecients of +1 and -1
图 1-24 显示了两个变量 X 和 y 之间的正相关和负相关。
一些常见的概率分布
在本节中,我们将介绍一些常用于机器学习和深度学习领域的常见概率分布。
均匀分布
均匀分布的概率密度函数是常数。对于取值在 a 和
之间的连续随机变量,概率密度函数表示为
![$$ P\left(X = x\right)=f(x)=\kern0.5em \left{\begin{array}{c}\hfill 1/\left(b\ \hbox{--}\ a\right)\kern1.5em for\kern0.5em x\ \in\ \left[a,b\right]\hfill \ {}\hfill \kern1.75em 0\kern0.75em elsewhere\kern5.5em \hfill \end{array}\right. $$](A448418_1_En_1_Chapter_Equcx.gif)

图 1-25
Uniform probability distribution
图 1-25 所示为均匀分布的概率密度曲线。这里概述了均匀分布的不同统计:
![$$ E\left[X\right]\kern2.75em = \frac{\left(b + a\right)}{2} $$](A448418_1_En_1_Chapter_Equcy.gif)
![$$ Median\left[X\right]\kern1.5em = \frac{\left(b + a\right)}{2} $$](A448418_1_En_1_Chapter_Equcz.gif)
![$$ Mode\left[X\right]\kern2em = All\ points\ in\ the\ in terval\ a\ to\ b $$](A448418_1_En_1_Chapter_Equda.gif)
![$$ Var\left[X\right]\kern2.25em = {\left(b\ \hbox{--}\ a\right)}²/12 $$](A448418_1_En_1_Chapter_Equdb.gif)
![$$ Skew\left[X\right]\kern2em = 0 $$](A448418_1_En_1_Chapter_Equdc.gif)
![$$ Excessive\ Kurt\left[X\right] = -6/5 $$](A448418_1_En_1_Chapter_Equdd.gif)
请注意,超额峰度是实际峰度减去 3,3 是正态分布的实际峰度。因此,过度峰度是相对于正态分布的相对峰度。
正态分布
这可能是现实世界中概率分布最重要的场景。在正态分布中,最大概率密度位于分布的均值,密度对称地以指数形式下降到距均值距离的平方。正态分布的概率密度函数可以表示为

其中μ为均值,σ 2 为随机变量 x 的方差,图 1-26 所示为一元正态分布的概率密度函数。

图 1-26
Normal probability distribution
如图 1-26 所示,正态分布中 68.2%的数据落在平均值的一个标准差(+1/-1σ)内,大约 95.4%的数据预计落在平均值的+2/-2σ内。正态分布的重要统计数据如下:
![$$ E\left[X\right]\kern2.75em =\kern0.5em \mu $$](A448418_1_En_1_Chapter_Equdf.gif)
![$$ Median\left[X\right]\kern1.5em =\kern0.5em \upmu $$](A448418_1_En_1_Chapter_Equdg.gif)
![$$ Mode\left[X\right]\kern2em =\kern0.5em \upmu $$](A448418_1_En_1_Chapter_Equdh.gif)
![$$ Var\left[X\right]\kern2.75em =\kern0.5em {\sigma}² $$](A448418_1_En_1_Chapter_Equdi.gif)
偏斜[X] = 0
![$$ Excess\ Kurt\left[X\right] = 0 $$](A448418_1_En_1_Chapter_Equdj.gif)
任何正态分布都可以通过以下变换转换成标准正态分布:

标准正态随机变量 z 的均值和标准差分别为 0 和 1。标准正态分布在统计推断测试中被大量使用。类似地,在线性回归中,误差被假定为正态分布。
多元正态分布
多元正态分布,或由向量
表示的 n 个变量的高斯分布,是由平均向量
和协方差矩阵
参数化的相关变量的联合概率分布。
多元正态分布的概率密度函数(pdf)如下:

其中![$$ x={\left[{x}_1{x}_2\dots {x}_n\right]}^T $$](A448418_1_En_1_Chapter_IEq238.gif)


图 1-27
Multivariate normal distribution in two variables
图 1-27 所示为多元正态分布的概率密度函数。多元正态分布或高斯分布在机器学习中有多种应用。例如,对于具有相关性的多元输入数据,通常假设输入要素遵循多元正态分布,并且基于概率密度函数,具有低概率密度的点被标记为异常。此外,多元正态分布广泛用于混合高斯模型中,其中具有多个特征的数据点被假设属于具有不同概率的多个多元正态分布。混合高斯分布被应用于多个领域,如聚类、异常检测、隐马尔可夫模型等。
二项分布
两个结果互斥且穷尽(两个结果的概率之和为 1)的实验称为伯努利试验。
伯努利轨迹遵循伯努利分布。假设在伯努利轨迹中,有两种结果,成功和失败。如果成功的概率是 p,那么由于这两个事件耗尽了样本空间,失败的概率是 1–p。让
表示成功。因此,成功或失败的概率可以表示为:

的前述表达式表示伯努利分布的概率质量函数。概率质量函数的期望和方差如下:
![$$ E\left[X\right] = \kern0.5em p $$](A448418_1_En_1_Chapter_Equdo.gif)
![$$ Var\left[X\right] = p\left(1 - p\right) $$](A448418_1_En_1_Chapter_Equdp.gif)
伯努利分布可以扩展到互斥和穷举的多类事件。任何两类分类问题都可以建模为伯努利轨迹。例如,逻辑回归似然函数基于每个训练数据点的伯努利分布,概率 p 由 sigmoid 函数给出。
二项分布
在一系列伯努利试验中,我们通常感兴趣的是成功和失败总数的概率,而不是它们发生的实际顺序。如果在 n 个连续伯努利试验的序列中,x 表示成功的次数,那么 n 个伯努利试验中 x 个成功的概率可以由表示为

的概率质量函数来表示
其中 p 是成功的概率。
分布的期望和方差如下:
![$$ E\left[X\right]\kern0.75em = np $$](A448418_1_En_1_Chapter_Equdr.gif)
![$$ Var\left[X\right] = np\left(1 - p\right) $$](A448418_1_En_1_Chapter_Equds.gif)

图 1-28
Probability Mass function of a Binomial Distribution with n=4 and p = 0.3
图 1-28 所示为具有
和
的二项分布的概率质量函数。
泊松分布
每当关注某个数量的比率时,例如 1000 个产品批次中的缺陷数量、放射性物质在之前的四个小时内发射的α粒子数量等,泊松分布通常是表示这种现象的最佳方式。泊松分布的概率质量函数如下:

![$$ E\left[X\right]\kern0.75em = \lambda $$](A448418_1_En_1_Chapter_Equdu.gif)
![$$ Var\left[X\right] = \lambda $$](A448418_1_En_1_Chapter_Equdv.gif)

图 1-29
Probability mass function of a Poisson distribution with mean = 15
图 1-29 所示为具有
均值的泊松分布的概率质量函数。
似然函数
可能性是在给定生成基础数据的参数的情况下观察到的数据的概率。假设我们观察 n 个观察值 x 1 ,x 2 ,.....x n 并且假设观察值是独立的,并且均值和方差同正态分布σ 2 。
这种情况下的似然函数如下:

由于观察值是独立的,我们可以如下分解可能性:

中的每一个,因此可能性可以进一步展开为:

最大似然估计
最大似然估计(MLE)是一种估计分布或模型参数的技术。这是通过导出最大化似然函数的参数来实现的,即,在给定模型参数的情况下,最大化观察到数据的概率。让我们通过一个例子来理解最大似然估计。
假设亚当投掷硬币 10 次,观察到 7 个头和 33 条尾巴。此外,假设投掷是独立的和相同的。给定硬币正面概率的最大似然估计是多少?
每一次抛硬币都是伯努利试验,正面的概率是,比如说,p,这是一个我们想要估计的未知参数。同样,让掷硬币产生正面的事件用 11 表示,反面用 0 表示。
似然函数可以表示如下:



为了澄清,让我们看看可能性 L 是如何变成 p7(1–p)3的。
对于每个人头,来自伯努利分布的概率是
。类似地,对于每条尾巴,概率是
。因为我们有 77 个头和 33 条尾巴,我们得到可能性 L(p)是 p7(1–p)3。
为了最大化似然 L,我们需要取 L 对 p 的导数,并将其设为 0。
现在,我们可以不用最大化似然性 L(p ),而是最大化似然性的对数,即 logL(p)。由于对数是单调递增的函数,使 L(p)最大化的参数值也会使 logL(p)最大化。在数学上,对可能性的对数求导比对原始可能性的乘积形式求导更方便。

对两边求导并设为零如下:


有兴趣的读者可以在
计算二阶导数
;你肯定会得到一个负值,确认
确实是最大值点。
你们中的一些人可能已经想到了
,甚至没有经过最大似然,仅仅是根据概率的基本定义。正如您稍后将看到的,通过这种简单的方法,在机器学习和深度学习世界中估计了许多复杂的模型参数。
让我们看看另一个在优化时可能会派上用场的小技巧。计算函数 f(x)的最大值与计算函数
的最小值是一样的。f(x)的最大值和
的最小值将出现在相同的 x 值处。类似地,f(x)的最大值和 1/f(x)的最小值将出现在相同的 x 值处。
通常在机器学习和深度学习应用中,我们使用高级优化包,这些包只知道最小化成本函数,以便计算模型参数。在这种情况下,我们通过改变符号或取函数的倒数,方便地将最大化问题转化为最小化问题,无论哪个更有意义。例如,在前面的问题中,我们可以取对数似然函数的负值,即 LogL(p),并将其最小化;我们会得到同样的概率估计值 0.7。
假设检验和 p 值
通常,我们需要根据从人群中收集的样本进行一些假设检验。我们从一个零假设开始,根据所做的统计测试,接受或拒绝零假设。
在开始假设检验之前,我们先来考虑统计学中的一个核心基本面,也就是中心极限定理。
设 x 1 ,x 2 ,x 3 ,。…..,x n 是来自具有均值μ和有限方差σ 2 的总体的样本的 n 个独立同分布观测值。
用
表示的样本均值服从正态分布,均值为μ,方差为
;即,
其中
这就是所谓的中心极限定理。随着样本量 n 的增加,
的方差减小,并趋向于零,如
图 1-30 说明了总体分布和从总体中抽取的固定大小 n 的样本的平均值分布。

图 1-30
Distribution for population and distribution for sample mean
请注意,无论总体变量是否正态分布,样本均值都遵循正态分布。现在,让我们考虑一个简单的假设检验问题。
已知 10 岁的男孩平均体重为 85 磅,标准差为 11.6。一个县的男孩被检查是否肥胖。为了验证这一点,从该县随机抽取了 25 名男孩,收集他们的平均体重。平均体重为 89.16 磅。
我们将不得不形成一个无效假设,如果反对无效假设的证据足够有力,我们将通过测试拒绝它。
让我们考虑零假设:H 0 。县城的孩子并不肥胖;也就是说,他们来自同一个群体,其平均值为
。
在零假设 0 下,样本均值如下:

观察到的样本均值越接近总体均值,零假设越有可能成立。另一方面,观察到的样本均值离总体均值越远,反对零假设的证据就越强。
标准正态变量
) =
= 
对于每个假设检验,我们确定一个 p 值。这种假设检验的 p 值是观察到的样本均值与被观察值相差较远的概率;即
。所以,p 值越小,反对零假设的证据就越强。
当 p 值小于指定的阈值百分比α(称为类型 1 错误)时,零假设被拒绝。
请注意,样本均值与总体的偏差可能纯粹是随机的结果,因为样本均值具有有限的方差σ2/n。α为我们提供了一个阈值,超过该阈值,即使零假设为真,我们也应该拒绝零假设。我们可能错了,巨大的偏差可能只是因为随机性。但是发生这种情况的概率非常小,特别是当我们有一个大样本的时候,因为样本的平均标准差会显著降低。当我们拒绝零假设时,即使零假设是真的,我们犯了第一类错误,因此α给出了第一类错误的概率。
本次测试的 p 值为
人们应该选择的 I 型误差α取决于人们对进行测试的具体领域的了解。一般来说,
是一个足够好的类型 1 错误设置。由于计算出的 p 值小于测试中规定的 I 型误差,我们不能接受零假设。我们说这个测试有统计学意义。p 值如图 1-31 所示。

图 1-31
Z test showing p value
深色区域对应 p 值;即
。
对应于 z 值,假设零假设为真,超过该值我们可能会犯类型 1 错误。超过
的区域,即
,代表第一类错误概率。因为 p 值小于测试的类型 1 错误概率,所以不能将零假设视为真。像这样的 Z 测试之后通常是另一个好的实践——置信区间测试。
此外,前面的测试,通常被称为 Z 测试。并不总是可能的,除非我们有提供给我们的人口方差。对于某些问题,我们可能没有总体方差。在这种情况下,Student-T 检验更方便,因为它使用样本方差而不是总体方差。
鼓励读者探索更多关于这些统计测试的内容。
机器学习算法和优化技术的公式化
建模的目的是在给定数据的情况下,通过使用不同的优化技术来最小化模型参数的成本函数。有人可能会问,如果我们将成本函数的导数或梯度设置为零,我们会有模型参数吗?这并不总是可能的,因为并非所有的解都有封闭形式的解,或者封闭形式的解在计算上可能是昂贵的或难以处理的。此外,当数据量很大时,采用封闭形式的解决方案会有内存限制。因此,迭代方法通常用于复杂的优化问题。
机器学习可以大致分为两种类型:
- 监督机器学习
- 无监督机器学习
监督学习
在监督学习中,每个训练数据点都与几个输入特征相关联,通常是一个输入特征向量及其对应的标注。用几个参数构建模型,这些参数试图在给定输入特征向量的情况下预测输出标签。通过优化基于预测误差的某种形式的成本函数来导出模型参数;即训练数据点的实际标签和预测标签之间的差异。或者,最大化训练数据的可能性也将为我们提供模型参数。
线性回归作为一种监督学习方法
我们可能有一个以房屋价格作为目标变量或输出标签的数据集,而房屋面积、卧室数量、浴室数量等特征是其输入特征向量。我们可以定义一个函数,根据输入的特征向量来预测房子的价格。
设输入特征向量用 x’表示,预测值为 y p 。假设房价的实际值(即输出标注)由 y 表示。我们可以定义一个模型,其中输出标注表示为输入要素向量的函数,如下式所示。该模型由我们希望通过训练过程学习的几个常数来参数化。
其中ϵ是预测和
中的随机变量。
因此,给定输入(
的住房价格是输入向量 x’加上偏差项 b 和随机分量ϵ的线性组合,其遵循均值为 0 且有限方差为σ 2 的正态分布。
由于ϵ是一个随机的组成部分,它是无法预测的,我们能预测的最好的是给定一个特征值的平均房价,即
预测值![$$ {y}_p/{x}^{\prime }=E\left[y/{x}^{\prime}\right] = {\theta{\prime}}T{x}^{\prime } + b $$](A448418_1_En_1_Chapter_IEq272.gif)
这里,θ’是线性组合器,b 是偏差或截距。θ′和 b 都是我们希望学习的模型参数。我们可以表示
,其中偏差已经被添加到对应于常数特征 1 的模型参数中。这个小技巧使表示更简单。
假设我们有 m 个样本(x (1) ,y (1) ),(x (2) ,y (2) ....(x (m) ,y (m) )。我们可以计算一个成本函数,该函数取住房价格的预测值和实际值之差的平方和,并试图将其最小化,以便导出模型参数。
成本函数可以定义为

我们可以最小化关于θ的成本函数来确定模型参数。这是一个线性回归问题,其中输出标签或目标是连续的。回归属于学习的监督类。图 1-32 说明了房价与卧室数量的关系。

图 1-32
Regression fit to the Housing Prices versus Number of Bedrooms data. The red points denote the data points, and the blue line indicates the fitted regression line.
设输入向量为
,其中



设输入特征向量对应的参数向量为
,其中



在考虑了偏差项之后,输入特征向量变成
,其中




还有
,其中




现在,我们对如何构建回归问题及其相关成本函数有了一些了解,让我们简化问题并继续推导模型参数。
模型参数
所有样本的输入向量可以组合成矩阵 X,相应的目标输出可以表示为向量 y。
![$$ X = \left[\begin{array}{c}\hfill \begin{array}{cc}\hfill {x}_0^{(1)}\hfill & \hfill {x}_1{(1)} {x}_2{(1)}{x}_3^{(1)}\hfill \end{array}\hfill \ {}\hfill \begin{array}{cc}\hfill {x}_0^{(2)}\hfill & \hfill {x}_1{(2)} {x}_2{(2)}{x}_3^{(2)}\hfill \end{array}\hfill \ {}\hfill \begin{array}{cc}\hfill {x}_0^{(3)}\hfill & \hfill {x}_1{(3)} {x}_2{(3)}{x}_3^{(3)}\hfill \end{array}\hfill \ {}\hfill .\hfill \ {}\hfill .\hfill \ {}\hfill \begin{array}{cc}\hfill {x}_0^{(m)}\hfill & \hfill {x}_1{(m)} {x}_2{(m)}{x}_3^{(m)}\hfill \end{array}\hfill \end{array}\right]Y = \left[\begin{array}{c}\hfill {y}^{(1)}\hfill \ {}\hfill {y}^{(2)}\hfill \ {}\hfill {y}^{(3)}\hfill \ {}\hfill .\hfill \ {}\hfill .\hfill \ {}\hfill {y}^{(m)}\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_Equew.gif)
如果我们把预测的向量表示为 Yp,那么
。因此,预测向量 e 中的误差可以表示为:

因此,C(θ)可以表示为误差向量 e 的 l 2 范数的平方,即

既然我们已经有了矩阵形式的简化成本函数,应用不同的优化技术将会很容易。这些技术适用于大多数成本函数,无论是凸成本函数还是非凸成本函数。对于非凸的,还有一些额外的东西需要考虑,我们会在考虑神经网络的时候详细讨论。
我们可以通过计算梯度并将其设置为零向量来直接导出模型参数。你可以应用我们之前学过的规则来检查是否满足最小值的条件。
成本函数相对于参数向量θ的梯度如下所示:

设置
,我们得到
。
如果仔细观察这个解,可以观察到 X 的伪逆(即
)出现在线性回归问题的解中。这是因为线性回归参数向量可以被视为方程
的解,其中 X 是具有
的
矩形矩阵
前面的
表达式是模型参数的闭合解。用这个导出的
为新的数据点 x new ,我们可以预测房价为
。
对于大型数据集来说,( X T X)的逆的计算是成本和存储器密集型的。还有,当矩阵 X T X 是奇异的并且因此它的逆没有被定义的情况。因此,我们需要寻找替代方法来达到最小值点。
建立线性回归模型后,需要验证的一件事是训练数据点的残差分布。误差应该近似正态分布,平均值为 0,方差有限。绘制误差分布的实际分位数值与误差分布的理论分位数值的 QQ 图可用于检查残差的高斯性假设是否得到满足。
通过向量空间方法的线性回归

图 1-33
Linear regression as a vector space problem
线性回归问题是确定参数向量θ,使得 Xθ尽可能接近输出向量 Y。
是数据矩阵,可以认为是 n 列向量
一个接一个的堆叠,如下图:
![$$ X = \left[{c}_{1\ }{c}_2 \dots ..\ {c}_n\right] $$](A448418_1_En_1_Chapter_Equfa.gif)
列空间的维数是 m,而列向量的数目是 n。因此,列向量最多可以跨越 m 维向量空间内的 n 维子空间。子空间如图 1-33 所示。虽然它看起来像一个二维表面,但我们需要将其想象为一个 n 维空间。X 列与参数的线性组合给出了预测向量,如下所示:
Xθ = 
因为 Xθ只不过是 X 的列向量的线性组合,所以 Xθ与列向量
所跨越的子空间位于相同的子空间中。
现在,实际的目标值向量 Y 位于 X 的列向量所跨越的子空间之外,因此,无论我们将 X 与什么θ组合,Xθ都不会等于 Y 方向或在 Y 方向上对齐。将会有一个由
给出的非零误差向量
现在我们知道我们有一个误差,我们需要研究如何降低误差的 l 2 范数。为了使误差向量的 l 2 范数最小,它应该垂直于预测向量 Xθ。因为
垂直于 Xθ,所以它应该垂直于那个子空间中的所有向量。
所以,X 的所有列向量与误差向量
的点积应该为零,这给了我们以下:
![$$ {c_1}^T\left[Y-X\theta \right]=0,\ {c_2}^T\left[Y-X\theta \right]=0,\dots ..{c_n}^T\left[Y-X\theta \right]=0 $$](A448418_1_En_1_Chapter_Equfb.gif)
这可以按如下矩阵形式重新排列:
![$$ {\left[{c}_{1\ }{c}_2 \dots ..\ {c}_n\right]}^T\left[Y-X\theta \right]=0 $$](A448418_1_En_1_Chapter_Equfc.gif)
![$$ = > {X}^T\left[Y-X\theta \right]=0= > \hat{\theta}\kern0.5em = {\left({X}TX\right)}{-1}{X}^T\ Y $$](A448418_1_En_1_Chapter_Equfd.gif)
此外,请注意,只有当 Xθ是 Y 在 x 的列向量所跨越的子空间中的投影时,误差向量才会垂直于预测向量。此说明的唯一目的是强调向量空间在解决机器学习问题中的重要性。
分类
类似地,我们可以考虑分类问题,其中我们预测与输入特征向量相关联的类别标签,而不是预测连续变量的值。例如,我们可以根据客户最近的付款历史和交易细节以及他的人口统计和就业信息来预测客户是否可能违约。在这样的问题中,我们将具有刚才提到的特征的数据作为输入,并且将指示客户是否已经默认的目标作为分类标签。基于这些标记数据,我们可以构建一个分类器,该分类器可以预测表明客户是否会违约的类别标签,或者我们可以提供客户违约的概率分数。在这个场景中,问题是一个包含两个类的二元分类问题——默认类和非默认类。当构建这样的分类器时,最小二乘法可能不会给出好的成本函数,因为我们试图猜测标签,而不是预测连续变量。用于分类问题的流行成本函数通常是基于最大似然的对数损失成本函数和基于熵的成本函数,例如基尼熵和香农熵。
具有线性决策边界的分类器称为线性分类器。决策边界是分隔不同类别的超平面或曲面。在线性决策边界的情况下,分离平面是超平面。

图 1-35
Classification by a non-linear decision boundary

图 1-34
Classification by a linear decision boundary
图 1-34 和 1-35 分别示出了两类分离的线性和非线性决策边界。
我想简单地讨论一下最流行和最简单的分类器之一,逻辑回归,以便弄清楚在分类问题的情况下,对数损失成本函数是如何通过最大似然法进入画面的。
假设(x (i) ,y (i) )为标注后的数据点,其中
为输入向量,包含常量特征值 1 作为分量,y (i) 确定类别。当数据点对应于已经拖欠贷款的客户时,y (i) 的值被设置为 1,如果客户没有拖欠,则设置为 0。输入向量 x (i) 可以按照其分量表示如下:
![$$ {x}^{(i)}=\left[1\begin{array}{cc}\hfill {x}_0^{(1)}\hfill & \hfill {x}_1{(1)} {x}_2{(1)}{x}_3^{(1)}\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_Equfe.gif)
一般来说,逻辑回归是一种线性分类器,使用挤压函数将线性得分转换为概率。设
为模型参数,每个
代表相对于输入向量 x 的第 j 个特征 x j 的模型参数分量
θ 0 项是偏置项。在线性回归中,偏差就是输出 y 轴上的截距。我们很快就会看到这个偏差项对逻辑回归(和其他线性分类器)意味着什么。
点积θ T x 决定了给定的数据点可能是正类还是负类。对于手头的问题,正类是客户拖欠还款的事件,负类是客户没有违约的事件。给定输入和模型,客户违约的概率由以下公式给出:


现在,让我们看看θ T x 的不同值的概率值:
- θ T x = 0 那么正类的概率是 1/2。
- 当θ T x > 0 时,正类的概率大于 1/2 且小于 1。
- 当θ T x < 0 时,正类的概率小于 1/2 且大于 0。
- 当θ T xis 足够大且为正数,即
时,概率
。 - 当θ T xis 足够大且为负,即
时,概率
。
这个概率公式的好处是它将值保持在 0 和 1 之间,这对于线性回归是不可能的。此外,它给出了连续的概率,而不是实际的类。因此,根据手头的问题,可以定义截止概率阈值来确定类别。
这种概率模型函数称为逻辑函数或 sigmoid 函数。它具有平滑、连续的梯度,这使得模型训练在数学上很方便。
如果我们仔细观察,就会发现每个训练样本的客户类 y 都遵循伯努利分布,这是我们前面讨论过的。对于每个数据点,类
)。基于伯努利分布的概率质量函数,我们可以说如下:
其中
现在,我们如何定义成本函数?给定模型参数,我们计算数据的似然性,然后确定使计算的似然性最大化的模型参数。我们用 L 来定义似然,可以表示为

其中 D (i) 代表第 I 个训练样本(x (i) ,y (i) )。
假设训练样本是独立的,给定模型,L 可以分解为:



我们可以取两边的对数,把概率的乘积转换成概率对数的和。此外,优化保持不变,因为 L 和 logL 的最大值点是相同的,因为 log 是单调递增的函数。
取两边的日志,我们得到如下:

现在,
。
我们不关心数据点的概率-即 P(x(I)/θ)-并假设训练中的所有数据点对于给定的模型都是同样可能的。所以,
,其中 k 是常数。
取两边的日志,我们得到如下:

对所有数据点求和,我们得到如下:

我们需要最大化 logL 来获得模型参数θ。最大化 logL 等同于最小化
,因此我们可以将
作为逻辑回归的成本函数,并将其最小化。此外,我们可以去掉 logk 和,因为它是一个常数,无论我们是否有 logk 和,最小值处的模型参数都是相同的。如果我们把代价函数表示为 C(θ),可以表示为这里看到的:

其中
C(θ)是θ中的凸函数,鼓励读者用之前在“微积分”一节中学到的规则来验证它。C(θ)可以通过常见的优化技术最小化。
超平面和线性分类器
线性分类器以这样或那样的方式与超平面相关,因此查看这种关系是有意义的。在某种程度上,学习线性分类器就是学习区分正类和负类的超平面。
n 维向量空间中的超平面是将 n 维向量空间分成两个区域的维度(
)的平面。一个区域由位于超平面上方的向量组成,另一个区域由位于超平面下方的向量组成。对于二维向量空间,直线充当超平面。同样,对于三维向量空间,二维平面充当超平面。
超平面由两个主要参数定义:它离原点的垂直距离由偏置项 b '表示,超平面的方向由垂直于超平面表面的单位向量 w 确定,如图 1-36 所示。
对于位于超平面上的向量
,向量在 w 方向上的投影应该等于超平面到原点的距离,即
。因此,位于超平面上的任何一点都必须满足
。类似地,位于超平面上方的点必须满足
,位于超平面下方的点必须满足
。

图 1-36
Hyperplanes separating the two classes
在线性分类器中,我们学习对超平面建模,或者学习模型参数 w 和 b。向量 w 通常对准正类。感知器和线性 SVM 是线性分类器。当然,SVM 和感知器学习超平面的方式是完全不同的,因此它们会得出不同的超平面,即使是对于相同的训练数据。
即使我们看逻辑回归,我们看到它是基于一个线性决策边界。线性决策边界只不过是一个超平面,并且位于该超平面上的点(即
)被分配给任一类 0.5 的概率。同样,逻辑回归学习决策边界的方式与 SVM 和感知器模型完全不同。
无监督学习
无监督的机器学习算法旨在发现数据集中的模式或内部结构,这些数据集中包含没有标签或目标的输入数据点。k-均值聚类、高斯混合等都是无监督学习的方法。甚至像主成分分析(PCA)、奇异值分解(SVD)、自编码器等数据简化技术都是无监督的学习方法。
机器学习的优化技术
梯度下降
梯度下降及其几种变体可能是机器学习和深度学习中使用最广泛的优化技术。这是一种迭代方法,从随机模型参数开始,使用成本函数相对于模型参数的梯度来确定模型参数的更新方向。
假设我们有一个成本函数 C(θ),其中θ代表模型参数。我们知道,成本函数相对于θ的梯度给出了 C(θ)在线性意义上的最大增加方向,在θ值处,梯度被评估。因此,为了在线性意义上获得 C(θ)的最大下降方向,应该使用梯度的负值。
模型参数θ在迭代
时的更新规则由

给出
其中η代表学习率,
和θ (t) 分别代表迭代
和 t 时的参数向量。
一旦通过该迭代过程达到最小值,成本函数在最小值处的梯度从技术上讲将为零,因此不会进一步更新θ。然而,由于舍入误差和计算机的其他限制,收敛到真正的最小值可能是不可实现的。因此,当我们相信我们已经达到最小值(或者至少足够接近最小值)时,我们必须想出一些其他的逻辑来停止迭代过程,以停止模型训练的迭代过程。通常使用的方法之一是检查梯度向量的大小,如果它小于某个预定义的微小量,比如ε,就停止迭代过程。另一种简单的方法是在固定的迭代次数(如 1000 次)后停止参数更新的迭代过程。
学习率在梯度下降算法收敛到极小点的过程中起着非常重要的作用。如果学习率很大,收敛可能会更快,但可能会导致在最小值点附近的剧烈振荡。小的学习率可能需要更长的时间来达到最小值,但是收敛通常是无振荡的。
要了解梯度下降法的工作原理,让我们采用一个只有一个参数和一个成本函数
的模型,并了解该方法的工作原理。

图 1-37
Gradient descent intuition with simple cost function in one variable.
从图 1-37 中我们可以看到,在点θ 1 处的梯度(在这种情况下只是导数)是正的,因此如果我们沿着梯度方向移动,成本函数将会增加。相反,在θ 1 处,如果我们沿着梯度的负值移动,成本会降低。还是那句话,我们取梯度为负的点θ 2 。如果我们在这里采用梯度方向来更新θ,成本函数将会增加。取梯度的负方向将确保我们向最小值移动。一旦我们在
达到最小值,梯度为 0,因此不会进一步更新θ。
多元成本函数的梯度下降
现在,我们通过查看基于一个参数的成本函数对梯度下降有了一些直觉,让我们来看看成本函数基于多个参数时的梯度下降。
让我们来看一个多元函数的泰勒级数展开。多个变量可以用向量θ来表示。让我们考虑一个成本函数 C(θ),其中
。
如前所述,围绕一点θ的泰勒级数展开可以用矩阵符号表示,如下所示:




假设在梯度下降的迭代 t 处,模型参数是θ,我们希望通过更新δθ将θ更新为
,使得
小于 C(θ)。
如果我们假设函数在θ的邻域内是线性的,那么从泰勒级数展开我们得到如下:

我们想这样选择δθ,使得
小于 C(θ)。
对于所有大小相同的∏θ,最大化点积
的方向应该与
的方向相同。但是那会给我们最大可能的
。因此,为了得到点积
的最小值,δθ的方向应该与
的方向完全相反,换句话说,δθ应该与梯度向量
:

的负值成比例
=>
,其中η是学习率
=> 
=> 
这就是著名的梯度下降方程。为了形象化梯度下降如何进行到最小值,我们需要对等高线图和等高线有一些基本的了解。
等高线图和等高线
让我们考虑一个函数 C(θ),其中
。等高线是θ的向量空间中连接具有相同函数值 C(θ)的点的线/曲线。对于 C(θ)的每个唯一值,我们将有单独的等高线。
让我们为函数
绘制等高线,其中
和
![$$ A = \left[\begin{array}{cc}\hfill 7\hfill & \hfill 2\hfill \ {}\hfill 2\hfill & \hfill 5\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_Equfw.gif)
扩展 C(θ)的表达式我们得到

成本函数的等高线图如图 1-38 所示。

图 1-38
Contour plots
每个椭圆都是特定于函数 C(θ 1 ,θ 2 )的固定值的轮廓线。如果
,其中 a 是常数,那么方程
代表椭圆。
对于常数 a 的不同值,我们得到不同的椭圆,如图 1-38 所示。特定等高线上的所有点具有相同的函数值。
现在我们知道了什么是等高线图,让我们看看成本函数 C(θ)的等高线图中的梯度下降级数,其中
。梯度下降步骤如图 1-39 所示。

图 1-39
Gradient descent for a two-variable cost function
让我们取对应于成本 C 1 的最大椭圆,并假设我们当前的θ在 C 1 中的点θ (1) 。
假设 C(θ)与θ成线性关系,成本函数 C(θ)的变化如下所示:
ΔC(θ) = 
如果我们取同一条等高线上彼此非常接近的两点之间的成本的微小变化,那么
,因为同一条等高线上的所有点都具有相同的固定值。此外,应该注意,当我们在同一轮廓线上取两个彼此非常接近的点时,δθ表示由轮廓线的切向箭头表示的轮廓线的切线。请不要将此δθ混淆为梯度下降中参数的δθ更新。
,基本上是指坡度垂直于等高线 C 1 中的点θ (1) 处的切线。梯度将指向外,而负梯度指向内,如垂直于切线的箭头所示。基于学习速率,它将到达由 C 2 表示的不同轮廓线中的点θ (2) ,其成本函数值将小于 C 1 的成本函数值。再次,梯度将在θ (2) 处被评估,并且相同的过程将被重复几次迭代,直到它到达最小值点,在该点处,梯度将在技术上下降到 0,在此之后,将不再有对θ的更新。
最陡下降
最速下降是梯度下降的一种形式,其中学习率不是常数,而是在每次迭代时计算,以确保通过梯度下降的参数更新使成本函数相对于学习率最小。换句话说,最陡下降中每次迭代的学习速率被优化,以确保梯度方向上的运动被最大程度地利用。
让我们以通常的成本函数 C(θ)为例,看看连续的迭代 t 和
。与梯度下降一样,我们的参数更新规则如下:

因此,迭代
时的成本函数可以表示为

为了最小化关于学习率的迭代
的成本函数,参见以下:

![$$ = > \nabla C\left({\theta}^{\left(t+1\right)}\right)\frac{\partial \left[C\Big({\theta}^{(t)}\ \hbox{--}\ \eta \nabla C\left({\theta}^{(t)}\right)\right]}{\partial \eta }=0 $$](A448418_1_En_1_Chapter_Equgb.gif)


因此,对于最陡下降,在
和 t 的梯度的点积是 0,这意味着每次迭代的梯度向量应该垂直于其前一次迭代的梯度向量。
随机梯度下降
最速下降和梯度下降都是全批次模型;即,基于整个训练数据集计算梯度。因此,如果数据集很大,梯度计算会变得很昂贵,内存需求也会增加。此外,如果数据集具有巨大的冗余,那么计算整个数据集的梯度是没有用的,因为类似的梯度可以通过使用小得多的批次(称为迷你批次)来计算。克服上述问题的最流行的方法是使用一种称为随机梯度下降的优化技术。
随机梯度下降是一种基于梯度下降方法最小化成本函数的技术,其中每一步的梯度不是基于整个数据集,而是基于单个数据点。
设 C(θ)为基于 m 个训练样本的代价函数。每一步的梯度下降不是基于 C(θ),而是基于 C (i) (θ),这是基于第 I 个训练样本的代价函数。因此,如果我们必须在每一次迭代中绘制相对于总成本函数的梯度向量,等高线将不会垂直于切线,因为它们基于 C (i) (θ)的梯度,而不是基于总 C(θ)。
成本函数 C (i) (θ)用于每次迭代的梯度计算,并且在每次迭代中通过标准梯度下降来更新模型参数向量θ,直到我们遍历了整个训练数据集。我们可以在整个数据集上执行几次这样的遍历,直到获得合理的收敛。
因为每次迭代的梯度不是基于整个训练数据集,而是基于单个训练样本,所以它们通常非常嘈杂,并且可能快速改变方向。这可能导致成本函数的最小值附近的振荡,因此在收敛到最小值时学习速率应该更小,使得对参数向量的更新尽可能小。梯度计算起来更便宜也更快,所以梯度下降趋向于收敛得更快。
在随机梯度下降中重要的一点是训练样本应该尽可能随机。这将确保在几个训练样本期间的随机梯度下降提供与实际梯度下降产生的模型参数相似的模型参数更新,因为随机样本更有可能代表整个训练数据集。如果随机梯度下降每次迭代中的样本有偏差,则它们不代表实际数据集,因此模型参数的更新可能会导致随机梯度下降需要很长时间才能收敛。

图 1-41
Stochastic gradient descent parameter update

图 1-40
Fluctuation in the total cost function value over iterations in stochastic gradient descent
如图 1-41 所示,随机梯度下降的每一步的梯度都不垂直于等高线处的切线。但是,如果我们绘制了它们,它们将垂直于单个训练样本的等高线的切线。此外,由于波动的梯度,迭代的相关成本降低是有噪声的。
当我们使用单个训练数据点时,梯度计算变得非常便宜。此外,收敛速度相当快,但它也有自己的缺点,如下所示:
- 因为每次迭代的估计梯度不是基于总成本函数,而是基于与单个数据点相关的成本函数,所以梯度噪声很大。这导致在最小值处的收敛问题,并可能导致振荡。
- 学习率的调整变得很重要,因为高学习率可能导致在收敛到最小值时的振荡。这是因为梯度噪声很大,因此如果收敛处的梯度估计不接近零,高学习率将使更新远远超过最小值点,并且该过程可以在最小值的任一侧重复。
- 由于梯度是有噪声的,每次迭代后的模型参数值也非常有噪声,因此需要将试探法添加到随机梯度下降中,以确定采用哪个模型参数值。这也带来了另一个问题:什么时候停止训练。
全批次模型和随机梯度下降之间的折衷是小批次方法,其中梯度既不基于全训练数据集也不基于单个数据点。相反,它使用少量训练数据点来计算成本函数。大多数深度学习算法使用随机梯度下降的小批量方法。梯度的噪音更小,同时也不会导致很多内存限制,因为小批量大小适中。
我们将在第二章更详细地讨论小批量。
牛顿方法
在我们开始用牛顿法优化成本函数的最小值之前,让我们看看梯度下降技术的局限性。
梯度下降法依赖于连续迭代之间的成本函数的线性;即,由于从 t 到
的成本函数 C(θ)的路径是线性的或者可以由直线连接,所以可以通过遵循梯度从时间 t 的参数值达到迭代
的参数值。这是一个非常简化的假设,如果成本函数是高度非线性的或具有曲率,则不会产生梯度下降的良好方向。为了更好地理解这一点,让我们看看三种不同情况下的单变量成本函数图。
线性曲线

图 1-42
Linear cost function
负曲率

图 1-43
Cost function with negative curvature at point P
正曲率

图 1-44
Cost function with positive curvature at point P
对于线性成本函数,如图 1-42 所示,梯度的负值会给我们达到最小值的最佳方向,因为函数是线性的,没有任何曲率。对于负曲率成本函数和正曲率成本函数,分别如图 1-43 和 1-44 所示,导数不会给我们一个好的最小值方向,因此为了处理曲率,我们需要 Hessian 和导数。正如我们所看到的,黑森数只不过是二阶导数的矩阵。它们包含关于曲率的信息,因此与简单的梯度下降相比,将为参数更新提供更好的方向。
梯度下降法是用于优化的一阶近似方法,而牛顿法是用于优化的二阶方法,因为它们使用 Hessian 连同梯度来处理成本函数中的曲率。
就拿我们通常的代价函数 C(θ)来说吧,这里
是一个 n 维的模型参数向量。我们可以通过它的二阶泰勒级数展开来近似θ邻域内的代价函数 C(θ),如下图:

是梯度,H(θ)是成本函数 C(θ)的 Hessian。
现在,如果θ是迭代 t 时模型参数向量的值,并且(
是迭代
时模型参数的值,那么

哪里
。
对
取梯度,我们有

设置渐变
为 0 我们得到


所以,牛顿法的参数更新如下:

我们没有牛顿方法的学习率,但是我们可以选择使用学习率,就像梯度下降一样。因为非线性成本函数的方向用牛顿方法更好,所以与梯度下降相比,收敛到最小值的迭代将更少。需要注意的一点是,如果我们试图优化的成本函数是二次成本函数,如线性回归中的成本函数,那么牛顿法在技术上可以一步收敛到最小值。
然而,计算 Hessian 矩阵及其逆矩阵有时计算量很大或很难处理,尤其是当输入要素的数量很大时。此外,有时可能会有 Hessian 甚至没有正确定义的功能。因此,对于大型机器学习和深度学习应用程序,梯度下降(尤其是随机梯度下降)技术与小批量一起使用,因为它们的计算强度相对较低,并且在数据量较大时可以很好地扩展。
约束优化问题
在约束优化问题中,除了我们需要优化的成本函数,我们还需要遵守一组约束。约束可能是等式或不等式。
每当我们想最小化一个受等式约束的函数时,我们就使用拉格朗日公式。假设我们必须根据
最小化 f(θ),其中
。对于这样一个约束优化问题,我们需要最小化一个函数
。取 L 相对于组合向量θ,λ的梯度(称为拉格朗日量),并将其设置为 0,将得到所需的θ,该θ使 f(θ)最小并符合约束条件。λ称为拉格朗日乘数。当有几个约束时,我们需要添加所有这样的约束,对每个约束使用单独的拉格朗日乘数。假设我们想使 f(θ)在 m 约束下最小化
拉格朗日量可以表示如下:

其中![$$ \lambda ={\left[{\lambda}_1{\lambda}_2\kern1.25em ..\ {\lambda}_m\right]}^T $$](A448418_1_En_1_Chapter_IEq362.gif)
为了最小化函数,L(θ,λ)相对于θ和λ向量的梯度应该为零向量;即


前面的方法不能直接用于带有不等式的约束。在这种情况下,可以使用一种更通用的方法,称为 Karush Kahn Tucker 方法。
设 C(θ)是我们希望最小化的成本函数,其中
。同样,对θ有 k 个约束,使得




…
…

这变成了一个约束优化问题,因为θ应该遵守一些约束。每个不等式都可以转化为标准形式,其中某个函数小于或小于等于零。例如:

让每个这样的约束严格小于,或者小于等于零,用 g i (θ)来表示。还有,假设有一些严格的等式方程 e j (θ)。这种最小化问题通过拉格朗日公式的 Karush Kuhn Tucker 版本来解决。
不是最小化 C(θ),我们需要最小化一个代价函数 L(θ,α,β)如下:

缩放器
和
被称为拉格朗日乘数,它们中的 k 个对应于 k 个约束。因此,我们将约束最小化问题转化为无约束最小化问题。
为了解决这个问题,卡鲁什·库恩·塔克条件应该在最小值点满足如下:
-
The gradient of L(θ, α, β)with respect to θ should be the zero vector; i.e.,
![$$ {\nabla}_{\theta}\left(\theta,\ \alpha, \beta \right)=0 $$]()
![$$ =>{\nabla}_{\theta }C\left(\theta \right) + {\displaystyle \sum_{i=1}^{k_1}}{\alpha}_i{\nabla}_{\theta }{g}_i\left(\theta \right) + {\displaystyle \sum_{j=1}^{k_2}}{\beta}_j{\nabla}_{\theta }{e}_j\left(\theta \right)=0 $$]()
-
The gradient of the L(θ, α, β) with respect to β, which is the Lagrange multiplier vector corresponding to the equality conditions, should be zero:
![$$ {\nabla}_{\beta}\left(\theta,\ \alpha, \beta \right)=0 $$]()
![$$ =>{\nabla}_{\beta }C\left(\theta \right) + {\displaystyle \sum_{i=1}^{k_1}}{\alpha}_i{\nabla}_{\beta }{g}_i\left(\theta \right) + {\displaystyle \sum_{j=1}^{k_2}}{\beta}_j{\nabla}_{\beta }{e}_j\left(\theta \right)=0 $$]()
-
The inequality conditions should become equality conditions at the minima point. Also, the inequality Lagrange multipliers should be non-negative:
![$$ {\alpha}_i{g}_i\left(\theta \right)=0\kern0.5em and\ {\alpha}_i\ge 0\kern1.5em \forall i\ \in \left{1,2,\dots, {k}_1\right} $$]()
求解前述条件将为约束优化问题提供最小值。
机器学习中的几个重要课题
在这一部分,我们将讨论几个与机器学习非常相关的重要话题。他们的底层数学非常丰富。
降维方法
主成分分析和奇异值分解是机器学习领域中最常用的降维技术。在这里,我们将在一定程度上讨论这些技术。请注意,这些数据简化技术是基于线性相关性的,并不捕捉非线性相关性,如共偏度、共峰度等。在本书的后半部分,我们将讨论一些基于人工神经网络的降维技术,比如自编码器。
主成分分析
主成分分析是一种降维技术,理论上应该在“线性代数”一节中讨论。然而,为了使它的数学更容易掌握,我有意把它留到约束优化问题之后。我们来看看图 1-45 中的二维数据图。正如我们所见,数据的最大方差既不是沿着 x 方向,也不是沿着 y 方向,而是在两者之间的某个方向。因此,如果我们将数据投射到方差最大的方向,它将覆盖数据中的大部分可变性。此外,方差的其余部分可以作为噪声忽略。

图 1-45
Correlated 2-D data. a 1 and a 2 are the directions along which the data is uncorrelated and are the principal components .
沿 x 方向和 y 方向的数据高度相关(见图 1-45 )。协方差矩阵将提供关于数据的所需信息,以减少冗余。我们可以不看 x 和 y 方向,而是看 a 1 方向,它具有最大的方差。
现在,让我们进行一些数学计算,假设我们没有图,只有 m 个样本的数据
。
我们想在 n 维平面中按照方差递减的顺序找出独立的方向。我说的独立方向是指这些方向之间的协方差应该是 0。设 a 1 为数据方差最大的单位向量。我们首先减去数据向量的平均值,使数据以原点为中心。设μ为数据向量 x 的均值向量;即![$$ E\left[x\right] = \mu . $$](A448418_1_En_1_Chapter_IEq367.gif)
向量在 1 方向上的一个分量是
在 1 方向上的投影;设用 z1:

var(z1)= var![$$ {a_1}^T\left(x - \mu \right) $$]= a1Tcov(x)a1表示,其中 var 表示方差,cov(x)表示协方差矩阵。
对于给定的数据点,方差是一个 1 的函数。因此,我们必须最大化相对于 a 1 的方差,因为 a 1 是一个单位向量:

因此,我们可以把要最大化的函数表示为
,其中λ为拉格朗日乘数。
对于最大值,设置 L 相对于 a 1 的梯度为 0,我们得到

我们可以看到一些我们之前学过的东西。a 1 向量无非是协方差矩阵的一个本征向量,λ是对应的本征值。
现在,把这个代入沿 a 1 的方差表达式,我们得到

由于沿 a 1 的方差的表达式是特征值本身,对应于最高特征值的特征向量给我们第一主分量,或数据中方差最大的方向。
现在,让我们得到第二个主成分,或者方差最大的方向就在 1 之后。
设第二主分量的方向由单位向量 a 2 给出。因为我们在寻找正交分量,所以 a 2 的方向应该垂直于 a 1 。
数据沿 a 2 的投影可以用变量 z 2 =
来表示
因此,数据沿 a 2 的方差为 Var(z2)= Var![$$ {a_2}^T\left(x - \mu \right) $$]= a2Tcov(x)a2。
我们必须在约束条件
下最大化
,因为 2 是一个单位向量
a2Ta1= 0 既然 a 2 应该与 a 1 正交。
我们需要最大化下面的函数 L(a 2 ,α,β)关于参数 a 2 ,α,β:

通过对 a 2 取一个梯度,并将其设置为零向量,我们得到

通过取梯度
与向量 a 1 的点积,我们得到

a1Tcov(x)a2是一个标量,可以写成2Tcov(x)a1。
论简化,a2Tcov(x)a1=
。还有,项 2αa1Ta2等于 0,剩下
。由于
,β必须等于 0。
将表达式中的
替换为
,我们得到:

因此,第二主成分也是协方差矩阵的本征向量,并且本征值α必须是紧随λ之后的第二大本征值。这样,我们将从协方差矩阵
获得 n 个本征向量,并且沿着这些本征值方向(或主成分)中的每一个的数据的方差将由本征值表示。需要注意的一点是,协方差矩阵总是对称的,因此本征向量总是彼此正交,因此给出独立的方向。
协方差矩阵总是半正定的。
这是真的,因为协方差矩阵的特征值代表方差,方差不能为负。如果 cov(x)是正定的,即
,那么协方差矩阵的所有特征值都是正的。

图 1-46
Principal component analysis centers the data and then projects the data into axes along which variance is maximum. The data along z 2 can be ignored if the variance along it is negligible.
图 1-46 说明了数据的主成分分析转换。正如我们所看到的,主成分分析使数据居中,并消除了 PCS 转换变量之间的任何相关性。
主成分分析在数据简化中什么时候有用?
当输入的不同维度之间存在高相关性时,将只有几个独立的方向,沿着这些方向,数据的方差将是高的,而沿着其他方向,方差将是不显著的。使用 PCA,可以保留方差高的几个方向上的数据成分,并对总体方差做出显著贡献,而忽略其余数据。
如何知道所选主成分保留了多少方差?
如果 z 向量表示输入向量 x 的变换分量,则 cov(z)将是对角矩阵,包含 cov(x)矩阵的特征值作为其对角元素。
![$$ cov(z) = \left[\begin{array}{ccc}\hfill {\lambda}_1\hfill & \hfill \cdots \hfill & \hfill 0\hfill \ {}\hfill \vdots \hfill & \hfill {\lambda}_{2\kern0.5em }\hfill & \hfill \vdots \hfill \ {}\hfill 0\hfill & \hfill \cdots\ \hfill & \hfill {\lambda}_n\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_Equhh.gif)
同样,让我们假设特征值像
一样排序
假设我们选择只保留前 k 个主成分;捕获的数据中方差的比例如下:

奇异值分解
奇异值分解是一种降维技术,它将矩阵
分解为三个矩阵的乘积,如
所示,其中
并且由矩阵 AA T 的所有特征向量组成
并且由矩阵 A 的所有特征向量组成 T A
并且由 A T A 和 AA T 的特征向量的 k 个平方根组成,其中 k 是矩阵 A 的秩
U 的列向量都彼此正交,因此形成正交基。同样,V 的列向量也形成正交基:
![$$ U=\left[{u}_1{u}_2\kern0.5em \dots \kern0.5em {u}_m\right] $$](A448418_1_En_1_Chapter_Equhj.gif)
其中
是 U.
![$$ V=\left[{v}_1{v}_2\kern0.5em \dots \kern0.5em {v}_m\right] $$](A448418_1_En_1_Chapter_Equhk.gif)
的列向量
其中
为
![$$ S = \left[\begin{array}{ccc}\hfill {\sigma}_1\hfill & \hfill \cdots \hfill & \hfill 0\hfill \ {}\hfill \vdots \kern1.5em \hfill & \hfill {\sigma}_2\hfill & \hfill \vdots \hfill \ {}\hfill 0\hfill & \hfill \cdots\ \hfill & \hfill 0\hfill \end{array}\right] $$](A448418_1_En_1_Chapter_Equhl.gif)
的列向量
根据 A 的等级,会有σ 1 ,σ 2 … …σ k 矩阵 A 的秩 k 对应的对角元:

也称为奇异值,是 A T A 和 AA T 的特征值的平方根,因此它们是数据中方差的度量。每个
都是一个秩 1 矩阵。我们只能保留奇异值显著的秩 1 矩阵,并解释数据中相当大比例的方差。
如果只取对应于最大幅度的前 p 个奇异值的前 p 个秩一矩阵,则数据中保留的方差由下式给出:

可以使用奇异值分解来压缩图像。类似地,在协同过滤中使用奇异值分解来将用户评级矩阵分解成包含用户向量和项目向量的两个矩阵。矩阵的奇异值分解由 USV T 给出。用户评级矩阵 R 可以分解如下:

其中 U’是用户向量矩阵并且等于
,V’是项目向量矩阵,其中
。
正规化
建立机器学习模型的过程包括导出符合训练数据的参数。如果模型是简单的,那么该模型对数据的变化缺乏敏感性,并且遭受高偏差。但是,如果模型过于复杂,它会尝试为尽可能多的变化建模,并在此过程中为训练数据中的随机噪声建模。这消除了简单模型产生的偏差,但引入了高方差;即,模型对输入中非常小的变化敏感。模型的高方差不是一件好事,尤其是当数据中的噪声相当大时。在这种情况下,追求在训练数据上表现太好的模型在测试数据集上表现很差,因为模型失去了用新数据很好地概括的能力。模型遭受高方差的问题被称为过拟合。

图 1-47
Illustration of models with high variance and high bias
如图 1-47 所示,我们有三个模型符合数据。平行于水平线的一条曲线遭受高偏差,而弯曲的一条曲线遭受高方差。与水平线成 45 度角的直线既没有高方差也没有高偏差。
具有高方差的模型在训练数据上表现良好,但在测试数据集上表现不佳,即使数据集的性质没有太大变化。用蓝色表示的模型可能不完全符合训练,但它在测试数据上表现得更好,因为该模型不会受到高方差的影响。关键是要有一个不会遭受高偏差的模型,同时也不会复杂到可以模拟随机噪声。
具有高方差的模型通常具有大幅度的模型参数,因为模型对数据中微小变化的敏感性很高。为了克服由高模型方差导致的过拟合问题,广泛使用一种称为正则化的流行技术。
为了客观地看待问题,让我们来看看我们之前看到的线性回归成本函数:


如前所述,具有高方差的模型具有大幅度的模型参数。我们可以在成本函数 C(θ)中加入一个额外的分量,在模型参数向量的幅度很高的情况下,该分量对总成本函数不利。
因此,我们可以有一个新的成本函数
,其中‖θ‖ 2 2 是模型参数向量的 l 2 范数的平方。最优化问题变成了

将梯度
与θ相对应,并将其设置为 0,得到
现在,我们可以看到,由于成本函数中的‖θ‖ 2 2 项,模型参数的幅度不能太大,因为它会对整体成本函数不利。λ决定正则项的权重。较高的λ值会导致较小的‖θ22值,从而使模型更简单,更容易出现高偏差或欠拟合。一般来说,更小的λ值对降低模型复杂性和模型方差大有帮助。λ通常使用交叉验证进行优化。
当 l 2 范数的平方作为正则化项时,该优化方法称为 l 2 正则化。有时,模型参数向量的 l 1 范数被用作正则化项,并且优化方法被称为 l 1 正则化。l 2 应用于回归问题的正则化称为岭回归,而 l 1 应用于此类回归问题的正则化称为 lasso 回归。
对于 l 1 正则化,前面的回归问题就变成了

岭回归在数学上更方便,因为它有封闭形式的解,而套索回归没有封闭形式的解。然而,与岭回归相比,lasso 回归对异常值更稳健。套索问题给出了稀疏解,因此适用于要素选择,尤其是当输入要素之间存在中度到高度相关性时。
视为约束优化问题的正则化
除了添加罚项,我们还可以添加一个约束,使模型参数向量的大小小于或等于某个常数值。于是我们可以有一个优化问题如下:

使得
其中 b 是常数。
我们可以通过创建一个新的拉格朗日公式,将这个约束极小化问题转化为无约束极小化问题,如下所示:

为了根据卡鲁什库恩塔克条件最小化拉格朗日成本函数,以下是重要的:
-
The gradient of Lwith respect to θ;i.e.,
should be the zero vector, which on simplification gives![$$ \theta ={\left({X}^TX + \lambda I\right)}{-1}{X}T\mathrm{Y} $$]()
(1)
-
同样在最优点
= 0 和
如果我们考虑正则化,即
,那么
(2)
从(1)可以看出,得到的θ是λ的函数。应调整λ,以满足(2)中的约束条件。
从(1)得到的解
与我们从 l 2 正则化得到的相同。在机器学习应用中,拉格朗日乘数通常通过超参数调整或交叉验证来优化,因为我们不知道 b 的最佳值是多少。当我们取较小的λ值时,b 的值增加,θ的范数也增加,而较大的λ值提供较小的 b,因此θ的范数也较小。
回到正则化,代价函数中惩罚模型复杂性的任何分量提供正则化。在基于树的模型中,随着叶节点数量的增加,树的复杂性也在增加。我们可以在成本函数中添加一个基于树中叶子节点数量的项,它将提供正则化。对于树的深度可以做类似的事情。
甚至提前停止模型训练过程也提供了正则化。例如,在梯度下降法中,我们运行的迭代次数越多,模型就变得越复杂,因为每次迭代梯度下降都试图进一步降低成本函数值。我们可以根据一些标准提前停止模型学习过程,例如迭代过程中测试数据集的成本函数值的增加。每当在训练的迭代过程中训练成本函数值降低而测试成本函数值增加时,这可能是过拟合开始的指示,因此停止迭代学习是有意义的。
每当训练数据少于模型必须学习的参数数量时,就很有可能过拟合,因为模型将学习小数据集的太多规则,并且可能无法很好地推广到看不见的数据。如果数据集与参数的数量相比是足够的,那么学习到的规则超过了群体数据的很大比例,因此模型过拟合的机会降低了。
摘要
在这一章中,我们已经触及了继续进行机器学习和深度学习概念所需的所有数学概念。读者仍然被建议在他或她的空闲时间浏览与这些主题相关的合适的教科书以获得更清晰的理解。然而,这一章是一个很好的起点。在下一章,我们将从人工神经网络和 TensorFlow 的基础知识开始。
二、深度学习概念和 TensorFlow 介绍
深度学习及其进展
深度学习是从人工神经网络进化而来的,人工神经网络自 20 世纪 40 年代就存在了。神经网络是被称为人工神经元的处理单元的互连网络,它松散地模仿生物大脑中的轴突。在生物神经元中,树突从各种相邻的神经元(通常超过一千个)接收输入信号。这些修改后的信号然后被传递到神经元的细胞体或胞体,在那里这些信号被累加在一起,然后被传递到神经元的轴突。如果接收到的输入信号超过指定的阈值,轴突就会释放一个信号,这个信号会传递给其他神经元的邻近树突。图 2-1 描绘了一个生物神经元的结构以供参考。

图 2-1。
Structure of a biological neuron
人工神经元单元受生物神经元的启发,为方便起见做了一些修改。很像树突,神经元的输入连接携带来自其他邻近神经元的衰减或放大的输入信号。信号被传递到神经元,在那里输入信号被累加,然后根据接收到的总输入决定输出什么。例如,对于二进制阈值神经元,当总输入超过预定义阈值时,提供输出值 1;否则,输出保持为 0。在人工神经网络中使用了几种其他类型的神经元,它们的实现仅在总输入产生神经元输出的激活函数方面有所不同。在图 2-2 中,不同的生物等效物被标记在人工神经元中,以便于类比和解释。

图 2-2。
Structure of an artificial neuron
人工神经网络始于 20 世纪 40 年代初,前景看好。我们将通过人工神经网络社区中的主要事件的年表来了解这个学科这些年来是如何发展的,以及在此过程中面临了哪些挑战。

图 2-3。
Evolution of artificial neural networks
- 1943 年,两位电气工程师 Warren McCullogh 和 Walter Pitts 发表了一篇题为“神经活动中固有思想的逻辑演算”的论文,与神经网络有关。论文可以位于
http://www.cs.cmu.edu/∼epxing/Class/10715/reading/McCulloch.and.Pitts.pdf。他们的神经元有一个二进制输出状态,神经元有两种类型的输入:兴奋性输入和抑制性输入。神经元的所有兴奋性输入都具有相等的正权重。如果神经元的所有输入都是兴奋性的,并且如果总输入为
,则神经元将输出 1。在任何抑制输入激活或
的情况下,输出将为 0。使用这种逻辑,所有布尔逻辑功能可以由一个或多个这样的神经元来实现。这些网络的缺点是它们无法通过训练来学习权重。人们必须手动计算出权重,并组合神经元来实现所需的计算。 - 下一件大事是感知机,由弗兰克·罗森布拉特于 1957 年发明。他和他的合作者亚历山大·斯蒂伯和罗伯特·h·沙茨在一份名为《感知机——一种感知和识别自动机,可以位于
https://blogs.umass.edu/brain-wars/files/2016/03/rosenblatt-1957.pdf的报告中记录了他们的发明。感知器是以二进制分类任务为动机构建的。神经元的权值和偏差都可以通过感知器学习规则来训练。权重可以是正的也可以是负的。弗兰克·罗森布拉特对感知机模型的能力提出了强有力的主张。不幸的是,并不是所有的都是真的。 - 马文·明斯基和西摩·a·帕尔特在 1969 年写了一本名为《感知器:计算几何导论》的书(麻省理工学院出版社),该书显示了感知器学习算法的局限性,即使是在简单的任务上,如用单个感知器开发 XOR 布尔函数。大部分人工神经网络团体认为,Minsky 和 Papert 所展示的这些局限性适用于所有的神经网络,因此人工神经网络的研究几乎停止了十年,直到 20 世纪 80 年代。
- 在 20 世纪 80 年代,Geoffrey Hilton、David Rumelhart、Ronald Williams 等人重新引起了对人工神经网络的兴趣,这主要是因为学习多层问题的反向传播方法以及神经网络解决非线性分类问题的能力。
- 在 20 世纪 90 年代,由 V. Vapnik 和 C. Cortes 发明的支持向量机(SVM)变得流行,因为神经网络没有扩大到大问题。
- 人工神经网络在 2006 年被重新命名为深度学习,当时 Geoffrey Hinton 等人引入了无监督预训练和深度信念网络的概念。他们在深度信念网络方面的工作发表在题为“深度信念网络的快速学习算法”的论文中论文可以位于
https://www.cs.toronto.edu/∼hinton/absps/fastnc.pdf。 - ImageNet 是一个标记图像的大型集合,由斯坦福的一个小组在 2010 年创建并发布。
- 2012 年,Alex Krizhevsky、 Ilya Sutskever 和 Geoffrey Hinton 赢得了 ImageNet 竞赛,实现了 16%的错误率,而在前两年,最好的模型有大约 28%和 26%的错误率。这是一次巨大胜利。该解决方案的实现具有深度学习的几个方面,这些方面是当今任何深度学习实现中的标准。
- 图形处理单元(GPU)用于训练模型。GPU 非常擅长做矩阵运算,计算速度非常快,因为它们有数千个内核来进行并行计算。
- 辍学被用作一种正则化技术,以减少过拟合。
- 校正线性单元(ReLU)被用作隐藏层的激活函数。
图 2-3 展示了人工神经网络向深度学习的演进。ANN 代表人工神经网络,MLP 代表多层感知器,AI 代表人工智能。
感知器和感知器学习算法
虽然感知器学习算法能够做的事情有局限性,但它们是我们今天看到的深度学习高级技术的先驱。因此,对感知器和感知器学习算法的详细研究是值得的。感知器是线性二元分类器,它使用一个超平面来区分两个类别。感知器学习算法保证获取一组正确分类所有输入的权重和偏差,只要存在这样一组可行的权重和偏差。
感知器是一个线性分类器,正如我们在第一章看到的,线性分类器一般通过构建一个将正类和负类分开的超平面来进行二元分类。
超平面由垂直于超平面的单位权重向量
和确定超平面离原点的距离的偏置项 b 来表示。选择矢量
指向正类。
如图 2-4 所示,对于任何输入向量
,与单位向量
的负值的点积将给出超平面到原点的距离 b,因为 x’和 w’位于原点的相反侧。形式上,对于躺在超平面上的点,


图 2-4。
Hyperplane separating two classes
类似地,对于位于超平面下面的点,即属于正类的输入向量
,w’上的
的投影的负值应该小于 b。因此,对于属于正类的点,

类似地,对于位于超平面上方的点,即属于负类的输入向量
,
在 w’上的投影的负值应该大于 b。因此,对于属于负类的点,

总结前面的推论,我们可以得出以下结论:
对应于超平面,所有位于该超平面上的
将满足该条件。通常,超平面上的点被认为属于负类。
对应正类中的所有点。
对应负类中的所有点。
然而,对于感知器,我们不将权重向量 w’保持为单位向量,而是将其保持为任何一般向量。在这种情况下,偏差 b 将不对应于超平面距原点的距离,而是距原点的距离的缩放版本,缩放因子是向量 w’的大小或 l 2 范数,即| | w’| |2。总结一下,如果 w’是垂直于超平面的任意一般向量,并且指向正类,那么
仍然代表一个超平面,其中 b 代表超平面到原点的距离乘以 w’的大小。
在机器学习领域中,任务是学习超平面的参数(即,w’和 b)。我们通常倾向于简化问题,去掉偏置项,将其作为 w 内的一个参数,对应于恒定输入特性 1,1,正如我们在第一章中所讨论的。
设添加偏差后的新参数向量为
,添加常数项 1 后的新输入特征向量为
,其中
![$$ {x}^{\prime }={\left[{x}_1\ {x}_2{x}_3..\kern0.5em {x}_n\right]}^T $$](A448418_1_En_2_Chapter_Equd.gif)
![$$ x={\left[1\ {x}_1\ {x}_2{x}_3..\kern0.5em {x}_n\right]}^T $$](A448418_1_En_2_Chapter_Eque.gif)
![$$ {w}^{\hbox{'}}={\left[{w}_1\ {w}_2{w}_3..\kern0.5em {w}_n\right]}^T $$](A448418_1_En_2_Chapter_Equf.gif)
![$$ w={\left[b\ {w}_1\ {w}_2{w}_3..\kern0.5em {w}_n\right]}^T $$](A448418_1_En_2_Chapter_Equg.gif)
通过前面的操作,我们已经使ℝ n 中距离原点一定距离的超平面通过了
向量空间中的原点。超平面现在仅由其权重参数向量
确定,分类规则简化如下:
对应超平面,所有位于超平面上的
都会满足这个条件。
对应正类中的所有点。这意味着分类现在仅由向量 w 和 x 之间的角度决定。如果输入向量 x 与权重参数向量 w 之间的角度在
度到
度之间,则输出分类为正。
对应负类中的点。在不同的分类算法中,相等条件被不同地对待。对于感知器,超平面上的点被视为属于负类。
现在,我们已经拥有了进行感知器学习算法所需的一切。
设
代表 m 个输入特征向量,
代表相应的类别标签。
感知器学习问题如下:
- 步骤 1–从一组随机重量开始
![$$ w\in {\mathrm{\mathbb{R}}}^{\left(n+1\right)\times 1}. $$]()
- 步骤 2-评估数据点的预测类别。对于一个输入数据点 x (i) 如果
则预测类
,否则
。对于感知器分类器,超平面上的点通常被认为属于负类。 - 步骤 3–更新权重向量 w,如下所示:
- 如果 y p (i) = 0 且实际类别
,则更新权重向量为![$$ w=w+{x}^{(i)}. $$]()
- 如果 y p (i) = 1 且实际类别
,则更新权重向量为![$$ w=w-{x}^{(i)}. $$]()
- 如果
,w 不需要更新。
- 如果 y p (i) = 0 且实际类别
- 第 4 步–转到第 2 步,处理下一个数据点。
- 第 5 步-当所有数据点都被正确分类后,停止。
感知器将仅能够正确地分类这两个类别,如果存在可以线性地分离这两个类别的可行的权重向量 w。在这种情况下,感知器收敛定理保证收敛。
感知机学习的几何解释
感知器学习的几何解释揭示了可行的权重向量 w,该权重向量 w 表示分离正类和负类的超平面。

图 2-5。
Hyperplanes in weight space and feasible set of weight vectors
我们取两个数据点,(x (1) ,y (1) )和(x (2) ,y (2) ,如图 2-5 所示。此外,让
包括截距项的常数特征 1。同样,我们取
和
(即数据点 1 属于正类,而数据点 2 属于负类)。
在输入特征向量空间中,权重向量确定超平面。同样,我们需要将各个输入向量视为权重空间中超平面的代表,以确定用于正确分类数据点的可行权重向量集。
在图 2-5 中,超平面 1 由输入向量 x (1) 确定,该向量垂直于超平面 1。此外,超平面穿过原点,因为偏置项已经作为权重向量 w 内的参数被消耗。对于第一个数据点,
。如果
,第一个数据点的预测将是正确的。与输入向量 x (1) 成-90 到+90 度角的所有权重向量 w 将满足条件
。它们形成了第一个数据点的可行权重向量集,如图 2-5 中超平面 1 上方的阴影区域所示。
类似地,超平面 2 由垂直于超平面 2 的输入向量 x (2) 确定。对于第二个数据点,
。如果
,第二个数据点的预测将是正确的。与输入向量 x (2) 成-90 度到+90 度角以外的所有权重向量 w 将满足条件
。它们形成了第二个数据点的可行权重向量集,如图 2-5 中超平面 2 下方的阴影区域所示。
因此,满足两个数据点的权重向量 w 的集合是两个阴影区域之间的重叠区域。重叠区域中的任何权重向量 w 将能够通过它们在输入向量空间中定义的超平面来线性分离这两个数据点。
感知器学习的局限性
感知器学习规则只能分离输入空间中可线性分离的类。连最基本的异或门逻辑都无法用感知器学习规则实现。
对于 XOR 逻辑下面是输入和相应的输出标签或类




让我们初始化权重向量
,其中权重向量的第一个分量对应于偏置项。类似地,所有输入向量的第一个分量都是 1。
- 对于
,预测值为
= 0。由于
,数据点将被归类为 0,这与 1 的实际类别不匹配。因此,根据感知器规则更新的权重向量应该是
。 - 对于
,预测值为
= 1。由于
,数据点将被正确分类为 1。因此,权重向量没有更新,它停留在
。 - 对于
,预测值为
= 2。由于
,数据点将被归类为 1,这与 0 的实际类别不匹配。因此,更新后的权重向量应该是
。 - 对于
,预测值为
= 0。由于
,数据点将被正确分类为 0。因此,没有对权重向量 w 的更新
因此,第一次通过数据点后的权重向量为
。基于更新后的权重向量 w,让我们评估这些点的分类情况。
- 对于数据点 1,
,因此被错误归类为 0 类。 - 对于数据点 2,
,因此被错误归类为 0 类。 - 对于数据点 3,
,因此被正确分类为 0 类。 - 对于数据点 4,
,因此被正确分类为 0 类。
基于前面的分类,我们看到在第一次迭代之后,感知器算法设法只正确地分类了负类。如果我们再次对数据点应用感知器学习规则,则在第二遍中对权重向量 w 的更新将如下:
- 对于数据点 1,
,因此被错误归类为 0 类。因此,根据感知器规则更新的权重是
。 - 对于数据点 2,
,因此被错误归类为 0 类。因此,根据感知器规则更新的权重是
。 - 对于数据点 3,
,因此被错误归类为 1 类。因此,根据感知器规则更新的权重是
。 - 对于数据点 4,
,因此被错误归类为 1 类。因此,根据感知器规则更新的权重是
。
第二遍后的权重向量为
,与第一遍后的权重向量相同。从感知机学习的第一次和第二次过程中进行的观察来看,很明显,无论我们对数据点进行多少次,我们最终都会得到权重向量
。正如我们前面看到的,这个权重向量只能正确地对否定类进行分类,因此我们可以在不失一般性的情况下有把握地推断,感知器算法将总是无法对 XOR 逻辑进行建模。
非线性需求
正如我们所看到的,感知器算法只能学习用于分类的线性决策边界,因此不能解决决策边界中需要非线性的问题。通过对 XOR 问题的说明,我们看到感知器不能正确地线性分离这两类。
我们需要两个超平面来分隔这两个类别,如图 2-6 所示,通过感知器算法学习的一个超平面不足以提供所需的分类。在图 2-6 中,两条超平面线之间的数据点属于正类,另外两个数据点属于负类。需要两个超平面来分离两个类相当于拥有一个非线性分类器。

图 2-6。
XOR problem with two hyperplanes to separate the two classes
多层感知器(MLP)可以通过在隐藏层中引入非线性来提供类别之间的非线性分离。请注意,当感知器基于接收到的总输入输出 0 或 1 时,输出是其输入的非线性函数。学习多层感知器的权重时所说的和所做的一切都不可能通过感知器学习规则来实现。

图 2-7。
XOR logic implementation with multi-layer Perceptrons network
在图 2-7 中,XOR 逻辑通过多层感知器网络实现。如果我们有一个包含两个感知器的隐藏层,其中一个能够执行 OR 逻辑,而另一个能够执行 AND 逻辑,那么整个网络将能够实现 XOR 逻辑。用于 or 和 and 逻辑的感知器可以使用感知器学习规则来训练。然而,网络作为一个整体不能通过感知器学习规则来训练。如果我们观察 XOR 门的最终输入,它将是其输入的非线性函数,以产生非线性判定边界。
非线性隐层感知器的激活函数
如果我们使隐藏层的激活函数是线性的,那么最终神经元的输出将是线性的,因此我们将不能学习任何非线性的决策边界。为了说明这一点,让我们尝试通过具有线性激活函数的隐藏层单元来实现 XOR 函数。

图 2-8。
Linear output hidden layers in a two-layer Perceptrons network
图 2-8 显示了一个带有一个隐藏层的两层感知器网络。隐藏层由两个神经元单元组成。当隐藏单元中的激活是线性时,我们观察网络的总输出:
隐藏单元的输出
隐藏单元的输出
输出单元的输出

如前所述,网络的最终输出——即单元 p 1 的输出——是其输入的线性函数,因此网络不会在类之间产生非线性分离。
如果我们引入表示为
的激活函数,而不是由隐藏层产生的线性输出,则隐藏单元的输出
。
同样,隐藏单元的输出
。
输出单元的输出
。
显然,前面的输出在其输入中是非线性的,因此可以学习更复杂的非线性决策边界,而不是使用线性超平面来解决分类问题。隐藏层的激活函数称为 sigmoid 函数,我们将在后面的章节中更详细地讨论它。
神经元/感知器的不同激活函数
神经单元有几个激活函数,它们的使用根据手头的问题和神经网络的拓扑而变化。在这一节中,我们将讨论当今人工神经网络中使用的所有相关激活函数。
线性激活函数
在线性神经元中,输出线性依赖于其输入。如果神经元接收三个输入 x 1 ,x 2 和 x 3 ,那么线性神经元的输出 y 由
给出,其中 w 1 ,w 2 ,w 3 分别是输入 x 1 ,x 2 和 x 3 的突触权重,b
用向量表示法,我们可以表示输出
。
如果我们取
,那么相对于净输入 z 的输出将如图 2-9 所示。

图 2-9。
Linear output hidden layers in a two-layer Perceptrons network
二元阈值激活函数
在二进制阈值神经元中(见图 2-10 ),如果神经元的净输入超过指定阈值,则该神经元被激活;即输出 1,否则输出 0。如果神经元的净线性输入是
,k 是神经元激活的阈值,那么



图 2-10。
Binary threshold neuron
通常,通过调整偏置,二进制阈值神经元被调整为在阈值 0 处激活。神经元在
时被激活。
Sigmoid 激活函数
乙状结肠神经元的输入-输出关系表示如下:

其中
是乙状结肠激活函数的净输入。
-
当 sigmoid 函数的净输入 z 是一个正大数时
➤为 0,因此 y ➤为 1。 -
当 sigmoid 的净输入 z 是一个负的大数时
➤ ∞,因此 y ➤为 0。 -
When the net input z to a sigmoid function is 0 then
= 1 and so
.![A448418_1_En_2_Fig11_HTML.gif]()
图 2-11。
Sigmoid activation function
图 2-11 显示了一个 sigmoid 激活函数的输入-输出关系。具有 sigmoid 激活函数的神经元的输出非常平滑,并且给出良好的连续导数,这在训练神经网络时工作良好。sigmoid 激活函数的输出范围在 0 和 1 之间。由于 sigmoid 函数能够提供 0 到 1 范围内的连续值,因此通常用于输出二元分类中给定类别的概率。隐藏层中的 sigmoid 激活函数引入了非线性,使得模型可以学习更复杂的特征。
SoftMax 激活功能
SoftMax 激活函数是 sigmoid 函数的推广,最适合多类分类问题。如果有 k 个输出类别,并且第 I 个类别的权重向量是 w (i) ,则给定输入向量
的第 I 个类别的预测概率由下式给出:

其中 b (i) 是 SoftMax 的每个输出单元的偏差项。
让我们试着看看 sigmoid 函数和两类 SoftMax 函数之间的联系。
假设这两个类是 y 1 和 y 2 ,它们对应的权重向量是 w (1) 和 w (2) 。同样,让它们的偏差分别为 b (1) 和 b (2) 。假设
对应的类是正类。
??
??
我们可以从前面的表达式中看到,两类 SoftMax 的正类概率与 sigmoid 激活函数的表达式相同,唯一的区别是在 sigmoid 中我们只使用一组权重,而在两类 SoftMax 中有两组权重。在 sigmoid 激活函数中,我们不会对两个不同的类使用不同的权重集,所取的权重集通常是正类相对于负类的权重。在 SoftMax 激活函数中,我们为不同的类显式地采用不同的权重集。
由图 2-12 表示的 SoftMax 层的损失函数被称为分类交叉熵,由下式给出:


图 2-12。
SoftMax activation function
整流线性单元(ReLU)激活功能
在整流线性单元中,如图 2-13 所示,如果总输入大于 0,则输出等于神经元的净输入;然而,如果总输入小于或等于 0,神经元输出 0。
ReLU 单元的输出可以表示为:


图 2-13。
Rectified linear unit
ReLU 是彻底改变深度学习的关键元素之一。它们更容易计算。ReLUs 结合了两个世界的优点——它们有一个恒定的梯度,而净输入是正的,其他地方是零梯度。例如,如果我们采用 sigmoid 激活函数,则对于非常大的正值和负值,sigmoid 激活函数的梯度几乎为零,因此神经网络可能会遇到梯度消失的问题。正网络输入的恒定梯度确保梯度下降算法不会因为梯度消失而停止学习。同时,非正净输入的零输出呈现非线性。
整流线性单元激活功能有多种版本,如参数整流线性单元(PReLU)和泄漏整流线性单元。
对于正常的 ReLU 激活函数,对于非正的输入值,输出和梯度都是零,因此训练可以因为零梯度而停止。即使在输入为负的情况下,对于具有非零梯度的模型,PReLU 可能会很有用。预卢激活函数的输入-输出关系由下面给出:

其中
是预卢激活函数的净输入,β是通过训练学习的参数。
当β设置为
时,则
和激活函数称为绝对值 ReLU。当β设置为某个较小的正值(通常约为 0.01)时,激活函数称为泄漏 ReLU。
Tanh 激活函数
双曲正切激活函数的输入-输出关系(见图 2-14 )表示为

,其中
为双曲正切激活函数的净输入。
- 当净输入 z 是一个正大数时
➤为 0,所以 y ➤为 1。 - 当净输入 z 是一个负数时,e z ➤为 0,因此 y ➤为 1。
- 当净输入 z 为 0 时,则
= 1,因此
。

图 2-14。
Tanh activation function
正如我们所看到的,tanh 激活函数可以输出-1 到+1 之间的值。
sigmoid 激活函数在输出 0 附近饱和。训练网络时,如果层中的输出接近于零,则梯度消失,训练停止。双曲正切激活函数在输出的-1 和+ 1 值处饱和,并在输出的 0 值附近具有明确定义的梯度。因此,利用双曲正切激活函数,可以在输出 0 附近避免这种消失梯度问题。
多层感知器网络的学习规则
在前面的章节中,我们看到感知器学习规则只能学习线性决策边界。非线性复杂决策边界可以通过多层感知器建模;然而,这样的模型不能通过感知器学习规则来学习。因此,人们需要不同的学习算法。
在感知器学习规则中,目标是不断更新模型的权重,直到所有训练数据点都被正确分类。如果没有这样一个可行的权向量来正确地分类所有的点,算法就不会收敛。在这种情况下,可以通过预先定义要训练的遍数(迭代次数)或者通过定义正确分类的训练数据点的数量的阈值来停止算法,在该阈值之后停止训练。
对于多层感知器和大多数深度学习训练网络,训练模型的最佳方式是基于错误分类的误差计算成本函数,然后最小化关于模型参数的成本函数。由于基于成本的学习算法会最小化成本函数,因此对于二元分类(通常是对数损失成本函数),会使用对数似然函数的负值。作为参考,在第一章“逻辑回归”中说明了如何从最大似然法中推导出对数损失成本函数
多层感知器网络将具有隐藏层,并且为了学习非线性决策边界,激活函数本身应该是非线性的,例如 sigmoid、ReLu、tanh 等等。用于二进制分类的输出神经元应该具有 sigmoid 激活函数,以便迎合对数损失成本函数并输出类别的概率值。
现在,根据前面的考虑,让我们尝试通过构建对数损失成本函数来求解 XOR 函数,然后根据模型的权重和偏差参数将其最小化。网络中的所有神经元都具有 sigmoid 激活函数。
参照图 2-7 ,设隐藏单元 h 1 处的输入输出分别为 i 1 和 z 1 。同样,设隐藏单元 h 2 处的输入输出分别为 i 2 和 z 2 。最后,设输出层 p 1 的输入和输出分别为 i 3 和 z 3 。






考虑对数损失成本函数,XOR 问题的总成本函数可以定义如下:

如果所有的权重和偏差放在一起可以认为是一个参数向量θ,我们可以通过最小化代价函数 C(θ)来学习模型:

对于最小值,成本函数 C(θ)相对于θ(即
)的梯度应该为零。通过梯度下降法可以达到最小值。梯度下降的更新规则为
=
,其中η为学习率,
和θ (t) 分别为迭代
和 t 时的参数向量。
如果我们考虑参数向量中的个体权重,梯度下降更新规则变成如下:

梯度向量不会像线性或逻辑回归中那样容易计算,因为在神经网络中,权重遵循等级顺序。然而,导数的链式法则提供了一些简化,以系统地计算相对于权重(包括偏差)的偏导数。
这种方法称为反向传播,它简化了梯度计算。
梯度计算的反向传播
反向传播是一种将输出层的误差反向传播的有用方法,因此可以使用导数的链式法则轻松计算前面层的梯度。
让我们考虑一个训练示例,并通过反向传播,将 XOR 网络结构考虑在内(见图 2-8 )。设输入为
,对应的类为 y,则单条记录的代价函数为:






正如我们所看到的,成本函数相对于最终层中的净输入的导数只不过是估计输出的误差
:


同样,


现在,让我们计算成本函数相对于前一层中的权重的偏导数:

可视为相对于隐含层单元 h 1 的输出的误差。误差的传播与将输出单元连接到隐藏层单元的权重成比例。如果有多个输出单元,那么
将有来自每个输出单元的贡献。我们将在下一节中详细了解这一点。
同样,

可以认为是相对于隐含层单元 h 1 的网络输入的误差。它可以通过将
因子乘以
:



来计算
一旦我们有了成本函数相对于每个神经元单元中的输入的偏导数,我们就可以计算成本函数相对于对输入有贡献的权重的偏导数——我们只需要乘以来自该权重的输入。
梯度计算中反向传播方法的推广
在这一节中,我们试图通过一个更复杂的网络来推广反向传播方法。我们假设最终输出层由三个独立的 sigmoid 输出单元组成,如图 2-15 所示。此外,为了便于标记和简化学习,我们假设网络只有一条记录。

图 2-15。
Network to illustrate backpropagation for independent sigmoid output layers
单个输入记录的成本函数由以下给出:


在前面的表达式中,
,取决于特定于 y i 的事件是否活动。
表示第 I 类的预测概率。
让我们计算成本函数相对于权重 w ji (2) 的偏导数。权重只会影响网络的第 I 个输出单元的输出。





因此,如前所述,成本函数相对于第 I 个输出单元的净输入的偏导数是
,这仅仅是第 I 个输出单元的预测误差。

结合
和
,我们得到,


前面给出了成本函数相对于网络最后一层中的权重和偏差的偏导数的通用表达式。接下来,让我们计算较低层中权重和偏差的偏导数。事情变得有点复杂,但仍然遵循一个普遍的趋势。让我们计算成本函数相对于权重 w kj (1) 的偏导数。重量会受到所有三个输出单元的误差的影响。基本上,隐藏层中第 j 个单元的输出端的误差将具有来自所有输出单元的误差贡献,该误差贡献由将输出层连接到第 j 个隐藏单元的权重来缩放。让我们用链式法则计算偏导数,看看它是否符合我们所说的:



现在,
是棘手的计算自 zj(2)影响所有三个输出单元:


结合
和
的表达,我们有

通常,对于多层神经网络,为了计算成本函数 C 相对于对神经元单元中的净输入 s 有贡献的特定权重 w 的偏导数,我们需要计算成本函数相对于净输入(即
)的偏导数,然后乘以与权重 w 相关联的输入 x,如下:

可以认为是神经单元的误差,并且可以通过将输出层的误差传递给较低层的神经单元来迭代计算。另一点要注意的是,较高层神经单元中的误差被分配到前一层神经单元的输出,与它们之间的权重连接成比例。同样,成本函数相对于 sigmoid 激活神经元的净输入的偏导数
可以通过将
乘以
从成本函数相对于神经元的输出 z(即
)的偏导数来计算。对于线性神经元,这个倍增因子变成 1。
神经网络的所有这些特性使得计算梯度变得容易。这就是神经网络通过反向传播在每次迭代中学习的方式。
每次迭代由一次正向传递和一次反向传递或反向传播组成。在前向传递中,计算每层中每个神经元单元的净输入和输出。基于预测输出和实际目标值,在输出层中计算误差。通过将误差与前向传递中计算的神经元输出和现有权重相结合,误差被反向传播。通过反向传播,梯度得到迭代计算。一旦计算出梯度,就通过梯度下降法更新权重。
请注意,所示的扣除对 sigmoid 激活功能有效。对于其他激活功能,虽然方法保持不变,但在实现中需要针对激活功能进行特定的更改。
SoftMax 函数的成本函数不同于独立多类分类的成本函数。

图 2-16。
Network to illustrate backpropagation for Softmax output layer
图 2-16 所示网络中 SoftMax 激活层的交叉熵代价由

给出
让我们计算成本函数相对于权重 w ji (2) 的偏导数。现在,权重将影响第 I 个 SoftMax 单元的净输入 sI??(3)。然而,与早期网络中的独立二进制激活不同,这里所有三个 SoftMax 输出单元
将受到 sI(3)的影响,因为

因此,导数
可以写成:

现在,正如刚才所说的,由于 s i (3) 影响所有的输出 z k (3) 在 SoftMax 层,

偏导数的各个分量如下:

对于
、









事实证明,相对于第 I 个 SoftMax 单元的净输入的成本导数是预测第 I 个 SoftMax 输出单元的输出的误差。结合
和
,我们得到如下:

类似地,对于第 I 个 SoftMax 输出单元的偏置项,我们有如下:

计算成本函数相对于前一层,即
中的权重 wkj【1】的偏导数,将具有与具有独立二进制类的网络的情况相同的形式。这是显而易见的,因为网络仅在输出单元的激活函数方面不同,即使如此,我们得到的
和
的表达式仍然相同。作为练习,感兴趣的读者可以验证
是否仍然成立。
深度学习与传统方法
在本书中,我们将使用谷歌的 TensorFlow 作为深度学习库,因为它有几个优点。在继续讨论 TensorFlow 之前,让我们看看深度学习的一些关键优势,以及如果它没有用在正确的地方,它的一些缺点。
- 深度学习在几个领域远远超过了传统的机器学习方法,特别是在计算机视觉、语音识别、自然语言处理和时间序列领域。
- 通过深度学习,随着深度学习神经网络中层数的增加,可以学习越来越复杂的特征。由于这种自动特征学习属性,深度学习减少了特征工程时间,这在传统的机器学习方法中是一种耗时的活动。
- 深度学习最适合非结构化数据,并且存在大量图像、文本、语音、传感器数据等形式的非结构化数据,这些数据在分析时将彻底改变不同的领域,如医疗保健、制造、银行、航空、电子商务等。
深度学习的一些限制如下:

图 2-17。
Performance comparison of traditional methods versus deep-learning methods
- 深度学习网络通常倾向于具有大量参数,对于这样的实现,应该有足够大的数据量来训练。如果没有足够的数据,深度学习方法将不会很好地工作,因为模型将遭受过拟合。
- 深度学习网络学习到的复杂特征通常很难解释。
- 由于模型中的大量权重以及数据量,深度学习网络需要大量的计算能力来训练。
当数据量较少时,传统方法往往比深度学习方法表现更好。然而,当数据量巨大时,深度学习方法以巨大的优势战胜传统方法,这已经在图 2-17 中粗略描绘。
TensorFlow
Google 的 TensorFlow 是一个开源库,主要专注于深度学习。它使用计算数据流图来表示复杂的神经网络架构。图中的节点表示数学计算,也称为 ops(运算),而边表示它们之间传输的数据张量。此外,相关梯度存储在计算图的每个节点处,并且在反向传播期间,这些梯度被组合以获得关于每个权重的梯度。张量是 TensorFlow 使用的多维数据数组。
常见的深度学习包
常见的深度学习包如下:
- torch——一个科学计算框架,使用底层 C 实现和 LuaJIT 作为脚本语言。Torch 最初发布于 2002 年。Torch 运行的操作系统有 Linux、Android、Mac OS X 和 iOS。著名的组织如脸书人工智能研究所和 IBM 使用火炬。Torch 可以利用 GPU 进行快速计算。
- the ano–是 Python 中的深度学习包,主要用于计算密集型的研究型活动。它与 Numpy 数组紧密集成,并具有高效的符号区分器。它还提供了对 GPU 的透明使用,以加快计算速度。
- caffe——由 Berkeley AI Research ( BAIR )开发的深度学习框架。速度使 Caffe 成为研究实验和工业部署的完美选择。Caffe 实现可以非常高效的使用 GPU。
- cud nn–cud nn 代表 CUDA 深度神经网络库。它为深度神经网络的 GPU 实现提供了一个原语库。
- tensor flow——谷歌的开源深度学习框架,灵感来自 Theano。TensorFlow 正在慢慢成为研究型工作和生产实施中深度学习的首选库。此外,对于云上的分布式生产实施,TensorFlow 正在成为首选库。
- MxNet——开源深度学习框架,可以扩展到多个 GPU 和机器。由 AWS、Azure 等主要云提供商支持。流行的机器学习库 GraphLab 使用 MxNet 实现了很好的深度学习。
- deep learning 4j——面向 Java 虚拟机的开源分布式深度学习框架。
这些深度学习框架的一些显著特征如下:
- Python 是 TensorFlow 和 Theano 选择的高级语言,而 Lua 是 Torch 选择的高级语言。MxNet 也有 Python APIs。
- TensorFlow 和 Theano 在性质上非常相似。TensorFlow 对分布式系统的支持更好。Theano 是一个学术项目,而 TensorFlow 是由谷歌资助的。
- TensorFlow、Theano、MxNet 和 Caffe 都使用自动微分器,而 Torch 使用 AutoGrad。自动微分器不同于符号微分和数值微分。自动微分器在神经网络中使用时非常有效,因为反向传播学习方法利用了微分的链式法则。
- 对于云上的生产实现,TensorFlow 正在成为面向大型分布式系统的应用程序的首选平台。
tensorflow 安装
TensorFlow 可以轻松安装在基于 Linux、Mac OS 和 Windows 的机器上。为 TensorFlow 创建单独的环境总是更可取。需要注意的一点是,在 Windows 中安装 TensorFlow 需要您的 Python 版本大于或等于 3.5。就此而言,基于 Linux 的机器或 Mac OS 不存在这样的限制。基于 Windows 的机器的安装细节在 TensorFlow 的官方网站上有很好的记录: https://www.tensorflow.org/install/install_windows 。基于 Linux 的机器和 Mac OS 的安装链接是:
https://www.tensorflow.org/install/install_linux
https://www.tensorflow.org/install/install_mac
TensorFlow 开发基础
TensorFlow 有自己的命令格式来定义和操作张量。此外,TensorFlow 在激活的会话中执行计算图形。清单 2-1 到 2-15 是一些基本的 TensorFlow 命令,用于定义张量和 TensorFlow 变量,并在会话中执行 TensorFlow 计算图。
import tensorflow as tf
import numpy as np
Listing 2-1.Import TensorFlow and Numpy Library
tf.InteractiveSession()
Listing 2-2.Activate a TensorFlow Interactive Session
a = tf.zeros((2,2));
b = tf.ones((2,2))
Listing 2-3.Define Tensors
tf.reduce_sum(b,reduction_indices = 1).eval()
-- output --
array([ 2., 2.], dtype=float32)
Listing 2-4.Sum the Elements of the Matrix (2D Tensor) Across the Horizontal Axis
要在交互模式下运行 TensorFlow 命令,可以调用清单 2-2 中的Interactive Session()命令,通过使用eval()方法,可以在激活的交互会话下运行 TensorFlow 命令,如清单 2-4 所示。
a.get_shape()
-- output --
TensorShape([Dimension(2), Dimension(2)])
Listing 2-5.
Check
the Shape of the Tensor
tf.reshape(a,(1,4)).eval()
-- output --
array([[ 0., 0., 0., 0.]], dtype=float32)
Listing 2-6.Reshape a Tensor
ta = tf.zeros((2,2))
print(ta)
-- output --
Tensor("zeros_1:0", shape=(2, 2), dtype=float32)
print(ta.eval())
-- output --
[[ 0\. 0.]
[ 0\. 0.]]
a = np.zeros((2,2))
print(a)
-- output --
[[ 0\. 0.]
[ 0\. 0.]]
Listing 2-7.
Explicit Evaluation
in TensorFlow and Difference with Numpy
a = tf.constant(1)
b = tf.constant(5)
c= a*b
Listing 2-8.Define TensorFlow Constants
with tf.Session() as sess:
print(c.eval())
print(sess.run(c))
-- output --
5
5
Listing 2-9.
TensorFlow Session
for Execution of the Commands Through Run and Eval
w = tf.Variable(tf.ones(2,2),name='weights')
Listing 2-10a.Define TensorFlow Variables
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(sess.run(w))
-- output --
[[ 1\. 1.]
[ 1\. 1.]]
Listing 2-10b.Initialize the Variables
After Invoking the Session
TensorFlow 会话一般通过清单 2-10b 所示的tf.Session() as激活,计算图操作(ops)在激活的会话下执行。
rw = tf.Variable(tf.random_normal((2,2)),name='random_weights')
Listing 2-11a.Define the TensorFlow Variable with Random Initial Values
from Standard Normal Distribution
with tf.Session()as sess:
sess.run(tf.global_variables_initializer())
print(sess.run(rw))
-- output --
[[ 0.37590656 -0.11246648]
[-0.61900514 -0.93398571]]
Listing 2-11b.Invoke Session and Display
the Initial State of the Variable
如清单 2-11b 所示,run方法用于在激活的会话中执行计算操作(ops),当run初始化定义的 TensorFlow 变量时使用tf.global_variables_initializer()。2-11a 中定义的随机变量rw在清单 2-11b 中被初始化。
var_1 = tf.Variable(0,name='var_1')
add_op = tf.add(var_1,tf.constant(1))
upd_op = tf.assign(var_1,add_op)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for i in xrange(5):
print(sess.run(upd_op))
-- output --
1
2
3
4
5
Listing 2-12.TensorFlow Variable State Update
x = tf.constant(1)
y = tf.constant(5)
z = tf.constant(7)
mul_x_y = x*y
final_op = mul_x_y + z
with tf.Session() as sess:
print(sess.run([mul_x_y,final_op]))
-- output --
5 12
Listing 2-13.Display the TensorFlow Variable State
a = np.ones((3,3))
b = tf.convert_to_tensor(a)
with tf.Session() as sess:
print(sess.run(b))
-- output --
[[ 1\. 1\. 1.]
[ 1\. 1\. 1.]
[ 1\. 1\. 1.]]
Listing 2-14.Convert a Numpy Array to Tensor
inp1 = tf.placeholder(tf.float32,shape=(1,2))
inp2 = tf.placeholder(tf.float32,shape=(2,1))
output = tf.matmul(inp1,inp2)
with tf.Session() as sess:
print(sess.run([output],feed_dict={inp1:[[1.,3.]],inp2:[[1],[3]]}))
-- output --
[array([[ 10.]], dtype=float32)]
Listing 2-15.
Placeholders and Feed Dictionary
TensorFlow 占位符定义了一个变量,该变量的数据将在稍后的时间点进行赋值。运行涉及 TensorFlow 占位符的 ops 时,数据通常通过feed_dict传递给占位符。清单 2-15 对此进行了说明。
深度学习视角下的梯度下降优化方法
在我们深入研究 TensorFlow 优化器之前,重要的是要了解关于整批梯度下降和随机梯度下降的几个关键点,包括它们的缺点,以便人们能够理解提出这些基于梯度的优化器的变体的需要。
椭圆形轮廓
具有最小平方误差的线性神经元的成本函数是二次的。当成本函数为二次型时,全批次梯度下降法得到的梯度方向给出了线性意义下成本降低的最佳方向,但它并不指向最小值,除非成本函数的不同椭圆轮廓是圆。在长椭圆轮廓的情况下,梯度分量在需要较少变化的方向上可能较大,而在需要更多变化以移动到最小点的方向上可能较小。
如图 2-18 所示,S 处的梯度并不指向最小值的方向;即点 m。这种情况的问题是,如果我们通过使学习率变小来采取小的步骤,那么梯度下降将需要一段时间来收敛,而如果我们使用大的学习率,梯度将在成本函数具有曲率的方向上快速改变方向,导致振荡。多层神经网络的成本函数不是二次函数,而是平滑函数。局部地,这种非二次成本函数可以用二次函数来近似,因此椭圆轮廓固有的梯度下降问题对于非二次成本函数仍然普遍存在。

图 2-18。
Contour plot for a quadratic cost function with elliptical contours
解决这个问题的最好方法是在梯度小但一致的方向上迈出较大的步伐,在梯度大但不一致的方向上迈出较小的步伐。如果我们对每个维度都有一个单独的学习率,而不是对所有维度都有一个固定的学习率,这是可以实现的。

图 2-19。
Gradient descent for a cost function with one variable
在图 2-19 中,A 和 C 之间的成本函数几乎是线性的,因此梯度下降效果很好。然而,从点 C 开始,成本函数的曲率接管,因此 C 处的梯度不能跟上成本函数的变化方向。基于梯度,如果我们在 C 取一个小的学习率,我们将在 D 结束,这是足够合理的,因为它没有超过最小值点。然而,C 处的较大步长会使我们到达 D’,这是不可取的,因为它在最小值的另一侧。同样,D '处的大步长将使我们到达 E,如果学习速率没有降低,算法倾向于在最小值两侧的点之间切换,导致振荡。当这种情况发生时,停止它并实现收敛的一种方法是在连续迭代中查看梯度
或
的符号,如果它们具有相反的符号,则降低学习速率,从而减少振荡。类似地,如果连续的梯度具有相同的符号,那么学习率可以相应地增加。当成本函数是多个权重的函数时,成本函数可能在权重的某些维度上具有曲率,而在其他维度上可能是线性的。因此,对于多元成本函数,可以类似地分析成本函数相对于每个权重
的偏导数,以更新成本函数的每个权重或维度的学习率。
成本函数的非凸性
神经网络的另一个大问题是成本函数大多是非凸的,因此梯度下降法可能会陷入局部最小点,导致次优解。神经网络的非凸性质是具有非线性激活函数(如 sigmoid)的隐藏层单元的结果。整批梯度下降使用整个数据集进行梯度计算。虽然这对于凸成本表面是好的,但是在非凸成本函数的情况下,它有它自己的问题。对于具有整批梯度的非凸成本表面,模型将以其吸引盆中的最小值结束。如果初始化的参数处于局部最小值的吸引盆中,而该局部最小值不能提供良好的泛化能力,则全批次梯度将给出次优解。
使用随机梯度下降,计算出的噪声梯度可能会迫使模型脱离不良局部极小值(不能提供良好泛化能力的极小值)的吸引范围,并将其置于更优的区域。具有单个数据点的随机梯度下降产生非常随机和嘈杂的梯度。与单个数据点的梯度相比,小批次的梯度往往会产生更稳定的梯度估计,但它们仍然比全批次产生的梯度更嘈杂。理想地,应该仔细选择小批量大小,使得梯度足够嘈杂以避免或逃脱坏的局部极小点,但是足够稳定以收敛于全局极小点或提供良好泛化的局部极小点。

图 2-20。
Contour plot showing basins of attraction for global and local minima and traversal of paths for gradient descent and stochastic gradient descent
在图 2-20 中,虚线箭头对应随机梯度下降(SGD)的路径,实线箭头对应全批次梯度下降的路径。全批次梯度下降计算某一点的实际梯度,如果该点在一个差的局部最小值的吸引盆中,梯度下降几乎肯定确保达到局部最小值 L。然而,在随机梯度下降的情况下,因为梯度仅基于部分数据,而不是基于整批数据,所以梯度方向只是粗略的估计。由于有噪声的粗略估计并不总是指向点 C 处的实际梯度,随机梯度下降可以避开局部最小值的吸引盆,并且幸运地落在全局最小值的吸引盆中。随机梯度下降也可以避开全局最小吸引盆,但是一般来说,如果吸引盆很大,并且仔细选择小批量大小,使得它产生的梯度适度嘈杂,则随机梯度下降最有可能达到全局最小值 G(如在这种情况下)或具有大吸引盆的一些其他最优最小值。对于非凸优化,也有其他启发式算法,如动量,当与随机梯度下降一起采用时,增加了 SGD 避免浅局部最小值的机会。动量通常通过速度分量跟踪先前的梯度。所以,如果梯度稳定地指向一个好的局部极小值,这个极小值有一个大的吸引盆,那么速度分量在好的局部极小值的方向上会很高。如果新的梯度是有噪声的,并且指向坏的局部最小值,速度分量将提供动量以在相同的方向上继续,并且不会受到新梯度太多的影响。
高维代价函数中的鞍点
优化非凸成本函数的另一个障碍是鞍点的存在。鞍点的数量随着代价函数的参数空间的维数增加而指数增加。鞍点是静止点(即梯度为零的点),但既不是局部最小值也不是局部最大值点。由于鞍点与具有与鞍点相同成本的点的长平台相关联,所以平台区域中的梯度为零或非常接近零。由于这种在所有方向上接近零的梯度,基于梯度的优化器很难走出这些鞍点。数学上,为了确定一个点是否是鞍点,必须在给定点计算成本函数的 Hessian 矩阵的特征值。如果既有正负特征值,那么它就是一个鞍点。只是为了刷新我们对局部和全局最小值测试的记忆,如果 Hessian 矩阵的所有特征值在静止点处都是正的,则该点是全局最小值,而如果 Hessian 矩阵的所有特征值在静止点处都是负的,则该点是全局最大值。成本函数的 Hessian 矩阵的特征向量给出了成本函数曲率的变化方向,而特征值表示曲率沿这些方向变化的幅度。此外,对于具有连续二阶导数的成本函数,Hessian 矩阵是对称的,因此将总是产生一组正交的特征向量,从而给出成本曲率变化的相互正交的方向。如果在所有这些由特征向量给出的方向上,曲率变化的值(特征值)是正的,那么该点一定是局部最小值,而如果所有曲率变化的值都是负的,那么该点就是局部最大值。这种推广适用于具有任何输入维度的成本函数,而用于确定极值点的判定规则随着成本函数的输入维度而变化。回到鞍点,由于特征值对于某些方向是正的,而对于其他方向是负的,所以成本函数的曲率在正特征值的方向上增加,而在具有负系数的特征向量的方向上减少。围绕鞍点的成本表面的这种性质通常导致具有接近于零的梯度的长平台区域,并且使得梯度下降方法难以脱离这种低梯度的平台。点(0,0)是函数
的鞍点,我们可以从下面的评估中看出:
= >
和


所以,
是一个不动点。接下来要做的是计算 Hessian 矩阵,并在
评估其特征值。海森矩阵 Hf(x,y)如下:
![$$ Hf\left(x,y\right)=\left[\begin{array}{cc}\frac{\partial²f}{\partial {x}²}& \frac{\partial²f}{\partial x\partial y}\ {}\frac{\partial²f}{\partial x\partial y}& \frac{\partial²f}{\partial {y}²}\end{array}\right]=\left[\begin{array}{cc}2& 0\ {}0& -2\end{array}\right] $$](A448418_1_En_2_Chapter_Equcb.gif)
所以,包括
在内的所有点的 Hessian Hf(x,y)是![$$ \left[\begin{array}{cc}2& 0\ {}0& -2\end{array}\right]. $$](A448418_1_En_2_Chapter_IEq150.gif)
Hf(x,Y)的两个本征值分别是 2 和-2,对应的本征向量
和
,无非是沿着 X 轴和 Y 轴的方向。由于一个特征值为正,另一个为负,
就是鞍点。
非凸函数
如图 2-21 所示,其中 S 为
处的鞍点

图 2-21。
Plot of 
随机梯度下降小批量方法中的学习速率
当数据集中存在高冗余度时,在小批量数据点上计算的梯度几乎与在整个数据集上计算的梯度相同,前提是小批量数据点是整个数据集的良好代表。在这种情况下,可以避免计算整个数据集的梯度,而是可以将小批量数据点的梯度用作整个数据集的近似梯度。这是梯度下降的小批量方法,也称为小批量随机梯度下降。当梯度由一个数据点近似而不是使用小批量时,它被称为在线学习或随机梯度下降。然而,与在线学习相比,使用随机梯度下降的小批量版本总是更好,因为与在线学习模式相比,小批量方法的梯度噪音更小。学习速率在小批量随机梯度下降算法的收敛性中起着至关重要的作用。以下方法倾向于提供良好的收敛性:
- 从最初的学习速度开始。
- 如果误差减小,则增加学习率。
- 如果误差增加,降低学习率。
- 如果误差不再减小,停止学习过程。
正如我们将在下一节中看到的,不同的优化器在它们的实现中采用了自适应学习率的方法。
TensorFlow 中的优化器
TensorFlow 拥有大量用于优化成本函数的优化器。优化器都是基于梯度的,还有一些特殊的优化器来处理局部极小问题。由于我们在第一章中讨论了机器学习和深度学习中使用的最常见的基于梯度的优化器,这里我们将强调 TensorFlow 中添加到基本算法的定制。
梯度下降优化器
GradientDescentOptimizer执行基本的整批梯度下降算法,并将学习率作为输入。梯度下降算法不会自动循环迭代,所以必须在实现中指定这样的逻辑,我们将在后面看到。
最重要的方法是minimize方法,其中需要指定最小化的成本函数(用loss表示)和成本函数必须最小化的变量列表(用var_list表示)。minimize方法在内部调用compute_gradients()和apply_gradients()方法。声明变量列表是可选的,如果未指定,则根据定义为 TensorFlow 变量的变量(即声明为tensorflow.Variable()的变量)计算梯度
使用
train_op = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)
其中learning_ rate是常数学习率,cost是需要通过梯度下降最小化的成本函数。成本函数相对于与成本函数相关联的 TensorFlow 变量被最小化。
adagradpoptimizer
AdagradOptimizer是一个一阶优化器,类似梯度下降,但有一些修改。不是具有全局学习率,而是对成本函数所依赖的每个维度标准化学习率。每次迭代中的学习率是全局学习率除以到每个维度的当前迭代为止的先前梯度的 l 2 范数。
如果我们有一个成本函数 C(θ)其中
,那么θ i 的更新规则如下:

其中η是学习率,θ i (t) 和
分别是迭代 t 和
时第 I 个参数的值。
在矩阵格式中,向量θ的参数更新可以表示为如下:

其中 G (t) 是对角矩阵,其包含每个维度直到迭代 t 之前的过去梯度的 l 2 范数。矩阵 G (t) 将具有以下形式:
![$$ {G}_{(t)}=\left[\begin{array}{ccc}\sqrt{\sum \limits_{\tau =1}t{{\theta_1}{\left(\tau \right)}}²+\epsilon }& \cdots & 0\ {}\vdots & \sqrt{\sum \limits_{\tau =1}t{{\theta_i}{\left(\tau \right)}}²+\epsilon }& \vdots \ {}0& \cdots & \sqrt{\sum \limits_{\tau =1}t{{\theta_n}{\left(\tau \right)}}²+\epsilon}\end{array}\right] $$](A448418_1_En_2_Chapter_Equce.gif)
有时,在数据中不常出现的稀疏特征对于优化问题非常有用。然而,对于基本梯度下降或随机梯度下降,学习率在每次迭代中给予所有特征同等的重要性。由于学习率相同,非稀疏特征的总体贡献将比稀疏特征大得多。因此,我们最终会丢失稀疏特征中的关键信息。使用Adagrad,每个参数以不同的学习率更新。特征越稀疏,其参数更新在迭代中就越高。这是因为对于稀疏特征,数量
会更少,因此整体学习速率会更高。
在数据稀疏的自然语言处理和图像处理应用程序中,这是一个很好的优化器。
使用
train_op = tf.train.AdagradOptimizer.(learning_rate=0.001, initial_accumulator_value=0.1)
其中learning_rate代表η,initial_accumulator_value代表每个权重的初始非零归一化因子。
RMSprop
RMSprop是弹性反向传播(Rprop)优化技术的小批量版本,最适合整批学习。Rprop解决了在成本函数轮廓为椭圆形的情况下梯度不指向最小值的问题。如前所述,在这种情况下,不同于全局学习规则,针对每个权重的单独的自适应更新规则会导致更好的收敛。Rprop的特别之处在于,它不使用权重梯度的大小,而只使用符号来确定如何更新每个权重。以下是Rprop的工作逻辑:
-
对于所有权重,以相同幅度的权重更新开始;即
。此外,将最大和最小允许重量更新分别设置为δmax和δmin。 -
在每次迭代中,检查先前和当前梯度分量的符号;即成本函数相对于不同权重的偏导数。
-
If the signs of the current and previous gradient components for a weight connection are the same—i.e.,
—then increase the learning by a factor
. The update rule becomes![$$ {\varDelta_{ij}}^{\left(t+1\right)}=\min \left({\eta}_{+}{\varDelta_{ij}}^{(t)},{\varDelta}_{max}\right) $$]()
![$$ {w_{ij}}{\left(t+1\right)}={w_{ij}}{(t)}-\mathit{\operatorname{sign}}\left(\frac{\partial {C}^{(t)}}{\partial {w}_{ij}}\right).{\varDelta_{ij}}^{\left(t+1\right)} $$]()
-
If the signs of the current and previous gradient components for a dimension are different—i.e.,
—then reduce the learning rate by a factor
. The update rule becomes![$$ {\varDelta_{ij}}^{\left(t+1\right)}=\max \left({\eta}_{-}{\varDelta_{ij}}^{(t)},{\varDelta}_{min}\right) $$]()
![$$ {w_{ij}}{\left(t+1\right)}={w_{ij}}{(t)}-\mathit{\operatorname{sign}}\left(\frac{\partial {C}^{(t)}}{\partial {w}_{ij}}\right).{\varDelta_{ij}}^{\left(t+1\right)} $$]()
-
If
, the update rule is as follows:![$$ {\varDelta_{ij}}{\left(t+1\right)}={\varDelta_{ij}}{(t)} $$]()
![$$ {w_{ij}}{\left(t+1\right)}={w_{ij}}{(t)}-\mathit{\operatorname{sign}}\left(\frac{\partial {C}^{(t)}}{\partial {w}_{ij}}\right).{\varDelta_{ij}}^{\left(t+1\right)} $$]()
在梯度下降期间,梯度在特定间隔内不改变符号的维度是重量变化一致的维度。因此,增加学习速率将导致这些权重更快地收敛到它们的最终值。
梯度沿其改变符号的维度表明,沿这些维度的权重变化是不一致的,因此通过降低学习速率,可以避免振荡并更好地赶上曲率。对于凸函数,当代价函数曲面存在曲率且学习率设置较高时,通常会出现梯度符号变化。由于梯度不具有曲率信息,大的学习率将更新的参数值带到极小点之外,并且该现象在极小点的任一侧持续重复。
Rprop适用于完整批次,但在涉及随机梯度下降时效果不佳。当学习速率非常小时,在随机梯度下降的情况下,来自不同小批量的梯度达到平均。如果通过成本函数的随机梯度下降,当学习速率较小时,重量的梯度对于九个小批量是+0.2,对于第十个小批量是-0.18,则随机梯度下降的有效梯度效应几乎为零,重量几乎保持在相同的位置,这是期望的结果。
然而,对于Rprop,学习率将增加大约九倍,并且仅减少一次,因此有效权重将远大于零。这是不可取的。
为了将Rprop针对每个权重的自适应学习规则的质量与随机梯度下降的效率相结合,RMSprop出现了。在Rprop中,我们不使用大小,而是仅仅使用每个重量梯度的符号。每个权重的梯度的符号可以被认为是将权重的梯度除以其大小。随机梯度下降的问题在于,对于每个小批量,成本函数不断变化,因此梯度也不断变化。因此,我们的想法是获得一个重量梯度的大小,它不会在附近的小批量中波动太大。比较好的方法是,在最近的小批量中,每个重量的平方梯度的均方根,以标准化梯度。


其中 g (t) 是迭代 t 时权重 w ij 的梯度均方根,α是每个权重 w ij 的梯度均方根的衰减率。
使用
train_op = tf.train.RMSPropOptimizer(learning_rate=0.001, decay =0.9, momentum=0.0, epsilon=1e-10)
其中decay代表α,ε代表ϵ,η代表学习率。
AdadeltaOptimizer
AdadeltaOptimizer是AdagradOptimizer的变体,在降低学习率方面不那么激进。对于每个权重连接,AdagradOptimizer通过将学习速率常数除以该权重直到该迭代的所有过去梯度的均方根来缩放迭代中的学习速率常数。因此,每个权重的有效学习率是迭代次数的单调递减函数,并且在相当多的迭代次数之后,学习率变得无穷小。AdagradOptimizer通过对每个重量或维度取指数衰减平方梯度的平均值,克服了这个问题。因此,AdadeltaOptimizer中的有效学习率更多的是对其当前梯度的局部估计,不会像AdagradOptimizer方法那样快速下降。这确保了即使在相当数量的迭代或时期之后学习仍在继续。Adadelta的学习规则可以总结如下:


其中γ是指数衰减常数,η是学习速率常数,g ij (t) 表示迭代 t 时的有效均方梯度。我们可以将
项表示为 RMS(gij【t】,其给出的更新规则如下:
如果我们仔细观察,重量变化的单位没有重量的单位。
和 RMS 的单位(g ij (t) )是相同的,即梯度的单位(成本函数变化/单位重量变化),因此它们相互抵消。因此,权重变化的单位是学习速率常数的单位。Adadelta通过将学习速率常数η替换为到当前迭代为止的指数衰减平方权重更新的平均值的平方根来解决这个问题。设 h ij (t) 为迭代 t 前权值更新的平方的平均值,β为衰减常数,δwij(t)为迭代 t 中的权值更新,则 h ij (t) 的更新规则和Adadelta的最终权值更新规则可表示如下:

如果我们将
表示为 RMS(h ij (t) ,那么更新规则变成-

使用
train_op = tf.train.AdadeltaOptimizer(learning_rate=0.001, rho=0.95, epsilon=1e-08)
其中rho代表γ,epsilon代表ϵ,η代表学习率。
Adadelta的一个显著优点是它完全消除了学习速率常数。如果我们比较Adadelta和RMSprop,,如果我们把学习率常数消去法放在一边,两者是一样的。Adadelta和RMSprop几乎同时独立开发,以解决Adagrad的快速学习率衰减问题。
阿达莫优化器
Adam,或自适应矩估计器,是另一种优化技术,很像RMSprop或Adagrad,对每个参数或权重有一个自适应的学习率。Adam不仅保持平方梯度的移动平均值,还保持过去梯度的移动平均值。
设每个权重 w ij 的梯度均值 mij??【t】和梯度平方均值 vij??【t】的衰减率分别为β 1 和β 2 。此外,假设η是常数学习率因子。那么,Adam的更新规则如下:


梯度的归一化平均值
和平方梯度的平均值
计算如下:


每个权重 w ij 的最终更新规则如下:

使用
train_op = tf.train.AdamOptimizer(learning_rate=0.001,beta1=0.9,beta2=0.999,epsilon=1e-08).minimize(cost)
其中learning_rate是常数学习率η,cost C 是需要通过AdamOptimizer最小化的代价函数。参数beta1和beta2分别对应β 1 和β 2 ,而epsilon代表
。
成本函数相对于与成本函数相关联的 TensorFlow 变量被最小化。
动量优化器和内斯特罗夫算法
基于动量的优化器已经发展到处理非凸优化。每当我们使用神经网络时,我们通常得到的成本函数本质上是非凸的,因此基于梯度的优化方法可能会陷入糟糕的局部最小值。如前所述,这是非常不可取的,因为在这种情况下,我们得到的是优化问题的次优解,很可能是次优模型。此外,梯度下降遵循每个点的斜率,并向局部最小值小幅前进,但它可能非常慢。基于动量的方法引入了称为速度 v 的分量,当所计算的梯度改变符号时,该分量抑制参数更新,而当梯度与速度方向相同时,该分量加速参数更新。这引入了更快的收敛以及围绕全局最小值或围绕提供良好泛化能力的局部最小值的更少振荡。基于动量的优化器的更新规则如下:


其中,α是动量参数,η是学习率。项 v i (t) 和
分别表示第 I 个参数在迭代 t 和
时的速度。类似地,w i (t) 和
分别代表第 I 个参数在迭代 t 和
时的权重。
想象在优化成本函数时,优化算法在
处达到局部最小值。在不考虑动量的正常梯度下降方法中,参数更新将在局部最小值或鞍点处停止。然而,在基于动量的优化中,考虑到局部极小值具有小的吸引盆,先验速度会驱使算法脱离局部极小值,因为
会因为来自先验梯度的非零速度而非零。此外,如果先前的梯度一致地指向全局最小值或局部最小值,具有良好的推广性和相当大的吸引盆地,则梯度下降的速度或动量将在该方向上。因此,即使存在具有小吸引盆的坏的局部最小值,动量分量也将不仅驱使算法脱离坏的局部最小值,而且将继续朝向全局最小值或好的局部最小值的梯度下降。
如果权重是参数向量θ的一部分,则基于动量的优化器的矢量化更新规则将如下(参见图 2-22 的基于向量的图示):



图 2-22。
Parameter vector update in momentum-based gradient-descent optimizer
基于动量的优化器的一个特殊变体是内斯特罗夫加速梯度技术。该方法利用现有的速度 v (t) 对参数向量进行更新。因为它是对参数向量的中间更新,所以用
来表示很方便。在
评估成本函数的梯度,并将其用于更新新的速度。最后,新的参数向量是前一次迭代的参数向量和新的速度之和。



使用
train_op = tf.train.MomentumOptimizer.(learning_rate=0.001, momentum=0.9,use_nesterov=False)
其中learning_rate代表η,momentum代表α,use_nesterov决定是否使用内斯特罗夫版动量。
纪元、批次数量和批次大小
深度学习网络,如前所述,一般通过小批量随机梯度下降来训练。我们需要熟悉的一些术语如下:
- 批量大小–批量大小决定了每个小批量中训练数据点的数量。应该选择批量大小,使得它对整个训练数据集的梯度给出足够好的估计,同时噪声足够大,以避开不提供良好泛化的坏的局部最小值。
- 批次数量–批次数量给出了整个训练数据集中小型批次的总数。它可以通过将总训练数据点的计数除以批量大小来计算。请注意,最后一个小批量的数据点数量可能比批量少。
- 历元–一个历元由对整个数据集的一次完整训练组成。更具体地说,一个历元相当于整个训练数据集上的一次正向传递加上一次反向传播。因此,一个时期将由 n 个(正向传递+反向传播)组成,其中 n 表示批次的数量。
使用 TensorFlow 实现 XOR
既然我们对人工神经网络所涉及的组件和训练方法有了大致的了解,我们将在隐藏层和输出中使用 sigmoid 激活函数来实现 XOR 网络。清单 2-16 中概述了详细的实现。
#-------------------------------------------------------------------------------------------
#XOR implementation in Tensorflow with hidden layers being sigmoid to
# introduce Non-Linearity
#-------------------------------------------------------------------------------------------
import tensorflow as tf
#-------------------------------------------------------------------------------------------
# Create placeholders for training input and output labels
#-------------------------------------------------------------------------------------------
x_ = tf.placeholder(tf.float32, shape=[4,2], name="x-input")
y_ = tf.placeholder(tf.float32, shape=[4,1], name="y-input")
#-------------------------------------------------------------------------------------------
#Define the weights to the hidden and output layer respectively.
#-------------------------------------------------------------------------------------------
w1 = tf.Variable(tf.random_uniform([2,2], -1, 1), name="Weights1")
w2 = tf.Variable(tf.random_uniform([2,1], -1, 1), name="Weights2")
#-------------------------------------------------------------------------------------------
# Define the bias to the hidden and output layers respectively
#-------------------------------------------------------------------------------------------
b1 = tf.Variable(tf.zeros([2]), name="Bias1")
b2 = tf.Variable(tf.zeros([1]), name="Bias2")
#-------------------------------------------------------------------------------------------
# Define the final output through forward pass
#-------------------------------------------------------------------------------------------
z2 = tf.sigmoid(tf.matmul(x_, w1) + b1)
pred = tf.sigmoid(tf.matmul(z2,w2) + b2)
#------------------------------------------------------------------------------------------
#Define the Cross-entropy/Log-loss Cost function based on the output label y and
# the predicted probability by the forward pass
#-------------------------------------------------------------------------------------------
cost = tf.reduce_mean(( (y_ * tf.log(pred)) +
((1 - y_) * tf.log(1.0 - pred)) ) * -1)
learning_rate = 0.01
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)
#-------------------------------------------------------------------------------------------
#Now that we have all that we need set up we will start the training
#-------------------------------------------------------------------------------------------
XOR_X = [[0,0],[0,1],[1,0],[1,1]]
XOR_Y = [[0],[1],[1],[0]]
#-------------------------------------------------------------------------------------------
# Initialize the variables
#-------------------------------------------------------------------------------------------
init = tf.initialize_all_variables()
sess = tf.Session()
writer = tf.summary.FileWriter("./Downloads/XOR_logs", sess.graph_def)
sess.run(init)
for i in range(100000):
sess.run(train_step, feed_dict={x_: XOR_X, y_: XOR_Y})
#-------------------------------------------------------------------------------------------
print('Final Prediction', sess.run(pred, feed_dict={x_: XOR_X, y_: XOR_Y}))
#-------------------------------------------------------------------------------------------
--output --
('Final Prediction', array([[ 0.06764214],
[ 0.93982035],
[ 0.95572311],
[ 0.05693595]], dtype=float32))
Listing 2-16.XOR Implementation with Hidden Layers That Have Sigmoid Activation Functions
在清单 2-16 中,XOR 逻辑是使用 TensorFlow 实现的。隐藏层单元具有 sigmoid 激活函数以引入非线性。输出激活函数具有 sigmoid 激活函数,以给出概率输出。我们使用梯度下降优化器,学习率为 0.01,总迭代次数约为 100,000 次。如果我们看到最终预测,第一个和第四个训练样本的概率值接近于零,而第二个和第四个训练样本的概率接近于 1。因此,该网络可以准确且高精度地预测类别。任何合理的阈值都会正确分类数据点。
异或网络的 TensorFlow 计算图
在图 2-23 中,说明了前面实现的 XOR 网络的计算图。通过包含以下代码行,计算图摘要被写入日志文件。短语". /Downloads/XOR_logs"表示摘要日志文件的存储位置。然而,它可以是你选择的任何位置。
writer = tf.summary.FileWriter("./Downloads/XOR_logs", sess.graph_def)
将摘要写入终端上的日志文件后,我们需要执行以下命令来激活 Tensorboard:
tensorboard --logdir=./Downloads/XOR_logs
这将启动 Tensorboard 会话,并提示我们在http://localhost:6006访问 Tensorboard,在这里可以可视化计算图形。

图 2-23。
Computation graph for the XOR network
现在,我们再次实现 XOR 逻辑,在隐藏层使用线性激活函数,并保持网络的其余部分不变。清单 2-17 展示了 TensorFlow 的实现。
#-------------------------------------------------------------------------------------------
#XOR implementation in TensorFlow with linear activation for hidden layers
#-------------------------------------------------------------------------------------------
import tensorflow as tf
#-------------------------------------------------------------------------------------------
# Create placeholders for training input and output labels
#-------------------------------------------------------------------------------------------
x_ = tf.placeholder(tf.float32, shape=[4,2], name="x-input")
y_ = tf.placeholder(tf.float32, shape=[4,1], name="y-input")
#-------------------------------------------------------------------------------------------
#Define the weights to the hidden and output layer respectively.
#-------------------------------------------------------------------------------------------
w1 = tf.Variable(tf.random_uniform([2,2], -1, 1), name="Weights1")
w2 = tf.Variable(tf.random_uniform([2,1], -1, 1), name="Weights2")
#------------------------------------------------------------------------------------------
# Define the bias to the hidden and output layers respectively
#-------------------------------------------------------------------------------------------
b1 = tf.Variable(tf.zeros([2]), name="Bias1")
b2 = tf.Variable(tf.zeros([1]), name="Bias2")
#-------------------------------------------------------------------------------------------
# Define the final output through forward pass
#-------------------------------------------------------------------------------------------
z2 = tf.matmul(x_, w1) + b1
pred = tf.sigmoid(tf.matmul(z2,w2) + b2)
#-------------------------------------------------------------------------------------------
#Define the Cross-entropy/Log-loss Cost function based on the output label y and the predicted
#probability by the forward pass
#-------------------------------------------------------------------------------------------
cost = tf.reduce_mean(( (y_ * tf.log(pred)) +
((1 - y_) * tf.log(1.0 - pred)) ) * -1)
learning_rate = 0.01
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)
#-------------------------------------------------------------------------------------------
#Now that we have all that we need, start the training
#-------------------------------------------------------------------------------------------
XOR_X = [[0,0],[0,1],[1,0],[1,1]]
XOR_Y = [[0],[1],[1],[0]]
init = tf.initialize_all_variables()
sess = tf.Session()
sess.run(init)
for i in range(100000):
sess.run(train_step, feed_dict={x_: XOR_X, y_: XOR_Y})
#------------------------------------------------------------------------------------------
print('Final Prediction', sess.run(pred, feed_dict={x_: XOR_X, y_: XOR_Y}))
#-------------------------------------------------------------------------------------------
-- output --
('Final Prediction', array([[ 0.5000003 ],
[ 0.50001115],
[ 0.49998885],
[ 0.4999997 ]], dtype=float32))
Listing 2-17.XOR Implementation with Linear Activation Functions in Hidden Layer
清单 2-2 中显示的最终预测都接近 0.5,这意味着实现的 XOR 逻辑不能很好地区分正类和负类。当我们在隐藏层中具有线性激活函数时,网络主要保持线性,正如我们之前所看到的,因此在需要非线性决策边界来分离类的情况下,该模型不能够做得很好。
TensorFlow 中的线性回归
线性回归可以表示为单神经元回归问题。预测误差的均方值作为成本函数,根据模型的系数进行优化。清单 2-18 显示了使用波士顿房价数据集进行线性回归的 TensorFlow 实现。
#-------------------------------------------------------------------------------------------
# Importing TensorFlow, Numpy, and the Boston Housing price dataset
#-------------------------------------------------------------------------------------------
import tensorflow as tf
import numpy as np
from sklearn.datasets import load_boston
#-------------------------------------------------------------------------------------------
# Function to load the Boston data set
#------------------------------------------------------------------------------------------
def read_infile():
data = load_boston()
features = np.array(data.data)
target = np.array(data.target)
return features,target
#-------------------------------------------------------------------------------------------
# Normalize the features by Z scaling; i.e., subtract from each feature value its mean and then divide by its #standard deviation. Accelerates gradient descent.
#-------------------------------------------------------------------------------------------
def feature_normalize(data):
mu = np.mean(data,axis=0)
std = np.std(data,axis=0)
return (data - mu)/std
#-------------------------------------------------------------------------------------------
# Append the feature for the bias term.
#-------------------------------------------------------------------------------------------
def append_bias(features,target):
n_samples = features.shape[0]
n_features = features.shape[1]
intercept_feature = np.ones((n_samples,1))
X = np.concatenate((features,intercept_feature),axis=1)
X = np.reshape(X,[n_samples,n_features +1])
Y = np.reshape(target,[n_samples,1])
return X,Y
#-------------------------------------------------------------------------------------------
# Execute the functions to read, normalize, and add append bias term to the data
#-------------------------------------------------------------------------------------------
features,target = read_infile()
z_features = feature_normalize(features)
X_input,Y_input = append_bias(z_features,target)
num_features = X_input.shape[1]
#------------------------------------------------------------------------------------------
# Create TensorFlow ops for placeholders, weights, and weight initialization
#-------------------------------------------------------------------------------------------
X = tf.placeholder(tf.float32,[None,num_features])
Y = tf.placeholder(tf.float32,[None,1])
w = tf.Variable(tf.random_normal((num_features,1)),name='weights')
init = tf.global_variables_initializer()
#-------------------------------------------------------------------------------------------
# Define the different TensorFlow ops and input parameters for Cost and Optimization.
#-------------------------------------------------------------------------------------------
learning_rate = 0.01
num_epochs = 1000
cost_trace = []
pred = tf.matmul(X,w)
error = pred - Y
cost = tf.reduce_mean(tf.square(error))
train_op = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)
#-------------------------------------------------------------------------------------------
# Execute the gradient-descent learning
#-------------------------------------------------------------------------------------------
with tf.Session() as sess:
sess.run(init)
for i in xrange(num_epochs):
sess.run(train_op,feed_dict={X:X_input,Y:Y_input})
cost_trace.append(sess.run(cost,feed_dict={X:X_input,Y:Y_input}))
error_ = sess.run(error,{X:X_input,Y:Y_input})
pred_ = sess.run(pred,{X:X_input})
print 'MSE in training:',cost_trace[-1]
-- output --
MSE in training: 21.9711
Listing 2-18.Linear Regression Implementation in TensorFlow

图 2-24。
Cost (MSE) versus epochs while training
#-------------------------------------------------------------------------------------------
# Plot the reduction in cost over iterations or epochs
#-------------------------------------------------------------------------------------------
import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(cost_trace)
Listing 2-18a.Linear Regression Cost Plot over Epochs
or Iterations

图 2-25。
Actual house price versus predicted house price
#-------------------------------------------------------------------------------------------
# Plot the Predicted House Prices vs the Actual House Prices
#-------------------------------------------------------------------------------------------
fig, ax = plt.subplots()
plt.scatter(Y_input,pred_)
ax.set_xlabel('Actual House price')
ax.set_ylabel('Predicted House price')
Listing 2-18b.Linear Regression Actual House Price Versus Predicted House Price
图 2-24 显示了各时期的成本进展,图 2-25 显示了培训后的预测房价与实际房价。
使用全批次梯度下降的 SoftMax 函数进行多类别分类
在本节中,我们将使用整批梯度下降来说明一个多类分类问题。之所以使用 MNIST 数据集,是因为有 10 个输出类对应于 10 个整数。清单 2-19 中提供了详细的实现。SoftMax 被用作输出层。
#-------------------------------------------------------------------------------------------
# Import the required libraries
#-------------------------------------------------------------------------------------------
import tensorflow as tf
import numpy as np
from sklearn import datasets
from tensorflow.examples.tutorials.mnist import input_data
#------------------------------------------------------------------------------------------
# Function to read the MNIST dataset along with the labels
#------------------------------------------------------------------------------------------
def read_infile():
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
train_X, train_Y,test_X, test_Y = mnist.train.images, mnist.train.labels, mnist.test.images, mnist.test.labels
return train_X, train_Y,test_X, test_Y
#-------------------------------------------------------------------------------------------
# Define the weights and biases for the neural network
#-------------------------------------------------------------------------------------------
def weights_biases_placeholder(n_dim,n_classes):
X = tf.placeholder(tf.float32,[None,n_dim])
Y = tf.placeholder(tf.float32,[None,n_classes])
w = tf.Variable(tf.random_normal([n_dim,n_classes],stddev=0.01),name='weights')
b = tf.Variable(tf.random_normal([n_classes]),name='weights')
return X,Y,w,b
#-------------------------------------------------------------------------------------------
# Define the forward pass
#-------------------------------------------------------------------------------------------
def forward_pass(w,b,X):
out = tf.matmul(X,w) + b
return out
#-------------------------------------------------------------------------------------------
# Define the cost function for the SoftMax unit
#-------------------------------------------------------------------------------------------
def multiclass_cost(out,Y):
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=out,labels=Y))
return cost
#-------------------------------------------------------------------------------------------
# Define the initialization op
#------------------------------------------------------------------------------------------
def init():
return tf.global_variables_initializer()
#-------------------------------------------------------------------------------------------
# Define the training op
#-------------------------------------------------------------------------------------------
def train_op(learning_rate,cost):
op_train = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)
return op_train
train_X, train_Y,test_X, test_Y = read_infile()
X,Y,w,b = weights_biases_placeholder(train_X.shape[1],train_Y.shape[1])
out = forward_pass(w,b,X)
cost = multiclass_cost(out,Y)
learning_rate,epochs = 0.01,1000
op_train = train_op(learning_rate,cost)
init = init()
loss_trace = []
accuracy_trace = []
#-------------------------------------------------------------------------------------------
# Activate the TensorFlow session and execute the stochastic gradient descent
#-------------------------------------------------------------------------------------------
with tf.Session() as sess:
sess.run(init)
for i in xrange(epochs):
sess.run(op_train,feed_dict={X:train_X,Y:train_Y})
loss_ = sess.run(cost,feed_dict={X:train_X,Y:train_Y})
accuracy_ = np.mean(np.argmax(sess.run(out,feed_dict={X:train_X,Y:train_Y}),axis=1) == np.argmax(train_Y,axis=1))
loss_trace.append(loss_)
accuracy_trace.append(accuracy_)
if (((i+1) >= 100) and ((i+1) % 100 == 0 )) :
print 'Epoch:',(i+1),'loss:',loss_,'accuracy:',accuracy_
print 'Final training result:','loss:',loss_,'accuracy:',accuracy_
loss_test = sess.run(cost,feed_dict={X:test_X,Y:test_Y})
test_pred = np.argmax(sess.run(out,feed_dict={X:test_X,Y:test_Y}),axis=1)
accuracy_test = np.mean(test_pred == np.argmax(test_Y,axis=1))
print 'Results on test dataset:','loss:',loss_test,'accuracy:',accuracy_test
-- output --
Extracting MNIST_data/train-images-idx3-ubyte.gz
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz
Epoch: 100 loss: 1.56331 accuracy: 0.702781818182
Epoch: 200 loss: 1.20598 accuracy: 0.772127272727
Epoch: 300 loss: 1.0129 accuracy: 0.800363636364
Epoch: 400 loss: 0.893824 accuracy: 0.815618181818
Epoch: 500 loss: 0.81304 accuracy: 0.826618181818
Epoch: 600 loss: 0.754416 accuracy: 0.834309090909
Epoch: 700 loss: 0.709744 accuracy: 0.840236363636
Epoch: 800 loss: 0.674433 accuracy: 0.845
Epoch: 900 loss: 0.645718 accuracy: 0.848945454545
Epoch: 1000 loss: 0.621835 accuracy: 0.852527272727
Final training result: loss: 0.621835 accuracy: 0.852527272727
Results on test dataset: loss: 0.596687 accuracy: 0.8614
Listing 2-19.Multi-class Classification with Softmax Function Using Full-Batch Gradient Descent

图 2-26。
Actual digits versus predicted digits for SoftMax classification through gradient descent
import matplotlib.pyplot as plt
%matplotlib inline
f, a = plt.subplots(1, 10, figsize=(10, 2))
print 'Actual digits: ', np.argmax(test_Y[0:10],axis=1)
print 'Predicted digits:',test_pred[0:10]
print 'Actual images of the digits follow:'
for i in range(10):
a[i].imshow(np.reshape(test_X[i],(28, 28)))
-- output --
Listing 2-19a.Display the Actual Digits Versus the Predicted Digits Along with the Images of the Actual Digits
图 2-26 显示了通过梯度下降全批次学习训练后,验证数据集样本的 SoftMax 分类的实际位数与预测位数。
使用随机梯度下降的 SoftMax 函数的多类分类
我们现在执行相同的分类任务,但是我们不使用整批学习,而是求助于批量大小为 1000 的随机梯度下降。清单 2-20 中概述了详细的实现。
def read_infile():
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
train_X, train_Y,test_X, test_Y = mnist.train.images, mnist.train.labels, mnist.test.images, mnist.test.labels
return train_X, train_Y,test_X, test_Y
def weights_biases_placeholder(n_dim,n_classes):
X = tf.placeholder(tf.float32,[None,n_dim])
Y = tf.placeholder(tf.float32,[None,n_classes])
w = tf.Variable(tf.random_normal([n_dim,n_classes],stddev=0.01),name='weights')
b = tf.Variable(tf.random_normal([n_classes]),name='weights')
return X,Y,w,b
def forward_pass(w,b,X):
out = tf.matmul(X,w) + b
return out
def multiclass_cost(out,Y):
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=out,labels=Y))
return cost
def init():
return tf.global_variables_initializer()
def train_op(learning_rate,cost):
op_train = tf.train.AdamOptimizer(learning_rate).minimize(cost)
return op_train
train_X, train_Y,test_X, test_Y = read_infile()
X,Y,w,b = weights_biases_placeholder(train_X.shape[1],train_Y.shape[1])
out = forward_pass(w,b,X)
cost = multiclass_cost(out,Y)
learning_rate,epochs,batch_size = 0.01,1000,1000
num_batches = train_X.shape[0]/batch_size
op_train = train_op(learning_rate,cost)
init = init()
epoch_cost_trace = []
epoch_accuracy_trace = []
with tf.Session() as sess:
sess.run(init)
for i in xrange(epochs):
epoch_cost,epoch_accuracy = 0,0
for j in xrange(num_batches):
sess.run(op_train,feed_dict={X:train_X[j*batch_size:(j+1)*batch_size],Y:train_Y[j*batch_size:(j+1)*batch_size]})
actual_batch_size = train_X[j*batch_size:(j+1)*batch_size].shape[0]
epoch_cost += actual_batch_size*sess.run(cost,feed_dict={X:train_X[j*batch_size:(j+1)*batch_size],Y:train_Y[j*batch_size:(j+1)*batch_size]})
epoch_cost = epoch_cost/float(train_X.shape[0])
epoch_accuracy = np.mean(np.argmax(sess.run(out,feed_dict={X:train_X,Y:train_Y}),axis=1) == np.argmax(train_Y,axis=1))
epoch_cost_trace.append(epoch_cost)
epoch_accuracy_trace.append(epoch_accuracy)
if (((i +1) >= 100) and ((i+1) % 100 == 0 )) :
print 'Epoch:',(i+1),'Average loss:',epoch_cost,'accuracy:',epoch_accuracy
print 'Final epoch training results:','Average loss:',epoch_cost,'accuracy:',epoch_accuracy
loss_test = sess.run(cost,feed_dict={X:test_X,Y:test_Y})
test_pred = np.argmax(sess.run(out,feed_dict={X:test_X,Y:test_Y}),axis=1)
accuracy_test = np.mean(test_pred == np.argmax(test_Y,axis=1))
print 'Results on test dataset:','Average loss:',loss_test,'accuracy:',accuracy_test
-- output --
Extracting MNIST_data/train-images-idx3-ubyte.gz
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz
Epoch: 100 Average loss: 0.217337096686 accuracy: 0.9388
Epoch: 200 Average loss: 0.212256691131 accuracy: 0.939672727273
Epoch: 300 Average loss: 0.210445133664 accuracy: 0.940054545455
Epoch: 400 Average loss: 0.209570150484 accuracy: 0.940181818182
Epoch: 500 Average loss: 0.209083143689 accuracy: 0.940527272727
Epoch: 600 Average loss: 0.208780818907 accuracy: 0.9406
Epoch: 700 Average loss: 0.208577176387 accuracy: 0.940636363636
Epoch: 800 Average loss: 0.208430663293 accuracy: 0.940636363636
Epoch: 900 Average loss: 0.208319870586 accuracy: 0.940781818182
Epoch: 1000 Average loss: 0.208232710849 accuracy: 0.940872727273
Final epoch training results: Average loss: 0.208232710849 accuracy: 0.940872727273
Results on test dataset: Average loss: 0.459194 accuracy: 0.9155
Listing 2-20.Multi-class Classification with Softmax Function Using Stochastic Gradient Descent

图 2-27。
Actual digits versus predicted digits for SoftMax classification through stochastic gradient descent
import matplotlib.pyplot as plt
%matplotlib inline
f, a = plt.subplots(1, 10, figsize=(10, 2))
print 'Actual digits: ', np.argmax(test_Y[0:10],axis=1)
print 'Predicted digits:',test_pred[0:10]
print 'Actual images of the digits follow:'
for i in range(10):
a[i].imshow(np.reshape(test_X[i],(28, 28)))
--output --
Listing 2-20a.Actual Digits Versus Predicted Digits for SoftMax Classification Through Stochastic Gradient Descent
图 2-27 显示了通过随机梯度下降训练后验证数据集样本的 SoftMax 分类的实际位数与预测位数。
国家政治保卫局。参见 OGPU
在我们结束本章之前,我们想谈谈 GPU,它彻底改变了深度学习世界。GPU 代表图形处理单元,最初用于游戏目的,以每秒显示更多屏幕,获得更好的游戏分辨率。深度学习网络使用大量矩阵乘法,尤其是卷积,用于正向传递和反向传播。GPU 擅长矩阵到矩阵的乘法;因此,几千个 GPU 核心被用来并行处理数据。这加快了深度学习训练的速度。市场上常见的 GPU 有
- NVIDIA GTX 泰坦 XGE
- NVIDIA GTX 泰坦 x
- NVIDIA GeForce GTX 1080
- NVIDIA GeForce GTX 1070
摘要
在这一章中,我们已经讲述了深度学习是如何从人工神经网络发展而来的。此外,我们讨论了感知器的学习方法,它的局限性,以及目前的训练神经网络的方法。详细讨论了与非凸成本函数、椭圆局部成本等值线和鞍点相关的问题,以及解决这些问题所需的不同优化器。此外,在本章的后半部分,我们学习了 TensorFlow 基础知识,以及如何通过 TensorFlow 执行与线性回归、多类 SoftMax 和 XOR 分类相关的简单模型。在下一章,重点将放在图像的卷积神经网络上。
三、卷积神经网络
近年来,人工神经网络在处理非结构化数据,尤其是图像、文本、音频和语音方面蓬勃发展。卷积神经网络(CNN)最适合这种非结构化数据。每当有拓扑结构与数据相关联时,卷积神经网络就能很好地从数据中提取出重要的特征。从架构的角度来看,CNN 的灵感来自多层感知器。通过在相邻层的神经元之间施加局部连通性约束,CNN 利用了局部空间相关性。
卷积神经网络的核心元素是通过卷积运算处理数据。任何信号与另一信号的卷积产生第三信号,该第三信号可能比原始信号本身揭示更多关于该信号的信息。在深入研究卷积神经网络之前,我们先来详细了解一下卷积。
卷积运算
时间或空间信号与另一信号的卷积产生初始信号的修改版本。修改后的信号可能比适合于特定任务的原始信号具有更好的特征表示。例如,通过将灰度图像作为 2D 信号与另一信号(通常称为滤波器或内核)进行卷积,可以获得包含原始图像边缘的输出信号。图像中的边缘可以对应于对象边界、照明的变化、材料属性的变化、深度的不连续性等等,这对于若干应用可能是有用的。关于系统的线性时间不变性或移位不变性的知识有助于人们更好地理解信号的卷积。在讨论卷积本身之前,我们将首先讨论这一点。
线性时不变(LTI) /线性移位不变(LSI)系统
系统以某种方式作用于输入信号,产生输出信号。如果一个输入信号 x(t)产生一个输出 y(t),那么 y(t)可以表示为

对于线性系统,以下用于缩放和叠加的属性应该成立:


同样,对于时间不变或一般平移不变的系统,

这种具有线性和移位不变性的系统通常被称为线性移位不变(LSI)系统。当这样的系统处理时间信号时,它们被称为线性时不变(LTI)系统。在本章的其余部分,我们将把这样的系统称为 LSI 系统,不失一般性。见图 3-1 。

图 3-1。
Input–Output system
LSI 系统的关键特性是,如果知道系统对脉冲响应的输出,就可以计算出对任何信号的输出响应。

图 3-2b。
Response of an LTI system to a unit step impulse

图 3-2a。
Response of an LSI system to an impulse (Dirac Delta) function
在图 3-2a 和 3-2b 中,我们展示了系统对不同类型脉冲函数的脉冲响应。图 3-2a 显示了系统对狄拉克δ脉冲的连续脉冲响应,而图 3-2b 显示了系统对阶跃脉冲函数的离散脉冲响应。图 3-2a 中的系统是一个连续的 LTI 系统,因此需要一个狄拉克δ来确定其脉冲响应。另一方面,图 3-2b 中的系统是一个离散 LTI 系统,因此需要一个单位阶跃脉冲来确定其脉冲响应。
一旦我们知道了 LSI 系统对脉冲函数δ(t)的响应 h(t ),我们就可以通过将其与 h(t)卷积来计算 LTI 系统对任意输入信号 x(t)的响应 y(t)。数学上可以表示为
,其中(*)运算表示卷积。
系统的脉冲响应既可以是已知的,也可以通过记录系统对脉冲函数的响应来确定。例如,哈勃太空望远镜的脉冲响应可以通过将其聚焦在黑暗夜空中的一颗遥远的恒星上,然后记下记录的图像来找出。记录的图像是望远镜的脉冲响应。
一维信号的卷积
直观地说,卷积测量一个函数与另一个函数的反向和平移版本之间的重叠程度。在离散的情况下,

同样,在连续域中两个函数的卷积可以表示为

让我们对两个离散信号进行卷积,以便更好地解释这一操作。参见图 3-3a 至 3-3c 。

图 3-3c。
Output function from convolution

图 3-3b。
Functions for computing convolution operation

图 3-3a。
Input signals
在图 3-3b 中,函数
需要通过在水平轴上滑动来计算不同的 t 值。在 t 的每个值处,需要计算卷积和
。总和可以被认为是 x(τ)的加权平均值,权重由
提供

图 3-3d。
Overlap of the functions in convolution at t = 2
- 当
时,权重由
给出,但是权重不与 x(τ)重叠,因此总和为 0。 - 当
的权重由
给出,x(τ)中唯一与权重重叠的元素是
,重叠的权重是 h( 0)。因此,卷积和为
h( 0) = 13 = 3。由此,![$$ y(0)=3. $$]()
- 当
时,权重由
给出。元素 x(0)和 x(1)分别与权重 h(1)和 h(0)重叠。因此,卷积和是![$$ x{(0)}^{\ast }h(1)+x{(1)}^{\ast }h(0)={1}^{\ast }2+{2}^{\ast }3=8. $$]()
- 当
权重由
给出时,元素 x(0)、x(1)和 x(2)分别与权重 h(2)、h(1)和 h(0)重叠。因此,卷积和是元素
。图 3-3d 显示了
的两个功能的重叠。
模拟和数字信号
通常,显示时间和/或空间变化的任何感兴趣的量代表一个信号。因此,信号是时间和/或空间的函数。例如,特定股票在一周内的市场价格代表一个信号。
信号本质上可以是模拟的或数字的。然而,计算机不能处理模拟的连续信号,所以信号被制成数字信号进行处理。例如,语音是时间上的声学信号,其中语音能量的时间和幅度都是连续信号。当语音通过麦克风传输时,该声学连续信号被转换成电连续信号。如果我们想通过数字计算机处理模拟电信号,我们需要将模拟连续信号转换成离散信号。这是通过模拟信号的采样和量化来实现的。
采样指的是仅在固定的空间或时间间隔获取信号幅度。图 3-4a 对此进行了说明。
一般不会标注信号幅度的所有可能的连续值,但信号幅度一般会量化为一些固定的离散值,如图 3-4b 所示。通过采样和量化,一些信息从模拟连续信号中丢失。

图 3-4b。
Quantization of signal at discrete amplitude values

图 3-4a。
Sampling of a signal
采样和量化活动将模拟信号转换成数字信号。
数字图像可以表示为二维空间域中的数字信号。彩色 RGB 图像有三个通道:红色、绿色和蓝色。每个通道可以被认为是空间域中的信号,使得在每个空间位置,信号由像素强度表示。每个像素可以由 8 位表示,这在二进制中允许从 0 到 255 的 256 个像素强度。任何位置处的颜色都由对应于三个通道的该位置处的像素强度矢量来确定。因此,为了表示一种特定的颜色,需要使用 24 位信息。对于灰度图像,只有一个通道,像素亮度范围从 0 到 255。255 代表白色,而 0 代表黑色。
视频是具有时间维度的图像序列。黑白视频可以表示为其空间和时间坐标(x,y,t)的信号。彩色视频可以表示为三个信号的组合,空间和时间坐标对应于三个颜色通道,即红色、绿色和蓝色。
因此,灰度图像可以表示为函数 I(x,y ),其中 I 表示 x,y 坐标处的像素强度。对于数字图像,x、y 是采样坐标,取离散值。类似地,像素强度在 0 和 255 之间量化。
2D 和 3D 信号
维度为
的灰度图像可以表示为其空间坐标的标量 2D 信号。信号可以表示为

,其中 n 1 和 n 2 分别是沿水平轴和垂直轴的离散空间坐标,x(n 1 ,n 2 表示空间坐标处的像素强度。像素亮度取值从 0 到 255。
彩色 RGB 图像是矢量 2D 信号,因为在每个空间坐标上有一个像素强度矢量。对于尺寸为
的 RGB 图像,信号可以表示为
![$$ x\left({n}_1,{n}_2\right)=\left[{x}_R\left({n}_1,{n}_2\right),{x}_G\left({n}_1,{n}_2\right),{x}_B\left({n}_1,{n}_2\right)\right],\kern0.5em 0<{n}_1<M-1,0<{n}_2<N-1 $$](A448418_1_En_3_Chapter_Equh.gif)
,其中 x R ,x G ,x B 表示沿着红色、绿色和蓝色通道的像素强度。参见图 3-5a 和 3-5b 。

图 3-5b。
Video as a 3D object

图 3-5a。
Grayscale image as a 2D discrete signal
2D 卷积
既然我们已经将灰度图像表示为 2D 信号,我们希望通过 2D 卷积来处理这些信号。图像可以与图像处理系统的脉冲响应进行卷积,以实现不同的目标,例如:
- 通过降噪滤波器去除图像中的可见噪声。对于白噪声,我们可以使用高斯滤波器。对于椒盐噪声,可以使用中值滤波器。
- 为了检测边缘,我们需要从图像中提取高频成分的滤波器。
图像处理滤波器可以被认为是线性和平移不变的图像处理系统。在我们开始图像处理之前,有必要了解不同的脉冲函数。
二维单位阶跃函数
一个二维单位阶跃函数δ(n 1 ,n 2 ,其中 n 1 和 n 2 为横坐标和纵坐标,可以表示为

类似地,移位的单位阶跃函数可以表示为

图 3-6 对此进行了说明。

图 3-6。
Unit step functions
任何离散的二维信号都可以表示为不同坐标下单位阶跃函数的加权和。让我们考虑如图 3-7 所示的信号 x(n 1 ,n 2 )。


图 3-7。
Representing a 2D discrete signal as the weighted sum of unit step functions
所以,一般来说,任何离散的 2D 信号都可以写成:

信号与大规模集成电路系统单位阶跃响应的 2D 卷积
当如上所述的任意离散 2D 信号通过具有变换 f 的 LSI 系统时,由于 LSI 系统的线性特性,

现在,LSI 系统的单位阶跃响应
,并且由于 LSI 系统是移位不变的,
因此,f (x(n 1 ,n 2 )可以表示为:

(1)
前面的表达式表示信号与 LSI 系统的单位阶跃响应的 2D 卷积的表达式。为了说明 2D 卷积,让我们看一个例子,其中我们将 x(n 1 ,n 2 )与 h(n 1 ,n 2 )进行卷积。信号和单位阶跃响应信号定义如下,并在图 3-8 :

中进行了说明

图 3-8。
2D signal and unit step response of LSI system
为了计算卷积,我们需要在一组不同的坐标点上绘制信号。我们分别在横轴和纵轴上选择了 k 1 和 k 2 。同样,我们将脉冲响应 h(k 1 ,k 2 )反转为
,如图 3-9(b) 所示。然后,我们将反转函数
置于 n 1 和 n 2 的不同偏移值。广义反函数可以表示为
。为了计算在 n 1 和 n 2 的特定值处卷积的输出 y(n 1 ,n 2 ,我们看到
与 x(k 1 ,k 2 )重叠的点,并取信号和脉冲响应值的坐标方向乘积的总和作为输出。
正如我们在图 3-9(c) 中看到的,对于
偏移,唯一的重叠点是
,因此
同样,对于偏移
,重叠的点是点
和
,如图 3-9(d) 所示。

对于偏移
,重叠的点就是点
,如图 3-9(e) 所示。

按照这种通过改变 n 1 和 n 2 来移动单位阶跃响应信号的方法,可以计算整个函数 y(n 1 ,n 2 )。

图 3-9。
Convolution at different coordinate points
图像对不同 LSI 系统响应的 2D 卷积
任何图像都可以与 LSI 系统的单位阶跃响应进行卷积。那些 LSI 系统单元阶跃响应被称为滤波器或内核。例如,当我们试图通过相机拍摄图像,而图像由于手的抖动而变得模糊时,引入的模糊可以被视为具有特定单位阶跃响应的 LSI 系统。这个单位阶跃响应对实际图像进行卷积,并产生模糊图像作为输出。我们通过相机拍摄的任何图像都会与相机的单位阶跃响应进行卷积。因此,摄像机可以被视为具有特定单位阶跃响应的 LSI 系统。
任何数字图像都是 2D 离散信号。
2D 图像 x(n 1 ,n 2 )与 2D 图像处理滤波器 h(n 1 ,n 2 )的卷积由


给出
图像处理滤波器处理灰度图像的(2D)信号,以产生另一个图像(2D 信号)。在多通道图像的情况下,通常使用 2D 图像处理滤波器进行图像处理,这意味着必须将每个图像通道作为 2D 信号进行处理,或者将图像转换为灰度图像。
既然我们已经学习了卷积的概念,我们知道可以将 LSI 系统的任何单位阶跃响应称为滤波器或内核。
2D 卷积的一个例子如图 3-10a 所示。

Figure 3-10b.

图 3-10a。
Example of 2D convolution of images
为了保持输出图像的长度与输入图像的长度相同,对原始图像进行了零填充。正如我们所看到的,翻转的滤波器或内核在原始图像的各个区域上滑动,并在每个坐标点计算卷积和。请注意,图 3-10b 中提到的强度 I[i,j]中的索引表示矩阵坐标。同样的示例问题通过 scipy 2D 卷积以及通过清单 3-1 中的基本逻辑来解决。在这两种情况下,结果是一样的。

Figure 3-11.
## Illustate 2D convolution of images through an example
import scipy.signal
import numpy as np
# Take a 7x7 image as example
image = np.array([[1, 2, 3, 4, 5, 6, 7],
[8, 9, 10, 11, 12, 13, 14],
[15, 16, 17, 18, 19, 20, 21],
[22, 23, 24, 25, 26, 27, 28],
[29, 30, 31, 32, 33, 34, 35],
[36, 37, 38, 39, 40, 41, 42],
[43, 44, 45, 46, 47, 48, 49]])
# Defined an image-processing kernel
filter_kernel = np.array([[-1, 1, -1],
[-2, 3, 1],
[2, -4, 0]])
# Convolve the image with the filter kernel through scipy 2D convolution to produce an output image of same dimension as that of the input
I = scipy.signal.convolve2d(image, filter_kernel,mode='same', boundary='fill', fillvalue=0)
print(I)
# We replicate the logic of a scipy 2D convolution by going through the following steps
# a) The boundaries need to be extended in both directions for the image and padded with zeroes.
# For convolving the 7x7 image by 3x3 kernel, the dimensions need to be extended by (3-1)/2—i.e., 1—
#on either side for each dimension. So a skeleton image of 9x9 image would be created
# in which the boundaries of 1 pixel are pre-filled with zero.
# b) The kernel needs to be flipped—i.e., rotated—by 180 degrees
# c) The flipped kernel needs to placed at each coordinate location for the image and then the sum of
#coordinate-wise product with the image intensities needs to be computed. These sums for each coordinate would give
#the intensities for the output image.
row,col=7,7
## Rotate the filter kernel twice by 90 degrees to get 180 rotation
filter_kernel_flipped = np.rot90(filter_kernel,2)
## Pad the boundaries of the image with zeroes and fill the rest from the original image
image1 = np.zeros((9,9))
for i in xrange(row):
for j in xrange(col):
image1[i+1,j+1] = image[i,j]
print(image1)
## Define the output image
image_out = np.zeros((row,col))
## Dynamic shifting of the flipped filter at each image coordinate and then computing the convolved sum.
for i in xrange(1,1+row):
for j in xrange(1,1+col):
arr_chunk = np.zeros((3,3))
for k,k1 in zip(xrange(i-1,i+2),xrange(3)):
for l,l1 in zip(xrange(j-1,j+2),xrange(3)):
arr_chunk[k1,l1] = image1[k,l]
image_out[i-1,j-1] = np.sum(np.multiply(arr_chunk,filter_kernel_flipped))
print(image_out)
Listing 3-1.
基于图像处理滤波器的选择,输出图像的性质会有所不同。例如,高斯滤波器将创建输入图像的模糊版本的输出图像,而 Sobel 滤波器将检测图像中的边缘并产生包含输入图像边缘的输出图像。
常见的图像处理过滤器
让我们讨论一下 2D 图像上常用的图像处理滤波器。一定要弄清楚符号,因为索引图像的自然方式与定义 x 轴和 y 轴的方式不太一致。每当我们在坐标空间中表示图像处理滤波器或图像时,n 1 和 n 2 是 x 和 y 方向的离散坐标。numpy 矩阵形式的图像的列索引正好与 x 轴重合,而行索引则向 y 轴的相反方向移动。此外,在进行卷积时,选择哪个像素位置作为图像信号的原点并不重要。基于是否使用零填充,可以相应地处理边缘。由于滤波器核的尺寸较小,我们通常翻转滤波器核,然后在图像上滑动,而不是反过来。
均值滤波器
均值滤波器或平均滤波器是一种低通滤波器,用于计算任意特定点的像素强度的局部平均值。均值滤波器的脉冲响应可以是这里看到的任何形式(见图 3-12 ):
![$$ \left[\begin{array}{ccc}1/9& 1/9& 1/9\ {}1/9& 1/9& 1/9\ {}1/9& 1/9& 1/9\end{array}\right] $$](A448418_1_En_3_Chapter_Equr.gif)

图 3-12。
Impulse response of a Mean filter
这里,矩阵条目 h 22 对应于原点处的条目。因此,在任何给定点,卷积将代表该点像素强度的平均值。清单 3-2 中的代码说明了如何使用图像处理滤波器(如均值滤波器)对图像进行卷积。
请注意,在许多 Python 实现中,我们将使用 OpenCV 对图像执行基本操作,例如读取图像、将图像从 RGB 格式转换为灰度格式等等。OpenCV 是一个开源的图像处理包,它有一套丰富的图像处理方法。建议读者探索 OpenCV 或任何其他图像处理工具箱,以便熟悉基本的图像处理功能。
import cv2
img = cv2.imread('monalisa.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
plt.imshow(gray,cmap='gray')
mean = 0
var = 100
sigma = var**0.5
row,col = 650,442
gauss = np.random.normal(mean,sigma,(row,col))
gauss = gauss.reshape(row,col)
gray_noisy = gray + gauss
plt.imshow(gray_noisy,cmap='gray')
## Mean filter
Hm = np.array([[1,1,1],[1,1,1],[1,1,1]])/float(9)
Gm = convolve2d(gray_noisy,Hm,mode='same')
plt.imshow(Gm,cmap='gray')
Listing 3-2.Convolution of an Image with Mean Filter
在清单 3-2 中,我们读取了蒙娜丽莎的图像,然后在图像中引入了一些高斯白噪声。高斯噪声的平均值为 0,方差为 100。然后,我们用均值滤波器卷积噪声图像,以减少白噪声。图 3-13 显示了噪声图像和卷积后的图像。

图 3-13。
Mean filter processing on Mona Lisa image
均值滤波器主要用于降低图像中的噪声。如果图像中存在一些白高斯噪声,则均值滤波器将减少噪声,因为它在其邻域上求平均值,因此零均值的白噪声将被抑制。从图 3-13 中我们可以看到,一旦图像与均值滤波器卷积,高斯白噪声就会减少。新图像具有较少的高频分量,因此与卷积之前的图像相比相对不太清晰,但是滤波器在减少白噪声方面做得很好。
中值滤波器
2D 中值滤波器根据滤波器大小,用邻域中的中值像素强度替换邻域中的每个像素。中值滤波器有利于去除椒盐噪声。这种类型的噪声以黑白像素的形式出现在图像中,通常是由捕捉图像时的突然干扰引起的。清单 3-3 展示了如何将椒盐噪声添加到图像中,然后如何使用中值滤波器抑制噪声。

图 3-14。
Median filter processing
## Generate random integers from 0 to 20
## If the value is zero we will replace the image pixel with a low value of 0 that corresponds to a black pixel
## If the value is 20 we will replace the image pixel with a high value of 255 that corresponds to a white pixel
## We have taken 20 integers, out of which we will only tag integers 1 and 20 as salt and pepper noise
## Hence, approximately 10% of the overall pixels are salt and pepper noise. If we want to reduce it
## to 5% we can take integers
from 0 to 40 and then treat 0 as an indicator for a black pixel and 40 as an indicator for a white pixel.
np.random.seed(0)
gray_sp = gray*1
sp_indices = np.random.randint(0,21,[row,col])
for i in xrange(row):
for j in xrange(col):
if sp_indices[i,j] == 0:
gray_sp[i,j] = 0
if sp_indices[i,j] == 20:
gray_sp[i,j] = 255
plt.imshow(gray_sp,cmap='gray')
## Now we want to remove the salt and pepper noise through a Median filter.
## Using the opencv Median filter for the same
gray_sp_removed = cv2.medianBlur(gray_sp,3)
plt.imshow(gray_sp_removed,cmap='gray')
##Implementation of the 3x3 Median filter without using opencv
gray_sp_removed_exp = gray*1
for i in xrange(row):
for j in xrange(col):
local_arr = []
for k in xrange(np.max([0,i-1]),np.min([i+2,row])):
for l in xrange(np.max([0,j-1]),np.min([j+2,col])):
local_arr.append(gray_sp[k,l])
gray_sp_removed_exp[i,j] = np.median(local_arr)
plt.imshow(gray_sp_removed_exp,cmap='gray')
Listing 3-3.
正如我们所看到的,椒盐噪声已经被中值滤波器去除了。
高斯滤波器
高斯滤波器是均值滤波器的修改版本,其中脉冲函数的权重正态分布在原点周围。重量在过滤器的中心最高,通常远离中心下降。可以用清单 3-4 中的代码创建一个高斯滤波器。正如我们所见,强度以高斯方式远离原点下降。当显示为图像时,高斯滤波器在原点处具有最高的强度,然后对于远离中心的像素逐渐减弱。高斯滤波器通过抑制高频分量来降低噪声。然而,在追求抑制高频成分的过程中,它最终产生了一个模糊的图像,称为高斯模糊。
在图 3-15 中,原始图像与高斯滤波器进行卷积,以产生具有高斯模糊的图像。然后,我们从原始图像中减去模糊图像,以获得图像的高频分量。高频图像的一小部分被添加到原始图像中,以提高图像的清晰度。

图 3-15。
Various activities with Gaussian filter kernel
Hg = np.zeros((20,20))
for i in xrange(20):
for j in xrange(20):
Hg[i,j] = np.exp(-((i-10)**2 + (j-10)**2)/10)
plt.imshow(Hg,cmap='gray')
gray_blur = convolve2d(gray,Hg,mode='same')
plt.imshow(gray_blur,cmap='gray')
gray_enhanced = gray + 0.025*gray_high
plt.imshow(gray_enhanced,cmap='gray')
Listing 3-4.
基于梯度的过滤器
回顾一下,二维函数 I(x,y)的梯度由下式给出:
![$$ \nabla I\left(x,y\right)={\left[\frac{\partial I\left(x,y\right)}{\partial x}\frac{\partial I\left(x,y\right)}{\partial y}\right]}^T $$](A448418_1_En_3_Chapter_Equs.gif)
其中沿水平方向的梯度由下式给出
或
基于方便和手头的问题。
对于离散坐标,我们可以取
,沿水平方向近似梯度如下:

信号的这种导数可以通过将信号与滤波器核
进行卷积来实现。
同样,

来自第二个表象。
这种形式的导数可以通过将信号与滤波器内核
进行卷积来实现。
对于垂直方向,离散情况下的梯度分量可表示为
或
通过卷积计算梯度对应的滤波器核分别是
和
。
请注意,这些过滤器采用 x 轴和 y 轴方向,如图 3-16 所示。x 方向与矩阵索引 n 2 增量一致,而 y 方向与矩阵索引 n 1 增量相反。

图 3-16。
Vertical and horizontal gradient filters
图 3-16 展示了蒙娜丽莎图像与水平和垂直渐变滤镜的卷积。
索贝尔边缘检测滤波器
索贝尔边缘检测器沿着水平轴和垂直轴的脉冲响应可以分别由下面的 H x 和 H y 矩阵表示。索贝尔检测器是刚才说明的水平和垂直梯度滤波器的扩展。它不是只取该点的梯度,而是取其两侧各点的梯度之和。此外,它赋予了兴趣点双倍的权重。见图 3-17 。
![$$ Hx=\left[\begin{array}{ccc}1& 0& -1\ {}2& 0& -2\ {}1& 0& -1\end{array}\right]=\left[1\kern0.5em 2\kern0.5em 1\right]\left[\begin{array}{ccc}1& 0& -1\end{array}\right] $$](A448418_1_En_3_Chapter_Equv.gif)
![$$ Hy=\left[\begin{array}{ccc}-1& -2& -1\ {}0& 0& 0\ {}1& 2& 1\end{array}\right] $$](A448418_1_En_3_Chapter_Equw.gif)

图 3-17。
Sobel filter impulse response
列表 3-5 中显示了图像与 Sobel 滤波器的卷积。

图 3-18。
Output of various Sobel filters
Hx = np.array([[ 1,0, -1],[2,0,-2],[1,0,-1]],dtype=np.float32)
Gx = convolve2d(gray,Hx,mode='same')
plt.imshow(Gx,cmap='gray')
Hy = np.array([[ -1,-2, -1],[0,0,0],[1,2,1]],dtype=np.float32)
Gy = convolve2d(gray,Hy,mode='same')
plt.imshow(Gy,cmap='gray')
G = (Gx*Gx + Gy*Gy)**0.5
plt.imshow(G,cmap='gray')
Listing 3-5.Convolution Using a Sobel Filter
清单 3-5 具有用 Sobel 滤波器卷积图像所需的逻辑。水平 Sobel 滤波器检测水平方向上的边缘,而垂直 Sobel 滤波器检测垂直方向上的边缘。两者都是高通滤波器,因为它们会衰减信号中的低频成分,只捕捉图像中的高频成分。边缘是图像的重要特征,有助于检测图像中的局部变化。边缘通常出现在图像中两个区域之间的边界上,并且通常是从图像中检索信息的第一步。我们在图 3-18 中看到列表 3-5 的输出。针对每个位置使用水平和垂直索贝尔滤波器获得的图像的像素值可以被认为是向量
,其中 I x (x,y)表示通过水平索贝尔滤波器获得的图像的像素强度,I y (x,y)表示通过垂直索贝尔滤波器获得的图像的像素强度。向量 I’(x,y)的大小可以用作组合 Sobel 滤波器的像素强度。
,其中 C(x,y)表示组合索贝尔滤波器的像素强度函数。
身份转换
通过卷积进行身份变换的滤波器如下:
![$$ \left[\begin{array}{ccc}0& 0& 0\ {}0& 1& 0\ {}0& 0& 0\end{array}\right] $$](A448418_1_En_3_Chapter_Equx.gif)
图 3-19 显示了通过卷积进行的单位变换。

图 3-19。
Identity transform through convolution
表 3-1 列出了几种有用的图像处理过滤器及其用途。
表 3-1。
Image-Processing Filters and Their Uses
| 过滤器 | 使用 | | --- | --- | | 均值滤波器 | 减少高斯噪声,平滑上采样后的图像 | | 中值滤波器 | 减少盐和胡椒的噪音 | | 索贝尔过滤器 | 检测图像中的边缘 | | 高斯滤波器 | 减少图像中的噪声 | | Canny 滤波器 | 检测图像中的边缘 | | 韦纳过滤器 | 减少附加噪声和模糊 |卷积神经网络
卷积神经网络(CNN)基于图像的卷积,并基于 CNN 通过训练学习的过滤器来检测特征。例如,我们不应用任何已知的滤波器,如用于检测边缘或去除高斯噪声的滤波器,但通过卷积神经网络的训练,该算法自己学习图像处理滤波器,这可能与普通的图像处理滤波器非常不同。对于监督训练,以尽可能降低总成本函数的方式学习滤波器。通常,第一卷积层学习检测边缘,而第二卷积层可以学习检测更复杂的形状,这些形状可以通过组合不同的边缘来形成,例如圆形和矩形等等。第三层和更高层基于前一层生成的特征学习更复杂的特征。
卷积神经网络的优点是权重共享导致的稀疏连接,这大大减少了要学习的参数数量。同一个滤波器可以通过它的等方差特性来学习检测图像的任何给定部分中的相同边缘,这是卷积对于特征检测非常有用的特性。
卷积神经网络的组件
以下是卷积神经网络的典型组件:

图 3-20。
Basic flow diagram of a convolutional neural network
- 输入层将保存图像的像素强度。例如,红色、绿色和蓝色通道(RGB)的宽度为 64、高度为 64、深度为 3 的输入图像的输入尺寸为
。 - 卷积图层将从前面的图层中提取图像,并与指定数量的过滤器进行卷积,以创建称为输出要素地图的图像。输出特征地图的数量等于指定的过滤器数量。到目前为止,TensorFlow 中的 CNN 大多使用 2D 滤波器;然而,最近已经引入了 3D 卷积滤波器。
- 细胞神经网络的激活函数通常是 ReLUs,我们在第二章中讨论过。通过 ReLU 激活层后,输出维度与输入维度相同。ReLU 层增加了网络的非线性,同时为正的网络输入提供了不饱和的梯度。
- 池图层将沿高度和宽度维度对 2D 激活图进行缩减采样。激活图的深度或数量没有受到损害,并且保持不变。
- 完全连接的层包含传统的神经元,这些神经元从前面的层接收不同组的权重;它们之间没有典型的卷积运算中的权重分配。该层中的每个神经元将通过单独的权重连接到前一层中的所有神经元,或者连接到输出图中的所有坐标输出。对于分类,类输出神经元从最终完全连接的层接收输入。
图 3-20 展示了一个基本的卷积神经网络(CNN ),它使用一个卷积层、一个 ReLU 层和一个池层,然后是一个全连接层,最后是输出分类层。该网络试图从非蒙娜丽莎图像中辨别出蒙娜丽莎图像。输出单元可以采用 sigmoid 激活函数,因为它是图像的二进制分类问题。通常,对于大多数 CNN 架构,在完全连接的层之前,几个到几个卷积层-ReLU 层-池层组合被一个接一个地堆叠。我们将在稍后讨论不同的架构。现在,让我们更详细地看看不同的层。
输入层
这一层的输入是图像。通常,图像作为四维张量被成批地馈送,其中第一维特定于图像索引,第二维和第三维特定于图像的高度和宽度,第四维对应于不同的通道。对于彩色图像,通常我们有红色(R)、绿色(G)和蓝色(B)通道,而对于灰度图像,我们只有一个通道。一批中图像的数量将由为小批量随机梯度下降选择的小批量大小决定。随机梯度下降的批量大小为 1。
输入可以通过 TensorFlow placeholder tf.placeholder at运行时以小批量的形式输入到输入层。
卷积层
卷积是任何 CNN 网络的核心。TensorFlow 支持 2D 和 3D 卷积。然而,2D 卷积更常见,因为三维卷积是计算内存密集型。输出特征图形式的输入图像或中间图像与指定大小的 2D 滤波器进行 2D 卷积。2D 卷积沿着空间维度发生,而沿着图像体的深度通道没有卷积。对于每个深度通道,生成相同数量的特征图,然后在它们通过 ReLU 激活之前,沿着深度维度将它们加在一起。这些过滤器有助于检测图像中的特征。网络中的卷积层越深,它学习的复杂特征就越多。例如,初始卷积层可以学习检测图像中的边缘,而第二卷积层可以学习连接边缘以形成几何形状,例如圆形和矩形。更深的卷积层可能学会检测更复杂的特征;例如,在猫和狗的分类中,它可以学习检测动物的眼睛、鼻子或其他身体部位。
在 CNN 中,只规定了过滤器的尺寸;在训练开始之前,权重被初始化为任意值。滤波器的权重是通过 CNN 训练过程学习的,因此它们可能不代表传统的图像处理滤波器,例如 Sobel、高斯、均值、中值或其他类型的滤波器。相反,所学习的滤波器将使得所定义的总损失函数最小化,或者基于验证实现良好的概括。虽然它可能不会学习传统的边缘检测滤波器,但它会学习几种以某种形式检测边缘的滤波器,因为边缘是图像的良好特征检测器。
定义卷积层时应该熟悉的一些术语如下:
过滤器尺寸–过滤器尺寸定义过滤器内核的高度和宽度。大小为
的滤波器核将具有九个权重。通常,这些滤波器被初始化并在输入图像上滑动以进行卷积,而不翻转这些滤波器。从技术上讲,当卷积在不翻转滤波器核的情况下执行时,它被称为互相关,而不是卷积。然而,这没关系,因为我们可以将学习到的过滤器视为图像处理过滤器的翻转版本。
跨距–跨距决定执行卷积时在每个空间方向上移动的像素数。在信号的正常卷积中,我们通常不跳过任何像素,而是在每个像素位置计算卷积和,因此对于 2D 信号,我们沿两个空间方向的步幅为 1。然而,在卷积时可以选择跳过每一个交替的像素位置,因此选择步幅 2。如果沿图像的高度和宽度选择步长 2,那么在卷积之后,输出图像将大约是输入图像大小的
。为什么它大约是原始图像或特征地图大小的
而不是精确的
将在我们下一个讨论的主题中涉及。
填充–当我们通过一个过滤器卷积一个特定大小的图像时,结果图像通常比原始图像小。例如,如果我们通过大小为
的滤波器对 5
5 的 2D 图像进行卷积,得到的图像就是
。
填充是一种将零附加到图像边界以控制卷积输出大小的方法。沿着特定空间维度的卷积输出图像长度 L′由

给出,其中
输入图像在特定维度的长度
特定维度中内核/过滤器的长度
沿维度两端填充的零
卷积的步幅
一般来说,对于 1 的步幅,沿着每个维度的图像尺寸在任一端都减少了
,其中 K 是沿着该维度的滤波器核的长度。因此,为了保持输出图像与输入图像相同,需要长度为
的焊盘。
可以从沿着特定方向的输出图像长度中找出特定步幅大小是否可行。例如,如果
和
,跨距
是不可能的,因为它将产生沿空间维度的输出长度
,它不是一个整数值。
在 TensorFlow 中,填充可以选择为"VALID"或"SAME"。"SAME"确保在选择步长为 1 的情况下,图像的输出空间尺寸与输入空间尺寸相同。它使用零填充来实现这一点。它试图在尺寸的两侧保持零填充长度均匀,但是如果该尺寸的总填充长度为奇数,则额外的长度将被添加到水平尺寸的右侧和垂直尺寸的底部。
"VALID"不使用零填充,因此输出图像尺寸将小于输入图像尺寸,即使步幅为 1。
tensorflow 用法
def conv2d(x,W,b,strides=1):
x = tf.nn.conv2d(x,W,strides=[1,strides,strides,1],padding='SAME')
x = tf.nn.bias_add(x,b)
return tf.nn.relu(x)
为了定义 TensorFlow 卷积层,我们使用tf.nn.conv2d来定义卷积的输入、与卷积相关的权重、步长和填充类型。此外,我们为每个输出特征图添加了一个偏差。最后,我们使用经过整流的线性单元 ReLUs 作为激活,以将非线性添加到系统中。
汇集层
对图像的汇集操作通常概括了图像的局部性,该局部性由滤波器核的大小给出,也称为感受域。汇总通常以最大池或平均池的形式进行。在最大池中,一个位置的最大像素强度被作为该位置的代表。在平均池中,一个地点周围的像素强度的平均值被作为该地点的代表。池减少了图像的空间维度。决定局部性的内核大小通常选择为
,而步幅选择为 2。这将图像大小缩小到原始图像的大小。
tensorflow 用法
''' P O O L I N G L A Y E R'''
def maxpool2d(x,stride=2):
return tf.nn.max_pool(x,ksize=[1,stride,stride,1],strides=[1,stride,stride,1],padding='SAME')
tf.nn.max_pool定义用于定义最大池层,而tf.nn.avg_pool用于定义平均池层。除了输入,我们还需要通过ksize参数输入最大池的感受野或内核大小。此外,我们需要提供用于最大池的步长。为了确保池化输出要素地图的每个空间位置中的值来自输入中的独立邻域,每个空间维度中的步幅应选择为等于相应空间维度中的核大小。
通过卷积层的反向传播

图 3-21。
Backpropagation through the convolutional layer
通过卷积层的反向传播非常类似于多层感知器网络的反向传播。唯一的区别是权重连接是稀疏的,因为不同的输入邻域共享相同的权重来创建输出要素地图。每个输出特征图是来自前一层的图像或特征图与滤波器核的卷积的结果,滤波器核的值是我们需要通过反向传播来学习的权重。对于特定的输入-输出特征映射组合,滤波器内核中的权重是共享的。
在图 3-21 中,层 L 中的特征图 A 与一个滤波器核卷积,以产生层
中的输出特征图 B。
输出特征图的值是卷积的结果,可以表示为

概括地说:

现在,让成本函数 L 相对于净输入 s ij 的梯度由

表示
让我们计算成本函数相对于权重 w 22 的梯度。权重与所有的 s ij 相关联,因此将具有来自所有δij:


的梯度分量
此外,从不同 s ij 的前述等式中,可以导出以下等式:

于是,

同样,


再次,
,
,
,
于是,

用同样的方法对另外两个权重进行处理,我们得到







基于成本函数 L 相对于滤波器核的四个权重的先前梯度,我们得到以下关系:

当以矩阵形式排列时,我们得到下面的关系;(x)表示互相关:
![$$ \left[\begin{array}{cc}\frac{\partial \mathrm{L}}{\partial {w}_{22}}& \frac{\partial \mathrm{L}}{\partial {w}_{21}}\ {}\frac{\partial \mathrm{L}}{\partial {w}_{12}}& \frac{\partial \mathrm{L}}{\partial {w}_{11}}\end{array}\right]=\left[\begin{array}{ccc}{a}_{11}& {a}_{12}& {a}_{13}\ {}{a}_{21}& {a}_{22}& {a}_{23}\ {}{a}_{31}& {a}_{32}& {a}_{33}\end{array}\right]\left(\mathrm{x}\right)\left[\begin{array}{cc}{\delta}_{11}& {\delta}_{12}\ {}{\delta}_{21}& {\delta}_{22}\end{array}\right] $$](A448418_1_En_3_Chapter_Equar.gif)
与
的互相关也可以认为是
与翻转
的卷积;亦即
。
因此,梯度矩阵的翻转是
与
的卷积;即
![$$ \left[\begin{array}{cc}\frac{\partial \mathrm{L}}{\partial {w}_{22}}& \frac{\partial \mathrm{L}}{\partial {w}_{21}}\ {}\frac{\partial \mathrm{L}}{\partial {w}_{12}}& \frac{\partial \mathrm{L}}{\partial {w}_{11}}\end{array}\right]=\left[\begin{array}{ccc}{a}_{11}& {a}_{12}& {a}_{13}\ {}{a}_{21}& {a}_{22}& {a}_{23}\ {}{a}_{31}& {a}_{32}& {a}_{33}\end{array}\right]\left({}^{\ast}\right)\left[\begin{array}{cc}{\delta}_{22}& {\delta}_{12}\ {}{\delta}_{21}& {\delta}_{11}\end{array}\right] $$](A448418_1_En_3_Chapter_Equas.gif)
就层而言,可以说梯度矩阵的翻转是
层的梯度与层 l 的特征图输出的互相关。同样,等效地,梯度矩阵的翻转是
层的梯度矩阵翻转与层 l 的特征图输出的卷积
通过池层反向传播

图 3-22。
Backpropagation through max pooling layer
图 3-22 说明了最大汇集操作。让一个特征映射,在经过层 L 的卷积和 ReLU 激活之后,经过层
的最大池操作,以产生输出特征映射。最大池的核心或感受域的大小为
,步长为 2。最大池层的输出是
输入特征图的大小,其输出值由
表示
我们可以看到 z 11 的值是 5,因为
块中的最大值是 5。如果 z 11 处的误差导数是
,那么整个梯度被传递到值为 5 的 x 21 ,并且其块中的其余元素——x11,x 12 和 x22——从 z 11 接收零梯度。

图 3-23。
Backpropagation through average pooling layer
对于同一个示例使用平均池,输出是输入的
块中值的平均值。因此,z 11 得到 x 11 ,x 12 ,x 21 ,x 22 的平均值。这里,z 11 处的误差梯度
将由 x 11 ,x 12 ,x 21 和 x 22 平均分担。于是,

卷积权重分配及其优势
通过卷积的权重共享极大地减少了卷积神经网络中的参数数量。想象一下,我们从一个大小为
的图像中创建了一个大小为
的特征图,它具有完整的连接而不是卷积。仅对于那一个特征地图,就有 k 个 2 n 个 2 权重,这是要学习的大量权重。相反,因为在卷积中,由滤波器核大小定义的位置共享相同的权重,所以要学习的参数数量大大减少。在卷积的情况下,就像在这个场景中,我们只需要学习特定滤波器核的权重。由于滤波器尺寸相对于图像相对较小,因此权重的数量显著减少。对于任何图像,我们生成对应于不同滤波器核的若干特征图。每个过滤器内核学习检测不同种类的特征。创建的特征映射再次与其他滤波器核卷积,以学习后续层中更复杂的特征。
翻译等值
卷积运算提供平移等变。也就是说,如果输入中的特征 A 在输出中产生特定的特征 B,那么即使特征 A 在图像中被平移,特征 B 也将继续在输出的不同位置产生。

图 3-24。
Translational equivariance illustration
在图 3-24 中,我们可以看到数字 9 在图像(B)中已经从它在图像(A)中的位置被平移。输入图像(A)和(B)都已经用相同的滤波器核进行了卷积,并且在输出图像(C)和(D)中基于其在输入中的位置在不同的位置检测到了数字 9 的相同特征。不管翻译如何,卷积仍然为数字产生相同的特征。卷积的这个性质叫做平移等方差。事实上,如果数字用一组像素强度 x 来表示,f 是对 x 的平移运算,而 g 是带滤波核的卷积运算,那么以下对卷积成立:

在我们的例子中,f (x)产生图像(B)中的平移 9,平移 9 通过 g 卷积以产生 9 的激活特征,如图像(D)所示。图像(D)中的 9(即 g( f (x)))的激活特征也可以通过相同的平移 f 平移图像(C)中的激活 9(即 g(x))来实现

图 3-25。
Illustration of equivariance with an example
用一个小例子更容易看出等方差,如图 3-25 所示。我们感兴趣的输入图像或 2D 信号的部分是左边最上面的块;亦即
。为了便于参考,我们将该块命名为 a。
在将输入与求和滤波器(即
)进行卷积时,模块 A 将对应于输出值 183,该值可被视为 A 的特征检测器。
在将相同的求和滤波器与平移图像进行卷积时,移位后的块 A 仍将产生 183 的输出值。此外,我们可以看到,如果我们将相同的转换应用于原始的卷积图像输出,183 的值将出现在与转换后的卷积图像的输出相同的位置。
池化带来的平移不变性
汇集基于汇集的受体场内核大小提供了某种形式的平移不变性。让我们以最大池为例,如图 3-26 所示。图像 A 中特定位置处的数字通过卷积滤波器 H 以输出特征图 P 中的值 100 和 80 的形式被检测到。同样,相同的数字出现在另一个图像 B 中相对于图像 A 稍微平移的位置处。当这些特征映射由于最大汇集而以步幅 2 通过大小为
的受体域核时,100 和 80 值出现在输出 M 和 M’的相同位置。以这种方式,如果平移距离相对于最大汇集的受体区域或核心的大小不是很高,则最大汇集为特征检测提供了一些平移不变性。

图 3-26。
Translational invariance through max pooling
类似地,平均池基于受体域内核的大小,取特征映射的局部中的值的平均值。因此,如果某个特定特征在其局部特征图中被高值检测到,比如说在边缘区域,那么即使图像有一点平移,平均值也会继续很高。
辍学和正规化
丢弃是调整卷积神经网络的全连接层中的权重以避免过拟合的活动。然而,它不限于卷积神经网络,而是适用于所有前馈神经网络。对于小批量的每个训练样本,在训练时随机丢弃指定比例的神经网络单元,包括隐藏的和可见的,以便剩余的神经元可以自己学习重要的特征,而不依赖于其他神经元的合作。当神经元被随机丢弃时,到这些神经元的所有传入和传出连接也被丢弃。神经元之间过多的合作使得神经元相互依赖,它们无法学习不同的特征。这种高度合作导致过拟合,因为它在训练数据集上表现良好,而如果测试数据集与训练数据集有些不同,则测试数据集上的预测会变得混乱。
当神经元单元被随机丢弃时,剩余可用神经元的每个这样的设置产生不同的网络。假设我们有一个有 N 个神经单元的网络;可能的神经网络配置的可能数量是 N 2 。对于小批量中的每个训练样本,基于退出概率随机选择一组不同的神经元。因此,训练一个辍学的神经网络相当于训练一组不同的神经网络,其中每个网络很少得到训练,如果有的话。
正如我们所猜测的,对许多不同模型的预测进行平均会减少总体模型的方差并减少过拟合,因此我们通常会获得更好、更稳定的预测。
对于两类问题,在两个不同的模型 M 1 和 M 2 上训练,如果一个数据点的类概率对于模型 M 1 是 p 11 和 p 12 ,对于模型 M 2 是 p 21 和 p 22 ,那么我们取集合模型 M 1 的平均概率集合模型对于类别 1 的概率为
,对于类别 2 的概率为
。
另一种平均方法是取不同模型预测值的几何平均值。在这种情况下,我们需要对几何平均值进行归一化,以使新概率的总和为 1。
先前示例的集合模型的新概率将分别是
和
。
在测试时,不可能从所有这些可能的网络中计算出预测值,然后进行平均。相反,使用了具有所有权重和连接的单一神经网络——但是进行了权重调整。如果在训练期间以概率 p 保留神经网络单元,则通过将权重乘以概率 p 来缩小该单元的输出权重。通常,这种对测试数据集进行预测的近似效果很好。可以证明,对于具有 SoftMax 输出层的模型,前面的排列相当于从那些由漏失产生的单个模型中取出预测,然后计算它们的几何平均值。
在图 3-27 中,展示了一个随机丢弃了三个单元的神经网络。正如我们所看到的,被丢弃单元的所有输入和输出连接也被丢弃。

图 3-27。
Neural network with three units dropped randomly
对于卷积神经网络,全连接层中的单元及其相应的传入和传出连接通常被丢弃。因此,在预测测试数据集时,不同的过滤器核权重不需要任何调整。
卷积神经网络在 MNIST 数据集上的数字识别
现在,我们已经完成了卷积神经网络的基本构建模块,让我们看看 CNN 在学习分类 MNIST 数据集方面有多好。TensorFlow 中基本实现的详细逻辑记录在清单 3-6 中。CNN 接收对应于 RGB 通道的高度 28、宽度 28 和深度 3 的图像。图像经过一系列卷积、ReLU 激活和最大池操作两次,然后被馈送到完全连接的层,最后到达输出层。第一卷积层产生 64 个特征图,第二卷积层提供 128 个特征图,全连通层有 1024 个单元。选择最大池图层是为了通过
减少要素地图的大小。特征地图可以被认为是 2D 图像。

图 3-28。
Predicted digits versus actual digits from CNN model
##########################################################
##Import the required libraries and read the MNIST dataset
##########################################################
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
from tensorflow.examples.tutorials.mnist import input_data
import time
mnist = input_data.read_data_sets("MNIST_data/",one_hot=True)
###########################################
## Set the value of the Parameters
###########################################
learning_rate = 0.01
epochs = 20
batch_size = 256
num_batches = mnist.train.num_examples/batch_size
input_height = 28
input_width = 28
n_classes = 10
dropout = 0.75
display_step = 1
filter_height = 5
filter_width = 5
depth_in = 1
depth_out1 = 64
depth_out2 = 128
###########################################
# input output definition
###########################################
x = tf.placeholder(tf.float32,[None,28*28])
y = tf.placeholder(tf.float32,[None,n_classes])
keep_prob = tf.placeholder(tf.float32)
###########################################
## Store the weights
## Number of weights of filters to be learnt in 'wc1' => filter_height*filter_width*depth_in*depth_out1
## Number of weights of filters to be learnt in 'wc1' => filter_height*filter_width*depth_out1*depth_out2
## No of Connections to the fully Connected layer => Each maxpooling operation reduces the image size to 1/4.
## So two maxpooling reduces the imase size to /16\. There are depth_out2 number of images each of size 1/16 ## of the original image size of input_height*input_width. So there is total of
## (1/16)*input_height* input_width* depth_out2 pixel outputs which when connected to the fully connected layer ## with 1024 units would provide (1/16)*input_height* input_width* depth_out2*1024 connections.
###########################################
weights = {
'wc1' : tf.Variable(tf.random_normal([filter_height,filter_width,depth_in,depth_out1])),
'wc2' : tf.Variable(tf.random_normal([filter_height,filter_width,depth_out1,depth_out2])),
'wd1' : tf.Variable(tf.random_normal([(input_height/4)*(input_height/4)* depth_out2,1024])),
'out' : tf.Variable(tf.random_normal([1024,n_classes]))
}
#################################################
## In the 1st Convolutional Layer there are 64 feature maps and that corresponds to 64 biases in 'bc1'
## In the 2nd Convolutional Layer there are 128 feature maps and that corresponds to 128 biases in 'bc2'
## In the Fully Connected Layer there are 1024units and that corresponds to 1024 biases in 'bd1'
## In the output layet there are 10 classes for the Softmax and that corresponds to 10 biases in 'out'
#################################################
biases = {
'bc1' : tf.Variable(tf.random_normal([64])),
'bc2' : tf.Variable(tf.random_normal([128])),
'bd1' : tf.Variable(tf.random_normal([1024])),
'out' : tf.Variable(tf.random_normal([n_classes]))
}
##################################################
## Create the different layers
##################################################
'''C O N V O L U T I O N L A Y E R'''
def conv2d(x,W,b,strides=1):
x = tf.nn.conv2d(x,W,strides=[1,strides,strides,1],padding='SAME')
x = tf.nn.bias_add(x,b)
return tf.nn.relu(x)
''' P O O L I N G L A Y E R'''
def maxpool2d(x,stride=2):
return tf.nn.max_pool(x,ksize=[1,stride,stride,1],strides=[1,stride,stride,1],padding='SAME')
##################################################
## Create the feed forward model
##################################################
def conv_net(x,weights,biases,dropout):
##################################################
## Reshape the input in the 4 dimensional image
## 1st dimension - image index
## 2nd dimension - height
## 3rd dimension - width
## 4th dimension - depth
x = tf.reshape(x,shape=[-1,28,28,1])
##################################################
## Convolutional layer 1
conv1 = conv2d(x,weights['wc1'],biases['bc1'])
conv1 = maxpool2d(conv1,2)
## Convolutional layer 2
conv2 = conv2d(conv1,weights['wc2'],biases['bc2'])
conv2 = maxpool2d(conv2,2)
## Now comes the fully connected layer
fc1 = tf.reshape(conv2,[-1,weights['wd1'].get_shape().as_list()[0]])
fc1 = tf.add(tf.matmul(fc1,weights['wd1']),biases['bd1'])
fc1 = tf.nn.relu(fc1)
## Apply Dropout
fc1 = tf.nn.dropout(fc1,dropout)
## Output class prediction
out = tf.add(tf.matmul(fc1,weights['out']),biases['out'])
return out
#######################################################
# Defining the tensorflow Ops for different activities
#######################################################
pred = conv_net(x,weights,biases,keep_prob)
# Define loss function and optimizer
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=pred,labels=y))
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
# Evaluate model
correct_pred = tf.equal(tf.argmax(pred,1),tf.argmax(y,1))
accuracy = tf.reduce_mean(tf.cast(correct_pred,tf.float32))
## initializing all variables
init = tf.global_variables_initializer()
####################################################
## Launch the execution Graph
####################################################
start_time = time.time()
with tf.Session() as sess:
sess.run(init)
for i in range(epochs):
for j in range(num_batches):
batch_x,batch_y = mnist.train.next_batch(batch_size)
sess.run(optimizer, feed_dict={x:batch_x,y:batch_y,keep_prob:dropout})
loss,acc = sess.run([cost,accuracy],feed_dict={x:batch_x,y:batch_y,keep_prob: 1.})
if epochs % display_step == 0:
print("Epoch:", '%04d' % (i+1),
"cost=", "{:.9f}".format(loss),
"Training accuracy","{:.5f}".format(acc))
print('Optimization Completed')
y1 = sess.run(pred,feed_dict={x:mnist.test.images[:256],keep_prob: 1})
test_classes = np.argmax(y1,1)
print('Testing Accuracy:',sess.run(accuracy,feed_dict={x:mnist.test.images[:256],y:mnist.test.labels[:256],keep_prob: 1}))
f, a = plt.subplots(1, 10, figsize=(10, 2))
for i in range(10):
a[i].imshow(np.reshape(mnist.test.images[i],(28, 28)))
print test_classes[i]
end_time = time.time()
print('Total processing time:',end_time - start_time)
Listing 3-6.
前面的基本卷积神经网络包括两个卷积–最大池–ReLU 对以及最终输出 SoftMax 单元之前的全连接层,我们可以在短短 20 个时期内实现 0.9765625 的测试集精度。正如我们之前在第二章中通过多层感知器方法看到的,使用这种方法,我们只能在 1000 个时期内获得大约 91%的准确度。这证明了对于图像识别问题,卷积神经网络工作得最好。
我想强调的另一件事是,使用正确的超参数集和先验信息调整模型的重要性。诸如学习率选择的参数可能非常棘手,因为神经网络的成本函数通常是非凸的。大的学习率可以导致更快地收敛到局部最小值,但是可能引入振荡,而低的学习率将导致非常慢的收敛。理想情况下,学习率应该足够低,以使网络参数能够收敛到有意义的局部最小值,同时它应该足够高,以使模型能够更快地达到最小值。通常,对于前面的神经网络,0.01 的学习率有点偏高,但是因为我们只训练 20 个时期的数据,所以它工作得很好。较低的学习率不会在仅仅 20 个历元的情况下达到如此高的准确度。类似地,为随机梯度下降的小批量版本选择的批量大小影响训练过程的收敛。批量越大越好,因为梯度估计的噪声越小;然而,这可能以增加计算量为代价。人们还需要尝试不同的过滤器大小,以及在每个卷积层中试验不同数量的特征图。我们选择的模型体系结构是网络的先验知识。
用于解决现实世界问题的卷积神经网络
现在,我们将简要讨论如何解决现实世界中的图像分析问题,方法是研究最近由英特尔在 Kaggle 主办的一个问题,该问题涉及对不同类型的宫颈癌进行分类。在这场比赛中,需要建立一个模型,根据图像识别女性的子宫颈类型。这样做将允许对患者进行有效治疗。为竞赛提供了三种癌症的特定图像。因此,商业问题归结为一个三级图像分类问题。清单 3-7 中提供了解决该问题的基本方法。
########################################################
## Load the relevant libraries
########################################################
from PIL import ImageFilter, ImageStat, Image, ImageDraw
from multiprocessing import Pool, cpu_count
from sklearn.preprocessing import LabelEncoder
import pandas as pd
import numpy as np
import glob
import cv2
import time
from keras.utils import np_utils
import os
import tensorflow as tf
import shuffle
##########################################################
## Read the input images and then resize the image to 64 x 64 x 3 size
###########################################################
def get_im_cv2(path):
img = cv2.imread(path)
resized = cv2.resize(img, (64,64), cv2.INTER_LINEAR)
return resized
###########################################################
## Each of the folders corresponds to a different class
## Load the images into array and then define their output classes based on
## the folder number
###########################################################
def load_train():
X_train = []
X_train_id = []
y_train = []
start_time = time.time()
print('Read train images')
folders = ['Type_1', 'Type_2', 'Type_3']
for fld in folders:
index = folders.index(fld)
print('Load folder {} (Index: {})'.format(fld, index))
path = os.path.join('.', 'Downloads', 'Intel','train', fld, '*.jpg')
files = glob.glob(path)
for fl in files:
flbase = os.path.basename(fl)
img = get_im_cv2(fl)
X_train.append(img)
X_train_id.append(flbase)
y_train.append(index)
for fld in folders:
index = folders.index(fld)
print('Load folder {} (Index: {})'.format(fld, index))
path = os.path.join('.', 'Downloads', 'Intel','Additional', fld, '*.jpg')
files = glob.glob(path)
for fl in files:
flbase = os.path.basename(fl)
img = get_im_cv2(fl)
X_train.append(img)
X_train_id.append(flbase)
y_train.append(index)
print('Read train data time: {} seconds'.format(round(time.time() - start_time, 2)))
return X_train, y_train, X_train_id
###################################################################
## Load the test images
###################################################################
def load_test():
path = os.path.join('.', 'Downloads', 'Intel','test', '*.jpg')
files = sorted(glob.glob(path))
X_test = []
X_test_id = []
for fl in files:
flbase = os.path.basename(fl)
img = get_im_cv2(fl)
X_test.append(img)
X_test_id.append(flbase)
path = os.path.join('.', 'Downloads', 'Intel','test_stg2', '*.jpg')
files = sorted(glob.glob(path))
for fl in files:
flbase = os.path.basename(fl)
img = get_im_cv2(fl)
X_test.append(img)
X_test_id.append(flbase)
return X_test, X_test_id
##################################################
## Normalize the image data to have values between 0 and 1
## by diving the pixel intensity values by 255.
## Also convert the class label into vectors of length 3 corresponding to
## the 3 classes
## Class 1 - [1 0 0]
## Class 2 - [0 1 0]
## Class 3 - [0 0 1]
##################################################
def read_and_normalize_train_data():
train_data, train_target, train_id = load_train()
print('Convert to numpy...')
train_data = np.array(train_data, dtype=np.uint8)
train_target = np.array(train_target, dtype=np.uint8)
print('Reshape...')
train_data = train_data.transpose((0, 2,3, 1))
train_data = train_data.transpose((0, 1,3, 2))
print('Convert to float...')
train_data = train_data.astype('float32')
train_data = train_data / 255
train_target = np_utils.to_categorical(train_target, 3)
print('Train shape:', train_data.shape)
print(train_data.shape[0], 'train samples')
return train_data, train_target, train_id
###############################################################
## Normalize test-image data
###############################################################
def read_and_normalize_test_data():
start_time = time.time()
test_data, test_id = load_test()
test_data = np.array(test_data, dtype=np.uint8)
test_data = test_data.transpose((0,2,3,1))
train_data = test_data.transpose((0, 1,3, 2))
test_data = test_data.astype('float32')
test_data = test_data / 255
print('Test shape:', test_data.shape)
print(test_data.shape[0], 'test samples')
print('Read and process test data time: {} seconds'.format(round(time.time() - start_time, 2)))
return test_data, test_id
##########################################################
## Read and normalize the train data
##########################################################
train_data, train_target, train_id = read_and_normalize_train_data()
##########################################################
## Shuffle the input training data to aid stochastic gradient descent
##########################################################
list1_shuf = []
list2_shuf = []
index_shuf = range(len(train_data))
shuffle(index_shuf)
for i in index_shuf:
list1_shuf.append(train_data[i,:,:,:])
list2_shuf.append(train_target[i,])
list1_shuf = np.array(list1_shuf,dtype=np.uint8)
list2_shuf = np.array(list2_shuf,dtype=np.uint8)
##########################################################
## TensorFlow activities for Network Definition and Training
##########################################################
## Create the different layers
channel_in = 3
channel_out = 64
channel_out1 = 128
'''C O N V O L U T I O N L A Y E R'''
def conv2d(x,W,b,strides=1):
x = tf.nn.conv2d(x,W,strides=[1,strides,strides,1],padding='SAME')
x = tf.nn.bias_add(x,b)
return tf.nn.relu(x)
''' P O O L I N G L A Y E R'''
def maxpool2d(x,stride=2):
return tf.nn.max_pool(x,ksize=[1,stride,stride,1],strides=[1,stride,stride,1],padding='SAME')
## Create the feed-forward model
def conv_net(x,weights,biases,dropout):
## Convolutional layer 1
conv1 = conv2d(x,weights['wc1'],biases['bc1'])
conv1 = maxpool2d(conv1,stride=2)
## Convolutional layer 2
conv2a = conv2d(conv1,weights['wc2'],biases['bc2'])
conv2a = maxpool2d(conv2a,stride=2)
conv2 = conv2d(conv2a,weights['wc3'],biases['bc3'])
conv2 = maxpool2d(conv2,stride=2)
## Now comes the fully connected layer
fc1 = tf.reshape(conv2,[-1,weights['wd1'].get_shape().as_list()[0]])
fc1 = tf.add(tf.matmul(fc1,weights['wd1']),biases['bd1'])
fc1 = tf.nn.relu(fc1)
## Apply Dropout
fc1 = tf.nn.dropout(fc1,dropout)
## Another fully Connected Layer
fc2 = tf.add(tf.matmul(fc1,weights['wd2']),biases['bd2'])
fc2 = tf.nn.relu(fc2)
## Apply Dropout
fc2 = tf.nn.dropout(fc2,dropout)
## Output class prediction
out = tf.add(tf.matmul(fc2,weights['out']),biases['out'])
return out
######################################################
## Define several parameters for the network and learning
#######################################################
start_time = time.time()
learning_rate = 0.01
epochs = 200
batch_size = 128
num_batches = list1_shuf.shape[0]/128
input_height = 64
input_width = 64
n_classes = 3
dropout = 0.5
display_step = 1
filter_height = 3
filter_width = 3
depth_in = 3
depth_out1 = 64
depth_out2 = 128
depth_out3 = 256
#######################################################
# input–output definition
#######################################################
x = tf.placeholder(tf.float32,[None,input_height,input_width,depth_in])
y = tf.placeholder(tf.float32,[None,n_classes])
keep_prob = tf.placeholder(tf.float32)
########################################################
## Define the weights and biases
########################################################
weights = {
'wc1' : tf.Variable(tf.random_normal([filter_height,filter_width,depth_in,depth_out1])),
'wc2' : tf.Variable(tf.random_normal([filter_height,filter_width,depth_out1,depth_out2])),
'wc3' : tf.Variable(tf.random_normal([filter_height,filter_width,depth_out2,depth_out3])),
'wd1' : tf.Variable(tf.random_normal([(input_height/8)*(input_height/8)*256,512])),
'wd2' : tf.Variable(tf.random_normal([512,512])),
'out' : tf.Variable(tf.random_normal([512,n_classes]))
}
biases = {
'bc1' : tf.Variable(tf.random_normal([64])),
'bc2' : tf.Variable(tf.random_normal([128])),
'bc3' : tf.Variable(tf.random_normal([256])),
'bd1' : tf.Variable(tf.random_normal([512])),
'bd2' : tf.Variable(tf.random_normal([512])),
'out' : tf.Variable(tf.random_normal([n_classes]))
}
######################################################
## Define the TensorFlow ops for training
######################################################
pred = conv_net(x,weights,biases,keep_prob)
# Define loss function and optimizer
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=pred,labels=y))
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
# Evaluate model
correct_pred = tf.equal(tf.argmax(pred,1),tf.argmax(y,1))
accuracy = tf.reduce_mean(tf.cast(correct_pred,tf.float32))
## Define the initialization op
init = tf.global_variables_initializer()
######################################################
## Launch the execution graph and invoke the training
######################################################
start_time = time.time()
with tf.Session() as sess:
sess.run(init)
for i in range(epochs):
for j in range(num_batches):
batch_x,batch_y = list1_shuf[i*(batch_size):(i+1)*(batch_size)],list2_shuf[i*(batch_size):(i+1)*(batch_size)]
sess.run(optimizer, feed_dict={x:batch_x,y:batch_y,keep_prob:dropout})
loss,acc = sess.run([cost,accuracy],feed_dict={x:batch_x,y:batch_y,keep_prob: 1.})
if epochs % display_step == 0:
print("Epoch:", '%04d' % (i+1),
"cost=", "{:.9f}".format(loss),
"Training accuracy","{:.5f}".format(acc))
print('Optimization Completed')
end_time = time.time()
print('Total processing time:',end_time - start_time)
-- output --
('Epoch:', '0045', 'cost=', '0.994687378', 'Training accuracy', '0.53125')
('Epoch:', '0046', 'cost=', '1.003623009', 'Training accuracy', '0.52344')
('Epoch:', '0047', 'cost=', '0.960040927', 'Training accuracy', '0.56250')
('Epoch:', '0048', 'cost=', '0.998520255', 'Training accuracy', '0.54688')
('Epoch:', '0049', 'cost=', '1.016047001', 'Training accuracy', '0.50781')
('Epoch:', '0050', 'cost=', '1.043521643', 'Training accuracy', '0.49219')
('Epoch:', '0051', 'cost=', '0.959320068', 'Training accuracy', '0.58594')
('Epoch:', '0052', 'cost=', '0.935006618', 'Training accuracy', '0.57031')
('Epoch:', '0053', 'cost=', '1.031400681', 'Training accuracy', '0.49219')
('Epoch:', '0054', 'cost=', '1.023633003', 'Training accuracy', '0.50781')
('Epoch:', '0055', 'cost=', '1.007938623', 'Training accuracy', '0.53906')
('Epoch:', '0056', 'cost=', '1.033236384', 'Training accuracy', '0.46094')
('Epoch:', '0057', 'cost=', '0.939492166', 'Training accuracy', '0.60938')
('Epoch:', '0058', 'cost=', '0.986051500', 'Training accuracy', '0.56250')
('Epoch:', '0059', 'cost=', '1.019751549', 'Training accuracy', '0.51562')
('Epoch:', '0060', 'cost=', '0.955037951', 'Training accuracy', '0.57031')
('Epoch:', '0061', 'cost=', '0.963475347', 'Training accuracy', '0.58594')
('Epoch:', '0062', 'cost=', '1.019685864', 'Training accuracy', '0.50000')
('Epoch:', '0063', 'cost=', '0.970604420', 'Training accuracy', '0.53125')
('Epoch:', '0064', 'cost=', '0.962844968', 'Training accuracy', '0.54688')
Listing 3-7.
该模型在竞赛排行榜中的对数损失约为 0.97,而该竞赛的最佳模型的对数损失约为 0.78。这是因为该模型是一个基本的实现,没有考虑图像处理中的其他高级概念。我们将在本书的后面研究一种叫做迁移学习的高级技术,这种技术在提供的图像数量较少时效果很好。读者可能感兴趣的关于实现的几点如下:
- 这些图像被读取为三维 Numpy 数组,并通过 OpenCV 调整大小,然后附加到一个列表中。然后,该列表被转换为 Numpy 数组,因此,我们为训练和测试数据集都获得了一个四维 Numpy 数组或张量。训练和测试图像张量已经被转置,以具有按照图像编号、沿着图像高度的位置、沿着图像宽度的位置和图像通道的顺序排列的维度。
- 通过除以像素强度的最大值,图像已经被归一化为具有 0 和 1 之间的值;即 255。这有助于基于梯度的优化。
- 图像被随机打乱,使得小批量具有随机排列的三个类别的图像。
- 网络实现的其余部分类似于 MNIST 分类问题,但在最终的 SoftMax 输出层之前有三层卷积-ReLU-max 池组合和两层完全连接的层。
- 这里省略了涉及预测和提交的代码。
批量标准化
批量规范化是由 Sergey Ioffe 和 Christian Szegedy 发明的,是深度学习领域的先驱元素之一。批量归一化的原论文题目为《批量归一化:通过减少内部协变量移位加速深度网络训练》,可位于 https://arxiv.org/abs/1502.03167 。
当通过随机梯度下降训练神经网络时,由于前面层上权重的更新,每层的输入分布发生变化。这减慢了训练过程,并且使得很难训练非常深的神经网络。神经网络的训练过程很复杂,因为任何层的输入都依赖于所有前面层的参数,因此随着网络的增长,即使很小的参数变化也会产生放大效应。这导致层中输入分布的变化。
现在,让我们试着理解当一层中的激活函数的输入分布由于前一层中的权重改变而改变时,什么可能出错。
sigmoid 或 tanh 激活函数仅在其输入的特定范围内具有良好的线性梯度,一旦输入变大,梯度下降到零。

图 3-29。
Sigmoid function with its small unsaturated region
前面层中的参数变化可能会改变 sigmoid 单元层的输入概率分布,使得 sigmoid 的大部分输入属于饱和区,从而产生近零梯度,如图 3-29 所示。由于这些零或接近零的梯度,学习变得非常缓慢或完全停止。避免这个问题的一个方法是使用校正线性单元(ReLUs)。避免这一问题的另一种方法是在非饱和区内保持 sigmoid 单元的输入分布稳定,以便随机梯度下降不会卡在饱和区内。
这种内部网络单元输入分布的变化现象被批量标准化过程的发明者称为内部协变量移位。
批量归一化通过将图层的输入归一化为零均值和单位标准差来减少内部协变量偏移。训练时,从每层的小批量样本中估计平均值和标准偏差,而在测试预测时,通常使用总体方差和平均值。
如果一个层从前面的层接收输入激活的矢量
,那么在由 m 个数据点组成的每个小批量中,输入激活被归一化如下:
![$$ \hat{x_i}=\frac{x_i-E\left[{x}_i\right]}{\sqrt{Var\left[{x}_i\right]+\in }} $$](A448418_1_En_3_Chapter_Equav.gif)
其中

![$$ {\upsigma}_B²=\frac{1}{m}\sum \limits_{k=1}m{\left({x_i}{(k)}-E\left[{x}_i\right]\right)}² $$](A448418_1_En_3_Chapter_Equax.gif)
在统计学上,u B 和σB2 不过是样本均值和有偏样本标准差。
一旦完成归一化,
不直接馈送给激活函数,而是在馈送给激活函数之前,通过引入参数γ和β进行缩放和移位。如果我们将输入激活限制为归一化值,它们可能会改变图层所能表示的内容。因此,我们的想法是通过以下变换对标准化值应用线性变换,这样,如果网络通过训练认为任何变换前的原始值对网络都有好处,它就可以恢复原始值。由

给出馈入激活函数的实际变换输入激活 y i
参数 u B 、σ B 2 、γ和β将像其他参数一样通过反向传播来学习。如前所述,如果模型认为来自网络的原始值更理想,它可以学习γ =Var[x i 和
。
可能出现的一个非常自然的问题是,为什么我们将小批量平均值 u B 和方差σBB2作为要通过批量传播学习的参数,而不是出于标准化目的将它们估计为小批量的运行平均值。这不起作用,因为 u B 和σB2 通过 x i 依赖于模型的其他参数,并且当我们直接将这些参数估计为运行平均值时,在优化过程中不考虑这种依赖性。为了保持这些依赖性不变,u B 和σBB2应该作为参数参与优化过程,因为 u B 和σBB2相对于 x i 所依赖的其他参数的梯度对于学习过程至关重要。这种优化的总体效果是以输入
保持零均值和单位标准偏差的方式修改模型。
在推断或测试期间,总体统计量 E[xI和 Var[xI通过保持小批量统计量的运行平均值而用于标准化。![$$ E\left[{x}_i\right]=E\left[{u}_B\right] $$](A448418_1_En_3_Chapter_Equaz.gif)
![$$ Var\left[{x}_i\right]=\left(\frac{m}{m-1}\right)E\left[{\upsigma}_B²\right] $$](A448418_1_En_3_Chapter_Equba.gif)
需要这个校正因子来获得总体方差的无偏估计。
这里提到了批处理规范化的几个优点:
- 由于内部协变量移位的消除或减少,模型可以更快地被训练。获得好的模型参数需要较少的训练迭代次数。
- 批处理规范化有一定的规范化能力,有时可以消除退出的需要。
- 批量归一化适用于卷积神经网络,其中每个输出特征图都有一组γ和β。
卷积神经网络的不同结构
在本节中,我们将介绍几种目前广泛使用的卷积神经网络架构。这些网络结构不仅用于分类,而且稍加修改后,还用于分割、定位和检测。此外,每个网络都有预先训练的版本,使社区能够进行迁移学习或微调模型。除了 LeNet,几乎所有的 CNN 模型都赢得了 ImageNet 的千类分类比赛。
蓝尼
第一个成功的卷积神经网络是由 Yann LeCunn 在 1990 年开发的,用于为基于 OCR 的活动成功地分类手写数字,例如读取邮政编码、支票等。LeNet5 是 Yann LeCunn 及其同事的最新成果。它接收
大小的图像作为输入,并通过一个卷积层产生 6 个 28x28 大小的特征图。然后,对六个特征图进行子采样,以产生大小为 14×14 的六个输出图像。子采样可以被认为是一种汇集操作。第二卷积层具有大小为
的 16 个特征图,而第二子采样层将特征图大小减小到
。接下来是两个全连接层,分别为 120 和 84 个单元,然后是输出层,十个类对应十个数字。图 3-30 表示 LeNet5 的架构图。

图 3-30。
LeNet5 architectural diagram
LeNet5 网络的主要特点如下:
-
通过子采样的汇集采用
个邻域片,并对四个像素的亮度值求和。通过可训练的权重和偏差对总和进行缩放,然后通过 sigmoid 激活函数进行馈送。这与最大池化和平均池化有一点不同。 -
The filter kernel used for convolution is of size
The output units are radial basis function (RBF) units instead of the SoftMax functions that we generally use. The 84 units of the fully connected layers had 84 connections to each of the classes and hence 84 corresponding weights. The 84 weights/class represent each class’s characteristics. If the inputs to those 84 units are very close to the weights corresponding to a class, then the inputs are more likely to belong to that class. In a SoftMax we look at the dot product of the inputs to each of the class’s weight vectors, while in RBF units we look at the Euclidean distance between the input and the output class representative’s weight vectors. The greater the Euclidean distance , the smaller the chance is of the input belonging to that class. The same can be converted to probability by exponentiating the negative of the distance and then normalizing over the different classes. The Euclidean distances over all the classes for an input record would act as the loss function for that input. Let
be the output vector of the fully connected layer. For each class, there would be 84 weight connections. If the representative class’s weight vector for the ith class is
then the output of the ith class unit can be given by the following:![$$ x-{w_i}_2²=\sum \limits_{j=1}^{84}{\left({x}_j-{w}_{ij}\right)}² $$]()
-
每个类别的代表性权重是预先固定的,并且不是学习的权重。
阿勒克斯网
AlexNet CNN 架构由 Alex Krizhevsky、Ilya Sutskever 和 Geoffrey Hinton 于 2012 年开发,赢得了 2012 ImageNet ILSVRC (ImageNet 大规模视觉识别挑战赛)。关于 AlexNet 的原始论文题为“使用深度卷积神经网络的 ImageNet 分类”,可以位于 https://papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networks.pdf 。
这是 CNN 架构第一次以巨大优势击败其他方法。与第二好的条目的 26.2%的错误率相比,他们的网络在前五个预测中实现了 15.4%的错误率。AlexNet 的架构图如图 3-31 所示。
AlexNet 包括五个卷积层、最大池层和丢弃层,以及除了一千个类单元的输入和输出层之外的三个全连接层。网络的输入是大小为
的图像。第一卷积层产生 96 个特征图,对应于大小为
的 96 个滤波器核,步长为 4 个像素单位。第二卷积层产生 256 个对应于大小为
的滤波器核的特征图。前两个卷积层之后是最大池层,而接下来的三个卷积层一个接一个放置,没有任何中间最大池层。第五卷积层之后是 max 池化层、4096 个单元的两个全连接层,最后是一千个类的 SoftMax 输出层。第三卷积层具有 384 个大小为
的滤波器核,而第四和第五卷积层分别具有 384 和 256 个大小为
的滤波器核。在最后两个完全连接的层中使用 0.5 的压差。你会注意到,对于除了第三卷积层之外的所有卷积层,卷积的滤波器核的深度是前一层中特征映射数量的一半。这是因为 AlexNet 当时的计算成本很高,因此训练必须在两个独立的 GPU 之间进行。然而,如果您仔细观察,对于第三个卷积活动,卷积存在交叉连接,因此滤波器核的维数是
而不是
。相同类型的交叉连接适用于全连接层,因此它们表现为具有 4096 个单元的普通全连接层。

图 3-31。
AlexNet architecture
AlexNet 的主要特点如下:
- ReLU 激活功能用于非线性。它们产生了巨大的影响,因为 RELUs 更容易计算,并且具有恒定的不饱和梯度,而 sigmoid 和 tanh 激活函数的梯度在输入值非常高和非常低时趋于零。
- 在模型中,放弃用于减少过拟合。
- 相对于非重叠汇集而言,使用了重叠汇集。
- 该模型在两个 GPU GTX 580 上训练了大约五天,以进行快速计算。
- 通过数据扩充技术,如图像平移、水平反射和碎片提取,数据集的大小增加了。
VGG16
2014 年,VGG 集团凭借名为 VGG16 的 16 层架构在 ILSVRC-2014 竞赛中获得亚军。它使用了一种深入而简单的体系结构,从那以后,这种体系结构受到了广泛的欢迎。与 VGG 网络相关的论文题为“用于大规模图像识别的非常深度的卷积网络”,由卡伦·西蒙扬和安德鲁·齐泽曼撰写。纸张可以位于 https://arxiv.org/abs/1409.1556 。
VGG16 架构使用了
过滤器,并随后使用了 ReLU 激活和带有
感受域的最大池,而不是使用大型内核过滤器进行卷积。发明人的推理是,使用两个
卷积层相当于具有一个
卷积,同时保留了较小内核滤波器尺寸的优点;即,由于两个卷积 ReLU 对而不是一个卷积 ReLU 对,实现了参数数量的减少和更多的非线性。该网络的一个特殊属性是,随着输入体积的空间维度因卷积和最大池化而减小,随着我们深入网络,特征图的数量会因过滤器数量的增加而增加。

图 3-32。
VGG16 architecture
图 3-32 表示 VGG16 的架构。网络的输入是大小为
的图像。前两个卷积层产生 64 个特征图,每个特征图之后是最大池。卷积滤波器的空间大小为
,跨距为 1,填充为 1。最大池的大小为
,整个网络的步幅为 2。第三和第四卷积层产生 128 个特征图,每个特征图之后是最大汇集层。网络的其余部分以类似的方式运行,如图 3-32 所示。在网络的末端,有三个 4096 个单元的全连接层,每个层之后是一千个类的输出 SoftMax 层。对于完全连接的层,压差设置为 0.5。网络中的所有单元都有 ReLU 激活。
瑞斯网
ResNet 是微软的一个 152 层深度卷积神经网络,它以仅 3.6%的错误率赢得了 ILSVRC 2015 比赛,这被认为优于人类 5-10%的错误率。ResNet 上的论文由何、、、任、孙健撰写,论文题目为《图像识别的深度残差学习》,可定位于 https://arxiv.org/abs/1512.03385 。除了深度之外,ResNet 还实现了一种独特的剩余块思想。在每一系列卷积 ReLUs 卷积运算之后,运算的输入被反馈到运算的输出。在传统方法中,当进行卷积和其他变换时,我们试图将基础映射拟合到原始数据以解决分类任务。然而,使用 ResNet 的残差块概念,我们试图学习残差映射,而不是从输入到输出的直接映射。在形式上,在每个小的活动块中,我们将输入添加到输出块中。如图 3-33 所示。这个概念基于这样的假设,即拟合残差映射比拟合从输入到输出的原始映射更容易。

图 3-33。
Residual block
迁移学习
广义的迁移学习指的是储存在解决问题时获得的知识,并将这些知识用于类似领域的不同问题。由于各种原因,迁移学习在深度学习领域取得了巨大成功。
由于隐藏层的性质和不同单元内的连接方案,深度学习模型通常具有大量参数。为了训练这样一个庞大的模型,需要大量的数据,否则模型将会遇到过拟合的问题。在许多问题中,训练模型所需的大量数据不可用,但问题的性质需要深度学习解决方案,以便产生合理的影响。例如,在用于对象识别的图像处理中,深度学习模型已知能够提供最先进的解决方案。在这种情况下,迁移学习可以用于从预训练的深度学习模型中生成通用特征,然后使用这些特征建立一个简单的模型来解决问题。因此,这个问题的唯一参数是用于构建简单模型的参数。预训练模型通常是在巨大的数据语料库上训练的,因此具有可靠的参数。
当我们通过几层卷积处理图像时,初始层学会检测非常普通的特征,如卷曲和边缘。随着网络越来越深,更深层中的卷积层学会检测与特定种类的数据集相关的更复杂的特征。例如,在分类中,较深的层将学习检测诸如眼睛、鼻子、脸等特征。
让我们假设我们有一个在 ImageNet 数据集的一千个类别上训练的 VGG16 架构模型。现在,如果我们得到一个更小的数据集,它具有更少的与 VGG16 预训练模型数据集相似的图像类别,那么我们可以使用相同的 VGG16 模型直到完全连接的层,然后用新的类别替换输出层。此外,我们保持网络的权重固定,直到全连接层,并且仅训练模型来学习从全连接层到输出层的权重。这是因为数据集的性质与较小数据集的性质相同,因此通过不同参数在预训练模型中学习的特征对于新的分类问题来说足够好,并且我们只需要学习从完全连接的层到输出层的权重。这是要学习的参数数量的巨大减少,并且它将减少过拟合。如果我们使用 VGG16 架构训练小数据集,它可能会遭受严重的过拟合,因为要在小数据集上学习大量参数。
当数据集的性质与用于预训练模型的数据集的性质非常不同时,您会怎么做?
在这种情况下,我们可以使用相同的预训练模型,但只固定前几组卷积–ReLUs–max 池图层的参数,然后添加几个卷积–ReLU–max 池图层,这些图层将学习检测新数据集的内在特征。最后,我们必须有一个完全连接的层,然后是输出层。由于我们使用来自预训练 VGG16 网络的初始卷积–ReLUs–max 池层集合的权重,因此无需学习这些层的相关参数。如前所述,卷积的早期层学习适用于所有类型图像的非常通用的特征,例如边缘和曲线。网络的其余部分将需要被训练以学习特定问题数据集固有的特定特征。
使用迁移学习的指南
以下是关于何时以及如何使用预训练模型进行迁移学习的一些指导原则:
- 问题数据集的大小很大,并且数据集类似于用于预训练模型的数据集,这是理想的情况。我们可以保留整个模型体系结构,除了输出层,当它的类的数量与预训练的不同时。然后,我们可以使用预训练模型的权重作为模型的初始权重来训练模型。
- 问题数据集的大小很大,但数据集不同于用于预训练模型的数据集,在这种情况下,由于数据集很大,我们可以从头开始训练模型。预训练模型在这里不会给出任何收益,因为数据集的性质非常不同,并且因为我们有一个大的数据集,所以我们可以负担得起从头训练整个网络,而不会过度适应与在小数据集上训练的大网络相关的内容。
- 问题数据集的大小很小,并且数据集类似于用于预训练模型的数据集,这就是我们之前讨论的情况。由于数据集内容相似,我们可以重用大部分模型的现有权重,并且只根据问题数据集中的类更改输出图层。然后,我们只为最后一层的权重训练模型。例如,如果我们只为狗和猫获得像 ImageNet 这样的图像,我们可以选择在 ImageNet 上预先训练的 VGG16 模型,只需修改输出层,使其具有两个类,而不是一千个。对于新的网络模型,我们只需要训练特定于最终输出层的权重,保持所有其他权重与预训练的 VGG16 模型的权重相同。
- 问题数据集的大小很小,并且数据集与预训练模型中使用的数据集不同,这不是一个好的情况。如前所述,我们可以冻结预训练网络的几个初始层的权重,然后在问题数据集上训练模型的其余部分。像往常一样,输出图层需要根据问题数据集中类的数量进行更改。由于我们没有大型数据集,我们正试图通过重用预训练模型的初始层的权重来尽可能减少参数的数量。由于 CNN 的前几层了解任何类型图像固有的一般特征,这是可能的。
用 Google 的 InceptionV3 迁移学习
InceptionV3 是谷歌最先进的卷积神经网络之一。它是 GoogLeNet 的高级版本,凭借其开箱即用的卷积神经网络架构赢得了 ImageNetILSVRC-2014 竞赛。该网络的细节记录在由克里斯蒂安·塞格迪和他的合作者撰写的题为“重新思考计算机视觉的初始架构”的论文中。论文可以位于 https://arxiv.org/abs/1512.00567 。GoogLeNet 及其修改版本的核心元素是引入了一个 inception 模块来进行卷积和池化。在传统的卷积神经网络中,在卷积层之后,我们要么执行另一个卷积,要么执行最大池,而在初始模块中,一系列卷积和最大池在每一层并行完成,然后合并特征图。此外,在每一层中,卷积不是用一个内核滤波器大小来完成的,而是用多个内核滤波器大小来完成的。初始模块如下图 3-34 所示。正如我们所看到的,有一系列并行的卷积和最大池,最后所有的输出特征映射在 gilter 连接块中合并。
卷积进行维度缩减,并执行类似平均池的操作。比如说我们有一个
的输入量,160 是特征图的数量。与
滤波器内核的卷积将创建
的输出音量
这种网络工作良好,因为不同的核大小基于过滤器的感受域的大小在不同的粒度级别提取特征信息。
感受野将比
感受野提取更多的颗粒信息。

图 3-34。
Inception module
谷歌的 TensorFlow 提供了一个预训练模型,该模型是在 ImageNet 数据上训练的。它可以用于迁移学习。我们使用来自谷歌的预训练模型,并在从 https://www.kaggle.com/c/dogs-vs-cats/data 提取的一组猫和狗的图像上重新训练它。train.zip数据集包含 25000 张图片,其中猫和狗各有 12500 张图片。
预训练模型可以在 TensorFlow GitHub 示例文件夹中找到。清单 3-8 显示了执行和使用迁移学习模型需要遵循的步骤。克隆 TensorFlow GitHub 存储库,因为模型位于 Examples 文件夹中。一旦完成,我们就可以进入克隆的 TensorFlow 文件夹并执行清单 3-8 中的命令。




-- Output Log from Model retraining in the Final Few Steps of Learning --
2017-07-05 09:28:26.133994: Step 3750: Cross entropy = 0.006824
2017-07-05 09:28:26.173795: Step 3750: Validation accuracy = 100.0% (N=100)
2017-07-05 09:28:26.616457: Step 3760: Train accuracy = 99.0%
2017-07-05 09:28:26.616500: Step 3760: Cross entropy = 0.017717
2017-07-05 09:28:26.656621: Step 3760: Validation accuracy = 100.0% (N=100)
2017-07-05 09:28:27.055419: Step 3770: Train accuracy = 100.0%
2017-07-05 09:28:27.055461: Step 3770: Cross entropy = 0.004180
2017-07-05 09:28:27.094449: Step 3770: Validation accuracy = 99.0% (N=100)
2017-07-05 09:28:27.495100: Step 3780: Train accuracy = 100.0%
2017-07-05 09:28:27.495154: Step 3780: Cross entropy = 0.014055
2017-07-05 09:28:27.540385: Step 3780: Validation accuracy = 99.0% (N=100)
2017-07-05 09:28:27.953271: Step 3790: Train accuracy = 99.0%
2017-07-05 09:28:27.953315: Step 3790: Cross entropy = 0.029298
2017-07-05 09:28:27.992974: Step 3790: Validation accuracy = 100.0% (N=100)
2017-07-05 09:28:28.393039: Step 3800: Train accuracy = 98.0%
2017-07-05 09:28:28.393083: Step 3800: Cross entropy = 0.039568
2017-07-05 09:28:28.432261: Step 3800: Validation accuracy = 99.0% (N=100)
2017-07-05 09:28:28.830621: Step 3810: Train accuracy = 98.0%
2017-07-05 09:28:28.830664: Step 3810: Cross entropy = 0.032378
2017-07-05 09:28:28.870126: Step 3810: Validation accuracy = 100.0% (N=100)
2017-07-05 09:28:29.265780: Step 3820: Train accuracy = 100.0%
2017-07-05 09:28:29.265823: Step 3820: Cross entropy = 0.004463
2017-07-05 09:28:29.304641: Step 3820: Validation accuracy = 98.0% (N=100)
2017-07-05 09:28:29.700730: Step 3830: Train accuracy = 100.0%
2017-07-05 09:28:29.700774: Step 3830: Cross entropy = 0.010076
2017-07-05 09:28:29.741322: Step 3830: Validation accuracy = 100.0% (N=100)
2017-07-05 09:28:30.139802: Step 3840: Train accuracy = 99.0%
2017-07-05 09:28:30.139847: Step 3840: Cross entropy = 0.034331
2017-07-05 09:28:30.179052: Step 3840: Validation accuracy = 100.0% (N=100)
2017-07-05 09:28:30.575682: Step 3850: Train accuracy = 97.0%
2017-07-05 09:28:30.575727: Step 3850: Cross entropy = 0.032292
2017-07-05 09:28:30.615107: Step 3850: Validation accuracy = 100.0% (N=100)
2017-07-05 09:28:31.036590: Step 3860: Train accuracy = 100.0%
2017-07-05 09:28:31.036635: Step 3860: Cross entropy = 0.005654
2017-07-05 09:28:31.076715: Step 3860: Validation accuracy = 99.0% (N=100)
2017-07-05 09:28:31.489839: Step 3870: Train accuracy = 99.0%
2017-07-05 09:28:31.489885: Step 3870: Cross entropy = 0.047375
2017-07-05 09:28:31.531109: Step 3870: Validation accuracy = 99.0% (N=100)
2017-07-05 09:28:31.931939: Step 3880: Train accuracy = 99.0%
2017-07-05 09:28:31.931983: Step 3880: Cross entropy = 0.021294
2017-07-05 09:28:31.972032: Step 3880: Validation accuracy = 98.0% (N=100)
2017-07-05 09:28:32.375811: Step 3890: Train accuracy = 100.0%
2017-07-05 09:28:32.375855: Step 3890: Cross entropy = 0.007524
2017-07-05 09:28:32.415831: Step 3890: Validation accuracy = 99.0% (N=100)
2017-07-05 09:28:32.815560: Step 3900: Train accuracy = 100.0%
2017-07-05 09:28:32.815604: Step 3900: Cross entropy = 0.005150
2017-07-05 09:28:32.855788: Step 3900: Validation accuracy = 99.0% (N=100)
2017-07-05 09:28:33.276503: Step 3910: Train accuracy = 99.0%
2017-07-05 09:28:33.276547: Step 3910: Cross entropy = 0.033086
2017-07-05 09:28:33.316980: Step 3910: Validation accuracy = 98.0% (N=100)
2017-07-05 09:28:33.711042: Step 3920: Train accuracy = 100.0%
2017-07-05 09:28:33.711085: Step 3920: Cross entropy = 0.004519
2017-07-05 09:28:33.750476: Step 3920: Validation accuracy = 99.0% (N=100)
2017-07-05 09:28:34.147856: Step 3930: Train accuracy = 100.0%
2017-07-05 09:28:34.147901: Step 3930: Cross entropy = 0.005670
2017-07-05 09:28:34.191036: Step 3930: Validation accuracy = 99.0% (N=100)
2017-07-05 09:28:34.592015: Step 3940: Train accuracy = 99.0%
2017-07-05 09:28:34.592059: Step 3940: Cross entropy = 0.019866
2017-07-05 09:28:34.632025: Step 3940: Validation accuracy = 98.0% (N=100)
2017-07-05 09:28:35.054357: Step 3950: Train accuracy = 100.0%
2017-07-05 09:28:35.054409: Step 3950: Cross entropy = 0.004421
2017-07-05 09:28:35.100622: Step 3950: Validation accuracy = 96.0% (N=100)
2017-07-05 09:28:35.504866: Step 3960: Train accuracy = 100.0%
2017-07-05 09:28:35.504910: Step 3960: Cross entropy = 0.009696
2017-07-05 09:28:35.544595: Step 3960: Validation accuracy = 99.0% (N=100)
2017-07-05 09:28:35.940758: Step 3970: Train accuracy = 99.0%
2017-07-05 09:28:35.940802: Step 3970: Cross entropy = 0.013898
2017-07-05 09:28:35.982500: Step 3970: Validation accuracy = 100.0% (N=100)
2017-07-05 09:28:36.381933: Step 3980: Train accuracy = 99.0%
2017-07-05 09:28:36.381975: Step 3980: Cross entropy = 0.022074
2017-07-05 09:28:36.422327: Step 3980: Validation accuracy = 100.0% (N=100)
2017-07-05 09:28:36.826422: Step 3990: Train accuracy = 100.0%
2017-07-05 09:28:36.826464: Step 3990: Cross entropy = 0.009017
2017-07-05 09:28:36.866917: Step 3990: Validation accuracy = 99.0% (N=100)
2017-07-05 09:28:37.222010: Step 3999: Train accuracy = 99.0%
2017-07-05 09:28:37.222055: Step 3999: Cross entropy = 0.031987
2017-07-05 09:28:37.261577: Step 3999: Validation accuracy = 99.0% (N=100)
Final test accuracy = 99.2% (N=2593)
Converted 2 variables to const ops.
# Step3 - Once the model is built with the preceding command we are all set to retrain the model based on our input.
# In this case we will test with the Cat vs Dog dataset extracted from Kaggle. The dataset has two classes. For using the dataset on this model the images pertaining to the classes have to be kept in different folders. The Cat and Dog sub-folders were created within an animals folder. Next we retrain the model using the pre-trained InceptionV3 model. All the layers and weights of the pre-trained model would be transferred to the re-trained model. Only the output layer would be modified to have two classes instead of the 1000 on which the pre-trained model is built. In the re-training only the weights from the last fully connected layer to the new output layer
of two classes would be learned in the retraining. The following command does the retraining:
# Step2 - Enter the cloned tensorflow folder and build the image retrainer by executing the following command
#Step1 - Download the following dataset and un-tar it since it would be used for building the retrainer for transfer learning
Listing 3-8.
我们可以从清单 3-8 的输出中看到,我们在猫与狗的分类问题上实现了 99.2%的测试准确率,通过只训练新输出层的权重来重用预训练的 InceptionV3 模型。这就是迁移学习在恰当的环境下的力量。
使用预训练的 VGG16 进行迁移学习
在本节中,我们将通过使用在一千类 ImageNet 上预先训练的 VGG16 网络来执行迁移学习,以对来自 Kaggle 的猫与狗数据集进行分类。到数据集的链接是 https://www.kaggle.com/c/dogs-vs-cats/data 。首先,我们将从 TensorFlow Slim 导入 VGG16 模型,然后在 VGG16 网络中加载预训练的权重。权重来自在 ImageNet 数据集的一千个类上训练的 VGG16。由于对于我们的问题,我们只有两个类,我们将从最后一个完全连接的层获取输出,并将其与一组新的权重组合,导致输出层将一个神经元对来自 Kaggle 的猫狗数据集进行二进制分类。这个想法是使用预先训练的权重来生成特征,最后我们只学习一组权重,从而得到输出。通过这种方式,我们可以学习相对较少的一组权重,并且可以用较少的数据来训练模型。请在清单 3-9 中找到详细的实现。

图 3-35。
Validation set images and their actual versus predicted classes
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import tensorflow as tf
from scipy.misc import imresize
from sklearn.model_selection import train_test_split
import cv2
from nets import vgg
from preprocessing import vgg_preprocessing
from mlxtend.preprocessing import shuffle_arrays_unison
sys.path.append("/home/santanu/models/slim")
%matplotlib inline
batch_size = 32
width = 224
height = 224
cat_train = '/home/santanu/CatvsDog/train/cat/'
dog_train = '/home/santanu/CatvsDog/train/dog/'
checkpoints_dir = '/home/santanu/checkpoints'
slim = tf.contrib.slim
all_images = os.listdir(cat_train) + os.listdir(dog_train)
train_images, validation_images = train_test_split(all_images, train_size=0.8, test_size=0.2)
MEAN_VALUE = np.array([103.939, 116.779, 123.68])
################################################
# Logic to read the images and also do mean correction
################################################
def image_preprocess(img_path,width,height):
img = cv2.imread(img_path)
img = imresize(img,(width,height))
img = img - MEAN_VALUE
return(img)
################################################
# Create generator for image batches so that only the batch is in memory
################################################
def data_gen_small(images, batch_size, width,height):
while True:
ix = np.random.choice(np.arange(len(images)), batch_size)
imgs = []
labels = []
for i in ix:
data_dir = ' '
# images
if images[i].split('.')[0] == 'cat':
labels.append(1)
data_dir = cat_train
else:
if images[i].split('.')[0] == 'dog':
labels.append(0)
data_dir = dog_train
#print 'data_dir',data_dir
img_path = data_dir + images[i]
array_img = image_preprocess(img_path,width,height)
imgs.append(array_img)
imgs = np.array(imgs)
labels = np.array(labels)
labels = np.reshape(labels,(batch_size,1))
yield imgs,labels
#######################################################
## Defining the generators for training and validation batches
#######################################################
train_gen = data_gen_small(train_images,batch_size,width,height)
val_gen = data_gen_small(validation_images,batch_size, width,height)
with tf.Graph().as_default():
x = tf.placeholder(tf.float32,[None,width,height,3])
y = tf.placeholder(tf.float32,[None,1])
##############################################
## Load the VGG16 model from slim extract the fully connected layer
## before the final output layer
###############################################
with slim.arg_scope(vgg.vgg_arg_scope()):
logits, end_points = vgg.vgg_16(x,
num_classes=1000,
is_training=False)
fc_7 = end_points['vgg_16/fc7']
###############################################
## Define the only set of weights that we will learn W1 and b1
###############################################
Wn =tf.Variable(tf.random_normal([4096,1],mean=0.0,stddev=0.02),name='Wn')
b = tf.Variable(tf.random_normal([1],mean=0.0,stddev=0.02),name='b')
##############################################
## Reshape the fully connected layer fc_7 and define
## the logits and probability
##############################################
fc_7 = tf.reshape(fc_7, [-1,W1.get_shape().as_list()[0]])
logitx = tf.nn.bias_add(tf.matmul(fc_7,W1),b1)
probx = tf.nn.sigmoid(logitx)
####################################################
# Define Cost and Optimizer
# Only we wish to learn the weights Wn and b and hence included them in var_list
####################################################
cost = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=logitx,labels=y))
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost,var_list=[W1,b1])
####################################################
# Loading the pre-trained weights for VGG16
####################################################
init_fn = slim.assign_from_checkpoint_fn(
os.path.join(checkpoints_dir, 'vgg_16.ckpt'),
slim.get_model_variables('vgg_16'))
####################################################
# Running the optimization for only 50 batches of size 32
####################################################
with tf.Session() as sess:
init_op = tf.global_variables_initializer()
sess.run(init_op)
# Load weights
init_fn(sess)
for i in xrange(1):
for j in xrange(50):
batch_x,batch_y = next(train_gen)
#val_x,val_y = next(val_gen)
sess.run(optimizer,feed_dict={x:batch_x,y:batch_y})
cost_train = sess.run(cost,feed_dict={x:batch_x,y:batch_y})
cost_val = sess.run(cost,feed_dict={x:val_x,y:val_y})
prob_out = sess.run(probx,feed_dict={x:val_x,y:val_y})
print "Training Cost",cost_train,"Validation Cost",cost_val
out_val = (prob_out > 0.5)*1
print 'accuracy', np.sum(out_val == val_y)*100/float(len(val_y))
plt.imshow(val_x[1] + MEAN_VALUE)
print "Actual Class:",class_dict[val_y[1][0]]
print "Predicted Class:",class_dict[out_val[1][0]]
plt.imshow(val_x[3] + MEAN_VALUE)
print "Actual Class:",class_dict[val_y[2][0]]
print "Predicted Class:",class_dict[out_val[2][0]]
--output--
Training Cost 0.12381 Validation Cost 0.398074
Training Cost 0.160159 Validation Cost 0.118745
Training Cost 0.196818 Validation Cost 0.237163
Training Cost 0.0502732 Validation Cost 0.183091
Training Cost 0.00245218 Validation Cost 0.129029
Training Cost 0.0913893 Validation Cost 0.104865
Training Cost 0.155342 Validation Cost 0.050149
Training Cost 0.00783684 Validation Cost 0.0179586
Training Cost 0.0533897 Validation Cost 0.00746072
Training Cost 0.0112999 Validation Cost 0.00399635
Training Cost 0.0126569 Validation Cost 0.00537223
Training Cost 0.315704 Validation Cost 0.00140141
Training Cost 0.222557 Validation Cost 0.00225646
Training Cost 0.00431023 Validation Cost 0.00342855
Training Cost 0.0266347 Validation Cost 0.00358525
Training Cost 0.0939392 Validation Cost 0.00183608
Training Cost 0.00192089 Validation Cost 0.00105589
Training Cost 0.101151 Validation Cost 0.00049641
Training Cost 0.139303 Validation Cost 0.000168802
Training Cost 0.777244 Validation Cost 0.000357215
Training Cost 2.20503e-06 Validation Cost 0.00628659
Training Cost 0.00145492 Validation Cost 0.0483692
Training Cost 0.0259771 Validation Cost 0.102233
Training Cost 0.278693 Validation Cost 0.11214
Training Cost 0.0387182 Validation Cost 0.0736753
Training Cost 9.19127e-05 Validation Cost 0.0431452
Training Cost 1.19147 Validation Cost 0.0102272
Training Cost 0.302676 Validation Cost 0.0036657
Training Cost 2.22961e-07 Validation Cost 0.00135369
Training Cost 8.65403e-05 Validation Cost 0.000532816
Training Cost 0.00838018 Validation Cost 0.00029422
Training Cost 0.0604016 Validation Cost 0.000262787
Training Cost 0.648359 Validation Cost 0.000327267
Training Cost 0.00821085 Validation Cost 0.000334495
Training Cost 0.178719 Validation Cost 0.000776928
Training Cost 0.362365 Validation Cost 0.000317593
Training Cost 0.000330557 Validation Cost 0.000139824
Training Cost 0.0879459 Validation Cost 5.76907e-05
Training Cost 0.0881795 Validation Cost 1.21865e-05
Training Cost 1.11339 Validation Cost 1.9081e-05
Training Cost 0.000440863 Validation Cost 3.60468e-05
Training Cost 0.00730334 Validation Cost 6.98846e-05
Training Cost 3.65983e-05 Validation Cost 0.000141883
Training Cost 0.296884 Validation Cost 0.000196292
Training Cost 2.10772e-06 Validation Cost 0.000269568
Training Cost 0.179874 Validation Cost 0.000185331
Training Cost 0.380936 Validation Cost 9.48413e-05
Training Cost 0.0146583 Validation Cost 3.80007e-05
Training Cost 0.387566 Validation Cost 5.26306e-05
Training Cost 7.43922e-06 Validation Cost 7.17469e-05
accuracy 100.0
Listing 3-9.Transfer Learning with Pre-trained VGG16
我们看到,仅在每批 32 个的中等规模的 50 个批次上训练模型后,验证准确率为 100%。由于批量较小,准确性和成本会有一些问题,但总的来说,验证成本会下降,而验证准确性会上升。在图 3-35 中,绘制了几个验证集图像以及它们的实际和预测类,以说明预测的正确性。因此,恰当地利用迁移学习有助于我们在解决一个新问题时重用为一个问题学习的特征检测器。迁移学习丨大丨大减少了需要学习的参数数量,从而减少了网络的计算负担。此外,由于较少的参数需要较少的数据来进行训练,因此减少了训练数据大小的限制。
摘要
在本章中,我们学习了卷积运算以及如何用它来构造卷积神经网络。此外,我们还学习了 CNN 的各种关键组件,以及训练卷积层和池层的反向传播方法。我们讨论了 CNN 在图像处理中取得成功的两个关键概念——卷积提供的等方差特性和合并操作提供的平移不变性。此外,我们讨论了几种已建立的 CNN 架构,以及如何使用这些 CNN 的预训练版本来执行迁移学习。在下一章,我们将讨论自然语言处理领域中的循环神经网络及其变体。
四、使用循环神经网络的自然语言处理
在现代信息和分析时代,自然语言处理(NLP)是最重要的技术之一。从人工智能的角度来看,理解语言中的复杂结构并从中获得洞察力和行动是至关重要的。在几个领域中,自然语言处理的重要性是至关重要的,并且日益增长,因为以语言形式的数字信息无处不在。自然语言处理的应用包括语言翻译、情感分析、网络搜索应用、客户服务自动化、文本分类、从文本中检测主题、语言建模等等。传统的自然语言处理方法依赖于单词模型包、单词模型的向量空间以及现成的编码知识库和本体。自然语言处理的一个关键领域是语言的句法和语义分析。句法分析是指单词在句子中是如何分组和连接的。句法分析的主要任务是标注词性,检测句法类别(如动词、名词、名词短语等。),并通过构造语法树来组装句子。语义分析指的是复杂的任务,例如查找同义词、执行单词-动词歧义消除等等。
向量空间模型(VSM)
在 NLP 信息检索系统中,文档通常被简单地表示为它所包含的字数的向量。为了检索与特定文档相似的文档,计算该文档与其他文档之间的角度余弦或点积。两个向量之间角度的余弦给出了基于它们向量组成之间的相似性的相似性度量。为了说明这个事实,让我们看两个向量
,如下所示:
![$$ {\displaystyle \begin{array}{l}x={\left[\ 2\kern0.5em 3\right]}^T\ {}y={\left[\ 4\kern0.5em 6\right]}^T\end{array}} $$](A448418_1_En_4_Chapter_Equa.gif)
虽然向量 x 和 y 是不同的,但是它们的余弦相似度是最大可能值 1。这是因为这两种载体的成分组成是相同的。两个向量的第一分量与第二分量的比率是
,因此在内容构成方面它们被视为是相似的。因此,具有高余弦相似性的文档通常被认为本质上是相似的。
假设我们有两个句子:
![$$ Doc1=\left[ The\kern0.5em dog\kern0.5em chased\kern0.5em the\kern0.5em cat\right] $$](A448418_1_En_4_Chapter_Equb.gif)
![$$ Doc2=\left[\ The\ cat\ was chased down\ by\ the\ dog\right] $$](A448418_1_En_4_Chapter_Equc.gif)
两个句子中不同单词的数量将是这个问题的向量空间维度。不同的单词是 The、dog、chasing、The、cat、down、by 和 was,因此我们可以将每个文档表示为一个单词计数的八维向量。

![$$ Doc1=\left[\kern1em 1\kern2.75em 1\kern3em 1\kern3em 1\kern2.25em 1\kern2.5em 0\kern2.75em 0\kern2.75em 0\right]\in {\mathrm{\mathbb{R}}}^{8\times 1} $$](A448418_1_En_4_Chapter_Eque.gif)
![$$ Doc2=\left[\kern1em 1\kern3em 1\kern2.75em 1\kern3em 1\kern2.25em 1\kern2.5em 1\kern2.75em 1\kern3em 1\right]\in {\mathrm{\mathbb{R}}}^{8\times 1} $$](A448418_1_En_4_Chapter_Equf.gif)
如果我们用 v 1 表示 Doc1,用 v 2 表示 Doc2,那么余弦相似度可以表示为

其中||v 1 ||是向量 v 1 的大小或 l 2 范数。
如前所述,余弦相似性基于每个向量的分量组成给出了相似性的度量。如果文档向量的分量在某种程度上比例相似,余弦距离将会很高。它没有考虑矢量的大小。
在某些情况下,当文档的长度变化很大时,采用文档向量之间的点积,而不是余弦相似度。这是在与文档内容一起比较文档大小时完成的。例如,我们可以有一条 tweet,其中单词 global 和 economics 的字数可能分别为 1 和 2,而报纸文章对于相同的单词的字数可能分别为 50 和 100。假设两个文档中的其他单词数量不多,则推文和报纸文章之间的余弦相似度将接近 1。由于推文的大小明显较小,全球和经济的字数比例为 1:2,与报纸文章中这些词的比例 1:2 相比并不真正。因此,为几个应用程序的这些文档分配如此高的相似性度量实际上没有意义。在这种情况下,采用点积而不是余弦相似性作为相似性度量是有帮助的,因为它通过两个文档的单词向量的幅度来放大余弦相似性。对于可比较的余弦相似性,具有较高幅度的文档将具有较高的点积相似性,因为它们具有足够的文本来证明它们的单词组成。小文本的单词组合可能只是偶然的,而不是其预期表示的真实表示。对于大多数文档长度相当的应用程序来说,余弦相似性是一个足够公平的度量。

图 4-1。
Cosine similarity between two word vectors
图 4-1 图示了两个向量 v 1 和 v 2 ,余弦相似度为它们之间夹角θ的余弦。
有时,使用余弦相似性的距离对应物是有意义的。余弦距离被定义为需要计算距离的原始向量方向上的单位向量之间的欧几里德距离的平方。对于两个向量 v 1 和 v 2 成θ角,余弦距离由
给出
这可以通过取单位向量
和
之间的欧几里德距离的平方很容易地推导出来,如下所示:

现在,u 1 和 u 2 是单位向量,它们的大小||u 1 ||和||u 2 ||分别都等于 1,因此

通常,当使用文档术语频率向量时,不获取原始术语/单词计数,而是通过单词在语料库中的使用频率来归一化。例如,“the”这个词在任何语料库中都是一个频繁出现的词,它在两个文档中的出现频率很高。的这种高计数可能会增加余弦相似性,而我们知道该术语在任何语料库中都是频繁出现的词,对文档相似性的贡献应该很小。文档术语向量中的这种词的计数受到一个称为逆文档频率的因子的惩罚。
对于在文档 d 中出现 N 次并且在语料库中 M 个文档中的 N 个文档中出现的术语词 t,在应用逆文档频率之后的归一化计数是


正如我们所见,随着 N 相对于 M 的增加,
分量逐渐减小,直到
的分量为零。因此,如果一个单词在语料库中非常流行,那么它不会对单个文档术语向量有太大贡献。在文档中具有高频率但在语料库中不太频繁的单词将对文档术语向量贡献更多。这种规范化方案通常被称为
,它是术语频率逆文档频率的缩写形式。一般来说,出于实用目的,将
作为分母,以避免零导致对数函数未定义。因此,逆文档频率可以重新表述为
归一化方案甚至应用于频率 n 项,使其成为非线性的。一种流行的这种归一化方案是 BM25,其中对于小的 n 值,文档频率贡献是线性的,然后随着 n 的增加,该贡献饱和。术语频率在 BM25 中被归一化如下:

其中 k 是为不同的 k 值提供不同形状的参数,并且需要基于语料库来优化 k。

图 4-2。
Normalized term frequency versus term frequency for different methods
在图 4-2 中,不同规格化方案的规格化词频与词频相对应。平方根变换使相关性呈亚线性,而
的 BM25 图非常激进,曲线在超过 5 的项频率时饱和。如前所述,k 可以通过交叉验证或其他基于问题需求的方法进行优化。
单词的向量表示
正如文档被表示为不同单词计数的向量一样,语料库中的单词也可以被表示为向量,其组成部分是该单词在每个文档中的计数。
将单词表示为向量的其他方式是,如果单词出现在文档中,则将特定于文档的组件设置为 1,如果单词不在文档中,则将组件设置为 0。
![$$ {\displaystyle \begin{array}{l}{\kern5.25em }^{\hbox{'}} Th{e}^{\prime}\kern1em do{g}^{\prime}\kern1em chase{d}^{\prime}\kern0.75em th{e}^{\prime}\kern0.75em ca{t}^{\prime}\kern1em do w{n}^{\prime}\kern0.5em b{y}^{\prime \hbox{'}}{was}^{\hbox{'}}\ {} Doc1=\left[\kern1em 1\kern2.75em 1\kern3em 1\kern3em 1\kern2.25em 1\kern2.5em 0\kern2.75em 0\kern2.75em 0\right]\kern1.25em \in {\mathrm{\mathbb{R}}}^{8\times 1}\ {} Doc2=\left[\kern1em 1\kern3em 1\kern2.75em 1\kern3em 1\kern2.25em 1\kern2.5em 1\kern2.75em 1\kern3em 1\right]\kern1em \in {\mathrm{\mathbb{R}}}^{8\times 1}\end{array}} $$](A448418_1_En_4_Chapter_Equm.gif)
再次使用相同的例子,单词 the 可以在两个文档的语料库中表示为二维向量[1 1] T 。在一个巨大的文档语料库中,单词向量的维数也会很大。像文档相似性一样,单词相似性可以通过余弦相似性或点积来计算。
另一种在语料库中表示单词的方法是一次性编码。在这种情况下,每个单词的维度将是语料库中唯一单词的数量。每个单词将对应于一个索引,该索引将被设置为 1,而所有其他剩余的条目将被设置为 0。所以,每一个都非常稀少。对于不同的索引,即使是相似的单词也会有设置为 1 的条目,因此任何类型的相似性度量都不起作用。
为了更好地表示单词向量,以便可以更有意义地捕捉单词的相似性,同时也为了减少单词向量的维数,引入了 Word2Vec。
Word2Vec
Word2Vec 是一种将单词表示为向量的智能方式,它通过将单词与其邻域中的单词进行训练来实现。当考虑单词的 Word2Vec 表示时,上下文与给定单词相似的单词将产生高余弦相似度或点积。
通常,语料库中的单词相对于其邻域中的单词被训练,以导出 Word2Vec 表示的集合。提取 Word2Vec 表示的两种最流行的方法是 CBOW(连续单词包)方法和 Skip-Gram 方法。CBOW 背后的核心思想如图 4-3 所示。
连续单词包(CBOW)
CBOW 方法试图从特定窗口长度内相邻单词的上下文中预测中心单词。让我们看看下面的句子,并考虑一个五个窗口作为一个邻域。
??
在第一种情况下,我们将试图预测这个词是从它的邻居那只猫身上蹦出来的。在第二个例子中,当我们将窗口滑动一个位置时,我们将尝试从相邻的单词 cat jumped the fence 中预测单词 over。这一过程将对整个语料库重复进行。

图 4-3。
Continuous Bag of Words model for word embeddings
如图 4-3 所示,连续词袋模型(CBOW)以上下文词为输入,以中心词为输出进行训练。输入层中的单词被表示为独热编码矢量,其中特定单词的分量被设置为 1,所有其他分量被设置为 0。语料库中唯一单词的数量 V 决定了这些独一无二的编码向量的维度,因此也决定了
。每个独热编码向量 x (t) 乘以输入嵌入矩阵
以提取特定于该单词的单词嵌入向量
。u (k) 中的索引 k 表示 u (k) 是为词汇表中的第 k 个单词嵌入的单词。隐藏层向量 h 是窗口中所有上下文单词的输入嵌入向量的平均值,因此
具有与单词嵌入向量相同的维数。

其中 l 是窗口的长度尺寸。
为了清楚起见,假设我们有一个六个单词的词汇表——即
——单词是猫、老鼠、追逐、花园、the 和 was。
让它们的独热编码按顺序占据索引,这样它们可以表示如下:
![$$ {x}_{cat}=\left[\begin{array}{c}1\ {}0\ {}0\ {}0\ {}0\ {}0\end{array}\right]\kern2em {x}_{rat}=\left[\begin{array}{c}0\ {}1\ {}0\ {}0\ {}0\ {}0\end{array}\right]{x}_{chased}=\left[\begin{array}{c}0\ {}0\ {}1\ {}0\ {}0\ {}0\end{array}\right]{x}_{garden}=\left[\begin{array}{c}0\ {}0\ {}0\ {}1\ {}0\ {}0\end{array}\right]{x}_{the}=\left[\begin{array}{c}0\ {}0\ {}0\ {}0\ {}1\ {}0\end{array}\right]{x}_{was}=\left[\begin{array}{c}0\ {}0\ {}0\ {}0\ {}0\ {}1\end{array}\right] $$](A448418_1_En_4_Chapter_Equp.gif)
让输入嵌入表示如下:
![$$ {\displaystyle \begin{array}{l}\kern2.75em cat\kern1em rat\kern1em chased\kern0.5em garden\kern0.5em the\kern2em was\ {} WI=\left[\begin{array}{cccccc}0.5& 0.3& 0.1& \kern1.5em 0.01& \kern0.5em 0.2& 0.2\ {}0.7& 0.2& 0.1& \kern1.5em 0.02& \kern0.5em 0.3& 0.3\ {}0.9& 0.7& 0.3& \kern1.5em 0.4& \kern0.5em 0.4& 0.33\ {}0.8& 0.6& 0.3& \kern1.5em 0.53& \kern0.5em 0.91& 0.4\ {}0.6& 0.5& 0.2& \kern1.5em 0.76& \kern0.5em 0.6& 0.5\end{array}\right]\end{array}} $$](A448418_1_En_4_Chapter_Equq.gif)
一旦我们将单词嵌入矩阵乘以单词的独热编码向量,我们就得到该单词的单词嵌入向量。因此,通过将 cat 的独热向量(即,x cat )乘以输入嵌入矩阵 WI,将得到对应于 cat 的 WI 矩阵的第一列,如下所示:
![$$ {\displaystyle \begin{array}{l}\kern27.25em \left[ WI\right]\left[{x}_{cat}\right]\ {}=\left[\begin{array}{cccccc}0.5& 0.3& 0.1& 0.01& 0.2& 0.2\ {}0.7& 0.2& 0.1& 0.02& 0.3& 0.3\ {}0.9& 0.7& 0.3& 0.4& 0.4& 0.33\ {}0.8& 0.6& 0.3& 0.53& 0.91& 0.4\ {}0.6& 0.5& 0.2& 0.76& 0.6& 0.5\end{array}\right]\left[\begin{array}{c}1\ {}0\ {}0\ {}0\ {}0\ {}0\end{array}\right]=\left[\begin{array}{c}0.5\ {}0.7\ {}0.9\ {}0.8\ {}0.6\end{array}\right]\end{array}} $$](A448418_1_En_4_Chapter_Equr.gif)
是单词 cat 的单词嵌入向量。
类似地,提取输入单词的所有单词嵌入向量,并且它们的平均值是隐藏层的输出。
隐藏层 h 的输出应该表示目标单词的嵌入。
词汇表中的所有单词在输出嵌入矩阵
中都有另一组单词嵌入。让 WO 中的单词嵌入由
表示,其中索引 j 表示按照在独热编码方案和输入嵌入矩阵中维护的顺序,词汇表中的第 j 个单词。![$$ WO=\left[\begin{array}{c}{v{(1)}}T\to \ {}{v{(2)}}T\to \ {}.\ {}{v{(j)}}T\to \ {}.\ {}{v{(V)}}T\to \end{array}\right] $$](A448418_1_En_4_Chapter_Equs.gif)
通过将矩阵 WO 乘以 h 来计算每个 v ( j) 的隐藏层嵌入 h 的点积。如我们所知,点积将给出每个输出单词嵌入
和隐藏层计算嵌入 h 的相似性度量。通过 SoftMax 将点积标准化为概率,并且基于目标单词 w (t) ,计算分类交叉熵损失并通过梯度下降进行反向传播,以更新输入和输出嵌入矩阵的矩阵权重。
对 SoftMax 层的输入可以表示如下:
![$$ \left[ WO\right]\left[h\right]=\left[\begin{array}{c}{v{(1)}}T\to \ {}{v{(2)}}T\to \ {}.\ {}{v{(j)}}T\to \ {}.\ {}{v{(V)}}T\to \end{array}\right]\left[h\right]=\left[{v{(1)}}Th\kern0.75em {v{(2)}}Th\dots .\kern1em {v{(j)}}Th\kern2.25em {v{(V)}}Th\right] $$](A448418_1_En_4_Chapter_Equt.gif)
给定上下文单词,词汇表 w (j) 的第 j 个单词的 SoftMax 输出概率由下面给出:

如果实际输出由独热码编码向量
表示,其中 y j 中只有一个为 1(即
,那么目标单词及其上下文单词的特定组合的损失函数可以由下面给出:

不同的 p (j) s 取决于输入和输出嵌入矩阵的分量,这些分量是成本函数 c 的参数。成本函数可以通过反向传播梯度下降技术相对于这些嵌入参数最小化。
为了更直观,假设我们的目标变量是cat。如果隐藏层向量 h 给出了与cat的外部矩阵字嵌入向量的最大点积,而与其他外部字嵌入的点积较低,则嵌入向量或多或少是正确的,并且非常小的误差或对数损失将被反向传播以校正嵌入矩阵。然而,假设 h 与cat的点积较小,而其他外部嵌入向量的点积较大;SoftMax 的损失将非常高,因此更多的误差/对数损失将被反向传播以减少误差。
TensorFlow 中连续词袋的实现
连续单词包 TensorFlow 的实现已经在本节中进行了说明。距离任一侧两个以内的相邻单词被用来预测中间的单词。输出层是整个词汇表的一个大的软 Max。单词嵌入向量被选择为大小为 128。清单 4-1a 中概述了详细的实现。另见图 4-4 。

图 4-4。
TSNE plot for the word-embeddings vectors learned from CBOW
import numpy as np
import tensorflow as tf
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
%matplotlib inline
def one_hot(ind,vocab_size):
rec = np.zeros(vocab_size)
rec[ind] = 1
return rec
def create_training_data(corpus_raw,WINDOW_SIZE = 2):
words_list = []
for sent in corpus_raw.split('.'):
for w in sent.split():
if w != '.':
words_list.append(w.split('.')[0]) # Remove if delimiter is tied to the end of a word
words_list = set(words_list) # Remove the duplicates for each word
word2ind = {} # Define the dictionary for converting a word to index
ind2word = {} # Define dictionary for retrieving a word from its index
vocab_size = len(words_list) # Count of unique words in the vocabulary
for i,w in enumerate(words_list): # Build the dictionaries
word2ind[w] = i
ind2word[i] = w
print word2ind
sentences_list = corpus_raw.split('.')
sentences = []
for sent in sentences_list:
sent_array = sent.split()
sent_array = [s.split('.')[0] for s in sent_array]
sentences.append(sent_array) # finally sentences would hold arrays of word array for sentences
data_recs = [] # Holder for the input output record
for sent in sentences:
for ind,w in enumerate(sent):
rec = []
for nb_w in sent[max(ind - WINDOW_SIZE, 0) : min(ind + WINDOW_SIZE, len(sent)) + 1] :
if nb_w != w:
rec.append(nb_w)
data_recs.append([rec,w])
x_train,y_train = [],[]
for rec in data_recs:
input_ = np.zeros(vocab_size)
for i in xrange(WINDOW_SIZE-1):
input_ += one_hot(word2ind[ rec[0][i] ], vocab_size)
input_ = input_/len(rec[0])
x_train.append(input_)
y_train.append(one_hot(word2ind[ rec[1] ], vocab_size))
return x_train,y_train,word2ind,ind2word,vocab_size
corpus_raw = "Deep Learning has evolved from Artificial Neural Networks, which has been there since the 1940s. Neural Networks are interconnected networks of processing units called artificial neurons that loosely mimic axons in a biological brain. In a biological neuron, the dendrites receive input signals from various neighboring neurons, typically greater than 1000\. These modified signals are then passed on to the cell body or soma of the neuron, where these signals are summed together and then passed on to the axon of the neuron. If the received input signal is more than a specified threshold, the axon will release a signal which again will pass on to neighboring dendrites of other neurons. Figure 2-1 depicts the structure of a biological neuron for reference. The artificial neuron units are inspired by the biological neurons with some modifications as per convenience. Much like the dendrites, the input connections to the neuron carry the attenuated or amplified input signals from other neighboring neurons. The signals are passed on to the neuron, where the input signals are summed up and then a decision is taken what to output based on the total input received. For instance, for a binary threshold neuron an output value of 1 is provided when the total input exceeds a pre-defined threshold; otherwise, the output stays at 0\. Several other types of neurons are used in artificial neural networks, and their implementation only differs with respect to the activation function on the total input to produce the neuron output. In Figure 2-2 the different biological equivalents are tagged in the artificial neuron for easy analogy and interpretation."
corpus_raw = (corpus_raw).lower()
x_train,y_train,word2ind,ind2word,vocab_size= create_training_data(corpus_raw,2)
import tensorflow as tf
emb_dims = 128
learning_rate = 0.001
#---------------------------------------------
# Placeholders for Input output
#----------------------------------------------
x = tf.placeholder(tf.float32,[None,vocab_size])
y = tf.placeholder(tf.float32,[None,vocab_size])
#---------------------------------------------
# Define the Embedding matrix weights and a bias
#----------------------------------------------
W = tf.Variable(tf.random_normal([vocab_size,emb_dims],mean=0.0,stddev=0.02,dtype=tf.float32))
b = tf.Variable(tf.random_normal([emb_dims],mean=0.0,stddev=0.02,dtype=tf.float32))
W_outer = tf.Variable(tf.random_normal([emb_dims,vocab_size],mean=0.0,stddev=0.02,dtype=tf.float32))
b_outer = tf.Variable(tf.random_normal([vocab_size],mean=0.0,stddev=0.02,dtype=tf.float32))
hidden = tf.add(tf.matmul(x,W),b)
logits = tf.add(tf.matmul(hidden,W_outer),b_outer)
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=y))
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
epochs,batch_size = 100,10
batch = len(x_train)//batch_size
# train for n_iter iterations
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print 'was here'
for epoch in xrange(epochs):
batch_index = 0
for batch_num in xrange(batch):
x_batch = x_train[batch_index: batch_index +batch_size]
y_batch = y_train[batch_index: batch_index +batch_size]
sess.run(optimizer,feed_dict={x: x_batch,y: y_batch})
print('epoch:',epoch,'loss :', sess.run(cost,feed_dict={x: x_batch,y: y_batch}))
W_embed_trained = sess.run(W)
W_embedded = TSNE(n_components=2).fit_transform(W_embed_trained)
plt.figure(figsize=(10,10))
for i in xrange(len(W_embedded)):
plt.text(W_embedded[i,0],W_embedded[i,1],ind2word[i])
plt.xlim(-150,150)
plt.ylim(-150,150)
--output--
('epoch:', 99, 'loss :', 1.0895648e-05)
Listing 4-1a.Continuous Bag of Words Implementation in TensorFlow
学习到的单词嵌入通过 TSNE 情节被投射到一个 2D 平面。TSNE 图给出了一个给定单词的邻域的粗略概念。我们可以看到学习到的单词嵌入向量是合理的。例如,单词 deep 和 learning 彼此非常接近。同样,生物和参照这两个词也很接近。
单词嵌入的跳格模型
跳过 gram 模型的工作方式正好相反。在 Skip-gram 模型中,上下文单词是基于当前单词来预测的,而不是像在连续单词包中那样试图从上下文单词中预测当前单词。通常,给定一个当前单词,在每个窗口中取其邻域中的上下文单词。对于给定的五个单词的窗口,将有四个上下文单词需要基于当前单词来预测。图 4-5 显示了 Skip-gram 模型的高级设计。很像连续的单词包,在 Skip-gram 模型中,需要学习两组单词嵌入:一组用于输入单词,一组用于输出上下文单词。跳格模型可以被看作是一个反向的连续单词袋模型。

图 4-5。
Skip-gram model for word embeddings
在 CBOW 模型中,模型的输入是当前单词的独热编码向量
,其中 V 是语料库的词汇大小。然而,与 CBOW 不同,这里输入的是当前单词,而不是上下文单词。假定 x (t) 表示词汇表中的第 k 个单词,当输入 x (t) 乘以输入单词嵌入矩阵 WI 时,产生单词嵌入向量
。如前所述,n 代表单词嵌入的维度。隐层输出 h 无非是 u (k) 。
正如在 CBOW 中一样,通过计算[WO][h]用外部嵌入矩阵
的每个字向量 v[j]计算隐藏层输出 h 的点积。然而,基于我们将要预测的上下文单词的数量,有多个 SoftMax 层,而不是一个 SoftMax 输出层。例如,在图 4-5 中,有四个 SoftMax 输出层对应于四个上下文单词。每个 SoftMax 层的输入都是[WO][h]中相同的点积集,表示输入单词与词汇表中每个单词的相似程度。
![$$ \left[ WO\right]\left[h\right]=\kern0.5em \left[{v{(1)}}Th\kern0.75em {v{(2)}}Th\dots .\kern1em {v{(j)}}Th\kern2.25em {v{(V)}}Th\right] $$](A448418_1_En_4_Chapter_Equw.gif)
类似地,所有 SoftMax 层将接收对应于所有词汇单词的相同概率集,其中给定当前单词的第 j 个单词 w (j) 或中心单词 w (k) 的概率由以下给出:

如果有四个目标单词,并且它们的独热编码向量由
表示,则单词组合的总损失函数 C 将是所有四个 SoftMax 损失的总和,如这里所表示的:

使用反向传播的梯度下降可用于最小化成本函数并导出输入和输出嵌入矩阵的分量。
以下是 Skip-gram 和 CBOW 模型的一些显著特征:
-
对于跳格模型,窗口大小通常不是固定的。给定最大窗口大小,随机选择每个当前单词的窗口大小,使得较小的窗口比较大的窗口被更频繁地选择。使用 Skip-gram,可以从有限数量的文本中生成大量的训练样本,并且不常用的单词和短语也可以得到很好的表示。
-
CBOW 的训练速度比 Skip-gram 快得多,对常用词的准确率也略高。
-
Both Skip-gram and CBOW look at local windows for word co-occurrences and then try to predict either the context words from the center word (as with Skip-gram) or the center word from the context words (as with CBOW). So, basically, if we observe in Skip-gram that locally within each window the probability of the co-occurrence of the context word w c and the current word w t , given by P(w c /w t ), is assumed to be proportional to the exponential of the dot product of their word-embedding vectors. For example:
![$$ P\left({w}_c/{w}_t\right)\propto {e}{uTv} $$]()
where u and v are the input and output word-embedding vectors for the current and context words respectively. Since the co-occurrence is measured locally, these models miss utilizing the global co-occurrence statistics for word pairs within certain window lengths. Next, we are going to explore a basic method to look at the global co-occurrence statistics over a corpus and then use SVD (singular value decomposition) to generate word vectors.
TensorFlow 中的跳格实现
在这一节中,我们将用 TensorFlow 实现来说明学习单词向量嵌入的 Skip-gram 模型。该模型在一个小数据集上进行训练,以便于表示。然而,该模型可用于根据需要训练大型语料库。如跳格图部分所示,该模型被训练为分类网络。然而,我们更感兴趣的是单词嵌入矩阵,而不是单词的实际分类。单词嵌入的大小被选择为 128。清单 4-1b 给出了详细的代码。一旦单词嵌入向量被学习,它们通过 TSNE 被投影到二维表面上用于视觉解释。

图 4-6。
TSNE plot of word-embeddings vectors learned from Skip-gram model
import numpy as np
import tensorflow as tf
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
%matplotlib inline
#------------------------------------------------------------
# Function to one-hot encode the words
#------------------------------------------------------------
def one_hot(ind,vocab_size):
rec = np.zeros(vocab_size)
rec[ind] = 1
return rec
#----------------------------------------------------------------------
# Function to create the training data from the corpus
#----------------------------------------------------------------------
def create_training_data(corpus_raw,WINDOW_SIZE = 2):
words_list = []
for sent in corpus_raw.split('.'):
for w in sent.split():
if w != '.':
words_list.append(w.split('.')[0]) # Remove if delimiter is tied to the end of a word
words_list = set(words_list) # Remove the duplicates for each word
word2ind = {} # Define the dictionary for converting a word to index
ind2word = {} # Define dictionary for retrieving a word from its index
vocab_size = len(words_list) # Count of unique words in the vocabulary
for i,w in enumerate(words_list): # Build the dictionaries
word2ind[w] = i
ind2word[i] = w
print word2ind
sentences_list = corpus_raw.split('.')
sentences = []
for sent in sentences_list:
sent_array = sent.split()
sent_array = [s.split('.')[0] for s in sent_array]
sentences.append(sent_array) # finally sentences would hold arrays of word array for sentences
data_recs = [] # Holder for the input output record
for sent in sentences:
for ind,w in enumerate(sent):
for nb_w in sent[max(ind - WINDOW_SIZE, 0) : min(ind + WINDOW_SIZE, len(sent)) + 1] :
if nb_w != w:
data_recs.append([w,nb_w])
x_train,y_train = [],[]
for rec in data_recs:
x_train.append(one_hot(word2ind[ rec[0] ], vocab_size))
y_train.append(one_hot(word2ind[ rec[1] ], vocab_size))
return x_train,y_train,word2ind,ind2word,vocab_size
corpus_raw = "Deep Learning has evolved from Artificial Neural Networks which has been there since the 1940s. Neural Networks are interconnected networks of processing units called artificial neurons, that loosely mimics axons in a biological brain. In a biological neuron, the Dendrites receive input signals from various neighboring neurons, typically greater than 1000\. These modified signals are then passed on to the cell body or soma of the neuron where these signals are summed together and then passed on to the Axon of the neuron. If the received input signal is more than a specified threshold, the axon will release a signal which again will pass on to neighboring dendrites of other neurons. Figure 2-1 depicts the structure of a biological neuron for reference.The artificial neuron units are inspired from the biological neurons with some modifications as per convenience. Much like the dendrites the input connections to the neuron carry the attenuated or amplified input signals from other neighboring neurons. The signals are passed onto the neuron where the input signals are summed up and then a decision is taken what to output based on the total input received. For instance, for a binary threshold neuron output value of 1 is provided when the total input exceeds a pre-defined threshold, otherwise the output stays at 0\. Several other types of neurons are used in artificial neural network and their implementation only differs with respect to the activation function on the total input to produce the neuron output. In Figure 2-2 the different biological equivalents are tagged in the artificial neuron for easy analogy and interpretation."
corpus_raw = (corpus_raw).lower()
x_train,y_train,word2ind,ind2word,vocab_size= create_training_data(corpus_raw,2)
#----------------------------------------------------------------------------
# Define TensorFlow ops and variable and invoke training
#----------------------------------------------------------------------------
emb_dims = 128
learning_rate = 0.001
#---------------------------------------------
# Placeholders for Input output
#----------------------------------------------
x = tf.placeholder(tf.float32,[None,vocab_size])
y = tf.placeholder(tf.float32,[None,vocab_size])
#---------------------------------------------
# Define the embedding matrix weights and a bias
#----------------------------------------------
W = tf.Variable(tf.random_normal([vocab_size,emb_dims],mean=0.0,stddev=0.02,dtype=tf.float32))
b = tf.Variable(tf.random_normal([emb_dims],mean=0.0,stddev=0.02,dtype=tf.float32))
W_outer = tf.Variable(tf.random_normal([emb_dims,vocab_size],mean=0.0,stddev=0.02,dtype=tf.float32))
b_outer = tf.Variable(tf.random_normal([vocab_size],mean=0.0,stddev=0.02,dtype=tf.float32))
hidden = tf.add(tf.matmul(x,W),b)
logits = tf.add(tf.matmul(hidden,W_outer),b_outer)
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=y))
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
epochs,batch_size = 100,10
batch = len(x_train)//batch_size
# train for n_iter iterations
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print 'was here'
for epoch in xrange(epochs):
batch_index = 0
for batch_num in xrange(batch):
x_batch = x_train[batch_index: batch_index +batch_size]
y_batch = y_train[batch_index: batch_index +batch_size]
sess.run(optimizer,feed_dict={x: x_batch,y: y_batch})
print('epoch:',epoch,'loss :', sess.run(cost,feed_dict={x: x_batch,y: y_batch}))
W_embed_trained = sess.run(W)
W_embedded = TSNE(n_components=2).fit_transform(W_embed_trained)
plt.figure(figsize=(10,10))
for i in xrange(len(W_embedded)):
plt.text(W_embedded[i,0],W_embedded[i,1],ind2word[i])
plt.xlim(-150,150)
plt.ylim(-150,150)
--output--
('epoch:', 99, 'loss :', 1.022735)
Listing 4-1b.Skip-gram Implementation in TensorFlow
很像来自连续词袋方法的词嵌入向量,从 Skip-gram 方法学习的嵌入向量是合理的。例如,单词 deep 和 learning 在 Skip-grams 中也非常接近,正如我们从图 4-6 中看到的。此外,我们还看到了其他有趣的模式,例如单词“衰减”与单词“信号”非常接近。
基于全局共现统计的词向量
可以使用全局共现方法来导出有意义的单词向量,在全局共现方法中,收集整个语料库的每个窗口中的单词共现的全局计数。首先,我们将查看一种方法,该方法通过对全局共现矩阵进行 SVD(奇异值分解)来进行矩阵分解,以导出单词的有意义的低维表示。稍后,我们将研究单词向量表示的 GloVe 技术,该技术结合了最佳的全局共现统计以及 CBOW 和/或 Skip-gram 的预测方法来表示单词向量。
让我们考虑一个文集:

我们首先在一个窗口内收集每个单词组合的全局共现计数。在处理前面的语料时,我们会得到一个共现矩阵。此外,我们通过假设无论何时两个词 w 1 和 w 2 一起出现都会对两个概率 P(w 1 /w 2 和
)有所贡献来使共生矩阵对称,因此我们增加计数桶 c(w 1 /w 2 和 c(w 2 /w 1 的计数术语 c(w 1 /w 2 )表示单词 w 1 和 w 2 的共现,其中 w 2 作为上下文,w 1 作为单词。对于单词-出现对,角色可以颠倒,以便可以将上下文视为单词,而将单词视为上下文。正是因为这个原因,每当我们遇到一个共现的词对(w 1 ,w 2 ),计数桶 c(w 1 /w 2 )和 c(w 2 /w 1 )都会递增。
说到递增计数,我们不需要总是为两个单词的共现而递增 1。如果我们查看用于填充共现矩阵的 K 窗口,我们可以定义差分加权方案来为离上下文距离较小的共现单词提供更大的权重,并且随着距离的增加而惩罚它们。一种这样的加权方案是将同现计数器增加
,其中 k 是单词和上下文之间的偏移。当单词和上下文彼此相邻时,则偏移量为 1,并且同现计数器可以增加 1,而当偏移量对于 K 个窗口最大时,计数器增量在
处最小。
在生成单词向量嵌入的 SVD 方法中,假设单词 w i 和上下文 w j 之间的全局同现计数 c(w i /w j 可以表示为单词 w i 和上下文 w j 的单词向量嵌入的点积。通常,考虑两组单词嵌入,一组用于单词,另一组用于上下文。如果
和
分别表示语料库中第 I 个单词 w i 的单词向量和上下文向量,那么同现次数可以表示为:

让我们看一个三个单词的语料库,用单词和上下文向量的点积来表示共现矩阵
。进一步,设单词为
,其对应的单词向量和上下文向量分别为
和
。
正如我们所看到的,共生矩阵是两个矩阵的乘积,这两个矩阵分别是单词和上下文的单词向量嵌入矩阵。单词向量嵌入矩阵
和上下文单词嵌入矩阵
,其中 D 是单词和上下文嵌入向量的维度。
既然我们知道单词共现矩阵是单词向量嵌入矩阵和上下文嵌入矩阵的乘积,我们可以通过任何适用的矩阵分解技术来分解共现矩阵。奇异值分解(SVD)是一种被广泛采用的方法,因为它有效,即使矩阵不是正方形或对称的。
从 SVD 中我们知道,任何矩形矩阵 X 都可以分解成三个矩阵 U、σ和 V,使得
X = [U][S][V T ]
通常选择矩阵 U 作为单词向量嵌入矩阵 W,而选择σVT作为上下文向量嵌入矩阵 C,但是没有这样的限制,并且可以选择在给定语料库上工作良好的矩阵。人们可以很好地选择 W 作为 uσ1/2和 C 作为σ1/2VT。一般来说,基于有效奇异值的数据中选择较少的维度以减少 U、σ和 v 的大小,如果
,则
。然而,对于截断的 SVD,我们只取数据具有最大可变性的几个重要方向,而忽略其余的无关紧要的和/或噪声。如果我们选择 D 维,新的词向量嵌入矩阵
,其中 D 是每个词向量嵌入的维数。
共现矩阵
通常通过遍历整个语料库一次在更一般化的设置中获得。然而,由于语料库可能随着时间的推移获得新的文档或内容,这些新的文档或内容可以被递增地处理。下图 4-7 展示了单词向量或单词嵌入的三步推导过程。
-
In the first step, singular value decomposition (SVD) is performed on the co-occurrence matrix
to produce
, which contains the left singular vectors,
, which contains the singular values, and
, which contains the right singular vectors.![$$ {\left[X\right]}_{m\times n}={\left[U\right]}_{m\times m}{\left[\varSigma \right]}_{m\times n}{\left[{V}^T\right]}_{n\times n} $$]()
Generally, for word-to-word co-occurrence matrices, the dimensions m and n should be equal. However, sometimes instead of expressing words by words, words are expressed by contexts, and hence for generalization we have taken separate m and n.
-
In the second step, the co-occurrence matrix is approximated by taking only k significant singular values from Σ that explain the maximum variance in data and by also choosing the corresponding k left singular and right singular vectors in U and V.If we start with
after truncation, we would have![$$ {U}^{\hbox{'}}=\left[{u}_1{u}_{2\kern1.75em }{u}_3\dots {u}_k\right],\kern0.5em {\varSigma}^{\prime }=\left[\begin{array}{ccc}{\sigma}_1& \cdots & 0\ {}\vdots & \ddots & \vdots \ {}0& \cdots & {\sigma}_k\end{array}\right],\kern1em V{\hbox{'}}T=\left[\begin{array}{c}\begin{array}{c}{v_1}T\to \ {}{v_2}^T\to \ {}..\end{array}\ {}{v_k}^T\to \end{array}\right] $$]()
-
在第三步中,σ′和 v′T被丢弃,并且矩阵
被带到单词嵌入向量矩阵。单词向量具有对应于所选的 k 个奇异值的 k 个密集维数。因此,从稀疏的共现矩阵中,我们得到了词向量嵌入的密集表示。将有 m 个单词嵌入对应于处理过的语料库的每个单词。

图 4-7。
Extraction of word embeddings through SVD of word co-occurrence matrix
清单 4-1c 中提到的是通过 SVD 分解不同单词的共现矩阵,从给定语料库构建单词向量的逻辑。伴随列表的是图 4-8 中派生的词向量嵌入的图。
import numpy as np
corpus = ['I like Machine Learning.','I like TensorFlow.','I prefer Python.']
corpus_words_unique = set()
corpus_processed_docs = []
for doc in corpus:
corpus_words_ = []
corpus_words = doc.split()
print corpus_words
for x in corpus_words:
if len(x.split('.')) == 2:
corpus_words_ += [x.split('.')[0]] + ['.']
else:
corpus_words_ += x.split('.')
corpus_processed_docs.append(corpus_words_)
corpus_words_unique.update(corpus_words_)
corpus_words_unique = np.array(list(corpus_words_unique))
co_occurence_matrix = np.zeros((len(corpus_words_unique),len(corpus_words_unique)))
for corpus_words_ in corpus_processed_docs:
for i in xrange(1,len(corpus_words_)) :
index_1 = np.argwhere(corpus_words_unique == corpus_words_[i])
index_2 = np.argwhere(corpus_words_unique == corpus_words_[i-1])
co_occurence_matrix[index_1,index_2] += 1
co_occurence_matrix[index_2,index_1] += 1
U,S,V = np.linalg.svd(co_occurence_matrix,full_matrices=False)
print 'co_occurence_matrix follows:'
print co_occurence_matrix
import matplotlib.pyplot as plt
for i in xrange(len(corpus_words_unique)):
plt.text(U[i,0],U[i,1],corpus_words_unique[i])
plt.xlim((-0.75,0.75))
plt.ylim((-0.75,0.75))
plt.show()
Listing 4-1c.
-输出-

图 4-8。
Word-embeddings plot
co_occurence_matrix
follows
:
[[ 0\. 2\. 0\. 0\. 1\. 0\. 0\. 1.]
[ 2\. 0\. 1\. 0\. 0\. 0\. 0\. 0.]
[ 0\. 1\. 0\. 0\. 0\. 1\. 0\. 0.]
[ 0\. 0\. 0\. 0\. 0\. 1\. 1\. 1.]
[ 1\. 0\. 0\. 0\. 0\. 0\. 1\. 0.]
[ 0\. 0\. 1\. 1\. 0\. 0\. 0\. 0.]
[ 0\. 0\. 0\. 1\. 1\. 0\. 0\. 0.]
[ 1\. 0\. 0\. 1\. 0\. 0\. 0\. 0.]]
Word-Embeddings Plot
我们可以在图 4-8 中的单词向量图中看到一个清晰的模式,即使是在这个小语料库中。以下是一些调查结果:
- 常见的我、喜欢之类的词,离别人很远。
- 机器、学习、Python 和 Tensorflow 与不同的学习领域相关联,彼此紧密地聚集在一起。
接下来,我们转向全局向量,通常称为 GloVe,用于生成词向量嵌入。
手套
GloVe 是来自斯坦福大学的一个预先训练好的、随时可用的单词嵌入向量库。手套的训练方法明显不同于 CBOW 和 Skip-gram。GloVe 不是基于单词的局部运行窗口进行预测,而是使用来自语料库的全局单词到单词共现统计来训练模型并导出 GloVe 向量。手套代表全局向量。在 https://nlp.stanford.edu/projects/glove/ 有预先训练好的手套字嵌入。Jeffrey Pennington、Richard Socher 和 Christopher D. Manning 是手套向量的发明者,他们在题为“手套:单词表示的全局向量”的论文中记录了手套向量。论文可以位于 https://nlp.stanford.edu/pubs/glove.pdf 。
像 SVD 方法一样,GloVe 查看全局共现统计,但是单词和上下文向量相对于共现计数的关系略有不同。如果有两个单词 w i 和 w j 和一个上下文单词 w k ,那么概率 P(w k /w i )和 P(w k /w j )的比值提供了比概率本身更多的信息。
让我们考虑两个词,
和
,以及几个上下文词,
。个体共现概率可能较低;然而,如果我们取同现概率的比率,例如

,前面的比率将远大于 1,表明植物更可能与花园而不是市场相关联。
同样,我们来考虑
,看看下面的共现概率的比值:

在这种情况下,比率将会非常小,这意味着单词 shop 更可能与单词 market 而不是 garden 相关联。
因此,我们可以看到,共现概率的比率提供了更多的词之间的区分。因为我们正在尝试学习单词向量,这种区分应该通过单词向量的差异来编码,因为在线性向量空间中,这是表示向量之间区分的最佳方式。类似地,表示线性向量空间中向量之间的相似性的最方便的方式是考虑它们的点积,因此同现概率将由单词和上下文向量之间的点积的某个函数来很好地表示。考虑所有这些有助于我们在高层次上推导出手套向量推导的逻辑。
如果 u i ,u j 是单词 w i 和 w j 的单词向量嵌入,并且 v k 是单词 w k 的上下文向量,则两个共现概率的比率可以表示为单词向量(即,
和上下文向量 v k 的差的某个函数。逻辑函数应该在单词向量和上下文向量的差的点积上工作,主要是因为它在被函数操纵之前保留了向量之间的线性结构。如果我们没有取点积,函数可能会以某种方式作用于向量,破坏线性结构。基于前面的解释,两个同现概率的比值可以表示为:

(4.1.1)
其中 f 是一个给定的函数,我们试图找出它。
此外,如所讨论的,共现概率 P(w k /w i )应该通过线性向量空间中向量之间的某种形式的相似性来编码,并且这样做的最佳操作是通过单词向量 w i 和上下文向量 w k 之间的点积的某个函数 g 来表示共现概率,如下所示:

(4.1.2)
结合(4.1.1)和(4.1.2),我们有

(4.1.3)
现在的任务是确定有意义的函数 f 和 g,使前面的方程有意义。如果我们选择 f 和 g 作为指数函数,它使得概率的比率能够编码字向量的差,同时保持同现概率依赖于点积。向量的点积和差保持了向量在线性空间中的相似性和区分性的概念。如果函数 f 和 g 是某种核函数,那么相似性和不相似性的度量就不会被限制在线性向量空间中,并且会使得词向量的可解释性非常困难。
用(4.1.3)中的指数函数替换 f 和 g,得到

,得到

(4.1.4)
对抽象代数中的群论有所了解的感兴趣的读者可以看出,选择函数
是为了定义群(
和(
)之间的群同态
单词 w i 与上下文单词 w k 的共现概率可以表示如下:
??(4 . 1 . 5)其中 c(w i ,w k 表示单词 w i 与上下文单词 w k 的共现次数,c(w i 表示该单词的总出现次数任何单词的总计数可以通过将它与语料库中所有其他单词的共现计数相加来计算,如下所示:

结合(4.1.4)和(4.1.5),我们得到

log c(w i )可以表示为对于字 w i 的一个偏差 b i ,对于字 w k 也引入了一个额外的偏差
使方程对称。所以,最终的关系可以表示为

正如我们有两组词向量嵌入一样,类似地,我们看到两组偏差——一组针对由
给出的上下文词,另一组针对由 b i 给出的词,其中 I 表示语料库的第 I 个词。
最终目标是最小化所有单词-上下文对的实际 log c(w i ,w k )和预测的
之间的误差成本函数的平方和,如下:

u 和 V 是单词向量嵌入和上下文向量嵌入的参数集。类似地,
和 B 是对应于单词和上下文的偏差的参数。相对于
中的这些参数,成本函数
必须最小化。
这种最小二乘法方案的一个问题是,它在成本函数中对所有共现进行同等加权。这阻止了模型获得好的结果,因为罕见的同现携带非常少的信息。处理这个问题的方法之一是给具有较高计数的同现分配更多的权重。可以修改成本函数,以使每个同现对具有一个权重分量,该权重分量是同现计数的函数。修改后的代价函数可以表示为:

其中 h 是新引入的函数。
函数 h(x)(见图 4-9 )可以选择如下:


人们可以用不同的α值进行实验,α作为模型的超参数。

图 4-9。
Weightage function for the co-occurrence counts
用词向量进行词类比
单词向量嵌入的好处在于它能够使类比线性化。我们使用清单 4-2a 、清单 4-2b 和清单 4-3c 中的预训练手套向量来看一些类比。
import numpy as np
import scipy
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
%matplotlib inline
########################
# Loading glove vector
########################
EMBEDDING_FILE = '∼/Downloads/glove.6B.300d.txt'
print('Indexing word vectors')
embeddings_index = {}
f = open(EMBEDDING_FILE)
count = 0
for line in f:
if count == 0:
count = 1
continue
values = line.split()
word = values[0]
coefs = np.asarray(values[1:], dtype='float32')
embeddings_index[word] = coefs
f.close()
print('Found %d word vectors of glove.' % len(embeddings_index))
-- output --
Indexing word vectors
Found 399999 word vectors of glove.
Listing 4-2a.
king_wordvec = embeddings_index['king']
queen_wordvec = embeddings_index['queen']
man_wordvec = embeddings_index['man']
woman_wordvec = embeddings_index['woman']
pseudo_king = queen_wordvec - woman_wordvec + man_wordvec
cosine_simi = np.dot(pseudo_king/np.linalg.norm(pseudo_king),king_wordvec/np.linalg.norm(king_wordvec))
print 'Cosine Similarity',cosine_simi
--output --
Cosine Similarity 0.663537
Listing 4-2b.

图 4-10。
2D TSNE vector plot for pre-trained GloVe vectors
tsne = TSNE(n_components=2)
words_array = []
word_list = ['king','queen','man','woman']
for w in word_list:
words_array.append(embeddings_index[w])
index1 = embeddings_index.keys()[0:100]
for i in xrange(100):
words_array.append(embeddings_index[index1[i]])
words_array = np.array(words_array)
words_tsne = tsne.fit_transform(words_array)
ax = plt.subplot(111)
for i in xrange(4):
plt.text(words_tsne[i, 0], words_tsne[i, 1],word_list[i])
plt.xlim((-50,20))
plt.ylim((0,50))
--output--
Listing 4-2c.
在列表 4-2a 中,维度 300 的预训练手套向量被加载并存储在字典中。我们通过手套向量来寻找国王、王后、男人和女人的相似之处。以单词 vector 为皇后、男人和女人,创建单词 vector pseudo_king 如下:

这个想法是看前面创建的向量是否多少代表了国王的概念。字向量 pseudo_king 和 king 之间的角度的余弦很高,大约为 0.67,这表明
非常好地代表了 king 的概念。
接下来,在清单 4-2c 中,我们试图表示一个类比,为此,通过 TSNE,我们在二维空间中表示维度 300 的手套向量。结果绘制在图 4-10 中。我们可以看到,表示国王和王后的词向量彼此靠近并聚集在一起,表示男人和女人的词向量也彼此靠近。此外,我们看到国王和男人之间的矢量差以及女王和女人之间的矢量差几乎是平行排列的,并且长度相当。
在我们继续讨论循环神经网络之前,我想提到的一件事是,在自然语言处理的背景下,单词嵌入对于循环神经网络的重要性。循环神经网络不理解文本,因此文本中的每个单词都需要某种形式的数字表示。单词嵌入向量是一个很好的选择,因为单词可以由单词嵌入向量的组件给出的多个概念来表示。循环神经网络可以双向工作,要么提供单词嵌入向量作为输入,要么让网络自己学习这些嵌入向量。在后一种情况下,单词嵌入向量将更倾向于通过循环神经网络来解决最终问题。然而,有时循环神经网络可能有许多其他参数要学习,或者网络可能只有很少的数据来训练。在这种情况下,必须学习单词嵌入向量作为参数可能会导致过拟合或次优结果。在这种情况下,使用预先训练的单词向量嵌入可能是更明智的选择。
循环神经网络导论
循环神经网络(RNNs)被设计成利用序列信息并从中学习。RNN 架构应该为序列中的每个元素执行相同的任务,因此在其命名中称为递归。由于任何语言中单词的顺序依赖性,rnn 在自然语言处理的任务中具有很大的用途。例如,在预测句子中的下一个单词的任务中,出现在它之前的单词的优先顺序是至关重要的。通常,在序列的任何时间步,rnn 基于其迄今为止的计算来计算一些内存;即先前记忆和当前输入。计算出的内存用于预测当前时间步长,并作为输入传递给下一步。图 4-11 显示了循环神经网络的基本架构原理。

图 4-11。
Folded and unfolded structure of an RNN
在图 4-11 中,RNN 架构被及时展开以描绘完整的序列。如果我们希望处理七个单词的句子序列,那么展开的 RNN 体系结构将代表七层前馈神经网络,唯一的区别是每层的权重是公共共享的权重。这显著减少了在循环神经网络中要学习的参数数量。
只是为了让我们熟悉所使用的符号,x t ,h t 和 o t 分别表示在时间步长 t 的输入、计算的内存或隐藏状态以及输出。W hh 表示从时间 t 的内存状态 h t 到时间
的内存状态
的权重矩阵。W xh 表示从输入 x t 到隐藏状态 h t 的权重矩阵,而 W ho 表示从存储状态 h t 到 o t 的权重矩阵。当输入以独热编码形式呈现时,权重矩阵 W xh 充当某种单词向量嵌入矩阵。或者,在独热编码输入的情况下,可以选择具有可学习的独立嵌入矩阵,使得当独热编码输入向量通过嵌入层时,其期望的嵌入向量作为输出呈现。
现在,让我们深入了解每个组件的细节:
-
输入 x t 是表示步骤 t 的输入单词的向量。例如,它可以是对于词汇表中的相应单词索引,分量被设置为 1 的独热编码向量。它也可以是来自诸如 GloVe 的一些预先训练的知识库的单词向量嵌入。一般来说,我们假设
。同样,如果我们希望预测 V 类,那么输出![$$ {y}_t\in {\mathrm{\mathbb{R}}}^{V\times 1}. $$]()
-
存储器或隐藏状态向量 h t 可以根据用户的选择具有任何长度。如果选择的状态数是 n,那么
和权重矩阵![$$ {W}_{hh}\in {\mathrm{\mathbb{R}}}^{n\times n}. $$]()
-
将输入连接到存储状态的权重矩阵
和将存储状态连接到输出的权重矩阵![$$ {W}_{ho}\in {\mathrm{\mathbb{R}}}^{n\times V}. $$]()
-
The memory h t at step t is computed as follows:
, where f is a chosen non-linear activation function.The dimension of
is n; i.e.,
The function f works element-wise on
to produce h t , and hence
and h t have the same dimension.If
, then the following holds true for h t :![$$ {h}_t=\left[\begin{array}{c}f\left({s}_{1t}\right)\ {}f\left({s}_{2t}\right)\ {}.\ {}.\ {}f\left({s}_{nt}\right)\end{array}\right] $$]()
-
The connections from the memory states to the output are just like the connections from a fully connected layer to the output layer. When a multi-class classification problem is involved, such as predicting the next word, then the output layer would be a huge SoftMax of the number of words in the vocabulary. In such cases the predicted output vector
can be expressed as SoftMax(W ho h t ). Just to keep the notations simple, the biases have not been mentioned. In every unit, we can add bias to the input before it is acted upon by the different functions. So, o t can be represented as![$$ {o}_t= SoftMax\left({W}_{ho}{h}_t+{b}_o\right) $$]()
where
is the bias vector for the output units.Similarly, bias can be introduced in the memory units, and hence h t can be expressed as![$$ {h}_t=f\left({W}_{hh}{h}_{t-1}+\kern0.5em {W}_{xh}\ {x}_t+{b}_h\right) $$]()
where
is the bias vector at the memory units. -
For a classification problem predicting the next word for a text sequence of T time steps, the output at each time step is a big SoftMax of V classes, where V is the size of the vocabulary. So, the corresponding loss at each time step is the negative log loss over all the vocabulary size V. The loss at each time step can be expressed as follows:
![$$ {C}_t=-\sum \limits_{j=1}V{y}_t{(j)}\mathit{\log}{\mathrm{o}}_t^{(j)} $$]()
-
To get the overall loss over all the time steps T, all such C t needs to be summed up or averaged out. Generally, the average of all C t is more convenient for stochastic gradient descent so as to get a fair comparison when the sequence length varies. The overall cost function for all the time steps T can thus be represented by the following:
![$$ C=-\sum \limits_{t=1}^T\sum \limits_{j=1}V{y}_t{(j)}\mathit{\log}{\mathrm{o}}_t^{(j)} $$]()
语言建模
在语言建模中,通过事件交集的乘积规则来计算单词序列的概率。单词序列的概率 w1w2w3…..长度为 n 的 w n 给出如下:

在传统方法中,时间步长 k 处的单词的概率通常不以在此之前的长度为
的整个序列为条件,而是以 t 之前的小窗口 L 为条件。因此,概率通常近似如下:

这种基于 L 个最近状态来调节状态的方法被称为链式规则概率的马尔可夫假设。虽然这是一个近似值,但对于传统的语言模型来说,这是一个必要的近似值,因为由于内存限制,单词不能以大的单词序列为条件。
语言模型通常用于与自然语言处理相关的各种任务,例如通过预测接下来的单词来完成句子、机器翻译、语音识别等。在机器翻译中,另一种语言的单词可能被翻译成英语,但可能在语法上不正确。例如,印地语中的一个句子已经被机器翻译成英语句子“美丽无比是天空”。如果一个人计算机器翻译序列的概率(
),它将比排列的对应序列的概率小得多,(P(“天空非常美丽”)。通过语言建模,可以对文本序列的概率进行这种比较。
通过 RNN 和传统方法预测句子中的下一个单词
如前所述,在传统的语言模型中,下一个单词出现的概率通常取决于特定数量的先前单词的窗口。为了估计概率,通常计算不同的 n-grams 计数。根据双字组和三字组计数,条件概率可以计算如下:


以类似的方式,我们可以通过对更大的 n 元语法进行计数来对更长的单词序列的单词概率进行调节。一般来说,当在更高的 n 元语法上找不到匹配时,比如说四元语法,然后尝试更低的 n 元语法,比如三元语法。这种方法被称为后退,与固定的 n 元语法方法相比,它提供了一些性能增益。
由于单词预测仅取决于基于所选窗口大小的前几个单词,所以这种通过 n-gram 计数计算概率的传统方法不如那些在下一个单词预测中考虑整个单词序列的模型那样好。
在 RNNs 中,每一步的输出都以所有之前的单词为条件,因此 RNNs 在语言模型任务中比 n-gram 模型做得更好。为了理解这一点,让我们在考虑一个序列(x 1 x 2 x 3 )时,来看看生成式循环神经网络的工作原理..长度为 n 的 x n 。
RNN 递归地将其隐藏状态 h t 更新为
。隐藏状态
具有为字序列
累积的信息,并且当序列 x t 中的新字到达时,更新的序列信息
通过递归更新被编码在 h t 中。
现在,如果我们必须根据到目前为止看到的单词序列来预测下一个单词,即
,我们需要查看的条件概率分布是

,其中 o i 代表词汇表中的任何广义单词。
对于神经网络来说,这种概率分布由基于目前所见的序列计算出的隐藏状态 h t 控制,即 x1x2x3…..x t 和模型参数 V,其将隐藏状态转换成对应于词汇表中每个单词的概率。
所以,

或
对应于词汇表上所有索引 I 的
的概率向量由 Softmax(
给出。
穿越时间的反向传播(BPTT)
循环神经网络的反向传播与前馈神经网络的反向传播相同,唯一的区别是梯度是关于每一步对数损失的梯度之和。
首先,RNN 在时间上展开,然后向前执行一步以获得内部激活和输出的最终预测。基于预测输出和实际输出标签,计算每个时间步长的损耗和相应的误差。每个时间步长的误差被反向传播以更新权重。因此,任何权重更新都与所有 T 时间步长的误差梯度贡献的总和成比例。
让我们看一个长度为 T 的序列,以及通过 BPTT 更新的权重。我们取记忆状态数为 n(即
),选择输入向量长度为 D(即
)。在每个序列步骤 t,我们通过 SoftMax 函数从 V 个单词的词汇表中预测下一个单词。
长度为 T 的序列的总成本函数被给定为

让我们计算成本函数相对于将隐藏存储器状态连接到输出状态层的权重的梯度,即,属于矩阵 W ho 的权重。权重 w ij 表示将隐藏状态 I 连接到输出单元 j 的权重
成本函数 C t 相对于 w ij 的梯度可以通过偏导数的链式法则分解为成本函数相对于第 j 个单元的输出(即
)的偏导数、第 j 个单元的输出相对于第 j 个单元的净输入 s t (j) (即
)的偏导数的乘积, 以及最后第 j 个单元的净输入相对于从第 I 个存储单元到第 j 个隐藏层的相关权重的偏导数(即
)。

(4 . 2 . 1)

(4 . 2 . 2)
考虑词汇 V 上的 SoftMax 和 t 时刻的实际输出为![$$ {y}_t={\left[{y}_t{(1)}{y}_t{(2)}..{y}_t{(V)}\right]}T\in {\mathrm{\mathbb{R}}}^{V\times 1}, $$](A448418_1_En_4_Chapter_IEq96.gif)

(4 . 2 . 3)

(4 . 2 . 4)
将(4.2.2)、(4.2.3)和(4.2.4)中各个梯度的表达式代入(4.2.1),我们得到

(4.2.5)
为了得到总成本函数 C 相对于 w ij 的梯度的表达式,需要对每个序列步骤的梯度求和。因此,梯度
的表达式如下:

(4.2.6)
因此,我们可以看到,确定从存储状态到输出层的连接的权重与对前馈神经网络的完全连接层进行的操作非常相似,唯一的区别是将每个连续步骤的效果相加,以获得最终梯度。
现在,让我们看看成本函数相对于将一个步骤中的存储器状态连接到下一个步骤中的存储器状态的权重的梯度——即矩阵 W hh 的权重。我们取广义权重
,其中 k 和 I 是连续存储单元中存储单元的索引。
由于记忆单元连接的循环性,这将会变得更加复杂。为了理解这一事实,让我们看看在步骤 t 由 I 索引的存储单元的输出—即 ht(I):

(4 . 2 . 7)
现在,让我们看看在步骤 t 的成本函数相对于权重 u ki :

的梯度(4.2.8)
我们只对将 h t (i) 表示为 u ki 的函数感兴趣,因此我们将(4.2.7)重新排列如下:

(4.2.9)其中

我们已经将 h t (i) 重新安排为包含所需权重 u ki 的函数,并保留
,因为它可以通过递归表示为
每个时间步长 t 的这种递归性质将持续到第一步,因此需要考虑从
到
的所有相关梯度的总和效应。如果 h t (i) 相对于权重 u ki 的梯度遵循递归,并且如果我们对 h t (i) 进行如(4.2.9)中所表达的关于 u ki 的导数,那么下面将成立:

(4.2.10)
请注意表达式
的横杠。表示 h t (i) 相对于 u ki 的局部梯度,保持
不变。
用(4.2.8)代替(4.2.9)我们得到

(4.2.11)
等式(4.2.11)给出了时间 t 时成本函数梯度的一般等式。因此,为了得到总梯度,我们需要在每个时间步对成本梯度求和。因此,总梯度可以用

(4.2.12)来表示
表达式
遵循乘积递归,因此可以表述为

(4.2.13)
结合(4.2.12)和(4.2.13),我们得到

(4.2.14)
针对矩阵 W xh 的权重的成本函数 C 的梯度的计算可以以类似于针对对应于存储器状态的权重的方式来计算。
RNN 的消失和爆炸梯度问题
循环神经网络(RNNs)的目的是学习长依存关系,以便捕获相距遥远的单词之间的相互关系。例如,一个句子试图表达的实际意思可能被彼此不太接近的词很好地捕捉到。循环神经网络应该能够学习这些依赖性。然而,rnn 有一个固有的问题:它们不能捕捉单词之间的长距离依赖。这是因为在长序列的情况下,梯度很有可能很快变为零或者变为零。当梯度很快下降到零时,该模型不能学习时间上相距很远的事件之间的关联或相关性。成本函数的梯度相对于隐藏记忆层的权重的公式将帮助我们理解为什么会发生这种消失梯度问题。
用于广义权重
的成本函数 C t 在步骤 t 的梯度由

给出,其中符号符合它们在“通过时间的反向传播(BPTT)”部分中提到的原始解释。
相加形成
的分量称为其时间分量。这些组件中的每一个测量步骤 t’处的权重 u ki 如何影响步骤 t 处的损失。组件
将步骤 t 处的误差反向传播回步骤 t’。
还有,

结合前面两个方程,我们得到

让我们将在时间步长 g 时存储器单元 I 处的净输入设为 z g (i) 。所以,如果我们把记忆单元的激活看作是 sigmoid,那么

其中σ是 sigmoid 函数。
现在,

其中σ′(zg(I))表示σ(z g (i) )相对于 zg【I】的梯度。
如果我们有一个长序列,即
和
,那么下面的量就会有很多 sigmoid 函数的导数,如下所示:

结合乘积符号形式的梯度表达式,这个重要的方程可以改写如下:

Sigmoid 函数仅在很小的值范围内具有良好的梯度,并且很快饱和。此外,sigmoid 激活函数的梯度小于 1。因此,当来自在
的长距离步骤的误差传递到在
的步骤时,将存在误差必须经过的
sigmoid 激活函数梯度,并且小于 1 的
梯度的乘法效应可以使梯度分量
以指数速度消失。如所讨论的,
将步骤
的误差反向传播回步骤
,从而学习步骤
和
的单词之间的长距离相关性。然而,由于消失梯度问题,
可能接收不到足够的梯度,并且可能接近零,因此不可能学习句子中长距离单词之间的相关性或依赖性。
rnn 也可能遭受爆炸梯度。我们在
的表达式中看到,权重 u ii 被重复乘以
倍。如果
,并且为了简单起见,我们假设
,那么在从序列步骤 T 到序列步骤
的 50 步反向传播之后,梯度将放大大约 2 50 倍,并且因此将驱动模型训练进入不稳定状态。
RNNs 中消失梯度和爆炸梯度问题的解决方法
深度学习社区采用了几种方法来解决消失梯度问题。在这一节中,我们将讨论这些方法,然后继续讨论 RNN 的一种变体,称为长短期记忆(LSTM)循环神经网络。LSTMs 对于消失和爆炸梯度更加鲁棒。
渐变剪辑
分解渐变可以通过一种叫做渐变裁剪的简单技术来解决。如果梯度向量的幅度超过指定的阈值,则梯度向量的幅度被设置为阈值,同时保持梯度向量的方向不变。因此,当在时间 t 对神经网络进行反向传播时,如果成本函数 C 相对于权重向量 w 的梯度超过阈值 k,则用于反向传播的梯度 g 被更新如下:
- 第一步:更新
![$$ g\leftarrow \nabla C\left(w={w}^{(t)}\right) $$]()
- 步骤 2:如果
则更新![$$ g\leftarrow \frac{k}{g}g $$]()
存储器到存储器权重连接矩阵和 ReLU 单元的智能初始化
代替随机初始化权重矩阵 W hh 中的权重,将其初始化为单位矩阵有助于防止消失梯度问题。消失梯度问题的主要原因之一是隐藏单元 I 在时间 t 相对于隐藏单元 I 在时间 t '的梯度,其中
由

给出
在 sigmoid 激活函数的情况下,每个项
可以展开如下:

其中σ(.)表示 sigmoid 函数,z g (t) 表示在步骤 t 对隐藏单元 I 的净输入。参数 u ii 是将任何序列步骤
的第 I 个隐藏存储器状态连接到序列步骤 t 的第 I 个隐藏单元存储器的权重
序列步长 t’和 t 之间的距离越大,误差在从 t 到 t’的过程中经历的 sigmoid 导数就越多。因为 sigmoid 激活函数导数总是小于 1 并且很快饱和,所以在长相关性的情况下梯度变为零的机会很高。
然而,如果我们选择 ReLU 单位,那么 sigmoid 梯度将被 ReLU 梯度代替,ReLU 梯度对于正净输入具有恒定值 1。这将减少消失梯度问题,因为当梯度为 1 时,梯度将无衰减地流动。此外,当权重矩阵被选择为单位时,则权重关系 u ii 将为 1,因此无论序列步长 t 和序列步长 t’之间的距离如何,量
都将为 1。这意味着从时间 t 的隐藏存储器状态 ht??(I)传播到任何先前时间步 t’的隐藏存储器状态 ht’(I)的误差将是恒定的,与从步 t 到 t’的距离无关。这将使 RNN 能够学习句子中长距离单词之间的相关相关性或依赖性。
长短期记忆(LSTM)
长短期记忆循环神经网络,俗称 LSTMs,是 RNNs 的特殊版本,可以学习句子中单词之间的远距离依赖关系。如前所述,基本的 rnn 无法学习远距离单词之间的这种相关性。长短期记忆(LSTM)循环神经网络的结构与传统的循环神经网络有很大的不同。图 4-12 所示为 LSTM 的高级表示。

图 4-12。
LSTM architecture
LSTM 的基本构建模块及其功能如下:
-
The new element in LSTMs is the introduction of the cell state C t , which is regulated by three gates. The gates are composed of sigmoid functions so that they output values between 0 and 1.At sequence step t the input x t and the previous step’s hidden states
decide what information to forget from cell state
through the forget-gate layer. The forget gate looks at x t and
and assigns a number between 0 and 1 for each element in the cell state vector
. An output of 0 means totally forget the state while an output of 1 means keep the state completely. The forget gate output is computed as follows:![$$ {f}_t=\sigma \left(\ {W}_f{x}_t+{U}_f{h}_{t-1}\right) $$]()
-
Next, the input gate decides which cell units should be updated with new information. For this, like the forget-gate output, a value between 0 to 1 is computed for each cell state component through the following logic:
![$$ {i}_t=\sigma \left(\ {W}_i{x}_t+{U}_i{h}_{t-1}\right) $$]()
Then, a candidate set of new values is created for the cell state using x t and
as input. The candidate cell state
is computed as follows:![$$ \tilde{C}t=\tanh \left({W}_c{x}_t+{U}_c{h}_{t-1}\right) $$]()
The new cell state C t is updated as follows:
![$$ {C}_t={f_t}^{\ast }{C}_{t-1}+{i_t}^{\ast}\tilde{C}t $$]()
-
The next job is to determine which cell states to output since the cell state contains a lot of information. For this, x t and
are passed through an output gate, which outputs a value between 0 to 1 for each component in the cell state vector C t . The output of this gate is computed as follows:![$$ {o}_t=\sigma \left(\ {W}_o{x}_t+{U}_o{h}_{t-1}\right) $$]()
The updated hidden state h t is computed from the cell state C t by passing each of its elements through a tanh function and then doing an element-wise product with the output gate values:
![$$ {h}_t={o_t}^{\ast}\tanh \left({C}_t\right) $$]()
请注意,前面等式中的符号*表示逐元素乘法。这样做是为了,基于门输出,我们可以给它所操作的向量的每个元素分配权重。此外,请注意,无论获得什么样的门输出值,它们都会按原样相乘。门输出不会转换为离散值 0 和 1。介于 0 和 1 之间的连续值为反向传播提供了平滑的梯度。
遗忘门在 LSTM 中起着至关重要的作用,当遗忘门单元输出零时,循环梯度变为零,相应的旧单元状态单元被丢弃。通过这种方式,LSTM 会丢弃它认为在未来不会有用的信息。此外,当遗忘门单元输出 1 时,误差无衰减地流过单元单元,并且该模型可以学习时间上相距较远的单词之间的长距离相关性。我们将在下一节对此进行更多的讨论。
LSTM 的另一个重要特征是引入了输出门。输出门单元确保不是单元状态 C t 单元具有的所有信息都暴露给网络的其余部分,因此只有相关的信息以 h t 的形式显示。这确保了网络的其余部分不会受到不必要数据的影响,而处于小区状态的数据仍然被抑制在小区状态中,以帮助推动未来的决策。
减少爆炸梯度和消失梯度问题的 LSTM
LSTMs 不会受到消失梯度或爆炸梯度问题的困扰。其主要原因是引入了遗忘门 f t 以及当前单元状态通过以下等式依赖于它的方式:

这个等式可以在具有通用索引 I 的单元状态单元级别上分解如下:

这里需要注意的是,C t (i) 线性依赖于
,因此激活函数是梯度为 1 的恒等函数。
当
很大时,循环神经网络反向传播中可能导致梯度消失或爆炸的臭名昭著的分量是分量
。该组件将序列步骤 t 处的误差反向传播到序列步骤 k,以便该模型学习长距离依赖性或相关性。正如我们在消失和爆炸渐变部分看到的,
的表达式由下面给出:

当梯度和/或权重小于 1 时,将出现消失梯度条件,因为它们中的
的乘积将迫使总乘积接近零。由于 sigmoid 梯度和 tanh 梯度大部分时间小于 1,并且在它们具有接近零的梯度时饱和得很快,所以消失梯度的问题会更加严重。类似地,当第 I 个隐藏单元与第 I 个隐藏单元之间的权重连接 u ii 大于 1 时,会发生爆炸梯度,因为它们中的
的乘积会使项
呈指数增大。
在 LSTM 与
相对应的是分量
,也可以用产品格式表示如下:

(4.3.1)
对单元状态更新方程
两边取偏导数,得到重要表达式

(4.3.2)
结合(1)和(2),得到

(4.3.3)
等式(4.3.3)表明,如果遗忘门值保持在 1 附近,LSTM 将不会遭受消失梯度或爆炸梯度问题。
基于循环神经网络的 TensorFlow MNIST 数字识别
我们看到了一个 RNN 的实现,它通过 LSTM 对 MNIST 数据集中的影像进行分类。MNIST 数据集的图像在维度上是
。每个图像将被视为具有 28 个序列步骤,并且每个序列步骤将由图像中的一行组成。在每个序列步骤之后不会有输出,而是在每个图像的 28 个步骤结束时只有一个输出。输出是对应于从 0 到 9 的十个数字的十个类别之一。因此,输出层将是十个类的 SoftMax。最终序列步骤 h 28 的隐藏单元状态将通过权重被馈送到输出层。因此,只有最后的序列步骤会对成本函数有贡献;没有与中间序列步骤相关联的成本函数。如果你还记得的话,反向传播是关于每个单独的序列步骤 t 中的成本函数 C t 的,当在每个序列步骤中涉及到输出时,最后关于每个成本函数的梯度被加在一起。这里,其他一切保持不变,并且反向传播将仅相对于序列步骤 28,C 28 处的成本来完成。同样,如前所述,图像的每一行将在序列步长 t 形成数据,因此将是输入向量 x t 。最后,我们将在小批量中处理图像,因此对于批量中的每个图像,将遵循类似的处理过程,从而最小化批量的平均成本。还有一点需要注意,TensorFlow 要求小批量输入张量中的每一步都有单独的张量。为便于理解,图 4-13 中描述了输入张量结构。

图 4-13。
Input tensor shape for RNN LSTM network in Tensorflow
现在我们对问题和方法有了一些了解,我们将继续在 TensorFlow 中实现。清单 4-3 展示了训练模型并在测试数据集上验证模型的详细代码。
#Import the Required Libraries
import tensorflow as tf
from tensorflow.contrib import rnn
import numpy as np
# Import MINST data
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
# Batch Learning Parameters
learning_rate = 0.001
training_iters = 100000
batch_size = 128
display_step = 50
num_train = mnist.train.num_examples
num_batches = (num_train//batch_size) + 1
epochs = 2
# RNN LSTM Network Parameters
n_input = 28 # MNIST data input (img shape: 28*28)
n_steps = 28 # timesteps
n_hidden = 128 # hidden layer num of features
n_classes = 10 # MNIST total classes (0-9 digits)
# Define th forward pass for the RNN
def RNN(x, weights, biases):
# Unstack to get a list of n_stepstensors of shape (batch_size, n_input) as illustrated in Figure 4-12
x = tf.unstack(x, n_steps, 1)
# Define a lstm cell
lstm_cell = rnn.BasicLSTMCell(n_hidden, forget_bias=1.0)
# Get lstm cell output
outputs, states = rnn.static_rnn(lstm_cell, x, dtype=tf.float32)
# Linear activation, using rnn inner loop last output
return tf.matmul(outputs[-1], weights['out']) + biases['out']
# tf Graph input
x = tf.placeholder("float", [None, n_steps, n_input])
y = tf.placeholder("float", [None, n_classes])
# Define weights
weights = {
'out': tf.Variable(tf.random_normal([n_hidden, n_classes]))
}
biases = {
'out': tf.Variable(tf.random_normal([n_classes]))
}
pred = RNN(x, weights, biases)
# Define loss and optimizer
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=pred, labels=y))
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
# Evaluate model
correct_pred = tf.equal(tf.argmax(pred,1), tf.argmax(y,1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
# Initializing the variables
init = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init)
i = 0
while i < epochs:
for step in xrange(num_batches):
batch_x, batch_y = mnist.train.next_batch(batch_size)
batch_x = batch_x.reshape((batch_size, n_steps, n_input))
# Run optimization op (backprop)
sess.run(optimizer, feed_dict={x: batch_x, y: batch_y})
if (step + 1) % display_step == 0:
# Calculate batch accuracy
acc = sess.run(accuracy, feed_dict={x: batch_x, y: batch_y})
# Calculate batch loss
loss = sess.run(cost, feed_dict={x: batch_x, y: batch_y})
print "Epoch: " + str(i+1) + ",step:"+ str(step+1) +", Minibatch Loss= " + \
"{:.6f}".format(loss) + ", Training Accuracy= " + \
"{:.5f}".format(acc)
i += 1
print "Optimization Finished!"
# Calculate accuracy
test_len = 500
test_data = mnist.test.images[:test_len].reshape((-1, n_steps, n_input))
test_label = mnist.test.labels[:test_len]
print "Testing Accuracy:", \
sess.run(accuracy, feed_dict={x: test_data, y: test_label})
xx---output--xx
Extracting MNIST_data/train-images-idx3-ubyte.gz
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz
Epoch: 1,step:50, Minibatch Loss= 0.822081, Training Accuracy= 0.69531
Epoch: 1,step:100, Minibatch Loss= 0.760435, Training Accuracy= 0.75781
Epoch: 1,step:150, Minibatch Loss= 0.322639, Training Accuracy= 0.89844
Epoch: 1,step:200, Minibatch Loss= 0.408063, Training Accuracy= 0.85156
Epoch: 1,step:250, Minibatch Loss= 0.212591, Training Accuracy= 0.93750
Epoch: 1,step:300, Minibatch Loss= 0.158679, Training Accuracy= 0.94531
Epoch: 1,step:350, Minibatch Loss= 0.205918, Training Accuracy= 0.92969
Epoch: 1,step:400, Minibatch Loss= 0.131134, Training Accuracy= 0.95312
Epoch: 2,step:50, Minibatch Loss= 0.161183, Training Accuracy= 0.94531
Epoch: 2,step:100, Minibatch Loss= 0.237268, Training Accuracy= 0.91406
Epoch: 2,step:150, Minibatch Loss= 0.130443, Training Accuracy= 0.94531
Epoch: 2,step:200, Minibatch Loss= 0.133215, Training Accuracy= 0.93750
Epoch: 2,step:250, Minibatch Loss= 0.179435, Training Accuracy= 0.95312
Epoch: 2,step:300, Minibatch Loss= 0.108101, Training Accuracy= 0.97656
Epoch: 2,step:350, Minibatch Loss= 0.099574, Training Accuracy= 0.97656
Epoch: 2,step:400, Minibatch Loss= 0.074769, Training Accuracy= 0.98438
Optimization Finished!
Testing Accuracy: 0.954102
Listing 4-3.TensorFlow Implementation of Recurrent Neural Network using LSTM for Classification
正如我们从清单 4-3 输出中看到的,仅通过运行 2 个时期,就在测试数据集上实现了 95%的准确率。
基于循环神经网络的 TensorFlow 下一词预测和句子完成
我们用《爱丽丝梦游仙境》中的一小段训练一个模型,用 LSTM 预测给定词汇中的下一个单词。对于这个问题,三个单词的序列作为输入,下一个单词作为输出。此外,选择了两层 LSTM 模型而不是一层。输入和输出的集合是从语料库中随机选择的,并作为大小为 1 的小批量进行馈送。我们看到该模型取得了良好的准确性,能够很好地学习文章。后来,一旦训练好模型,我们输入一个三个单词的句子,让模型预测接下来的 28 个单词。每次它预测到一个新单词,就会把它附加到更新的句子中。为了预测下一个单词,来自更新句子的前三个单词被作为输入。清单 4-4 中概述了该问题的详细实现。
# load the required libraries
import numpy as np
import tensorflow as tf
from tensorflow.contrib import rnn
import random
import collections
import time
# Parameters
learning_rate = 0.001
training_iters = 50000
display_step = 500
n_input = 3
# number of units in RNN cell
n_hidden = 512
# Function to read and process the input file
def read_data(fname):
with open(fname) as f:
data = f.readlines()
data = [x.strip() for x in data]
data = [data[i].lower().split() for i in range(len(data))]
data = np.array(data)
data = np.reshape(data, [-1, ])
return data
# Function to build dictionary and reverse dictionary of words.
def build_dataset(train_data):
count = collections.Counter(train_data).most_common()
dictionary = dict()
for word, _ in count:
dictionary[word] = len(dictionary)
reverse_dictionary = dict(zip(dictionary.values(), dictionary.keys()))
return dictionary, reverse_dictionary
# Function to one-hot the input vectors
def input_one_hot(num):
x = np.zeros(vocab_size)
x[num] = 1
return x.tolist()
# Read the input file and build the required dictionaries
train_file = 'alice in wonderland.txt'
train_data = read_data(train_file)
dictionary, reverse_dictionary = build_dataset(train_data)
vocab_size = len(dictionary)
# Place holder for Mini-batch input output
x = tf.placeholder("float", [None, n_input, vocab_size])
y = tf.placeholder("float", [None, vocab_size])
# RNN output node weights and biases
weights = {
'out': tf.Variable(tf.random_normal([n_hidden, vocab_size]))
}
biases = {
'out': tf.Variable(tf.random_normal([vocab_size]))
}
# Forward pass for the recurrent neural network
def RNN(x, weights, biases):
x = tf.unstack(x, n_input, 1)
# 2 layered LSTM Definition
rnn_cell = rnn.MultiRNNCell([rnn.BasicLSTMCell(n_hidden),rnn.BasicLSTMCell(n_hidden)])
# generate prediction
outputs, states = rnn.static_rnn(rnn_cell, x, dtype=tf.float32)
# there are n_input outputs but
# we only want the last output
return tf.matmul(outputs[-1], weights['out']) + biases['out']
pred = RNN(x, weights, biases)
# Loss and optimizer
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=pred, labels=y))
optimizer = tf.train.RMSPropOptimizer(learning_rate=learning_rate).minimize(cost)
# Model evaluation
correct_pred = tf.equal(tf.argmax(pred,1), tf.argmax(y,1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
# Initializing the variables
init = tf.global_variables_initializer()
# Launch the graph
with tf.Session() as session:
session.run(init)
step = 0
offset = random.randint(0,n_input+1)
end_offset = n_input + 1
acc_total = 0
loss_total = 0
while step < training_iters:
if offset > (len(train_data)-end_offset):
offset = random.randint(0, n_input+1)
symbols_in_keys = [ input_one_hot(dictionary[ str(train_data[i])]) for i in range(offset, offset+n_input) ]
symbols_in_keys = np.reshape(np.array(symbols_in_keys), [-1, n_input,vocab_size])
symbols_out_onehot = np.zeros([vocab_size], dtype=float)
symbols_out_onehot[dictionary[str(train_data[offset+n_input])]] = 1.0
symbols_out_onehot = np.reshape(symbols_out_onehot,[1,-1])
_, acc, loss, onehot_pred = session.run([optimizer, accuracy, cost, pred], \
feed_dict={x: symbols_in_keys, y: symbols_out_onehot})
loss_total += loss
acc_total += acc
if (step+1) % display_step == 0:
print("Iter= " + str(step+1) + ", Average Loss= " + \
"{:.6f}".format(loss_total/display_step) + ", Average Accuracy= " + \
"{:.2f}%".format(100*acc_total/display_step))
acc_total = 0
loss_total = 0
symbols_in = [train_data[i] for i in range(offset, offset + n_input)]
symbols_out = train_data[offset + n_input]
symbols_out_pred = reverse_dictionary[int(tf.argmax(onehot_pred, 1).eval())]
print("%s - Actual word:[%s] vs Predicted word:[%s]" % (symbols_in,symbols_out,symbols_out_pred))
step += 1
offset += (n_input+1)
print("TrainingCompleted!")
# Feed a 3-word sentence and let the model predict the next 28 words
sentence = 'i only wish'
words = sentence.split(' ')
try:
symbols_in_keys = [ input_one_hot(dictionary[ str(train_data[i])]) for i in range(offset, offset+n_input) ]
for i in range(28):
keys = np.reshape(np.array(symbols_in_keys), [-1, n_input,vocab_size])
onehot_pred = session.run(pred, feed_dict={x: keys})
onehot_pred_index = int(tf.argmax(onehot_pred, 1).eval())
sentence = "%s %s" % (sentence,reverse_dictionary[onehot_pred_index])
symbols_in_keys = symbols_in_keys[1:]
symbols_in_keys.append(input_one_hot(onehot_pred_index))
print "Complete sentence follows!'
print(sentence)
except:
print("Error while processing the sentence to be completed")
---output --
Iter= 30500, Average Loss= 0.073997, Average Accuracy= 99.40%
['only', 'you', 'can'] - Actual word:[find] vs Predicted word:[find]
Iter= 31000, Average Loss= 0.004558, Average Accuracy= 99.80%
['very', 'hopeful', 'tone'] - Actual word:[though] vs Predicted word:[though]
Iter= 31500, Average Loss= 0.083401, Average Accuracy= 99.20%
['tut', ',', 'tut'] - Actual word:[,] vs Predicted word:[,]
Iter= 32000, Average Loss= 0.116754, Average Accuracy= 99.00%
['when', 'they', 'met'] - Actual word:[in] vs Predicted word:[in]
Iter= 32500, Average Loss= 0.060253, Average Accuracy= 99.20%
['it', 'in', 'a'] - Actual word:[bit] vs Predicted word:[bit]
Iter= 33000, Average Loss= 0.081280, Average Accuracy= 99.00%
['perhaps', 'it', 'was'] - Actual word:[only] vs Predicted word:[only]
Iter= 33500, Average Loss= 0.043646, Average Accuracy= 99.40%
['you', 'forget', 'to'] - Actual word:[talk] vs Predicted word:[talk]
Iter= 34000, Average Loss= 0.088316, Average Accuracy= 98.80%
[',', 'and', 'they'] - Actual word:[walked] vs Predicted word:[walked]
Iter= 34500, Average Loss= 0.154543, Average Accuracy= 97.60%
['a', 'little', 'startled'] - Actual word:[when] vs Predicted word:[when]
Iter= 35000, Average Loss= 0.105387, Average Accuracy= 98.40%
['you', 'again', ','] - Actual word:[you] vs Predicted word:[you]
Iter= 35500, Average Loss= 0.038441, Average Accuracy= 99.40%
['so', 'stingy', 'about'] - Actual word:[it] vs Predicted word:[it]
Iter= 36000, Average Loss= 0.108765, Average Accuracy= 99.00%
['like', 'to', 'be'] - Actual word:[rude] vs Predicted word:[rude]
Iter= 36500, Average Loss= 0.114396, Average Accuracy= 98.00%
['make', 'children', 'sweet-tempered'] - Actual word:[.] vs Predicted word:[.]
Iter= 37000, Average Loss= 0.062745, Average Accuracy= 98.00%
['chin', 'upon', "alice's"] - Actual word:[shoulder] vs Predicted word:[shoulder]
Iter= 37500, Average Loss= 0.050380, Average Accuracy= 99.20%
['sour', '\xe2\x80\x94', 'and'] - Actual word:[camomile] vs Predicted word:[camomile]
Iter= 38000, Average Loss= 0.137896, Average Accuracy= 99.00%
['very', 'ugly', ';'] - Actual word:[and] vs Predicted word:[and]
Iter= 38500, Average Loss= 0.101443, Average Accuracy= 98.20%
["'", 'she', 'went'] - Actual word:[on] vs Predicted word:[on]
Iter= 39000, Average Loss= 0.064076, Average Accuracy= 99.20%
['closer', 'to', "alice's"] - Actual word:[side] vs Predicted word:[side]
Iter= 39500, Average Loss= 0.032137, Average Accuracy= 99.60%
['in', 'my', 'kitchen'] - Actual word:[at] vs Predicted word:[at]
Iter= 40000, Average Loss= 0.110244, Average Accuracy= 98.60%
[',', 'tut', ','] - Actual word:[child] vs Predicted word:[child]
Iter= 40500, Average Loss= 0.088653, Average Accuracy= 98.60%
["i'm", 'a', 'duchess'] - Actual word:[,] vs Predicted word:[,]
Iter= 41000, Average Loss= 0.122520, Average Accuracy= 98.20%
["'", "'", 'perhaps'] - Actual word:[it] vs Predicted word:[it]
Iter= 41500, Average Loss= 0.011063, Average Accuracy= 99.60%
['it', 'was', 'only'] - Actual word:[the] vs Predicted word:[the]
Iter= 42000, Average Loss= 0.057289, Average Accuracy= 99.40%
['you', 'forget', 'to'] - Actual word:[talk] vs Predicted word:[talk]
Iter= 42500, Average Loss= 0.089094, Average Accuracy= 98.60%
['and', 'they', 'walked'] - Actual word:[off] vs Predicted word:[off]
Iter= 43000, Average Loss= 0.023430, Average Accuracy= 99.20%
['heard', 'her', 'voice'] - Actual word:[close] vs Predicted word:[close]
Iter= 43500, Average Loss= 0.022014, Average Accuracy= 99.60%
['i', 'am', 'to'] - Actual word:[see] vs Predicted word:[see]
Iter= 44000, Average Loss= 0.000067, Average Accuracy= 100.00%
["wouldn't", 'be', 'so'] - Actual word:[stingy] vs Predicted word:[stingy]
Iter= 44500, Average Loss= 0.131948, Average Accuracy= 98.60%
['did', 'not', 'like'] - Actual word:[to] vs Predicted word:[to]
Iter= 45000, Average Loss= 0.074768, Average Accuracy= 99.00%
['that', 'makes', 'them'] - Actual word:[bitter] vs Predicted word:[bitter]
Iter= 45500, Average Loss= 0.001024, Average Accuracy= 100.00%
[',', 'because', 'she'] - Actual word:[was] vs Predicted word:[was]
Iter= 46000, Average Loss= 0.085342, Average Accuracy= 98.40%
['new', 'kind', 'of'] - Actual word:[rule] vs Predicted word:[rule]
Iter= 46500, Average Loss= 0.105341, Average Accuracy= 98.40%
['alice', 'did', 'not'] - Actual word:[much] vs Predicted word:[much]
Iter= 47000, Average Loss= 0.081714, Average Accuracy= 98.40%
['soup', 'does', 'very'] - Actual word:[well] vs Predicted word:[well]
Iter= 47500, Average Loss= 0.076034, Average Accuracy= 98.40%
['.', "'", "everything's"] - Actual word:[got] vs Predicted word:[got]
Iter= 48000, Average Loss= 0.099089, Average Accuracy= 98.20%
[',', "'", 'she'] - Actual word:[said] vs Predicted word:[said]
Iter= 48500, Average Loss= 0.082119, Average Accuracy= 98.60%
['.', "'", "'"] - Actual word:[perhaps] vs Predicted word:[perhaps]
Iter= 49000, Average Loss= 0.055227, Average Accuracy= 98.80%
[',', 'and', 'thought'] - Actual word:[to] vs Predicted word:[to]
Iter= 49500, Average Loss= 0.068357, Average Accuracy= 98.60%
['dear', ',', 'and'] - Actual word:[that] vs Predicted word:[that]
Iter= 50000, Average Loss= 0.043755, Average Accuracy= 99.40%
['affectionately', 'into', "alice's"] - Actual word:[,] vs Predicted word:[,]
Training Completed!
"Complete sentence follows!'
i only wish off together . alice was very glad to find her in such a pleasant temper, and thought to herself that perhaps it was only the pepper that
Listing 4-4.Next-Word Prediction and Sentence Completion in TensorFlow Using Recurrent Neural Networks
我们可以从清单 4-4 的输出中看到,该模型能够很好地预测实际的单词。在完成句子的任务中,虽然前两个预测开始时不太好,但对于其余的 28 个字符来说,它做得非常好。生成的句子语法和标点符号异常丰富。可以通过增加序列长度以及在序列中的每个单词后引入预测来提高模型的准确性。此外,训练语料库很小。如果在更大的数据语料库上训练该模型,单词预测和句子完成质量将进一步提高。清单 4-5 显示了用于训练模型的《爱丽丝梦游仙境》中的段落。
' You can't think how glad I am to see you again, you dear old thing ! ' said the Duchess, as she tucked her arm affectionately into Alice's, and they walked off together . Alice was very glad to find her in such a pleasant temper, and thought to herself that perhaps it was only the pepper that had made her so savage when they met in the kitchen . ' When I'm a Duchess, ' she said to herself, ( not in a very hopeful tone though ), ' I won't have any pepper in my kitchen at all . Soup does very well without — Maybe it's always pepper that makes people hot-tempered, ' she went on, very much pleased at having found out a new kind of rule, ' and vinegar that makes them sour — and camomile that makes them bitter — and — and barley-sugar and such things that make children sweet-tempered . I only wish people knew that : then they wouldn't be so stingy about it, you know — 'She had quite forgotten the Duchess by this time, and was a little startled when she heard her voice close to her ear . ' You're thinking about something, my dear, and that makes you forget to talk . I can't tell you just now what the moral of that is, but I shall remember it in a bit . ' ' Perhaps it hasn't one, ' Alice ventured to remark . ' Tut, tut, child ! ' said the Duchess . ' Everything's got a moral, if only you can find it . ' And she squeezed herself up closer to Alice's side as she spoke . Alice did not much like keeping so close to her : first, because the Duchess was very ugly ; and secondly, because she was exactly the right height to rest her chin upon Alice's shoulder, and it was an uncomfortably sharp chin . However, she did not like to be rude, so she bore it as well as she could .
Listing 4-5.
门控循环单元(GRU)
很像 LSTM,盖茨的循环单元,俗称 GRU,有控制内部信息流的门控单元。然而,不像 LSTM,他们没有单独的记忆细胞。在任何时间步长 t 的隐藏存储器状态 h t 是先前隐藏存储器状态
和候选新隐藏状态
之间的线性插值。GRU 的建筑示意图如图 4-14 所示。

图 4-14。
GRU architecture
以下是 GRU 工作原理的高级细节:
-
Based on the hidden memory state
and current input x t , the reset gate r t and the update gate z t are computed as follows:![$$ {r}_t=\sigma \left({W}_r{h}_{t-1}+{U}_r{x}_t\right) $$]()
![$$ {z}_t=\sigma \left({W}_z{h}_{t-1}+{U}_z{x}_t\right) $$]()
The reset gate determines how important
is in determining the candidate new hidden state. The update gate determines how much the new candidate state should influence the new hidden state. -
The candidate new hidden state
is computed as follows:![$$ {\tilde{h}}_t= \tanh \left({r}_t*U{h}_{t-1}+W{x}_t\right) $$]()
(4.4.1)
-
Based on the candidate state and the previous hidden state, the new hidden memory state is updated as follows:
![$$ {h}_t={\left(1-{z}_t\right)}^{\ast }{h}_{t-1}+{z_t}^{\ast }{\tilde{h}}_t $$]()
(4.4.2)
GRU 中强调的关键点是门控功能的作用,如下所述:
-
当来自 r t 的复位门输出单元接近零时,在候选新隐藏状态的计算中,这些单元的先前隐藏状态被忽略,如(4.4.1)中的等式所示。这允许模型丢弃在将来没有用的信息。
-
When the update gate output units from z t are close to zero, then the previous step states for those units are copied over to the current step. As we have seen before, the notorious component in recurrent neural network backpropagation that may lead to vanishing or exploding gradients is the component
, which backpropagates the error at sequence step t to sequence step k so that the model learns long-distance dependencies or correlations. The expression for
, as we saw in the vanishing and exploding gradients section, is given by![$$ \frac{\partial {h}_t^{(i)}}{\partial {h}_{\mathrm{k}}{(i)}}={\left({u}_{ii}\right)}{t-k}\prod \limits_{g=k+1}t{\sigma}{\hbox{'}}\left({z}_{\mathrm{g}}^{(i)}\right) $$]()
When
is large, a vanishing-gradient condition will arise when the gradients of the activations in the hidden state units and/or the weights are less than 1 since the product of
in them would force the overall product to near zero. Sigmoid and tanh gradients are often less than 1 and saturate fast where they have near-zero gradients, thus making the problem of vanishing gradient more severe. Similarly, exploding gradients can happen when the weight connection u ii between the ith hidden to the ithhidden unit is greater than 1 since the product of
in them would make the term
exponentially large for large values of
Now, coming back to the GRU , when the update-gate output units in z t are close to 0, then from the equation (4.4.2),![$$ {h_t}^{(i)}\approx {h_{t-1}}^{(i)}\kern1em \forall i\in K $$]()
(4.4.3) where K is the set of all hidden units for which
0.On taking the partial derivative of h t (i) with respect to
in (4.4.3) we get the following:![$$ \frac{\partial {h_t}^{(i)}}{\partial {h_{t-1}}^{(i)}}\approx 1 $$]()
This will ensure that the notorious term
is also close to 1 since it can be expressed as![$$ \frac{\partial {h}_t^{(i)}}{\partial {h}_{\mathrm{k}}^{(i)}}=\prod \limits_{g={t}^{\prime }+1}^t\frac{\partial {h}_g^{(i)}}{\partial {h}_{g-1}^{(i)}} $$]()
![$$ =\mathrm{1.1.1}\dots .1\ \left(t-k\right) times=1 $$]()
This allows the hidden states to be copied over many sequence steps without alteration, and hence the chances of a vanishing gradient diminish and the model is able to learn temporally long-distance association or correlation between words.
双向 RNN
在标准的循环神经网络中,我们将过去的序列状态考虑在内进行预测。例如,为了预测序列中的下一个单词,我们考虑出现在它之前的单词。然而,对于自然语言处理中的某些任务,例如词性,标记关于给定单词的过去单词和未来单词在确定给定单词的词性标记中是至关重要的。此外,对于词性标注应用程序,整个句子都可以进行标注,因此,对于每个给定的单词(除了句子开头和结尾的单词),其过去和未来的单词都可以使用。
双向 RNN 是一种特殊类型的 RNN,它利用过去和未来的状态来预测当前状态下的输出标签。双向 RNN 组合了两个 rnn,一个从左到右向前运行,另一个从右到左向后运行。双向 RNN 的高层架构如图 4-15 所示。

图 4-15。
Bidirectional RNN architectural diagram
对于双向 RNN,任何序列步长 t 都有两个隐藏存储器状态。对应于正向信息流的隐藏存储器状态,如在标准 RNN 中,可以表示为
,对应于反向信息流的隐藏存储器状态可以表示为
。在任何序列步骤 t 的输出取决于存储器状态
和
。以下是双向 RNN 的控制方程。


![$$ {y}_t=g\left(U\left[\overrightarrow{h_t};\overleftarrow{h_t}\right]+c\right) $$](A448418_1_En_4_Chapter_Equcd.gif)
表达式
表示 t 时刻的组合内存状态向量,可以通过串联两个向量
和
的元素得到。
和
分别是正向传递和反向传递的隐藏状态连接权重。类似地,
和
是向前和向后传递的隐藏状态权重的输入。分别由
和
给出前向和后向传递的隐藏存储器状态激活时的偏置。U 项表示从组合隐藏状态到输出状态的权重矩阵,而 c 表示输出端的偏差。
函数 f 通常是在隐藏存储器状态下选择的非线性激活函数。为 f 选择的激活函数通常是 sigmoid 和 tanh。然而,ReLU 激活现在也在使用,因为它们减少了渐变消失和爆炸的问题。函数 g 将取决于手头的分类问题。在多个类的情况下,将使用 SoftMax,而对于两类问题,可以使用 sigmoid 或两类 SoftMax。
摘要
在这一章之后,读者将会对循环神经网络及其变体的工作原理有更深刻的理解。此外,读者应该能够相对容易地使用 TensorFlow 实现 RNN 网络。RNNs 的消失梯度和爆炸梯度问题在有效训练它们方面提出了一个关键的挑战,因此已经发展了许多功能强大的 RNNs 版本来解决这个问题。LSTM,作为一个强大的 RNN 建筑,被广泛应用于社区,几乎取代了基本的 RNN。希望读者了解这些先进技术的用途和优势,如 LSTM、GRU 等,以便根据问题相应地实施。预训练的 Word2Vec 和 GloVe 单词向量嵌入被几个 RNN、LSTM 和其他网络使用,使得在每个序列步骤的模型的输入单词可以由其预训练的 Word2Vec 或 GloVe 向量来表示,而不是在循环神经网络本身内学习这些单词向量嵌入。
在下一章中,我们将讨论受限玻尔兹曼机(RBM),这是一种基于能量的神经网络,以及各种自编码器,作为无监督深度学习的一部分。此外,我们将讨论深度信念网络,它可以通过堆叠几个受限制的波尔兹曼机并以贪婪的方式训练这样的网络来形成,并通过 RBM 进行协同过滤。期待你参与下一章。
五、使用受限玻尔兹曼机和自编码器的无监督学习
无监督学习是机器学习的一个分支,它试图在未标记的数据中找到隐藏的结构,并从中获得洞察力。聚类、数据降维技术、降噪、分段、异常检测、欺诈检测和其他丰富的方法依赖于非监督学习来推动分析。今天,我们周围有这么多数据,不可能为监督学习标记所有数据。这使得无监督学习变得更加重要。受限玻尔兹曼机和自编码器是基于人工神经网络的无监督方法。它们在数据压缩和降维、数据降噪、异常检测、生成建模、协同过滤和深度神经网络初始化等方面具有广泛的用途。我们将详细讨论这些主题,然后触及几个无监督的图像预处理技术,即 PCA(主成分分析)白化和 ZCA (Mahalanobis)白化。此外,由于受限制的玻尔兹曼机在训练中使用抽样技术,为了读者的利益,我简要地提到了贝叶斯推理和马尔可夫链蒙特卡罗抽样。
玻尔兹曼分布
受限玻尔兹曼机是基于经典物理学玻尔兹曼分布定律的能量模型,其中任何系统的粒子状态都由它们的广义坐标和速度来表示。这些广义坐标和速度构成了粒子的相空间,粒子可以以特定的能量和概率位于相空间的任意位置。让我们考虑一个包含 N 个气体分子的经典系统,让任意粒子的广义位置和速度分别用
和
表示。粒子在相空间中的位置可以用(r,v)来表示。粒子可能取的每一个这样的(r,v)值叫做粒子的构型。此外,所有的 N 个粒子都是相同的,因为它们同样可能占据任何状态。给定这样一个处于热力学温度 T 的系统,任何这样的配置的概率如下:

E(r,v)是处于构型(r,v)的任何粒子的能量,K 是玻尔兹曼常数。因此,我们看到,相空间中任何构型的概率,与能量除以玻尔兹曼常数和热力学温度的乘积的负值的指数成正比。为了将关系转换成等式,概率需要通过所有可能配置的概率的总和来归一化。如果粒子有 M 种可能的相空间组态,那么任何广义组态(r,v)的概率可以表示为

其中 Z 是由

给出的配分函数
r 和 v 可以分别有几个值。然而,M 表示 r 和 v 的所有可能的唯一组合,这些组合在前面的等式中已经由(r,v) i 表示。如果 r 可以取 n 个不同的坐标值,而 v 可以取 m 个不同的速度值,那么可能配置的总数
。在这种情况下,配分函数也可以表示为:

这里要注意的是,当相关能量低时,任何构型的概率都较高。对于气体分子来说,这也很直观,因为高能态总是与不稳定平衡相联系,因此不太可能长时间保持高能构型。高能组态中的粒子总是在追求占据更稳定的低能态。
如果我们考虑两个组态
和
,并且如果这两个态的气体分子数分别为 N 1 和 N 2 ,那么这两个态的概率比就是这两个态的能量差的函数:

我们现在将稍微偏离主题,简要讨论贝叶斯推理和马尔可夫链蒙特卡罗(MCMC)方法,因为受限玻尔兹曼机通过 MCMC 技术使用采样,尤其是吉布斯采样,并且这些方面的一些知识将有助于帮助读者理解受限玻尔兹曼机的工作原理。
贝叶斯推理:可能性、先验和后验概率分布
正如在第一章中所讨论的,每当我们得到数据时,我们就通过定义一个基于模型参数的数据的似然函数来建立一个模型,然后尝试最大化该似然函数。似然性无非是给定模型参数后所看到或观察到的数据的概率:

为了得到由其参数定义的模型,我们最大化所见数据的可能性:

由于我们只是试图根据观察到的数据拟合一个模型,如果我们追求简单的可能性最大化,就很有可能过拟合而不能推广到新数据。
如果数据量很大,所看到的数据可能很好地代表了总体,因此最大化可能性可能就足够了。另一方面,如果看到的数据很小,它很可能不能很好地代表总体,因此基于可能性的模型不能很好地推广到新数据。在这种情况下,对模型有一定的先验信念并通过先验信念来约束可能性会导致更好的结果。假设先验信念的形式是我们以概率分布的形式知道模型参数的不确定性;即 P(模型)是已知的。在这种情况下,我们可以通过先验信息更新我们的可能性,以获得给定数据的模型分布。根据贝叶斯条件概率定理,

p(模型/数据)被称为后验分布,通常信息更丰富,因为它结合了一个人关于数据或模型的先验知识。由于数据的这种概率是独立于模型的,因此后验与似然和先验的乘积成正比:

可以通过最大化后验概率分布而不是似然性来建立模型。这种获得模型的方法被称为最大化后验概率,或 MAP。似然性和 MAP 都是模型的点估计,因此不能覆盖整个不确定性空间。采用最大化后验概率的模型意味着采用该模型的概率分布的模式。由最大似然函数给出的点估计不对应于任何模式,因为似然函数不是概率分布函数。如果概率分布是多模态的,这些点估计的表现会更差。
更好的方法是在整个不确定性空间内取模型的平均值;即取基于后验分布的模型的均值,如下:
![$$ Model=E\left[ Model/ Data\right]=\underset{Model}{\int } Model\ P\left( Model/ Data\right)d(Model) $$](A448418_1_En_5_Chapter_Equj.gif)
为了激发似然性和后验概率的概念,以及它们如何被用来导出模型参数,让我们再次回到硬币问题。
假设我们掷硬币六次,其中正面出现五次。如果要估计人头的概率,估计值会是多少?
这里,我们的模型是估计投掷硬币时正面θ的概率。每次投掷硬币都可以被视为一次独立的伯努利试验,正面的概率为θ。给定模型,数据的可能性由

给出,其中
表示正面(H)或反面(T)的事件。
由于硬币的投掷是独立的,可能性可以分解如下:

(5.1.1)
每次掷骰子遵循伯努利分布,因此正面的概率为θ,反面的概率为
,通常其概率质量函数由

(5.1.2)给出,其中
表示正面,
表示反面。
由于组合(1)和(2)有 5 个头和 1 个尾,所以作为θ的函数的似然 L 可以表示如下:

(5.1.3)
最大似然法将最小化 L(θ)的
视为模型参数。于是,

如果我们对(5.1.3)中计算的似然性求导并将其设置为零,我们将得到θ:

的似然估计
一般来说,如果有人问我们对θ的估计,而我们没有做类似的最大似然估计,我们会立刻根据我们在高中学到的概率的基本定义回答概率为
;即

在某种程度上,我们的大脑在思考可能性,并依赖迄今为止看到的数据。
现在,让我们假设我们没有看到数据,有人要求我们确定正面的概率;什么是合理的估计?
这取决于我们对硬币的任何先验信念,即概率会不同。如果我们假设一个公平的硬币,这通常是最明显的假设,因为我们没有关于硬币的信息,
将是一个很好的估计。然而,当我们假设而不是对θ进行先验点估计时,最好在θ上有一个概率分布,最大概率在
。先验概率分布是模型参数θ的分布。
在这种情况下,带有参数
的贝塔分布将是一个很好的先验分布,因为它在
具有最大概率,并且在它周围是对称的。
对于α和β的固定值,B(α,β)是常数,并且是该概率分布的归一化或配分函数。可以计算如下:

即使不记得公式,只要对
积分,取其倒数作为归一化常数,就可以知道,因为概率分布的积分应该是 1。
??
(5 . 1 . 4)
如果我们结合似然和先验,我们得到后验概率分布如下:

由于我们忽略了数据的概率,所以出现了正比例符号。其实我们也可以把 6 拿出来,把后验表示成:

现在,
既然θ是一个概率。在 0 到 1 的范围内对
进行积分并取倒数,将得到后验概率的归一化因子,结果为 252。于是,后验可以表述为:

(5.1.5)
现在我们有了后验概率,有两种方法可以估计θ。我们可以最大化后验,得到θ的 MAP 估计如下:


我们看到,
的 MAP 估计比
的似然估计更保守,因为它考虑了先验,而不是盲目相信数据。
现在,让我们来看看第二种方法,即纯贝叶斯方法,并取后验分布的平均值来平均θ:
![$$ {\displaystyle \begin{array}{l}E\left[\theta /D\right]=\underset{\theta =0}{\overset{1}{\int }}\theta P\left(\theta /D\right) d\theta \ {}\kern4.75em =\underset{\theta =0}{\overset{1}{\int }}\frac{\theta⁷{\left(1-\theta \right)}²}{252} d\theta\ \ {}\kern4.75em =0.7\end{array}} $$](A448418_1_En_5_Chapter_Equu.gif)
的所有不确定性

图 5-1c。
Posterior probability distribution

图 5-1b。
Prior probability distribution

图 5-1a。
Likelihood function plot
在图 5-1a 到图 5-1c 中绘制的是硬币问题的似然函数以及先验和后验概率分布。需要注意的一点是,似然函数不是概率密度函数或概率质量函数,而先验和后验是概率质量或密度函数。
对于复杂的分布,后验概率分布可能非常复杂,有几个参数,不太可能代表已知的概率分布形式,如正态分布、伽玛分布等。因此,为了计算后验概率的平均值,在模型的整个不确定性空间上计算积分似乎是不可能的。
在这种情况下,可以使用马尔可夫链蒙特卡罗抽样方法对模型参数进行抽样,然后它们的平均值就是后验分布平均值的合理估计。如果我们采样 n 组模型参数 M i 那么
![$$ E\left[ Model/ Data\right]\approx \sum \limits_{i=1}^n{M}_i $$](A448418_1_En_5_Chapter_Equv.gif)
通常,取分布的平均值,因为它使所有 c 的平方误差最小。
时
最小化。假设我们试图用单个代表来表示分布的概率,以使概率分布的平方误差最小化,则平均值是最佳候选值。
然而,如果分布是偏斜的和/或数据中有更多潜在异常值形式的噪声,则可以取分布的中值。这个估计的中值可以基于从后验样本中抽取的样本。
抽样的马尔可夫链蒙特卡罗方法
马尔可夫链蒙特卡罗方法(MCMC)是从复杂的后验概率分布或通常从多变量数据的任何概率分布中进行采样的一些最流行的技术。在我们讨论 MCMC 之前,让我们先讨论一下蒙特卡罗抽样方法。蒙特卡罗采样方法试图根据采样点计算曲线下的面积。
例如,超越数 Pi(π)的面积可以通过对半径为 1 的正方形内的点进行采样并记下该正方形内直径为 2 的圆的四分之一内的采样点数来计算。如图 5-2 所示,圆周率的面积计算如下:


图 5-2。
Area of Pi
在清单 5-1 中,举例说明了计算圆周率值的蒙特卡罗方法。正如我们所看到的,这个值接近圆周率的值。可以通过采样更多的点来提高精度。
import numpy as np
number_sample = 100000
inner_area,outer_area = 0,0
for i in range(number_sample):
x = np.random.uniform(0,1)
y = np.random.uniform(0,1)
if (x**2 + y**2) < 1 :
inner_area += 1
outer_area += 1
print("The computed value of Pi:",4*(inner_area/float(outer_area)))
--Output--
('The computed value of Pi:', 3.142)
Listing 5-1.
Computation of Pi
Through Monte Carlo Sampling
如果维度空间很大,简单的蒙特卡罗方法是非常低效的,因为维度越大,相关性的影响就越显著。马尔可夫链蒙特卡罗方法在这种情况下是有效的,因为它们从高概率区域比从低概率区域花费更多的时间来收集样本。正常的蒙特卡罗方法均匀地探索概率空间,因此花费与探索高概率区域一样多的时间来探索低概率区域。正如我们所知,当通过采样计算函数的期望值时,低概率区域的贡献是微不足道的,因此当算法在这样的区域中花费大量时间时,它会导致明显更长的处理时间。马尔可夫链蒙特卡罗方法背后的主要启发是不一致地探索概率空间,而是更集中于高概率区域。在高维空间中,由于相关性,大部分空间是稀疏的,仅在特定区域发现高密度。因此,我们的想法是花更多的时间从高概率区域收集更多的样本,尽可能少花时间探索低概率区域。
马尔可夫链可以被认为是一个随机/随机过程,用来生成一个随时间演变的随机样本序列。随机变量的下一个值仅由变量的前一个值决定。马尔可夫链,一旦进入高概率区,就试图尽可能多地收集高概率密度的点。它通过根据当前样本值生成下一个样本来实现这一点,从而以高概率选择靠近当前样本的点,以低概率选择远离当前样本的点。这确保了马尔可夫链从当前高概率区域收集尽可能多的点。然而,偶尔需要从当前样本进行长距离跳跃,以探索远离马尔可夫链工作的当前区域的其他潜在高概率区域。
马尔可夫链的概念可以用气体分子在一个封闭容器中的稳态运动来说明。容器的几个部分具有比其他区域更高密度的气体分子,并且由于气体分子处于稳定状态,即使可能有气体分子从一个位置移动到另一个位置,每个状态的概率(由气体分子的位置决定)也将保持恒定。

图 5-3。
Movement of gases in an enclosed container at steady state with only three states: A, B, and C
为了简单起见,让我们假设气体分子只有三种状态(在这种情况下是气体分子的位置),如图 5-3 所示。让我们用 A、B 和 C 来表示这些状态,用 P A ,P B ,P C 来表示它们相应的概率。
由于气体分子处于稳态,如果有气体分子跃迁到其他状态,需要保持平衡以保持概率分布不变。要考虑的最简单的假设是,从状态 A 到状态 B 的概率质量应该从 B 回到 A;即成对的,状态处于平衡状态。
假设 P(B/A)决定了从 A 到 B 的转移概率。因此,从 A 到 B 的概率质量由

(5.2.1)给出
同样,从 B 到 A 的概率质量由

(5.2.2)给出
所以,从(5.2.1)和(5.2.2)的稳态中,我们有

(5.2.3)来维持概率分布的平稳性。这叫做详细平衡条件,是一个概率分布平稳性的充分但非必要条件。气体分子可以以更复杂的方式处于平衡,但是由于当可能的状态空间是无限的时,这种形式的细节平衡在数学上是方便的,所以这种方法已经被广泛用于马尔可夫链蒙特卡罗方法中,以基于当前点对下一个点进行采样,并且具有高的接受概率。简而言之,马尔可夫链的运动预计会像稳态下的气体分子一样,在高概率区域比在低概率区域花费更多的时间,从而保持详细的平衡条件不变。
下面列出了马尔可夫链的良好实现需要满足的一些其他条件:
- 不可约性——马尔可夫链的一个理想性质是,我们可以从一个状态进入任何其他状态。这一点很重要,因为在马尔可夫链中,虽然我们希望以高概率继续探索给定状态的邻近状态,但有时我们可能不得不跳转并探索某个远邻域,同时预期新区域可能是另一个高概率区域。
- 非周期性——马尔可夫链不应该太频繁地重复,否则就不可能遍历整个空间。想象一个有 20 个状态的空间。如果在探索了五个状态之后,该链重复,则不可能遍历所有 20 个状态,从而导致次优采样。
大都会算法
Metropolis 算法是一种马尔可夫链蒙特卡罗方法,它使用当前接受的状态来确定下一个状态。时间
处的样本有条件地依赖于时间 t 处的样本。时间
处的建议状态来自正态分布,其平均值等于具有指定方差的时间 t 处的当前样本。一旦抽取,检查在时间
和时间 t 的样本之间的概率比。如果
大于或等于 1,则选择概率为 1 的样本
;如果小于 1,则随机选择样本。接下来提到的是详细的实现步骤。
- 从任意一个随机采样点 X (1) 开始。
- 选择条件依赖于 X (1) 的下一个点 X (2) 。可以从一个均值为 X (1) 和一些有限方差的正态分布中选择 X (2) ,假设 S (2) 。因此,
良好采样的一个关键决定因素是非常明智地选择方差 S 2 。方差不应该太大,因为在这种情况下,下一个样本 X (2) 停留在当前样本 X (1) 附近的机会较小,在这种情况下,高概率区域可能不会被探索太多,因为下一个样本在大多数时间被选择为远离当前样本。同时,方差不应该太小。在这种情况下,下一个样本将几乎总是停留在当前点附近,因此探索远离当前区域的不同高概率区域的概率将降低。 - 一旦 X (2) 已经从前面的步骤中生成,则使用一些特殊的试探法来确定是否接受它。
- 如果比率
则接受 X (2) 并将其作为有效样本点。接受的样本成为生成下一个样本的 X (1) 。 - 如果比率大于从 0 和 1 之间的均匀分布中随机产生的数字,则比率
被接受;即 U [0,1]。
- 如果比率
如我们所见,如果我们转向高概率样本,那么我们接受新样本,如果我们转向低概率样本,我们有时接受有时拒绝新样本。如果比率 P(X(2)/P(X(1))很小,则拒绝的概率增加。比方说 P(X (2) )/P(X (1) )的比值= 0.1。当我们从一个均匀分布中产生一个介于 0 和 1 之间的随机数 r u 时,那么
的概率是 0.9,这又意味着新样本被拒绝的概率是 0.9。一般情况下,

其中 r 是新样本和旧样本的概率之比。
让我们试着凭直觉理解为什么这种试探法适用于马尔可夫链蒙特卡罗方法。根据明细余额,

我们假设转移概率服从正态分布。我们不检查我们采用的转移概率框架是否足以以我们希望坚持的详细平衡的形式保持概率分布的平稳性。让我们考虑保持分布平稳的两个状态 X 1 和 X 2 之间的理想转移概率由 P(X 1 /X 2 )和 P(X 2 /X 1 )给出。因此,根据明细平衡,必须满足以下条件:

然而,发现这样一个理想的转移概率函数,通过强加一个详细的平衡条件来确保平稳性是困难的。我们从合适的转移概率函数开始,假设为 T(x/y),其中 y 表示当前状态,X 表示基于 y 采样的下一个状态。对于两个状态 X 1 和 X 2 ,假设的转移概率因此由从状态 X 2 到 X 1 的移动的 T(X 1 /X 2 和 T(X2给出由于假设的转移概率不同于通过详细平衡保持平稳性所需的理想转移概率,我们有机会根据下一步行动的好坏接受或拒绝样本。为了掩盖这个机会,考虑状态转移的接受概率,使得对于从 X 1 到 X22
的状态转移,其中 A(X 2 /X 1 是从 X 1 到 X 2 的移动的接受概率。
根据明细余额,

将理想转移概率替换为假设转移概率和接受概率的乘积,我们得到

重新排列这个,我们得到的接受概率比是

满足这一点的一个简单建议由 Metropolis 算法给出为

在 Metropolis 算法中,假设的转移概率一般假设为对称的正态分布,因此 T(X1/X2)= T(X2/X1)。这简化了从 X 1 到 X 2 移动为

的接受概率
如果接受概率为 1,那么我们接受概率为 1 的移动,而如果接受概率小于 1,比如说 r,那么我们接受概率为 r 的新样本,拒绝概率为
的样本。以概率
拒绝样本是通过将比率与从 0 和 1 之间的均匀分布中随机生成的样本 r u 进行比较,并在
的情况下拒绝样本来实现的。这是因为对于均匀分布概率
,这确保了保持期望的拒绝概率。
在清单 5-2 中,我们展示了通过 Metropolis 算法从二元高斯分布中进行抽样。

图 5-4。
Plot of sampled points from multi-variate Gaussian distribution using Metropolis algorithm
import numpy as np
import matplotlib.pyplot as plt
#Now let’s generate this with one of the Markov Chain Monte Carlo methods called Metropolis Hastings algorithm
# Our assumed transition probabilities would follow normal distribution X2 ∼ N(X1,Covariance= [[0.2 , 0],[0,0.2]])
import time
start_time = time.time()
# Set up constants and initial variable conditions
num_samples=100000
prob_density = 0
## Plan is to sample from a Bivariate Gaussian Distribution with mean (0,0) and covariance of
## 0.7 between the two variables
mean = np.array([0,0])
cov = np.array([[1,0.7],[0.7,1]])
cov1 = np.matrix(cov)
mean1 = np.matrix(mean)
x_list,y_list = [],[]
accepted_samples_count = 0
## Normalizer of the Probability distibution
## This is not actually required since we are taking ratio of probabilities for inference
normalizer = np.sqrt( ((2*np.pi)**2)*np.linalg.det(cov))
## Start wtih initial Point (0,0)
x_initial, y_initial = 0,0
x1,y1 = x_initial, y_initial
for i in xrange(num_samples):
## Set up the Conditional Probability distribution, taking the existing point
## as the mean and a small variance = 0.2 so that points near the existing point
## have a high chance of getting sampled.
mean_trans = np.array([x1,y1])
cov_trans = np.array([[0.2,0],[0,0.2]])
x2,y2 = np.random.multivariate_normal(mean_trans,cov_trans).T
X = np.array([x2,y2])
X2 = np.matrix(X)
X1 = np.matrix(mean_trans)
## Compute the probability density of the existing point and the new sampled
## point
mahalnobis_dist2 = (X2 - mean1)*np.linalg.inv(cov)*(X2 - mean1).T
prob_density2 = (1/float(normalizer))*np.exp(-0.5*mahalnobis_dist2)
mahalnobis_dist1 = (X1 - mean1)*np.linalg.inv(cov)*(X1 - mean1).T
prob_density1 = (1/float(normalizer))*np.exp(-0.5*mahalnobis_dist1)
## This is the heart of the algorithm. Comparing the ratio of probability density of the new
## point and the existing point(acceptance_ratio) and selecting the new point if it is to have more probability
## density. If it has less probability it is randomly selected with the probability of getting
## selected being proportional to the ratio of the acceptance ratio
acceptance_ratio = prob_density2[0,0] / float(prob_density1[0,0])
if (acceptance_ratio >= 1) | ((acceptance_ratio < 1) and (acceptance_ratio >= np.random.uniform(0,1)) ):
x_list.append(x2)
y_list.append(y2)
x1 = x2
y1 = y2
accepted_samples_count += 1
end_time = time.time()
print ('Time taken to sample ' + str(accepted_samples_count) + ' points ==> ' + str(end_time - start_time) + ' seconds' )
print 'Acceptance ratio ===> ' , accepted_samples_count/float(100000)
## Time to display the samples generated
plt.xlabel('X')
plt.ylabel('Y')
plt.scatter(x_list,y_list,color='black')
print "Mean of the Sampled Points"
print np.mean(x_list),np.mean(y_list)
print "Covariance matrix of the Sampled Points"
print np.cov(x_list,y_list)
-Output-
Time taken to sample 71538 points ==> 30.3350000381 seconds
Acceptance ratio ===> 0.71538
Mean of the Sampled Points
-0.0090486292629 -0.008610932357
Covariance matrix of the Sampled Points
[[ 0.96043199 0.66961286]
[ 0.66961286 0.94298698]]
Listing 5-2.Bivariate Gaussian Distribution Through Metropolis Algorithm
我们从输出中看到,采样点的均值和协方差非常接近于我们从中采样的二元高斯分布的均值和协方差。此外,图 5-4 中的散点图非常类似于二元高斯分布。
现在我们知道了从概率分布中采样的马尔可夫链蒙特卡罗方法,我们将在检查受限玻尔兹曼机时了解另一种称为吉布斯采样的 MCMC 方法。
受限玻尔兹曼机
受限玻尔兹曼机(RBM)属于利用概率分布的玻尔兹曼方程的无监督类机器学习算法。图 5-5 所示为双层受限玻尔兹曼机架构,有一个隐藏层和一个可见层。所有隐藏层和可见层的单元之间都有权重连接。但是,不存在隐藏到隐藏或可见到可见的单元连接。“受限”一词在 RBM 指的是对网络的这种限制。给定可见单元集,RBM 的隐藏单元有条件地相互独立。类似地,给定隐藏单元集,RBM 的可见单元有条件地相互独立。受限玻尔兹曼机通常被用作深层网络的构建模块,而不是作为一个单独的网络本身。在概率图形模型方面,受限玻尔兹曼机可以定义为包含一个可见层和一个单一隐藏层的无向概率图形模型。与 PCA 非常相似,RBMs 可以被认为是一种将一个维度(由可见层 v 给出)的数据表示到不同维度(由隐藏层 h 给出)的方法。当隐藏层的大小小于可见层的大小时,RBM 对数据进行降维。RBM 通常基于二进制数据进行训练。

图 5-5。
Restricted Boltzmann machine visible and hidden layers architecture
让 RBM 的可见单元用向量
表示,隐藏单元用
表示。同样,让连接第 I 个可见单元和第 j 个隐藏单元的权重用
来表示。设包含权重 w ij 的矩阵用
表示。
具有隐藏状态 h 和可见状态 v 的联合概率分布的能量由

(5.3.1)给出,其中 E(v,h)是联合配置(v,h)的能量,Z 是归一化因子,通常称为配分函数。这个概率基于玻尔兹曼分布,并假设玻尔兹曼常数和热温度为 1。

(5.3.2)
联合构型(v,h)的能量 E(v,h)由

(5 . 3 . 3)

(5 . 3 . 4)给出
向量
和
分别是可见单元和隐藏单元的偏差,我们将在后面看到。
在任何图形概率模型中,其思想是计算各种事件集合的联合概率分布。结合(5.3.1)和(5.3.3),我们得到

(5.3.5)
配分函数 Z 很难计算,这使得 P(v,h)的计算很难计算。对于一小组事件,可以计算配分函数。如果在 v 和 h 中有许多变量,可能的联合事件的数目将会非常大;考虑所有这些组合变得很困难。
然而,条件概率分布 P(h/v)很容易计算和采样。下面的推论将证明这一点:

我们可以根据所涉及的不同向量的分量展开分子和分母,如下:
![$$ {\displaystyle \begin{array}{l}{e}{cTh}{e}{vT Wh}={e}{cTh+{v}^T Wh}\ {}\kern3.5em ={e}^{\sum \limits_{j=1}n\left({c}_j{h}_j+{v}TW\left[:,j\right]{h}_j\right)}\end{array}} $$](A448418_1_En_5_Chapter_Equah.gif)
由于和的指数等于指数的乘积,前面的等式可以写成乘积形式如下:
![$$ {e}{cTh}{e}{vT Wh}=\prod \limits_{j=1}n{e}{c_j{h}_j+{v}^TW\left[:,j\right]{h}_j} $$](A448418_1_En_5_Chapter_Equ14.gif)
(5.3.6)
现在,让我们看看分母,它看起来类似于分子,只是具有所有可能的隐藏状态 h 的总和。使用(5.3.6)中的表达式
,分母可以表示为
![$$ \sum \limits_h{e}{cTh}{e}{vT Wh}=\kern0.5em \sum \limits_h\prod \limits_{j=1}n{e}{c_j{h}_j+{v}^TW\left[:,j\right]{h}_j} $$](A448418_1_En_5_Chapter_Equ15.gif)
(5.3.7)
矢量和是指其分量的所有组合的和。每个隐藏单元 h i 可以有二进制状态 0 或 1,因此有
。因此,( 5.3.7)中对向量 h 的求和可以展开成对应于其每个分量的多个求和:
![$$ {\displaystyle \begin{array}{l}\sum \limits_h{e}{cTh}{e}{vT Wh}=\sum \limits_{h_1=0}¹\sum \limits_{h_2=0}¹..\sum \limits_{h_n=0}¹\prod \limits_{j=1}n{e}{c_j{h}_j+{v}^TW\left[:,j\right]{h}_j}\ {}\kern4.5em =\sum \limits_{h_1=0}¹\sum \limits_{h_2=0}¹..\sum \limits_{h_n=0}¹\left({e}{c_1{h}_1+{v}TW\left[:,1\right]{h}_1}\right)\left({e}{c_2{h}_2+{v}TW\left[:,2\right]{h}_2}\right)\dots \left({e}{c_n{h}_n+{v}TW\left[:,n\right]{h}_n}\right)\end{array}} $$](A448418_1_En_5_Chapter_Equ16.gif)
(5.3.8)
现在,让我们来看一个非常简单的操作,涉及两个离散变量 a 和 b 的乘积和求和:

所以,我们看到,当我们取独立指数的变量元素时,变量乘积的和可以表示为变量和的乘积。类似于这个例子,h(即 h i )的元素一般独立地包含在乘积
中,因此(5.3.8)中的表达式可以简化为:
![$$ \sum \limits_h{e}{cTh}{e}{vT Wh}=\sum \limits_{h_1=0}¹\left({e}{c_1{h}_1+{v}TW\left[:,1\right]{h}_1}\right)\sum \limits_{h_1=0}¹\left({e}{c_2{h}_2+{v}TW\left[:,2\right]{h}_2}\right)..\sum \limits_{h_n=0}¹\left({e}{c_n{h}_n+{v}TW\left[:,n\right]{h}_n}\right) $$](A448418_1_En_5_Chapter_Equaj.gif)
![$$ \sum \limits_h{e}{cTh}{e}{vT Wh}=\prod \limits_{j=1}^n\sum \limits_{h_j=0}¹\left({e}{c_j{h}_j+{v}TW\left[:,j\right]{h}_j}\right) $$](A448418_1_En_5_Chapter_Equ17.gif)
(5.3.9)
将(5.3.6)和(5.3.9)中分子和分母的表达式组合起来,我们得到
![$$ P\left(h/v\right)=\frac{\prod_{j=1}n{e}{c_j{h}_j+{v}TW\left[:,j\right]{h}_j}}{\prod_{j=1}n{\sum}_{h_j=0}¹{e}{c_j{h}_j+{v}TW\left[:,j\right]{h}_j}} $$](A448418_1_En_5_Chapter_Equak.gif)
简化这两边 h 的分量,我们得到
![$$ P\left({h}_1{h}_2..{h}_n/v\right)=\prod \limits_{j=1}n\left(\frac{e{c_j{h}_j+{v}TW\left[:,j\right]{h}_j}}{\sum_{h_j=0}¹{e}{c_j{h}_j+{v}^TW\left[:,j\right]{h}_j}}\right) $$](A448418_1_En_5_Chapter_Equal.gif)
![$$ =\left(\frac{e{c_1{h}_1+{v}TW\left[:,1\right]{h}_1}}{\sum_{h_1=0}¹{e}{c_1{h}_1+{v}TW\left[:,1\right]{h}_1}}\right)\left(\frac{e{c_2{h}_2+{v}TW\left[:,1\right]{h}_2}}{\sum_{h_2=0}¹{e}{c_2{h}_2+{v}TW\left[:,2\right]{h}_2}}\right)\left(\frac{e{c_n{h}_n+{v}TW\left[:,n\right]{h}_n}}{\sum_{h_n=0}¹{e}{c_n{h}_n+{v}TW\left[:,n\right]{h}_{1n}}}\right) $$](A448418_1_En_5_Chapter_Equ18.gif)
(5.3.10)
以 v 为条件的 h 的元素的联合概率分布已经被分解成以 v 为条件的彼此独立的表达式的乘积。这导致这样的事实:给定 v,h 的分量(即,
)是彼此有条件独立的。这给出了我们

(5 . 3 . 11)
![$$ P\left({h}_j/v\right)=\frac{e{c_j{h}_j+{v}TW\left[:,j\right]{h}_j}}{\sum_{h_j=0}¹{e}{c_j{h}_j+{v}TW\left[:,j\right]{h}_j}} $$](A448418_1_En_5_Chapter_Equ20.gif)
(5 . 3 . 12)
替换(5.3.12)中的
和
,我们得到
![$$ P\left({h}_j=1/v\right)=\frac{e{c_j+{v}TW\left[:,j\right]}}{1+{e}{c_j+{v}TW\left[:,j\right]}} $$](A448418_1_En_5_Chapter_Equ21.gif)
(5 . 3 . 13)
![$$ P\left({h}_j=0/v\right)=\frac{1}{1+{e}{c_j+{v}TW\left[:,j\right]}} $$](A448418_1_En_5_Chapter_Equ22.gif)
(5 . 3 . 14)
(5.3.13)和(5.3.14)的表达式说明了隐藏单元
是独立的 sigmoid 单元:
![$$ P\left({h}_j=1/v\right)=\sigma \left({c}_j+{v}^TW\left[:,j\right]\right) $$](A448418_1_En_5_Chapter_Equ23.gif)
(5.3.15)
展开 v 和 W 的分量[:,j],我们可以将(5.3.15)改写为

(5.3.16)其中σ(。)表示 sigmoid 函数,使得

以类似的方式进行,可以证明

,这意味着给定可见状态,隐藏单元有条件地彼此独立。由于 RBM 是一个对称的无向网络,像可见单元一样,给定隐藏状态的可见单元的概率可以类似地表示为

(5.3.17)
从(5.3.16)和(5.3.17)我们可以清楚地看到,可见单元和隐藏单元实际上是二元 sigmoid 单元,向量 b 和 c 分别在可见单元和隐藏单元提供偏差。隐藏和可见单元的这种对称和独立的条件依赖在训练模型时可能是有用的。
训练受限玻尔兹曼机
我们需要训练玻尔兹曼机,以便导出模型参数 b、c、W,其中 b 和 c 分别是可见和隐藏单元处的偏置向量,W 是可见和隐藏层之间的权重连接矩阵。为了便于引用,模型参数可以统称为
![$$ \theta =\left[b;c;W\right] $$](A448418_1_En_5_Chapter_Equao.gif)
可以通过最大化输入数据点相对于模型参数的对数似然函数来训练模型。输入只是对应于每个数据点的可见单元的数据。似然函数由

给出
由于输入的数据点是独立给定的模型,

(5.3.18)
取两边的对数得到(5.3.18)中函数的对数似然表达式,我们有

(5.3.19)
将(5.3.19)中的概率按其联合概率形式展开,我们得到

(5.3.20)
与(5.3.20)中的第一项不同,配分函数 Z 不受可见层输入 v (t) 的约束。z 是 v 和 h 的所有可能组合的能量的负指数之和,因此可以表示为

用(5.3.20)中的这个表达式替换 Z,我们得到

(5.3.21)
现在,让我们取成本函数相对于组合参数θ的梯度。我们可以认为 C 由两部分组成,
和
,其中


取
相对于θ的梯度,我们得到

(5.3.22)
现在我们把
简化为分子和分母都除以 Z:

(5 . 3 . 23)
。使用(5.3.23)中概率的这些表达式,我们得到

(5.3.24)
如果愿意,可以从概率符号中删除θ,例如 P(v (t) 、h/θ)、P(v (t) 、h/θ)等等,以便于符号化,但是最好保留它们,因为它使得推导更加完整,这允许整个训练过程的更好的可解释性。
让我们看看函数的期望,它以一种更有意义的形式给出了(5.3.24)式中的表达式,这种形式对于训练是理想的。f(x)的期望,给定 x,遵循概率质量函数 P(x) nd 由
![$$ E\left[f(x)\right]=\sum \limits_xP(x)f(x) $$](A448418_1_En_5_Chapter_Equat.gif)
给出
如果
是多变量的,那么前面的表达式成立,并且
![$$ E\left[f(x)\right]=\sum \limits_xP(x)f(x)=\sum \limits_{x_1}\sum \limits_{x_2}..\sum \limits_{x_n}P\left({x}_1,{x}_2,..,{x}_n\right)f\left({x}_1,{x}_2,..,{x}_n\right) $$](A448418_1_En_5_Chapter_Equau.gif)
类似地,如果 f(x)是函数的向量,例如
,可以使用与期望相同的表达式。在这里,人们会得到一个期望向量,如下:
![$$ E\left[f(x)\right]=\sum \limits_xP(x)f(x)=\left[\begin{array}{c}\sum \limits_{x_1}\sum \limits_{x_2}..\sum \limits_{x_n}P\left({x}_1,{x}_2,..,{x}_n\right){f}_1\left({x}_1,{x}_2,..,{x}_n\right)\ {}\sum \limits_{x_1}\sum \limits_{x_2}..\sum \limits_{x_n}P\left({x}_1,{x}_2,..,{x}_n\right){f}_2\left({x}_1,{x}_2,..,{x}_n\right)\end{array}\right] $$](A448418_1_En_5_Chapter_Equ33.gif)
(5.3.25)
为了在期望符号中明确提及概率分布,可以将变量 x 遵循概率分布 P(x)的函数的期望或函数的向量的期望重写如下:
![$$ {E}_{P(x)}\left[f(x)\right]=\sum \limits_xP(x)f(x) $$](A448418_1_En_5_Chapter_Equav.gif)
由于我们正在处理梯度,梯度是不同偏导数的向量,并且对于给定的θ和 v 值,每个偏导数都是 h 的函数,因此(5.3.24)中的表达式可以表示为关于概率分布 P(h/v (t) ,θ】)的梯度期望值
为
![$$ {\nabla}_{\theta}\left({\rho}^{+}\right)=\sum \limits_{t=1}m{E}_{P\left(h/{v}{(t)},\theta \right)}\left[{\nabla}_{\theta}\left(-E\left({v}^{(t)},h\right)\right)\right] $$](A448418_1_En_5_Chapter_Equ34.gif)
(5.3.26)
注意期望
是一个期望向量,如(5.3.25)中所示。
现在,让我们得到
相对于θ :
![$$ {\displaystyle \begin{array}{l}{\nabla}_{\theta}\left({\rho}{-}\right)=m\frac{\sum_v{\sum}_h{e}{-E\left(v,h\right)}{\nabla}_{\theta}\left(-E\left(v,h\right)\right)}{\sum_v{\sum}_h{e}^{-E\left(v,h\right)}}\ {}\kern5.5em =m\frac{\sum_v{\sum}_h{e}^{-E\left(v,h\right)}{\nabla}_{\theta}\left(-E\left(v,h\right)\right)}{Z}\ {}\kern5.5em =m\sum \limits_v\sum \limits_h\frac{e^{-E\left(v,h\right)}}{Z}{\nabla}_{\theta}\left(-E\left(v,h\right)\right)\ {}\kern5.5em =m\sum \limits_v\sum \limits_hP\left(v,h/\theta \right){\nabla}_{\theta}\left(-E\left(v,h\right)\right)\ {}\kern5.5em =m{E}_{P\left(h,v/\theta \right)}\left[{\nabla}_{\theta}\left(-E\left(v,h\right)\right)\right]\end{array}} $$](A448418_1_En_5_Chapter_Equ35.gif)
(5.3.27)的梯度
(5.3.27)中的期望在 h 和 v 的联合分布上,而(5.3.26)中的期望在给定一个已知 v 的 h 上。结合(5.3.26)和(5.3.27),我们得到
![$$ {\nabla}_{\theta }(C)=\sum \limits_{t=1}m{E}_{P\left(h/{v}{(t)},\theta \right)}\left[{\nabla}_{\theta}\left(-E\left({v}^{(t)},h\right)\right)\right]-m{E}_{P\left(h,v/\theta \right)}\left[{\nabla}_{\theta}\left(-E\left(v,h\right)\right)\right] $$](A448418_1_En_5_Chapter_Equ36.gif)
(5.3.28)
如果我们观察(5.3.28)式中所有参数的梯度,它有两项。第一项取决于看到的数据 v (t) ,而第二项取决于来自模型的样本。第一项增加了给定观察数据的可能性,而第二项减少了来自模型的数据点的可能性。
现在,让我们对θ中每个参数集的梯度做一些简化;即 b、c、w .

(5 . 3 . 29)

(5 . 3 . 30)

(5 . 3 . 31)
使用(5.3.28)至(5.3.31),每个参数集的梯度表达式由
![$$ {\nabla}_b(C)=\sum \limits_{t=1}m{E}_{P\left(h/{v}{(t)},\theta \right)}\left[{v}^{(t)}\right]-m{E}_{P\left(h,v/\theta \right)}\left[v\right] $$](A448418_1_En_5_Chapter_Equ40.gif)
(5.3.32)给出
由于第一项的概率分布是以 v (t) 为条件的,v (t) 相对于 P(h/v (t) ,θ)的期望值就是 v (t) 。
![$$ {\nabla}_b(C)=\sum \limits_{t=1}m{v}{(t)}-m{E}_{P\left(h,v/\theta \right)}\left[v\right] $$](A448418_1_En_5_Chapter_Equ41.gif)
(5 . 3 . 33)
![$$ {\nabla}_c(C)=\sum \limits_{t=1}m{E}_{P\left(h/{v}{(t)},\theta \right)}\left[h\right]-m{E}_{P\left(h,v/\theta \right)}\left[h\right] $$](A448418_1_En_5_Chapter_Equ42.gif)
(5 . 3 . 34)
由于给定 v (t) 时 h(即 h j )的每个单位都是独立的,因此可以容易地计算出 h 在概率分布 P(h/v (t) ,θ)上的期望值。他们中的每一个都是具有两种可能结果的 sigmoid 单元,他们的期望只是 sigmoid 单元的输出;即
![$$ {E}_{P\left(h/{v}^{(t)},\theta \right)}\left[h\right]={\hat{h}}^{(t)}=\sigma \left(c+{W}T{v}{(t)}\right) $$](A448418_1_En_5_Chapter_Equaw.gif)
如果我们用ĥ代替期望,那么(5.3.34)中的表达式可以写成
![$$ {\nabla}_c(C)=\sum \limits_{t=1}m{\widehat{h}}{(t)}-m{E}_{P\left(h,v/\theta \right)}\left[h\right] $$](A448418_1_En_5_Chapter_Equ43.gif)
(5.3.35)
同样,
![$$ \begin{array}{l}{\nabla}_W;(C)={\displaystyle \sum_{t=1}m{E}_{P\left(h/{v}{(t)},;\theta \right)}}\left[{v}{(t)}{h}T\right]-m{E}_{P\left(h,;v/q\right)}\left[h\right]\ {}\kern1.92em ={\displaystyle \sum_{t=1}m{v}{(t)}{\widehat{h}}^{(t)T}}-m{E}_{P\left(h,;v/\theta \right)}\left[h\right]\end{array} $$](A448418_1_En_5_Chapter_Equ44.gif)
(5.3.36)
因此,(5.3.33)、(5.3.35)和(5.3.36)中的表达式代表关于三个参数集的梯度。为便于参考:
![$$ \left{\begin{array}{l}{\nabla}_b(C)=\sum \limits_{t=1}m{v}{(t)}-m{E}_{P\left(h,v/\theta \right)}\left[v\right]\ {}{\nabla}_c(C)=\sum \limits_{t=1}m{\widehat{h}}{(t)}-m{E}_{P\left(h,v/\theta \right)}\left[h\right]\ {}{\nabla}_W(C)=\sum \limits_{t=1}m{v}{(t)}{\widehat{h}}{(t)T}-m{E}_{P\left(h,v/\theta \right)}\left[h\right]\end{array}\right. $$](A448418_1_En_5_Chapter_Equ45.gif)
(5.3.37)
基于这些梯度,可以调用梯度下降技术来迭代地获得最大化似然函数的参数值。然而,在梯度下降的每次迭代中,计算关于联合概率分布 P(h,v/θ)的期望值有点复杂。联合分布很难计算,因为在 h 和 v 是中等至大维度向量的情况下,它们的组合数量似乎很大。马尔可夫链蒙特卡罗抽样(MCMC)技术,尤其是吉布斯抽样,可用于从联合分布中抽样,并计算(5.3.37)中不同参数集的期望值。然而,MCMC 技术需要很长时间才能收敛到平稳分布,之后它们提供了良好的样本。因此,在梯度下降的每次迭代中调用 MCMC 采样将使学习非常缓慢和不切实际。
吉布斯采样
吉布斯抽样是一种马尔可夫链蒙特卡罗方法,可用于从多变量概率分布中对观测值进行抽样。假设我们想从一个多元联合概率分布 P(x)中取样,其中
。
吉布斯采样根据其他变量的所有当前值生成变量 x i 的下一个值。设抽取的第 t 个样本用
表示。要生成接下来看到的
样本,请遵循以下逻辑:
-
Draw the variable
by sampling it from a probability distribution conditioned on the rest of the variables. In other words, draw
from![$$ P\left({x_j}{\left(t+1\right)}/{x_1}{\left(t+1\right)}{x_2}{\left(t+1\right)}..{x_{j-1}}{\left(t+1\right)}{x_{j+1}}{(t)}..{x_n}{(t)}\right) $$]()
So basically, for sampling x j conditioned on the rest of the variables, for the
variables before x j their values for the
instance are considered since they have already been sampled, while for the rest of the variables their values at instance t are considered since they are yet to be sampled. This step is repeated for all the variables.If each x j is discrete and can take, let’s say, two values 0 and 1, then we need to compute the probability p 1 =
. We can then draw a sample u from a uniform probability distribution between 0 and 1 (i.e., U[0, 1]), and if
set
, else set
. This kind of random heuristics ensure that the higher the probability p 1is, the greater the chances are of
getting selected as 1. However, it still leaves room for 0 getting selected with very low probability, even if p 1 is relatively large, thus ensuring that the Markov Chain doesn’t get stuck in a local region and can explore other potential high-density regions as well. There are the same kind of heuristics that we saw for the Metropolis algorithm as well. -
如果希望从联合概率分布 P(x)中生成 m 个样本,则前面的步骤必须重复 m 次。
基于联合概率分布的每个变量的条件分布将在采样可以进行之前确定。如果一个人正在研究贝叶斯网络或受限玻尔兹曼机,变量中存在某些约束,这些约束有助于以有效的方式确定这些条件分布。
例如,如果需要从具有均值[0 0]和协方差矩阵
的二元正态分布进行吉布斯采样,那么条件概率分布可以计算如下:


如果将边际分布 P(x 1 )和 P(x 2 )导出为
和
,则


块状吉布斯采样
吉布斯抽样有几种变体。块状吉布斯采样就是其中之一。在块吉布斯采样中,一个以上的变量被组合在一起,然后在其余变量的条件下对该组变量一起进行采样,这与单独对单个变量进行采样相反。例如,在受限玻尔兹曼机中,隐藏的单元状态变量
可以在可见单元状态
的条件下一起采样,反之亦然。因此,对于从 P(v,h)上的联合概率分布采样,通过块吉布斯采样,可以通过条件分布 P(h/v)采样给定可见单元状态的所有隐藏状态,并且通过条件分布 P(v/h)采样给定隐藏单元状态的所有可见单元状态。吉布斯采样的
迭代的样本可以生成为


因此,
是迭代
时的组合样本。
如果一定要计算一个函数 f(h,v)的期望,可以计算如下:
![$$ E\left[f\left(h,v\right)\right]\approx \frac{1}{M}\sum \limits_{t=1}Mf\left({h}{(t)},{v}^{(t)}\right) $$](A448418_1_En_5_Chapter_Eqube.gif)
其中 M 表示由联合概率分布 P(v,h)生成的样本数。
老化周期和 Gibbs 采样中的样本生成
为了考虑样本尽可能独立地用于期望计算或者基于联合概率分布,通常以 k 个样本的间隔拾取样本。k 的值越大,在产生的样本中消除自相关就越好。此外,在吉布斯采样开始时生成的样本被忽略。这些被忽略的样本据说是在老化期间产生的。
预烧期使用马尔可夫链来稳定到一个平衡分布,然后我们才能开始从中抽取样本。这是必需的,因为我们从任意样本生成马尔可夫链,该样本可能是相对于实际分布的低概率区域,因此我们可以丢弃那些不需要的样本。低概率样本对实际预期没有太大贡献,因此样本中有大量的低概率样本会模糊预期。一旦马尔可夫链运行了足够长的时间,它将会停留在某个高概率区域,此时我们可以开始收集样本。
在受限玻尔兹曼机中使用吉布斯采样
块吉布斯采样可用于计算关于联合概率分布 P(v,h/θ)的期望值,如(5.3.37)中的等式所述,用于计算关于模型参数 b、c 和 w 的梯度。以下是(5.3.37)中的等式,以方便参考:
![$$ \left{\begin{array}{c}{\nabla}_b(C)=\sum \limits_{t=1}m{v}{(t)}-m{E}_{P\left(h,v/\theta \right)}\left[v\right]\ {}{\nabla}_c(C)=\sum \limits_{t=1}^m\hat{h}-m{E}_{P\left(h,v/\theta \right)}\left[h\right]\ {}\kern1.25em {\nabla}_W(C)=\sum \limits_{t=1}m{v}{(t)}{\hat{h}}^T-m{E}_{P\left(h,v/\theta \right)}\left[v{h}^T\right]\end{array}\right. $$](A448418_1_En_5_Chapter_Equbf.gif)
期望 E P(h,v/θ)【v】,E P(h,v/θ)【h】,E P(h,v/θ)【VHT都需要从联合概率分布 P(v,h/θ)中抽样。通过块吉布斯采样,样本(v,h)可以基于它们的条件概率被绘制如下,其中 t 表示吉布斯采样的迭代次数:


让采样更容易的是,隐藏单元的 h j s 在给定可见单元状态的情况下是独立的,反之亦然:

这允许在给定可见单元状态的值的情况下,单独的隐藏单元 h j s 被独立地并行采样。参数θ已从前面的符号中删除,因为当我们执行吉布斯采样时,θ对于梯度下降的步长将保持不变。
现在,每个隐藏单元输出状态 h j 可以是 0 或 1,其呈现状态 1 的概率由(5.3.16)给出为

该概率可以基于当前值
和模型参数
来计算。将计算的概率
与从均匀分布 U[0,1]生成的随机样本 U 进行比较。如果
,则采样
,否则
。以这种方式采样的所有这种 hj形成组合的隐藏单元状态向量
。
类似地,给定隐藏单元状态,可见单元是独立的:

给定
,可以对每个可见单元进行独立采样,以与隐藏单元相同的方式获得组合 v (t+1) 。在
迭代中生成的所需样本由
给出。
所有的期望 E P(h,v/θ)【v】,E P(h,v/θ)【h】,E P(h,v/θ)【VHT都可以通过取 Gibbs 抽样产生的样本的平均值来计算。通过 Gibbs 采样,如果我们在考虑老化周期和自相关之后取 N 个样本,如前所述,所需的期望值可以计算如下:
![$$ {E}_{P\left(h,v/\theta \right)}\left[v\right]\approx \frac{1}{N}\sum \limits_{i=1}N{v}{(i)} $$](A448418_1_En_5_Chapter_Equbl.gif)
![$$ {E}_{P\left(h,v/\theta \right)}\left[h\right]\approx \frac{1}{N}\sum \limits_{i=1}N{h}{(i)} $$](A448418_1_En_5_Chapter_Equbm.gif)
![$$ {E}_{P\left(h,v/\theta \right)}\left[v{h}^T\right]\approx \frac{1}{N}\sum \limits_{i=1}N{v}{(i)}{h{(i)}}T $$](A448418_1_En_5_Chapter_Equbn.gif)
然而,对联合分布进行吉布斯采样以在梯度下降的每次迭代中生成 N 个样本成为一项繁琐的任务,并且通常是不切实际的。还有一种接近这些期望的方法,叫做对比差异,我们将在下一节讨论。
对比分歧
在梯度下降的每一步对联合概率分布 P(h,v/θ)执行吉布斯采样变得具有挑战性,因为诸如吉布斯采样的马尔可夫链蒙特卡罗方法需要很长时间来收敛,这是产生无偏样本所需要的。这些从联合概率分布中抽取的无偏样本用于计算期望项 E P(h,v/θ)【v】,E P(h,v/θ)【h】和 E P(h,v/θ)【VHT,它们只不过是(5.3.28)中推导出的梯度组合表达式中的项
的组成部分。
![$$ {\nabla}_{\theta }(C)=\sum \limits_{t=1}m{E}_{P\left(h/{v}{(t)},\theta \right)}\left[{\nabla}_{\theta}\left(-E\left({v}^{(t)},h\right)\right)\right]-m{E}_{P\left(h,v/\theta \right)}\left[{\nabla}_{\theta}\left(-E\left(v,h\right)\right)\right] $$](A448418_1_En_5_Chapter_Equbo.gif)
前面等式中的第二项可以重写为 m 个数据点的求和,因此得到
![$$ {\nabla}_{\theta }(C)=\sum \limits_{t=1}m{E}_{P\left(h/{v}{(t)},\theta \right)}\left[{\nabla}_{\theta}\left(-E\left({v}^{(t)},h\right)\right)\right]-\sum \limits_{t=1}^m{E}_{P\left(h,v/\theta \right)}\left[{\nabla}_{\theta}\left(-E\left(v,h\right)\right)\right] $$](A448418_1_En_5_Chapter_Equbp.gif)
对比散度通过对候选样本
进行点估计来近似总体期望
,该候选样本通过仅执行几次迭代的吉布斯采样来获得。![$$ {E}_{P\left(h,v/\theta \right)}\left[{\nabla}_{\theta}\left(-E\left(v,h\right)\right)\right]\kern0.5em \approx {\nabla}_{\theta}\left(-E\left(\overline{v},\overline{h}\right)\right) $$](A448418_1_En_5_Chapter_Equbq.gif)
对每个数据点 v【t】进行这种近似,因此整体梯度的表达式可以改写如下:
![$$ {\nabla}_{\theta }(C)\approx \sum \limits_{t=1}m{E}_{P\left(h/{v}{(t)},\theta \right)}\left[{\nabla}_{\theta}\left(-E\left({v}^{(t)},h\right)\right)\right]-\sum \limits_{t=1}m\left[{\nabla}_{\theta}\left(-E\left({\overline{v}}{(t)},{\overline{h}}^{(t)}\right)\right)\right] $$](A448418_1_En_5_Chapter_Equbr.gif)
下图 5-6 说明了如何对每个输入数据点 v (t) 进行吉布斯采样,以通过点估计获得联合概率分布的期望近似值。吉布斯采样从 v (t) 开始,基于条件概率分布 P(h/v (t) ,获得新的隐藏状态 h’。如前所述,每个隐藏单元 h j 可以被独立采样,然后被组合以形成隐藏状态向量 h’。然后基于条件概率分布 P(v/h’)对 v’进行采样。该迭代过程通常运行几次迭代,最终采样的 v 和 h 作为候选样本
。

图 5-6。
Gibbs sampling for two iterations to get one sample for Contrastive Divergence
对比散度方法使得梯度下降更快,因为梯度下降的每个步骤中的吉布斯采样被限制为仅几次迭代,通常每个数据点一次或两次。
TensorFlow 中的受限玻尔兹曼实现
在这一节中,我们将通过使用 MNIST 数据集实现受限玻尔兹曼机。在这里,我们试图通过定义一个受限的玻尔兹曼机网络来模拟 MNIST 图像的结构,该网络由作为可见单元的图像像素和 500 个隐藏层组成,以便破译每个图像的内部结构。由于 MNIST 图像是一维的,当作为一个矢量展平时,我们有 784 个可见单元。我们试图通过训练玻尔兹曼机来捕捉隐藏的结构。当给定输入图像的可视表示对所述隐藏状态进行采样时,表示相同数字的图像应该具有相似的隐藏状态,如果不是相同的话。当可视单元被采样时,给定它们的隐藏结构,当以图像形式构造时,可视单元值应该对应于图像的标签。详细代码如清单 5-3a 所示。

图 5-8。
Simulated images given the hidden states

图 5-7。
Actual test images
##Import the Required libraries
import numpy as np
import pandas as pd
import tensorflow as tf
import matplotlib.pyplot as plt
%matplotlib inline
## Read the MNIST files
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
## Set up the parameters for training
n_visible = 784
n_hidden = 500
display_step = 1
num_epochs = 200
batch_size = 256
lr = tf.constant(0.001, tf.float32)
## Define the tensorflow variables for weights and biases as well as placeholder for input
x = tf.placeholder(tf.float32, [None, n_visible], name="x")
W = tf.Variable(tf.random_normal([n_visible, n_hidden], 0.01), name="W")
b_h = tf.Variable(tf.zeros([1, n_hidden], tf.float32, name="b_h"))
b_v = tf.Variable(tf.zeros([1, n_visible], tf.float32, name="b_v"))
## Converts the probability into discrete binary states; i.e., 0 and 1
def sample(probs):
return tf.floor(probs + tf.random_uniform(tf.shape(probs), 0, 1))
## Gibbs sampling step
def gibbs_step(x_k):
h_k = sample(tf.sigmoid(tf.matmul(x_k, W) + b_h))
x_k = sample(tf.sigmoid(tf.matmul(h_k, tf.transpose(W)) + b_v))
return x_k
## Run multiple Gibbs sampling steps starting from an initial point
def gibbs_sample(k,x_k):
for i in range(k):
x_out = gibbs_step(x_k)
# Returns the Gibbs sample after k iterations
return x_out
# Constrastive Divergence algorithm
# 1\. Through Gibbs sampling locate a new visible state x_sample based on the current visible state x
# 2\. Based on the new x sample a new h as h_sample
x_s = gibbs_sample(2,x)
h_s = sample(tf.sigmoid(tf.matmul(x_s, W) + b_h))
# Sample hidden states based given visible states
h = sample(tf.sigmoid(tf.matmul(x, W) + bh))
# Sample visible states based given hidden states
x_ = sample(tf.sigmoid(tf.matmul(h, tf.transpose(W)) + b_v))
# The weight updated based on gradient descent
size_batch = tf.cast(tf.shape(x)[0], tf.float32)
W_add = tf.multiply(lr/size_batch, tf.subtract(tf.matmul(tf.transpose(x), h), tf.matmul(tf.transpose(x_s), h_s)))
bv_add = tf.multiply(lr/size_batch, tf.reduce_sum(tf.subtract(x, x_s), 0, True))
bh_add = tf.multiply(lr/size_batch, tf.reduce_sum(tf.subtract(h, h_s), 0, True))
updt = [W.assign_add(W_add), b_v.assign_add(bv_add), b_h.assign_add(bh_add)]
# TensorFlow graph execution
with tf.Session() as sess:
# Initialize the variables of the Model
init = tf.global_variables_initializer()
sess.run(init)
total_batch = int(mnist.train.num_examples/batch_size)
# Start the training
for epoch in range(num_epochs):
# Loop over all batches
for i in range(total_batch):
batch_xs, batch_ys = mnist.train.next_batch(batch_size)
# Run the weight update
batch_xs = (batch_xs > 0)*1
_ = sess.run([updt], feed_dict={x:batch_xs})
# Display the running step
if epoch % display_step == 0:
print("Epoch:", '%04d' % (epoch+1))
print("RBM training Completed !")
## Generate hidden structure for 1st 20 images in test MNIST
out = sess.run(h,feed_dict={x:(mnist.test.images[:20]> 0)*1})
label = mnist.test.labels[:20]
## Take the hidden representation of any of the test images; i.e., the 3rd record
## The output level of the 3rd record should match the image generated
plt.figure(1)
for k in range(20):
plt.subplot(4, 5, k+1)
image = (mnist.test.images[k]> 0)*1
image = np.reshape(image,(28,28))
plt.imshow(image,cmap='gray')
plt.figure(2)
for k in range(20):
plt.subplot(4, 5, k+1)
image = sess.run(x_,feed_dict={h:np.reshape(out[k],(-1,n_hidden))})
image = np.reshape(image,(28,28))
plt.imshow(image,cmap='gray')
print(np.argmax(label[k]))
sess.close()
--Output --
Listing 5-3a.Restricted Boltzmann Machine Implementation with MNIST Dataset
我们可以从图 5-7 和图 5-8 中看到,RBM 模型在模拟输入图像时表现出色,给出了它们的隐藏表示。因此,受限玻尔兹曼机也可以用作生成模型。
使用受限玻尔兹曼机的协同过滤
受限玻尔兹曼机可以用于在进行推荐时进行协同过滤。协同过滤是通过分析许多用户对项目的偏好来预测用户对项目的偏好的方法。给定一组项目和用户以及用户为各种项目提供的评级,协作过滤最常用的方法是矩阵分解方法,它为项目以及用户确定一组向量。然后,由用户分配给项目的评级可以被计算为用户向量 u (j) 与项目向量 v (k) 的点积。因此,评级可以表示为

,其中 j 和 k 分别表示第 j 个用户和第 k 个项目。一旦学习了每个项目和每个用户的向量,就可以通过相同的方法找出用户对他们尚未评级的产品的预期评级。矩阵分解可以被认为是将一个大的评分矩阵分解成用户向量和项目向量。

图 5-9。
Schematic diagram of a matrix factorization method for collaborative filtering
图 5-9 所示为将用户物品评分矩阵分解为用户向量和物品向量两个矩阵的矩阵分解方法示意图。用户向量和评分向量的维数必须相等,它们的点积才能为真,这就给出了用户可能给某个特定项目分配的评分的估计值。有几种矩阵分解方法,如奇异值分解(SVD),非负矩阵分解,交替最小二乘法,等等。根据用途,任何合适的方法都可以用于矩阵分解。一般来说,SVD 需要填充矩阵中缺失的评级(其中用户没有对项目进行评级),这可能是一项困难的任务,因此,诸如交替最小二乘法之类的方法(其仅采用所提供的评级而不采用缺失值)对于协同过滤来说工作得很好。
现在,我们将研究一种不同的使用受限玻尔兹曼机的协同过滤方法。受限玻尔兹曼机被协作过滤网飞挑战赛的获胜团队使用,因此让我们将这些项目视为此次讨论的电影。这个 RBM 网络的可视单元将对应于电影分级,并且不是二进制的,每个电影将是五向 SoftMax 单元,以考虑从 1 到 5 的五个可能的分级。隐藏单元的数量可以任意选择;我们在这里选了 d。对于不同的电影会有几个缺失值,因为所有的用户不会对所有的电影进行评级。处理它们的方法是仅基于用户已经评级的电影为每个用户训练单独的 RBM。从电影到隐藏单元的权重将由所有用户共享。例如,假设用户 A 和用户 B 评价同一部电影;他们将使用相同的权重来连接电影单元和隐藏单元。所以,所有的 RBM 都有相同的隐藏单元,当然,它们对隐藏单元的激活可能是非常不同的。

图 5-11。
Restricted Boltzmann view for User B

图 5-10。
Restricted Boltzmann view for User A
从图 5-10 和图 5-11 中我们可以看出,用户 A 和用户 B 的受限玻尔兹曼视图是不同的,因为他们对电影的选择不同。然而,对于他们都评价的电影,权重连接是相同的。这种架构——每个用户的 RBM 被单独训练,而 RBM 共享相同电影的权重——有助于克服缺失评级的问题,同时允许电影的通用权重与所有用户的隐藏层连接。从每部电影到隐藏单元,反之亦然,实际上有五个连接,每一个连接对应一部电影的一个可能的分级。然而,为了保持表示简单,图中只显示了一个组合连接。可以使用对比散度通过梯度下降分别训练每个模型,并且可以在不同的 RBM 上平均模型权重,使得所有的 RBM 共享相同的权重。
从 5.3.17,我们对于二进制可见单位有如下:

现在可见单元具有 K 个可能的评级,可见单元是 K 维向量,其中只有一个索引对应于设置为 1 的实际评级,其余的都设置为零。因此,超过 K 个可能评级的评级概率的新表达式将由 SoftMax 函数给出。此外,请注意,在这种情况下,m 是用户观看的电影数量,对于不同的用户,不同的 RBM 会有所不同。常数 n 表示每个 RBM 的隐藏层中隐藏单元的数量。

(5.4.1)其中 w ij (k) 是连接可见单元 I 的第 k 个等级指数和第 j 个隐藏单元的权重,b i (k) 表示可见单元 I 的第 k 个等级的偏差。
一个联合构形的能量 E(v,h)由

(5.4.2)给出
所以,

(5.4.3)
给定输入 v 的隐藏单元的概率是

(5.4.4)
现在,一个显而易见的问题是:我们如何预测用户没有看过的电影的评级?事实证明,这方面的计算并不复杂,可以用线性时间来计算。做出该决定的最具信息性的方式是调节用户的概率,假设对电影 q 的评级 r 是以用户已经提供的电影评级为条件的。假设用户已经提供的电影评分用 V 表示,那么我们需要计算概率 P(v q (k) /V)如下:

(5.4.5)
因为 P(V)对于所有电影分级 k 是固定的,所以从(5.4.5)我们有

(5.4.6)
这是一种三向能量配置,可以通过在(5.4.2)中添加 v q (k) 的贡献来轻松计算,如下所示:

(5.4.7)
将
代入(5.4.7)中,可以发现
的值与

成正比
对于等级 K 的所有 K 值,需要计算前面的量
,然后归一化以形成概率。然后,可以取概率最大的 k 值,或者从导出的概率中计算 k 的期望值,如下所示:


与基于最大概率的评级硬分配相比,得出评级的期望方法提供了更好的预测。
此外,利用评级矩阵 V 来导出用户对特定未评级电影 q 的评级 k 的概率的一种简单方法是,在给定可见评级输入 V 的情况下,首先对隐藏状态 h 进行采样;即绘制
。隐藏单元是所有电影共有的,因此携带了所有电影的模式信息。从采样的隐藏单元中,我们尝试采样 vq【k】的值;即绘制
。这种背靠背采样,首先从
开始,然后从
开始,相当于采样
。我希望这有助于提供一个更容易的解释。
深度信念网络
深度信念网络基于受限玻尔兹曼机,但与 RBN 不同,DBN 有多个隐藏层。通过保持先前
层中的所有权重不变来训练每个隐藏层 K 中的权重。
层中隐藏单元的活动作为第 k 层的输入。在训练期间的任何特定时间,两层都参与学习它们之间的重量联系。学习算法与受限玻尔兹曼机的算法相同。图 5-12 所示为深度信念网络的高层示意图。

图 5-12。
Deep belief network using RBMs
像 RBM 一样,在 DBN,每一层都可以通过使用对比散度的梯度下降来训练。DBN 学习算法用于学习用于监督学习的深度网络的初始权重,以便网络从一组良好的初始权重开始。一旦深度信念网络的预训练完成,我们就可以基于手头的监督问题向 DBN 添加输出层。假设我们想要训练一个模型来对 MNIST 数据集执行分类。在这种情况下,我们将不得不追加一个十级 SoftMax 层。然后,我们可以通过误差反向传播对模型进行微调。由于该模型已经具有来自无监督 DBN 学习的初始权重集,当调用反向传播时,该模型将有更快收敛的好机会。
每当我们在网络中有 sigmoid 单元时,如果网络权重没有正确初始化,就很有可能出现消失梯度问题。这是因为 sigmoid 单元的输出在小范围内是线性的,之后输出饱和,导致接近零的梯度。由于反向传播本质上是导数的链式法则,因此根据反向传播顺序,成本函数相对于任何权重的梯度将具有来自其之前层的 s 形梯度。因此,如果 sigmoid 层中的少数梯度在饱和区域中操作并产生接近于零的梯度,则后面的层,即成本函数相对于权重的梯度,将接近于零,并且学习很可能会停止。当权重未正确初始化时,网络 sigmoid 单元很有可能会进入不饱和区域,并导致 sigmoid 单元中的梯度接近零。然而,当通过 DBN 学习初始化网络权重时,sigmoid 单元在饱和区操作的机会较少。这是因为网络在预训练时已经学习了一些关于数据的知识,并且 sigmoid 单元在饱和区域中操作的机会较小。对于 ReLU 激活函数,不存在激活单元饱和的这种问题,因为对于大于零的输入值,它们具有 1 的恒定梯度。
我们现在来看看 DBN 预训练权重的实现,然后通过将输出层附加到 RBM 的隐藏层来训练分类网络。在清单 5-3a 中,我们实现了 RBM,其中我们学习了可见到隐藏连接的权重,假设所有的单元都是 sigmoid。对于该 RBM,我们将堆叠 MNIST 数据集的十个类的输出层,并使用从可见到隐藏单元学习的权重作为分类网络的初始权重来训练分类模型。当然,我们会有一组新的权重,对应于隐藏层到输出层的连接。具体实现见清单 5-3b 。
##Import the Required libraries
import numpy as np
import pandas as pd
import tensorflow as tf
import matplotlib.pyplot as plt
%matplotlib inline
## Read the MNIST files
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
## Set up the parameters for training
n_visible = 784
n_hidden = 500
display_step = 1
num_epochs = 200
batch_size = 256
lr = tf.constant(0.001, tf.float32)
learning_rate_train = tf.constant(0.01, tf.float32)
n_classes = 10
training_iters = 200
## Define the tensorflow variables for weights and biases as well as placeholder for input
x = tf.placeholder(tf.float32, [None, n_visible], name="x")
y = tf.placeholder(tf.float32, [None,10], name="y")
W = tf.Variable(tf.random_normal([n_visible, n_hidden], 0.01), name="W")
b_h = tf.Variable(tf.zeros([1, n_hidden], tf.float32, name="b_h"))
b_v = tf.Variable(tf.zeros([1, n_visible], tf.float32, name="b_v"))
W_f = tf.Variable(tf.random_normal([n_hidden,n_classes], 0.01), name="W_f")
b_f = tf.Variable(tf.zeros([1, n_classes], tf.float32, name="b_f"))
## Converts the probability into discrete binary states i.e. 0 and 1
def sample(probs):
return tf.floor(probs + tf.random_uniform(tf.shape(probs), 0, 1))
## Gibbs sampling step
def gibbs_step(x_k):
h_k = sample(tf.sigmoid(tf.matmul(x_k, W) + b_h))
x_k = sample(tf.sigmoid(tf.matmul(h_k, tf.transpose(W)) + b_v))
return x_k
## Run multiple Gibbs Sampling steps starting from an initial point
def gibbs_sample(k,x_k):
for i in range(k):
x_out = gibbs_step(x_k)
# Returns the gibbs sample after k iterations
return x_out
# Constrastive Divergence algorithm
# 1\. Through Gibbs sampling locate a new visible state x_sample based on the current visible state x
# 2\. Based on the new x sample a new h as h_sample
x_s = gibbs_sample(2,x)
h_s = sample(tf.sigmoid(tf.matmul(x_s, W) + b_h))
# Sample hidden states based given visible states
h = sample(tf.sigmoid(tf.matmul(x, W) + b_h))
# Sample visible states based given hidden states
x_ = sample(tf.sigmoid(tf.matmul(h, tf.transpose(W)) + b_v))
# The weight updated based on gradient descent
size_batch = tf.cast(tf.shape(x)[0], tf.float32)
W_add = tf.multiply(lr/size_batch, tf.subtract(tf.matmul(tf.transpose(x), h), tf.matmul(tf.transpose(x_s), h_s)))
bv_add = tf.multiply(lr/size_batch, tf.reduce_sum(tf.subtract(x, x_s), 0, True))
bh_add = tf.multiply(lr/size_batch, tf.reduce_sum(tf.subtract(h, h_s), 0, True))
updt = [W.assign_add(W_add), b_v.assign_add(bv_add), b_h.assign_add(bh_add)]
###############################################################
## Ops for the Classification Network
###############################################################
h_out = tf.sigmoid(tf.matmul(x, W) + b_h)
logits = tf.matmul(h_out,W_f) + b_f
prob = tf.nn.softmax(logits)
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=y))
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate_train).minimize(cost)
correct_pred = tf.equal(tf.argmax(logits,1), tf.argmax(y, 1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
## Ops for the hidden unit activation
# TensorFlow graph execution
with tf.Session() as sess:
# Initialize the variables of the Model
init = tf.global_variables_initializer()
sess.run(init)
total_batch = int(mnist.train.num_examples/batch_size)
# Start the training
for epoch in range(num_epochs):
# Loop over all batches
for i in range(total_batch):
batch_xs, batch_ys = mnist.train.next_batch(batch_size)
# Run the weight update
batch_xs = (batch_xs > 0)*1
_ = sess.run([updt], feed_dict={x:batch_xs})
# Display the running step
if epoch % display_step == 0:
print("Epoch:", '%04d' % (epoch+1))
print("RBM training Completed !")
out = sess.run(h,feed_dict={x:(mnist.test.images[:20]> 0)*1})
label = mnist.test.labels[:20]
plt.figure(1)
for k in range(20):
plt.subplot(4, 5, k+1)
image = (mnist.test.images[k]> 0)*1
image = np.reshape(image,(28,28))
plt.imshow(image,cmap='gray')
plt.figure(2)
for k in range(20):
plt.subplot(4, 5, k+1)
image = sess.run(x_,feed_dict={h:np.reshape(out[k],(-1,n_hidden))})
image = np.reshape(image,(28,28))
plt.imshow(image,cmap='gray')
print(np.argmax(label[k]))
####################################################
### Invoke the Classification Network training now
####################################################
for i in xrange(training_iters):
batch_x, batch_y = mnist.train.next_batch(batch_size)
# Run optimization op (backprop)
sess.run(optimizer, feed_dict={x: batch_x, y: batch_y})
if i % 10 == 0:
# Calculate batch loss and accuracy
loss, acc = sess.run([cost, accuracy], feed_dict={x: batch_x,
y: batch_y})
print "Iter " + str(i) + ", Minibatch Loss= " + \
"{:.6f}".format(loss) + ", Training Accuracy= " + \
"{:.5f}".format(acc)
print "Optimization Finished!"
# Calculate accuracy for 256 mnist test images
print "Testing Accuracy:", \
sess.run(accuracy, feed_dict={x: mnist.test.images[:256],
y: mnist.test.labels[:256]})
sess.close()
--output--
Iter 0, Minibatch Loss= 11.230852, Training Accuracy= 0.06641
Iter 10, Minibatch Loss= 2.809783, Training Accuracy= 0.60938
Iter 20, Minibatch Loss= 1.450730, Training Accuracy= 0.75000
Iter 30, Minibatch Loss= 0.798674, Training Accuracy= 0.83594
Iter 40, Minibatch Loss= 0.755065, Training Accuracy= 0.87891
Iter 50, Minibatch Loss= 0.946870, Training Accuracy= 0.82812
Iter 60, Minibatch Loss= 0.768834, Training Accuracy= 0.89062
Iter 70, Minibatch Loss= 0.445099, Training Accuracy= 0.92188
Iter 80, Minibatch Loss= 0.390940, Training Accuracy= 0.89062
Iter 90, Minibatch Loss= 0.630558, Training Accuracy= 0.90234
Iter 100, Minibatch Loss= 0.633123, Training Accuracy= 0.89844
Iter 110, Minibatch Loss= 0.449092, Training Accuracy= 0.92969
Iter 120, Minibatch Loss= 0.383161, Training Accuracy= 0.91016
Iter 130, Minibatch Loss= 0.362906, Training Accuracy= 0.91406
Iter 140, Minibatch Loss= 0.372900, Training Accuracy= 0.92969
Iter 150, Minibatch Loss= 0.324498, Training Accuracy= 0.91797
Iter 160, Minibatch Loss= 0.349533, Training Accuracy= 0.93750
Iter 170, Minibatch Loss= 0.398226, Training Accuracy= 0.90625
Iter 180, Minibatch Loss= 0.323373, Training Accuracy= 0.93750
Iter 190, Minibatch Loss= 0.555020, Training Accuracy= 0.91797
Optimization Finished!
Testing Accuracy: 0.945312
Listing 5-3b.Basic Implementation of DBN
正如我们从前面的输出中看到的,使用来自 RBM 的预训练权重作为分类网络的初始权重,我们可以在 MNIST 测试数据集上运行 200 个批次,获得大约 95%的良好准确性。鉴于网络没有任何卷积层,这一点令人印象深刻。
自编码器
自编码器是无监督的人工神经网络,用于生成输入数据的有意义的内部表示。自编码器网络通常由三层组成——输入层、隐藏层和输出层。输入层和隐藏层的组合充当编码器,而隐藏层和输出层的组合充当解码器。编码器尝试在隐藏层将输入表示为有意义的表示,而解码器在输出层将输入重构回其原始维度。通常,作为训练过程的一部分,重构输入和原始输入之间的一些成本函数被最小化。

图 5-13。
Architecture of a basic auto-encoder
图 5-13 表示一个基本的自编码器,带有一个隐藏层和输入输出层。输入
而隐藏层
。输出 y 被选择为等于 x,使得重构的输入
之间的误差可以被最小化,从而在隐藏层中获得输入的有意义的表示。出于通用性的目的,让我们取
![$$ x={\left[{x}_1{x}_2\ {x}_3..{x}_n\right]}^T\in {\mathrm{\mathbb{R}}}^{n\times 1} $$](A448418_1_En_5_Chapter_Equbx.gif)
![$$ h={\left[{h}_1{h}_2\ {h}_3..{h}_d\right]}^T\in {\mathrm{\mathbb{R}}}^{d\times 1} $$](A448418_1_En_5_Chapter_Equby.gif)
![$$ y=x={\left[{y}_1{y}_2\ {y}_3..{y}_n\right]}^T\in {\mathrm{\mathbb{R}}}^{n\times 1} $$](A448418_1_En_5_Chapter_Equbz.gif)
假设从 x 到 h 的权重由权重矩阵
表示,隐藏单元的偏差由
表示。
类似地,假设从 h 到 y 的权重由权重矩阵
表示,输出单元的偏差由
表示。
隐藏单元的输出可以表示为

,其中 f 1 是隐藏层的逐元素激活函数。根据其用途,激活函数可以是线性的、ReLU 的、sigmoid 的等等。
同样,输出层的输出可以表示为

如果输入特征具有连续的性质,可以最小化基于最小平方的成本函数如下,以基于训练数据导出模型权重和偏差:

其中
是重构的输出向量和原始输入向量之间的欧几里德或 l 2 范数距离,m 是模型被训练的数据点的数量。
如果我们用向量
表示模型的所有参数,那么成本函数 C 可以相对于模型θ的所有参数最小化,以导出模型

基于梯度下降的模型学习规则为

,其中ϵ为学习速率,t 代表迭代次数,
为代价函数在
处相对于θ的梯度。
现在,让我们考虑如下几种情况:
- 当隐藏层的维度小于输入层的维度时,即
,其中 d 是隐藏层,n 是输入层的维度,则自编码器作为数据压缩网络工作,将数据从高维空间投影到由隐藏层给出的低维空间。这是一种有损数据压缩技术。它还可以用于降低输入信号中的噪声。 - 当
和所有的激活函数都是线性的,那么网络学习进行线性 PCA(主成分分析)。 - 当
和激活函数是线性的时,网络可以学习一个身份函数,这可能没有任何用处。然而,如果成本函数被正则化以产生稀疏隐藏表示,则网络仍然可以学习数据的有趣表示。 - 输入数据的复杂非线性表示可以通过在网络中有更多的隐藏层和使激活函数非线性来学习。这种模型的示意图如图 5-14 所示。当采用多个隐藏层时,必须采用非线性激活函数来学习数据的非线性表示,因为几层线性激活等效于单个线性激活层。
用于监督学习的通过自编码器的特征学习

图 5-14。
Auto-encoder with multiple hidden layers
当我们处理多个隐藏层时,如图 5-14 所示,并且在神经单元中具有非线性激活函数,那么隐藏层学习输入数据的变量之间的非线性关系。如果我们正在处理两个类别的分类相关问题,其中输入数据由
表示,我们可以通过如图 5-14 所示训练自编码器,然后使用第二个隐藏层向量
的输出,来学习有趣的非线性特征。h (2) 给出的这种新的非线性特征表示可以作为分类模型的输入,如图 5-15 所示。当我们感兴趣的输出的隐藏层的维数小于输入的维数时,它相当于非线性版本的主成分分析,其中我们只是消耗重要的非线性特征,而将其余的作为噪声丢弃。

图 5-15。
Classifier with features learned from auto-encoder
如图 5-16 所示,通过组合两个网络,整个网络可以组合成一个单一网络,用于测试时的类别预测。从自编码器中,只需要考虑产生输出 h (2) 的直到第二隐藏层的网络部分,然后它需要与分类网络结合,如图 5-15 所示。

图 5-16。
Combined classification network for prediction of classes
人们可能会问一个显而易见的问题:为什么线性 PCA 不能满足自编码器在这个例子中执行的任务?线性 PCA 或主成分分析仅负责捕捉输入变量之间的线性关系,并试图将输入变量分解成彼此不线性依赖的成分。这些成分称为主成分,与原始输入变量不同,它们彼此不相关。然而,输入变量并不总是以导致线性相关的线性方式相关。输入变量可能以复杂得多的非线性方式相关,并且数据中的这种非线性结构只能通过自编码器中的非线性隐藏单元来捕获。
库尔贝克-莱布勒散度
KL 散度测量两个随机伯努利变量之间的差异或散度。如果两个随机伯努利变量 X 和 Y 分别有ρ 1 和ρ 2 的均值,那么变量 X 和 Y 之间的 KL 散度由

给出
从前面的表达式我们可以看出
时 KL 散度为 0;即当两种分布相同时。当
时,KL 散度随着均值的不同而单调增加。如果ρ 1 选择为 0.2,那么 KL 散度与ρ 2 的关系曲线如图 5-17 所示。

图 5-17。
KL divergence plot for mean 
如我们所见,KL 散度在
处最小,在
两侧单调增加。在下一节中,我们将使用 KL 散度在稀疏自编码器的隐藏层中引入稀疏性。
稀疏自编码器
正如我们前面所讨论的,自编码器的目的是学习输入数据的有趣的隐藏结构,或者更具体地说,是学习输入中不同变量之间的有趣关系。导出这些隐藏结构的最常见方式是使隐藏层的维度小于输入数据的维度,从而自编码器被迫学习输入数据的压缩表示。这个压缩的表示被强制重建原始数据,因此压缩的表示应该具有足够的信息来足够好地捕捉输入。只有当数据中存在输入变量之间的相关性和其他非线性关联形式的冗余时,这种压缩表示才能够有效地捕获输入数据。如果输入要素相对独立,那么这种压缩将无法很好地表示原始数据。因此,为了让自编码器给出输入数据的有趣的低维表示,数据中应该有足够的结构,其形式为输入变量之间的相关性和其他非线性关联。
我们之前提到的一件事是,当隐藏层单元的数量大于输入的维数时,在将对应于额外隐藏层的权重设置为零之后,自编码器很可能会学习恒等式变换。事实上,当输入层和隐藏层的数量相同时,连接输入层和隐藏层的权重矩阵的最优解就是单位矩阵。然而,即使当隐藏单元的数量大于输入的维数时,只要提供一些约束,自编码器仍然可以学习数据中有趣的结构。一个这样的约束是将隐藏层输出限制为稀疏的,使得隐藏层单元中的那些激活平均接近于零。我们可以通过在基于 KL 散度的成本函数中添加正则化项来实现这种稀疏性。这里,ρ 1 将非常接近于零,并且隐藏单元中所有训练样本的平均激活将充当该隐藏单元的ρ 2 。通常,ρ 1 被选择为非常小,大约为 0.04,因此如果每个隐藏单元中的平均激活不接近 0.04,那么代价函数将被扣分。
设
为输入
的隐藏层 sigmoid 激活,其中
。此外,将输入连接到隐藏层的权重由
给出,将隐藏层连接到输出层的权重由
给出。如果隐藏层和输出层的偏置向量分别由 b 和 b’给出,则以下关系成立:


其中 h (k) 和
是隐藏层输出向量和第 k 个输入训练数据点的重构输入向量。相对于模型参数(即 W,W′,b,b′)要最小化的成本函数由

给出,其中
是所有训练样本上隐藏层的第 j 个单元中的平均激活,可以表示如下。同样,h j (k) 表示第 k 个训练样本在单元 j 处的隐藏层激活。


通常,ρ被选择为 0.04 至 0.05,使得模型学习产生非常接近 0.04 的平均隐藏层单元激活,并且在该过程中,模型学习隐藏层中输入数据的稀疏表示。
稀疏自编码器在计算机视觉中用于学习低级特征,这些特征表示自然图像中不同位置和方向的不同种类的边缘。隐藏层输出给出了这些低级特征中的每一个的权重,这些权重可以被组合以重建图像。如果
图像被处理为 100 维输入,并且如果有 200 个隐藏单元,那么将输入连接到隐藏单元的权重-即,W 或隐藏单元到输出重建层 W '-将包括 200 个大小为 100 的图像(
)。可以显示这些图像来查看它们所代表的特征的性质。稀疏编码在辅以 PCA 白化时效果很好,我们将在本章稍后简要讨论。
TensorFlow 中稀疏自编码器的实现
在这一节中,我们将实现一个稀疏自编码器,它的隐藏单元比输入维数多。该实现的数据集是 MNIST 数据集。稀疏性已经通过 KL 散度被引入到实现的网络中。此外,编码器和解码器的权重用于 L2 正则化,以确保在追求稀疏性时,这些权重不会以不期望的方式调整自己。如前所述,自编码器和解码器权重表示过度表示的基础,并且这些基础中的每一个都试图学习图像的一些低级特征表示。编码器和解码器被认为是相同的。组成低级特征图像的这些权重已经被显示以突出它们所代表的内容。清单 5-4 中概述了详细的实现。

图 5-19。
Display of a few encoder/decoder weights as images

图 5-18。
Display of the original image followed by the reconstructed image
## Import the required libraries and data
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import time
# Import MNIST data
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
# Parameters for training the Network
learning_rate = 0.001
training_epochs = 200
batch_size = 1000
display_step = 1
examples_to_show = 10
# Network Parameters
# Hidden units are more than the input dimensionality since the intention
# is to learn sparse representation of hidden units
n_hidden_1 = 32*32
n_input = 784 # MNIST data input (img shape: 28*28)
X = tf.placeholder("float", [None, n_input])
weights = {
'encoder_h1': tf.Variable(tf.random_normal([n_input, n_hidden_1])),
}
biases = {
'encoder_b1': tf.Variable(tf.random_normal([n_hidden_1])),
'decoder_b1': tf.Variable(tf.random_normal([n_input])),
}
# Building the encoder
def encoder(x):
# Encoder Hidden layer with sigmoid activation #1
layer_1 = tf.nn.sigmoid(tf.add(tf.matmul(x, weights['encoder_h1']),
biases['encoder_b1']))
return layer_1
# Building the decoder
def decoder(x):
layer_1 = tf.nn.sigmoid(tf.add(tf.matmul(x, tf.transpose(weights['decoder_h1'])),
biases['decoder_b1']))
return layer_1
## Define the log-based function to be used in computing the KL Divergence
def log_func(x1, x2):
return tf.multiply(x1, tf.log(tf.div(x1,x2)))
def KL_Div(rho, rho_hat):
inv_rho = tf.subtract(tf.constant(1.), rho)
inv_rhohat = tf.subtract(tf.constant(1.), rho_hat)
log_rho = logfunc(rho,rho_hat) + log_func(inv_rho, inv_rhohat)
return log_rho
# Model definition
encoder_op = encoder(X)
decoder_op = decoder(encoder_op)
rho_hat = tf.reduce_mean(encoder_op,1)
# Reconstructed output
y_pred = decoder_op
# Targets in the input data.
y_true = X
# Define the TensorFlow Ops for loss and optimizer, minimize the combined error
# Squared Reconstruction error
cost_m = tf.reduce_mean(tf.pow(y_true - y_pred, 2))
# KL Divergence Regularization to introduce sparsity
cost_sparse = 0.001*tf.reduce_sum(KL_Div(0.2,rho_hat))
# L2 Regularization of weights to keep the network stable
cost_reg = 0.0001* (tf.nn.l2_loss(weights['decoder_h1']) + tf.nn.l2_loss(weights['encoder_h1']))
# Add up the costs
cost = tf.add(cost_reg,tf.add(cost_m,cost_sparse))
optimizer = tf.train.RMSPropOptimizer(learning_rate).minimize(cost)
# Initializing the variables
init = tf.global_variables_initializer()
# Launch the Session graph
start_time = time.time()
with tf.Session() as sess:
sess.run(init)
total_batch = int(mnist.train.num_examples/batch_size)
for epoch in range(training_epochs):
for i in range(total_batch):
batch_xs, batch_ys = mnist.train.next_batch(batch_size)
_, c = sess.run([optimizer, cost], feed_dict={X: batch_xs})
if epoch % display_step == 0:
print("Epoch:", '%04d' % (epoch+1),
"cost=", "{:.9f}".format(c))
print("Optimization Finished!")
# Applying encode and decode over test set
encode_decode = sess.run(
y_pred, feed_dict={X: mnist.test.images[:10]})
# Compare original images with their reconstructions
f, a = plt.subplots(2, 10, figsize=(10, 2))
for i in range(10):
a[0][i].imshow(np.reshape(mnist.test.images[i], (28, 28)))
a[1][i].imshow(np.reshape(encode_decode[i], (28, 28)))
# Store the Decoder and Encoder Weights
dec = sess.run(weights['decoder_h1'])
enc = sess.run(weights['encoder_h1'])
end_time = time.time()
print('elapsed time:',end_time - start_time)
-- Output --
Listing 5-4.
图 5-18 显示了稀疏编码器对图像的重建,而图 5-19 显示了图像形式的解码器权重。隐藏单元层对应的权重是图 5-19 中显示的图像。这给了你一些稀疏编码器正在学习的特性的概念。重建的最终图像是这些图像的线性组合,隐藏层激活充当线性权重。本质上,这些图像中的每一个都在检测手写数字的手写笔画形式的低级特征。就线性代数而言,这些图像形成了表示重建图像的基础。
去噪自编码器
去噪自编码器像标准自编码器一样工作,在隐藏层中进行非线性激活,唯一的区别是,代替原始输入 x 的是 x 的噪声版本,比如说
,被馈送到网络。在计算重建误差时,将输出层的重建图像与实际输入 x 进行比较。其思想是,从噪声数据中学习到的隐藏结构足够丰富,可以重构原始数据。因此,这种形式的自编码器可以用于减少数据中的噪声,因为它从隐藏层学习数据的鲁棒表示。例如,如果图像由于某种失真而变得模糊,那么可以使用去噪自编码器来消除模糊。通过引入随机噪声添加单元,可以将自编码器转换成去噪自编码器。
对于图像,去噪自编码器可以将隐藏层作为卷积层,而不是标准的神经单元。这确保了在定义自编码器网络时不会危及图像的拓扑结构。
TensorFlow 中去噪自编码器的实现
在这一节中,我们将通过一个去噪自编码器的实现来学习从输入图像中去除噪声。输入图像中引入了两种噪声,即高斯噪声和椒盐噪声,所实现的去噪自编码器可以有效地去除这两种噪声。详细实现如清单 5-5 所示。

图 5-21。
Reconstructed Images (without Gaussian Noise) generated by the Denoising Auto Encoder

图 5-20。
Images with Gaussian noise
# Import the required library
import tensorflow.contrib.layers as lays
import numpy as np
from skimage import transform
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
# Define the Network with Encoder and Decoder
def autoencoder(inputs):
# encoder
net = lays.conv2d(inputs, 32, [5, 5], stride=2, padding='SAME')
net = lays.conv2d(net, 16, [5, 5], stride=2, padding='SAME')
net = lays.conv2d(net, 8, [5, 5], stride=4, padding='SAME')
# decoder
net = lays.conv2d_transpose(net, 16, [5, 5], stride=4, padding='SAME')
net = lays.conv2d_transpose(net, 32, [5, 5], stride=2, padding='SAME')
net = lays.conv2d_transpose(net, 1, [5, 5], stride=2, padding='SAME', activation_fn=tf.nn.tanh)
return net
def resize_batch(imgs):
# Function to resize the image to 32x32 so that the dimensionality can be reduced in
# multiples of 2
imgs = imgs.reshape((-1, 28, 28, 1))
resized_imgs = np.zeros((imgs.shape[0], 32, 32, 1))
for i in range(imgs.shape[0]):
resized_imgs[i, ..., 0] = transform.resize(imgs[i, ..., 0], (32, 32))
return resized_imgs
## Function to introduce Gaussian Noise
def noisy(image):
row,col= image.shape
mean = 0
var = 0.1
sigma = var**0.5
gauss = np.random.normal(mean,sigma,(row,col))
gauss = gauss.reshape(row,col)
noisy = image + gauss
return noisy
## Function to define Salt and Pepper Noise
def s_p(image):
row,col = image.shape
s_vs_p = 0.5
amount = 0.05
out = np.copy(image)
# Salt mode
num_salt = np.ceil(amount * image.size * s_vs_p)
coords = [np.random.randint(0, i - 1, int(num_salt)) for i in image.shape]
out[coords] = 1
# Pepper mode
num_pepper = np.ceil(amount* image.size * (1\. - s_vs_p))
coords = [np.random.randint(0, i - 1, int(num_pepper)) for i in image.shape]
out[coords] = 0
return out
# Defining the ops
# input to which the reconstucted signal is compared to
a_e_inputs = tf.placeholder(tf.float32, (None, 32, 32, 1))
# input to the network (MNIST images)
a_e_inputs_noise = tf.placeholder(tf.float32, (None, 32, 32, 1))
a_e_outputs = autoencoder(a_e_inputs_noise) # create the Auto-encoder network
# calculate the loss and optimize the network
loss = tf.reduce_mean(tf.square(a_e_outputs - a_e_inputs)) # claculate the mean square error loss
train_op = tf.train.AdamOptimizer(learning_rate=0.001).minimize(loss)
# initialize the network
init = tf.global_variables_initializer()
# Invoking the TensorFlow Graph for Gaussian Noise reduction auto-encoder training and validation
batch_size = 1000 # Number of samples in each batch
epoch_num = 10 # Number of epochs to train the network
lr = 0.001 # Learning rate
# read MNIST dataset
mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
# calculate the number of batches per epoch
batch_per_ep = mnist.train.num_examples // batch_size
with tf.Session() as sess:
sess.run(init)
for epoch in range(epoch_num):
for batch_num in range(batch_per_ep):
batch_img, batch_label = mnist.train.next_batch(batch_size) # read a batch
batch_img = batch_img.reshape((-1, 28, 28, 1)) # reshape each sample to an (28, 28) image
batch_img = resize_batch(batch_img) # reshape the images to (32, 32)
## Introduce noise in the input images
image_arr = []
for i in xrange(len(batch_img)):
img = batch_img[i,:,:,0]
img = noisy(img)
image_arr.append(img)
image_arr = np.array(image_arr)
image_arr = image_arr.reshape(-1,32,32,1)
_, c = sess.run([train_op, loss], feed_dict={a_e_inputs_noise:image_arr,a_e_inputs: batch_img})
print('Epoch: {} - cost= {:.5f}'.format((ep + 1), c))
# test the trained network
batch_img, batch_label = mnist.test.next_batch(50)
batch_img = resize_batch(batch_img)
image_arr = []
for i in xrange(50):
img = batch_img[i,:,:,0]
img = noisy(img)
image_arr.append(img)
image_arr = np.array(image_arr)
image_arr = image_arr.reshape(-1,32,32,1)
reconst_img = sess.run([ae_outputs], feed_dict={ae_inputs_noise: image_arr})[0]
# plot the reconstructed images and the corresponding Noisy images
plt.figure(1)
plt.title('Input Noisy Images')
for i in range(50):
plt.subplot(5, 10, i+1)
plt.imshow(image_arr[i, ..., 0], cmap='gray')
plt.figure(2)
plt.title('Re-constructed Images')
for i in range(50):
plt.subplot(5, 10, i+1)
plt.imshow(reconst_img[i, ..., 0], cmap='gray')
plt.show()
--Output--
Listing 5-5.Denoising Auto-Encoder Using Convolution and Deconvolution Layers
从图 5-20 和图 5-21 可以看出,高斯噪声已经被去噪自编码器去除。

图 5-23。
Reconstructed images without the salt and pepper noise generated by the denoising auto-encoder

图 5-22。
Salt and pepper noisy images
# Invoking the TensorFlow Graph for Salt and Pepper Noise reduction auto-encoder training and validation
batch_size = 1000 # Number of samples in each batch
epoch_num = 10 # Number of epochs to train the network
lr = 0.001 # Learning rate
# read MNIST dataset
mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
# calculate the number of batches per epoch
batch_per_ep = mnist.train.num_examples // batch_size
with tf.Session() as sess:
sess.run(init)
for epoch in range(epoch_num):
for batch_num in range(batch_per_ep):
batch_img, batch_label = mnist.train.next_batch(batch_size) # read a batch
batch_img = batch_img.reshape((-1, 28, 28, 1)) # reshape each sample to an (28, 28) image
batch_img = resize_batch(batch_img) # reshape the images to (32, 32)
## Introduce noise in the input images
image_arr = []
for i in xrange(len(batch_img)):
img = batch_img[i,:,:,0]
img = noisy(img)
image_arr.append(img)
image_arr = np.array(image_arr)
image_arr = image_arr.reshape(-1,32,32,1)
_, c = sess.run([train_op, loss], feed_dict={a_e_inputs_noise:image_arr,a_e_inputs: batch_img})
print('Epoch: {} - cost= {:.5f}'.format((ep + 1), c))
# test the trained network
batch_img, batch_label = mnist.test.next_batch(50)
batch_img = resize_batch(batch_img)
image_arr = []
for i in xrange(50):
img = batch_img[i,:,:,0]
img = noisy(img)
image_arr.append(img)
image_arr = np.array(image_arr)
image_arr = image_arr.reshape(-1,32,32,1)
reconst_img = sess.run([ae_outputs], feed_dict={ae_inputs_noise: image_arr})[0]
# plot the reconstructed images and the corresponding Noisy images
plt.figure(1)
plt.title('Input Noisy Images')
for i in range(50):
plt.subplot(5, 10, i+1)
plt.imshow(image_arr[i, ..., 0], cmap='gray')
plt.figure(2)
plt.title('Re-constructed Images')
for i in range(50):
plt.subplot(5, 10, i+1)
plt.imshow(reconst_img[i, ..., 0], cmap='gray')
plt.show()
--Output--
从图 5-22 和图 5-23 可以明显看出,去噪自编码器在去除椒盐噪声方面做得很好。请注意,自编码器是单独训练的,一次用于处理高斯噪声,一次用于处理椒盐噪声。
PCA 和 ZCA 美白
通常,图像包含其亮度在图像的任何邻域中高度相关的像素,因此这种相关性对于学习算法来说是高度冗余的。这些相邻像素之间相关性形式的依赖性通常对任何算法都没什么用。因此,消除这种双向相关性是有意义的,这样算法就更加强调高阶相关性。类似地,在图像是自然图像的情况下,图像的平均强度可能对学习算法没有任何用处。因此,去除图像的平均强度是有意义的。请注意,我们不是减去平均每个像素的位置,而是每个图像的像素强度的平均值。这种均值归一化不同于我们在机器学习中使用的其他均值归一化,在机器学习中,我们减去通过训练集计算的每个特征的均值。回到美白的概念,美白的优势有两方面:
- 移除数据中要素之间的相关性
- 使沿每个特征方向的方差相等
PCA 和 ZCA 白化是通常用于在通过人工神经网络处理图像之前预处理图像的两种技术。这些技术几乎一样,有细微的区别。首先说明 PCA 增白所涉及的步骤,然后是 ZCA。
-
Remove the mean pixel intensity from each image separately. So, if a 2D image is converted into a vector, one can subtract the mean of the elements in the image vector from itself. If each image is represented by the vector
, where i represents the ith image in the training set, then the mean normalized image for x (i) is given by![$$ {x}{(i)}={x}{(i)}-\frac{1}{n}\sum \limits_{j=1}n{x}_j{(i)} $$]()
-
Once we have the mean normalized images we can compute the covariance matrix as follows:
![$$ C=\frac{1}{m}\sum \limits_{i=1}m{x}{(i)}{x{(i)}}T $$]()
-
Next, we need to decompose the covariance matrix through singular value decomposition (SVD) as follows:
![$$ C= UD{U}^T $$]()
In general, SVD decomposes as
, but since C is a symmetric matrix,
in this case. U gives the Eigen vectors of the covariance matrix. The Eigen vectors are aligned in a column-wise fashion in U. The variances of the data along the direction of the Eigen vectors are given by the Eigen values housed along the diagonals of D, while the rest of the entries in D are zero since D is the covariance matrix for the uncorrelated directions given by the Eigen vectors. -
In PCA whitening , we project the data along the Eigen vectors, or one may say principal components, and then divide the projection value in each direction by the square root of the Eigen value—i.e., the standard deviation along that direction on which the data is projected. So, the PCA whitening transformation is as follows:
![$$ T={D}{-\frac{1}{2}}{U}T $$]()
Once this transformation is applied to the data, the transformed data has zero correlation and unit variance along the newly transformed components. The transformed data for original mean-corrected image x (i) is
![$$ {x{(i)}}_{PW}=T{x}{(i)} $$]()
PCA 白化的问题是,尽管它去相关数据并使新的特征方差一致,但是特征不再在原始空间中,而是在变换的旋转空间中。这使得诸如图像之类的对象的结构在它们的空间方位方面丢失了大量信息,因为在变换的特征空间中,每个特征都是所有特征的线性组合。对于利用图像空间结构的算法,如卷积神经网络,这不是一件好事。因此,我们需要某种方法来白化数据,使得数据是去相关的,并且沿着其特征具有单位方差,但是特征仍然在原始特征空间中,而不在某个变换的旋转特征空间中。提供所有这些相关特性的变换称为 ZCA 变换。数学上,任何正交矩阵 R(其列向量彼此正交)乘以 PCA 白化变换 T 都会产生另一个白化变换。如果选择了
,

的变换称为 ZCA 变换。ZCA 变换的优点是图像数据仍然驻留在相同的像素特征空间中,因此,与 PCA 白化不同,原始像素不会因为新特征的创建而变得模糊。同时,数据被白化,即去相关,并且每个特征的单位方差被白化。单位方差和解相关特征可以帮助若干机器学习或深度学习算法实现更快的收敛。同时,由于特征仍然在原始空间中,它们保持它们的拓扑或空间方向,并且诸如利用图像的空间结构的卷积神经网络的算法可以利用该信息。

图 5-25。
ZCA whitening illustration

图 5-24。
PCA whitening illustration
PCA 增白和 ZCA 增白的主要区别如图 5-24 和图 5-25 所示。如我们所见,在两种情况下,2D 相关数据都被转换成不相关的新数据。然而,有一个主要的区别。而在 PCA 白化中,基于 p 1 和 p 2 给出的主成分,新轴已经从原始轴改变,轴保持与 ZCA 白化的原始轴相同。p 1 和 p 2 是数据的协方差矩阵的特征向量。此外,我们看到标记 AB 的方向在 PCA 白化的新轴上发生了变化,而在 ZCA 白化中保持不变。在这两种情况下,想法都是去除输入变量之间不太有用的双向协方差,以便模型可以专注于学习高阶相关性。
摘要
在这一章中,我们讨论了深度学习中最流行的无监督技术,即受限玻尔兹曼机和自编码器。此外,我们还讨论了使用这些方法的不同应用以及与这些算法相关的训练过程。最后,我们以 PCA 和 ZCA 白化技术结束,这是在几种有监督的深度学习方法中使用的相关预处理技术。到本章结束时,我们已经触及了深度学习的所有核心方法。鉴于到目前为止接触到的方法和数学,深度学习中的其他即兴方法可以很容易理解和实现。
在下一章也是最后一章,我们将讨论最近流行的几种即兴深度学习网络,如生成对抗网络、R-CNN 等,并触及轻松将 TensorFlow 应用程序投入生产的各个方面。
六、高级神经网络
在这一章中,我们将看看最近正在使用的深度学习中的一些高级概念和模型。图像分割和对象定位和检测是最近获得很多重视的一些关键领域。图像分割在通过处理医学图像来检测疾病和异常中起着至关重要的作用。同时,在诸如航空、制造和其他领域的工业中,检测诸如机器中的裂纹或其他不想要的状况的异常同样至关重要。类似地,夜空的图像可以被分割,以探测到以前未知的星系、恒星和行星。目标检测和定位在需要持续自动监控活动的地方具有深远的用途,例如在购物中心、本地商店、工厂等等。此外,它还可用于计算感兴趣区域内的物体和人员数量,并估计各种密度,例如各种信号下的交通状况。
我们将通过一些传统的图像分割方法来开始这一章,这样我们就可以理解神经网络与它们的传统对应物有什么不同。然后,我们将研究对象检测和定位技术,随后是生成对抗网络,该网络最近因其作为生成模型来创建合成数据的用途和潜力而受到广泛欢迎。这种合成数据可用于训练和推断,以防没有太多数据可用或数据获取昂贵。或者,创成式模型可用于从一个领域到另一个领域的风格转换。最后,我们以一些指南结束,说明如何使用 TensorFlow 的服务功能在生产中轻松实施 TensorFlow 模型。
图象分割法
图像分割是一项计算机视觉任务,涉及将图像分割成相关的片段,例如同一片段中具有一些共同属性的像素。属性可以因域和任务而异,主要属性是像素强度、纹理和颜色。在本节中,我们将介绍一些基本的分割技术,如基于像素强度直方图的阈值方法、分水岭阈值技术等,以便在开始基于深度学习的图像分割方法之前,获得一些关于图像分割的见解。
基于像素亮度直方图的二值阈值法
通常在一幅图像中只有两个重要的感兴趣区域——物体和背景。在这种情况下,像素强度的直方图将表示双峰的概率分布;即在两个像素的强度值附近具有高密度。通过选择阈值像素并将低于阈值的所有像素强度设置为 255,将高于阈值的所有像素强度设置为 00,将很容易分割对象和背景。这项活动将确保我们有一个由白色和黑色代表的背景和一个对象,不一定是这个顺序。如果图像被表示为 I(x,y)并且基于像素强度的直方图选择阈值 t,则新的分割图像 I’(x,y)可以被表示为

当双峰直方图没有被其间的零密度区域明显分开时,选择阈值 t 的好策略是取双峰区域达到峰值的像素强度的平均值。如果那些峰值强度由 p 1 和 p 2 表示,那么阈值 t 可以被选择为

或者,可以使用直方图密度最小的 p 1 和 p 2 之间的像素强度作为阈值像素强度。如果直方图密度函数由 H(p)表示,其中
表示像素强度,则
![$$ t=\underset{p\in \left[{p}_{1,}{p}_2\right]}{\underbrace{Arg\ \mathit{\operatorname{Min}}}}H(p) $$](A448418_1_En_6_Chapter_Equc.gif)
基于像素强度的直方图,这种二进制阈值的思想可以扩展到多阈值。
大津法
Otsu 的方法通过最大化图像不同部分之间的方差来确定阈值。如果通过 Otsu 方法使用二进制阈值,以下是要遵循的步骤:
-
Compute the probability of each pixel intensity in the image. Given that N pixel intensities are possible, the normalized histogram would give us the probability distribution of the image.
![$$ P(i)=\frac{count(i)}{M}\kern1.5em \forall i\in \left{0,1,2,\dots, N-1\right} $$]()
-
If the image has two segments C 1 and C 2 based on the threshold t, then the set of pixels {0, 2 …. t} belong to C 1 while the set of pixels
belong to C 2. The variance between the two segments is determined by the sum of the square deviation of the mean of the clusters with respect to the global mean. The square deviations are weighted by the probability of each cluster.![$$ \mathit{\operatorname{var}}\left({C}_{1,}{C}_2\right)=\kern0.5em P\left({C}_{1,}\right){\left({u}_1-u\right)}²+P\left({C}_2\right){\left({u}_2-u\right)}² $$]()
where u 1, u 2 are the means of cluster 1 and cluster 2 while u is the overall global mean.
![$$ {u}_1=\sum \limits_{i=0}^tP(i)i{u}_2=\sum \limits_{i=t+1}^{L-1}P(i)i\kern2.25em u=\sum \limits_{i=0}^{L-1}P(i)i $$]()
The probability of each of the segments is the number of pixels in the image belonging to that class. The probability of segment C 1 is proportional to the number of pixels that have intensities less than or equal to the threshold intensity t, while that of segment C 2 is proportional to the number of pixels with intensities greater than threshold t. Hence,
![$$ P\left({C}_1\right)=\sum \limits_{i=0}^tP(i)P\left({C}_2\right)=\sum \limits_{i=t+1}^{L-1}P(i) $$]()
-
If we observe the expressions for u 1,u 2, P(C 1), and P(C 2), each of them is a function of the threshold t while the overall mean u is constant given an image. Hence, the between-segment variance var(C 1, C 2) is a function of the threshold pixel intensity t. The threshold
that maximizes the variance would provide us with the optimal threshold to use for segmentation using Otsu’s method:![$$ \widehat{t}=\underset{t}{\underbrace{Arg\ \mathit{\operatorname{Max}}}}\ \mathit{\operatorname{var}}\left({C}_{1,}{C}_2\right) $$]()
Instead of computing a derivative and then setting it to zero to obtain
, one can evaluate the var(C 1, C 2) at all values of
and then choose the
at which the var(C 1, C 2) is maximum.
Otsu 的方法也可以扩展到多个分段,其中需要确定对应于图像的 k 个分段的
阈值,而不是一个阈值。
清单 6-1 中说明了刚刚说明的两种方法的逻辑,即基于像素强度直方图的二进制阈值以及 Otsu 方法,以供参考。为了便于解释,没有使用图像处理包来实现这些算法,而是使用了核心逻辑。此外,需要注意的一点是,这些分割过程通常适用于灰度图像,或者在每个颜色通道执行分割的情况下。

图 6-2。
Otsu’s method of thresholding

图 6-1。
Binary thresholding method based on histogram of pixel intensities
## Binary thresholding Method Based on Histogram of Pixel Intensities
import cv2
import matplotlib.pyplot as plt
#%matplotlib inline
import numpy as np
img = cv2.imread("coins.jpg")
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
plt.imshow(gray,cmap='gray')
row,col = np.shape(gray)
gray_flat = np.reshape(gray,(row*col,1))[:,0]
ax = plt.subplot(222)
ax.hist(gray_flat,color='gray')
gray_const = []
## 150 pixel intensity seems a good threshold to choose since the density is minimum
## round 150
for i in xrange(len(gray_flat)):
if gray_flat[i] < 150 :
gray_const.append(255)
else:
gray_const.append(0)
gray_const = np.reshape(np.array(gray_const),(row,col))
bx = plt.subplot(333)
bx.imshow(gray_const,cmap='gray')
## Otsu's thresholding Method
img = cv2.imread("otsu.jpg")
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
plt.imshow(gray,cmap='gray')
row,col = np.shape(gray)
hist_dist = 256*[0]
## Compute the frequency count of each of the pixels in the image
for i in xrange(row):
for j in xrange(col):
hist_dist[gray[i,j]] += 1
# Normalize the frequencies to produce probabilities
hist_dist = [c/float(row*col) for c in hist_dist]
plt.plot(hist_dist)
## Compute the between segment variance
def var_c1_c2_func(hist_dist,t):
u1,u2,p1,p2,u = 0,0,0,0,0
for i in xrange(t+1):
u1 += hist_dist[i]*i
p1 += hist_dist[i]
for i in xrange(t+1,256):
u2 += hist_dist[i]*i
p2 += hist_dist[i]
for i in xrange(256):
u += hist_dist[i]*i
var_c1_c2 = p1*(u1 - u)**2 + p2*(u2 - u)**2
return var_c1_c2
## Iteratively run through all the pixel intensities from 0 to 255 and choose the one that
## maximizes the variance
variance_list = []
for i in xrange(256):
var_c1_c2 = var_c1_c2_func(hist_dist,i)
variance_list.append(var_c1_c2)
## Fetch the threshold that maximizes the variance
t_hat = np.argmax(variance_list)
## Compute the segmented image based on the threshold t_hat
gray_recons = np.zeros((row,col))
for i in xrange(row):
for j in xrange(col):
if gray[i,j] <= t_hat :
gray_recons[i,j] = 255
else:
gray_recons[i,j] = 0
plt.imshow(gray_recons,cmap='gray')
--output --
Listing 6-1.Python Implementation of Binary Thresholding Method Based on Histogram of Pixel Intensities and Otsu’s Method
在图 6-1 中,硬币的原始灰度图像已经根据像素强度直方图进行了二值化处理,以将物体(即硬币)从背景中分离出来。基于像素强度的直方图,选择像素强度 150 作为阈值像素强度。低于 150 的像素亮度被设置为 255 来表示物体,而高于 150 的像素亮度被设置为 00 来表示背景。
图 6-2 说明了 Otsu 对图像进行阈值处理的方法,以产生由黑色和白色确定的两个部分。黑色代表背景,而白色代表房子。图像的最佳阈值是像素强度 143。
图像分割的分水岭算法
分水岭算法旨在分割在像素强度的局部最小值周围拓扑放置的局部区域。如果灰度图像像素强度值被认为是其水平和垂直坐标的函数,则该算法试图找到称为吸引盆地或集水盆地的局部最小值周围的区域。一旦识别出这些盆地,算法就试图通过沿着高峰或山脊构建分隔带或分水岭来将它们分开。为了更好地理解这个方法,让我们用一个简单的例子来展示这个算法,如图 6-3 所示。

图 6-3。
Watershed algorithm illustration
如果我们开始在集水池中注水,其最小值为 B,水将继续填充到水位 1,此时额外的一滴水有可能溢出到 a 处的集水池。为了防止水溢出,需要在 E 处建造一个大坝或分水岭。一旦我们在 E 处建造了一个分水岭, 我们可以继续在集水池 B 中注水,直到水位 2,在这一点上,额外的一滴水有可能溢出到集水池 C。为了防止水溢出到 C,需要在 d 处建造一个分水岭。使用这一逻辑,我们可以继续建造分水岭来分隔这些集水池。 这是分水岭算法背后的主要思想。这里,该函数是单变量的,而在灰度图像的情况下,表示像素强度的函数将是两个变量的函数:垂直坐标和水平坐标。
当目标之间存在重叠时,分水岭算法在检测目标时特别有用。阈值技术不能确定明显的对象边界。我们将在清单 6-2 中通过对包含重叠硬币的图像应用分水岭技术来说明这一点。

图 6-4。
Illustration of Watershed algorithm for image segmentation)
import numpy as np
import cv2
import matplotlib.pyplot as plt
from scipy import ndimage
from skimage.feature import peak_local_max
from skimage.morphology import watershed
## Load the coins image
im = cv2.imread("coins.jpg")
## Convert the image to grayscale
imgray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
plt.imshow(imgray,cmap='gray')
# Threshold the image to convert it to Binary image based on Otsu's method
thresh = cv2.threshold(imgray, 0, 255,
cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
## Detect the contours and display them
im2, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
y = cv2.drawContours(imgray, contours, -1, (0,255,0), 3)
## If we see the contour plots in the display of "y"
## we see that the coins have a common contour and hence it is not possible to separate them
plt.imshow(y,cmap='gray')
## Hence we will proceed with the Watershed algorithm so that each of the coins form its own
## cluster and thus it’s possible to have separate contours for each coin.
## Relabel the thresholded image to be consisting of only 0 and 1
## as the input image to distance_transform_edt should be in this format.
thresh[thresh == 255] = 5
thresh[thresh == 0] = 1
thresh[thresh == 5] = 0
## The distance_transform_edt and the peak_local_max functions help building the markers by detecting
## points near the center points of the coins. One can skip these steps and create a marker
## manually by setting one pixel within each coin with a random number representing its cluster
D = ndimage.distance_transform_edt(thresh)
localMax = peak_local_max(D, indices=False, min_distance=10,
labels=thresh)
markers = ndimage.label(localMax, structure=np.ones((3, 3)))[0]
# Provide the EDT distance matrix and the markers to the watershed algorithm to detect the cluster’s
# labels for each pixel. For each coin, the pixels corresponding to it will be filled with the cluster number
labels = watershed(-D, markers, mask=thresh)
print("[INFO] {} unique segments found".format(len(np.unique(labels)) - 1))
# Create the contours for each label(each coin) and append to the plot
for k in np.unique(labels):
if k != 0 :
labels_new = labels.copy()
labels_new[labels == k] = 255
labels_new[labels != k] = 0
labels_new = np.array(labels_new,dtype='uint8')
im2, contours, hierarchy = cv2.findContours(labels_new,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
z = cv2.drawContours(imgray,contours, -1, (0,255,0), 3)
plt.imshow(z,cmap='gray')
--output --
Listing 6-2.Image Segmentation Using Watershed Algorithm
从图 6-4 中我们可以看到,应用分水岭算法后,重叠硬币的边界是清晰的,而其他阈值方法无法为每枚硬币提供清晰的边界。
基于 K-均值聚类的图像分割
著名的 K-means 算法可以用来分割图像,尤其是医学图像。K 项是算法的一个参数,它决定了要形成的不同簇的数量。该算法通过形成聚类来工作,并且每个这样的聚类由基于特定输入特征的聚类质心来表示。对于通过 K-均值的图像分割,通常分割基于输入特征,例如像素强度及其三个空间维度;即水平和垂直坐标以及颜色通道。所以,输入的特征向量可以表示为
,其中
![$$ u={\left[I\left(x,y,z\right),x,y,z\right]}^T $$](A448418_1_En_6_Chapter_Equi.gif)
类似地,可以忽略空间坐标,并且将沿着三个颜色通道的像素强度作为输入特征向量;即
![$$ u={\left[{I}_R\left(x,y\right),{I}_G\left(x,y\right),{I}_B\left(x,y\right)\right]}^T $$](A448418_1_En_6_Chapter_Equj.gif)
其中 I R (x,y),I G (x,y),I B (x,y)分别表示在空间坐标(x,y)处沿着红色、绿色和蓝色通道的像素强度。
该算法使用距离度量,如 L 2 或 L 1 范数,如下所示:


以下是 K-means 算法的工作细节
-
步骤 1–从 K 个随机选择的群集质心 C 1 ,C 2 开始。C k 对应的 K 个簇 S 1 ,S 2 …S k 。
-
Step 2 – Compute the distance of each pixel feature vector u (i) from the cluster centroids and tag it to the cluster S j if the pixel has a minimum distance from its cluster centroid C j :
![$$ j=\underset{j}{\underbrace{Arg\ \mathit{\operatorname{Min}}}}{\left\Vert {u}^{(i)}-{C}_j\right\Vert}_2 $$]()
This process needs to be repeated for all the pixel-feature vectors so that in one iteration of K-means all the pixels are tagged to one of the K clusters.
-
Step 3 – Once the new centroids clusters have been assigned for all the pixels, the centroids are recomputed by taking the mean of the pixel-feature vectors in each cluster:
![$$ {C}_j=\sum \limits_{u^{(i)}\in {S}_j}{u}^{(i)} $$]()
-
Repeat Step 2 and Step 3 for several iterations until the centroids no longer change. Through this iterative process, we are reducing the sum of the intra-cluster distances, as represented here:
![$$ L=\sum \limits_{j=1}^K\sum \limits_{u^{(i)}\in {S}_j}{u}^{(i)}-{C_j}_2 $$]()
清单 6-3 显示了 K-means 算法的一个简单实现,将三个颜色通道中的像素强度作为特征。用
实现图像分割。输出以灰度显示,因此可能不会显示分割的实际质量。然而,如果清单 6-3 中产生的相同分割图像以彩色格式显示,它将揭示分割的更精细细节。还有一点需要补充:最小化的成本或损失函数(即,类内距离的总和)是非凸函数,因此可能会遇到局部极小问题。可以用聚类质心的不同初始值多次触发分割,然后选择最小化成本函数最多或产生合理分割的那个。

图 6-5。
Illustration of segmentation through the K-means algorithm
import cv2
import numpy as np
import matplotlib.pyplot as plt
#np.random.seed(0)
img = cv2.imread("kmeans.jpg")
imgray_ori = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
plt.imshow(imgray_ori,cmap='gray')
## Save the dimensions of the image
row,col,depth = img.shape
## Collapse the row and column axis for faster matrix operation.
img_new = np.zeros(shape=(row*col,3))
glob_ind = 0
for i in xrange(row):
for j in xrange(col):
u = np.array([img[i,j,0],img[i,j,1],img[i,j,2]])
img_new[glob_ind,:] = u
glob_ind += 1
# Set the number of clusters
K = 5
# Run the K-means for
num_iter = 20
for g in xrange(num_iter):
# Define cluster for storing the cluster number and out_dist to store the distances from centroid
clusters = np.zeros((row*col,1))
out_dist = np.zeros((row*col,K))
centroids = np.random.randint(0,255,size=(K,3))
for k in xrange(K):
diff = img_new - centroids[k,:]
diff_dist = np.linalg.norm(diff,axis=1)
out_dist[:,k] = diff_dist
# Assign the cluster with minimum distance to a pixel location
clusters = np.argmin(out_dist,axis=1)
# Recompute the clusters
for k1 in np.unique(clusters):
centroids[k1,:] = np.sum(img_new[clusters == k1,:],axis=0)/np.sum([clusters == k1])
# Reshape the cluster labels in two-dimensional image form
clusters = np.reshape(clusters,(row,col))
out_image = np.zeros(img.shape)
#Form the 3D image with the labels replaced by their correponding centroid pixel intensities
for i in xrange(row):
for j in xrange(col):
out_image[i,j,0] = centroids[clusters[i,j],0]
out_image[i,j,1] = centroids[clusters[i,j],1]
out_image[i,j,2] = centroids[clusters[i,j],2]
out_image = np.array(out_image,dtype="uint8")
# Display the output image after converting into grayscale
# Readers advised to display the image as it is for better clarity
imgray = cv2.cvtColor(out_image,cv2.COLOR_BGR2GRAY)
plt.imshow(imgray,cmap='gray')
---Output ---
Listing 6-3.Image Segmentation Using K-means
我们可以从图 6-5 中看到,K-means 聚类在分割 K = 3 的图像方面做得很好。
语义分割
近年来,通过卷积神经网络的图像分割获得了很大的普及。当通过神经网络分割图像时,显著不同的事情之一是将每个像素分配给一个对象类的注释过程,以便这样的分割网络的训练是完全监督的。虽然注释图像的过程是一件昂贵的事情,但它通过有一个比较的基础事实简化了问题。基本事实将是图像的形式,其中像素保持特定对象的代表颜色。例如,如果我们正在处理一个猫和狗的图像,并且这些图像可以有一个背景,那么图像的每个像素可以属于三个类别之一——猫、狗和背景。此外,每一类对象通常由代表性颜色表示,使得地面实况可以显示为分割的图像。让我们通过一些卷积神经网络,可以执行语义分割。
滑动窗口方法
可以通过使用滑动窗口从原始图像中提取图像碎片,然后将这些碎片馈送到分类卷积神经网络,以预测每个图像碎片的中心像素的类别。用这种滑动窗口方法训练这种卷积神经网络在训练和测试时都将是计算密集型的,因为每个图像至少需要 N 个小块被馈送到分类 CNN,其中 N 表示图像中的像素数。

图 6-6。
Sliding-window semantic segmentation
图 6-6 所示的是一个滑动窗口语义分割网络,用于分割猫、狗和背景的图像。它从原始图像中裁剪出小块,并通过分类 CNN 对小块中的中心像素进行分类。诸如 AlexNet、VGG19、Inception V3 等预训练网络可以用作分类 CNN,输出层被替换为仅具有三个与狗、猫和背景的标签相关的 3 类。然后,可以通过反向传播对 CNN 进行微调,将图像块作为输入,将输入图像块的中心像素的类别标签作为输出。从图像卷积的角度来看,这种网络是非常低效的,因为彼此相邻的图像补片将具有显著的重叠,并且每次独立地重新处理它们会导致不必要的计算开销。为了克服上述网络的缺点,可以使用全卷积网络,这是我们下一个讨论的主题。
全卷积网络(FCN)
全卷积网络由一系列卷积层组成,没有任何全连接层。选择卷积,使得输入图像在空间维度没有任何变化的情况下被传输;即图像的高度和宽度保持不变。与滑动窗口方法不同,完全卷积网络一次预测所有像素类别,而不是对图像中的各个块独立评估像素类别。该网络的输出层由 C 个特征图组成,其中 C 是每个像素可以被分类到的类别数,包括背景。如果原始图像的高度和宽度分别为 h 和 w,那么输出由 C 个
特征图组成。此外,对于地面实况,应该有对应于 C 个类别的 C 个分割图像。在任何空间坐标(
,每个特征图包含与该特征图所关联的类别相关的像素的分数。针对每个空间像素位置(
)的特征地图上的这些分数形成了不同类别上的 SoftMax。

图 6-7。
Fully convolutional network architecture
图 6-7 包含全卷积网络的架构设计。对应于三个类别,输出特征地图以及地面真实特征地图的数量将是三个。如果第 k 个类在空间坐标(I,j)处的输入净激活或得分由 sk??【I,j】表示,则第 k 个类在空间坐标(I,j)处的像素的概率由 SoftMax 概率给出,如下所示:

此外,如果第 k 类的空间坐标(I,j)处的地面真实标签由 y k (i,j)给出,则空间位置(I,j)处的像素的交叉熵损失可以由

表示
如果输入网络的图像的高度和宽度分别为 M 和 N,那么图像的总损失为

图像可以作为小批量馈送到网络,因此每个图像的平均损失可以作为在通过梯度下降的小批量学习的每个时期中要优化的损失或成本函数。
空间位置(I,j)处的像素的输出类别
可以通过取概率 P k (i,j)最大的类别 k 来确定;即

需要对图像的所有空间位置的像素执行相同的活动,以获得最终的分割图像。
在图 6-8 中,说明了用于分割猫、狗和背景图像的网络的输出特征图。正如我们所看到的,对于三个类别中的每一个,都有一个单独的特征图。特征图的空间维度与输入图像的空间维度相同。所有三个类别的净输入激活、相关概率和相应的地面标记都显示在空间坐标(I,j)处。

图 6-8。
Output feature maps corresponding to each of the three classes for dog, cat, and background
该网络中的所有卷积保持输入图像的空间维度。因此,对于高分辨率图像,网络将是计算密集型的,特别是如果每个卷积中的特征图或通道的数量很高。为了解决这个问题,更广泛地使用完全卷积神经网络的不同变体,其在网络的前半部分对图像进行下采样,然后在网络的后半部分对图像进行上采样。这个全卷积网络的修改版本将是我们下一个讨论的话题。
具有下采样和上采样的全卷积网络
与先前网络中在所有卷积层中保留图像的空间维度不同,这种全卷积网络的变体使用卷积的组合,其中图像在网络的前半部分被下采样,然后在最终层被上采样,以恢复原始图像的空间维度。通常,这种网络由通过步进卷积和/或池操作的几层下采样和几层上采样组成。到目前为止,通过卷积运算,我们要么对图像进行下采样,要么保持输出图像的空间维度与输入图像的空间维度相同。在这个网络中,我们需要对图像,或者更确切地说是特征图进行上采样。图 6-9 展示了这种网络的高级架构设计。

图 6-9。
Fully convolutional network with downsampling and upsampling
通常用于对图像或特征图进行上采样的技术如下所述。
取消排队
取消池化可以被视为池化的反向操作。在最大池或平均池中,我们通过基于池内核的大小取像素值的最大值或平均值来减少图像的空间维度。因此,如果我们有一个用于池化的
内核,图像的空间维度在每个空间维度上减少了
。在 unpooling 中,我们一般通过在一个邻域内重复一个像素值来增加图像的空间维度,如图 6-10 (A)所示。

图 6-10。
Unpooling operation
类似地,可以选择仅填充邻域中的一个像素,并将其余像素设置为零,如图 6-10 (B)所示。
最大取消轮询
许多完全卷积层是对称的,因为网络前半部分的池化操作将在网络后半部分有相应的取消池化操作,以恢复图像大小。每当执行汇集时,由于一个代表性元素对相邻像素的结果进行汇总,关于输入图像的微小空间信息会丢失。例如,当我们通过一个
内核进行最大池化时,每个邻域的最大像素值被传递到输出来表示
邻域。根据输出,不可能推断出最大像素值的位置。因此,在这个过程中,我们丢失了关于输入的空间信息。在语义分割中,我们希望将每个像素分类为尽可能接近其真实标签。但是,由于 max pooling,许多关于图像边缘和其他细节的信息都丢失了。当我们尝试通过取消池化来重建图像时,我们可以恢复一点丢失的空间信息的一种方法是将输入像素的值放置在与最大池化输出获得其输入的位置相对应的输出位置。为了更好地形象化,让我们看看图 6-11 中的插图。

图 6-11。
Max unpooling illustration for a symmetric fully connected segmentation network
正如我们从图 6-11 中所看到的,当取消池化时,只有输出映射 D 中与输入 A 中最大元素的位置相对应的位置被填充了值。这种取消轮询的方法通常称为最大取消轮询。
转置卷积
通过取消轮询或最大取消轮询完成的上采样是固定的变换。这些变换不涉及网络在训练网络时需要学习的任何参数。进行上采样的一种可学的方法是通过转置卷积来执行上采样,这很像我们所知道的卷积运算。由于转置卷积涉及网络将学习的参数,网络将学习以这样一种方式进行上采样,即训练网络的总成本函数减少。现在,让我们进入转置卷积如何工作的细节。

图 6-12。
Strided convolution operation for downsampling an image
在步长卷积中,对于步长为 2 的每个空间维度,输出维度几乎是输入维度的一半。图 6-12 说明了将维度为
的 2D 输入与步长为 2 且补零为 1 的
内核进行卷积的操作。我们在输入上滑动内核,在内核所在的每个位置,内核的点积通过内核与输入重叠的部分来计算。
在转置卷积中,我们使用相同的逻辑,但不是下采样,而是大于 1 的步长提供上采样。因此,如果我们使用步幅 2,那么输入大小在每个空间维度上都加倍。图 6-13a 、 6-13b 和 6-13c 示出了通过滤波器或内核大小
对维度
的输入进行转置卷积以产生
输出的操作。与卷积中滤波器和输入部分之间的点积不同,在转置卷积中,在特定位置,滤波器值由放置滤波器的输入值加权,加权后的滤波器值填充到输出中的相应位置。沿着相同空间维度的连续输入值的输出被放置在由转置卷积的步幅确定的间隙处。对所有输入值执行相同的操作。最后,将对应于每个输入值的输出相加以产生最终输出,如图 6-13c 所示。

图 6-13c。
Transpose convolution for upsampling

图 6-13b。
Transpose convolution for upsampling

图 6-13a。
Transpose convolution for upsampling
在 TensorFlow 中,函数tf.nm.conv2d_transpose可用于通过转置卷积进行上采样。
掌中宽带
U-Net 卷积神经网络是近来用于图像分割,尤其是医学图像分割的最有效的架构之一。这种 U-Net 架构赢得了 2015 年 ISBI 手机追踪挑战赛。网络拓扑从输入层到输出层遵循 U 形模式,因此被称为 U-Net。 Olaf Ronneberger 、 Philipp Fischer 和 Thomas Brox 提出了这种用于分割的卷积神经网络,该模型的细节在白皮书“U-Net:用于生物医学图像分割的卷积网络”中进行了说明论文可以位于 https://arxiv.org/abs/1505.04597 。
在网络的第一部分,图像通过卷积和最大池操作的组合进行下采样。卷积与逐像素的 ReLU 激活相关联。每个卷积操作都使用
滤波器大小,并且没有零填充,这导致输出特征图的每个空间维度减少了两个像素。在网络的第二部分中,向下采样的图像被向上采样直到最终层,其中输出特征图对应于被分割的对象的特定类别。正如我们前面看到的,每幅图像的成本函数将是整个图像上分类总和的像素分类交叉熵或对数损失。需要注意的一点是,U-Net 中输出要素地图的空间维度小于输入要素地图的空间维度。例如,具有空间维度
的输入图像产生空间维度
的输出特征图。人们可能会问,对于训练的损失计算,如何进行像素到像素类的比较。想法很简单——将分割的输出特征图与从输入图像中心提取的面片大小为
的地面真实分割图像进行比较。中心思想是,如果一个人有更高分辨率的图像,比如说
,他可以从它那里随机创建许多空间维度的图像
用于训练目的。此外,通过提取中心
小块并用相应的类别标记每个像素,从这些
子图像中创建地面真实图像。这有助于用大量数据训练网络,即使周围没有很多图像用于训练。图 6-14 所示为一个 U-Net 的架构图。

图 6-14。
U-Net architecture diagram
我们可以从架构图中看到,在网络的第一部分中,图像经历卷积和最大汇集,以减少空间维度,同时增加通道深度;即增加特征地图的数量。每两个连续的卷积及其相关的 ReLU 激活之后是一个最大池化操作,该操作将图像大小减少
。每一次最大池化操作都将网络向下带到下一组卷积,并在网络的第一部分形成 U 形。类似地,上采样层在每个维度上将空间维度增加 2,因此将图像大小增加 4 倍。此外,在第二部分中,它为网络提供了 U 型结构。每次上采样后,图像都要经过两次卷积及其相关的 ReLU 激活。
就最大汇集和上采样操作而言,U-Net 是一个非常对称的网络。然而,对于相应的最大汇集和上采样对,最大汇集之前的图像大小与上采样之后的图像大小不同,这与其他完全卷积层不同。如前所述,当进行最大池操作时,由于输出中的代表性像素对应于图像的局部邻域,因此会丢失大量空间信息。当图像被上采样回其原始尺寸时,恢复这种丢失的空间信息变得困难,因此新图像缺少边缘周围的大量信息以及图像的其他更精细的方面。这导致次优分割。如果上采样后的图像与其对应的最大汇集操作之前的图像具有相同的空间维度,则可以在最大汇集之前将随机数量的特征地图附加到上采样后的输出特征地图,以帮助网络恢复一点丢失的空间信息。由于在 U-Net 的情况下,这些要素地图的尺寸不匹配,U-Net 在最大汇集之前裁剪要素地图,使其与来自上采样的输出要素地图具有相同的空间尺寸,并连接它们。这可以更好地分割图像,因为它有助于恢复在 max pooling 期间丢失的一些空间信息。还有一点需要注意的是,上采样可以通过我们目前所了解的任何一种方法来完成,比如去卷积、最大去卷积和转置卷积,后者也称为去卷积。
U-Net 细分的几个重大胜利如下:
- 大量的训练数据可以仅用几个带注释的或手工标记的分割图像来生成。
- 即使存在需要分离的同类接触物体,U-Net 也能很好地分割。正如我们前面看到的传统图像处理方法,分离同一类的触摸对象是很困难的,分水岭算法等方法需要大量的对象标记输入,才能实现合理的分割。U-Net 通过为接触段边界周围的像素的错误分类引入高权重,在同一类的接触段之间进行良好的分离。
基于全连接神经网络的 TensorFlow 语义分割
在本节中,我们将介绍 TensorFlow 实现的工作细节,该实现基于名为 Carvana 的 Kaggle 竞赛,用于从背景中分割汽车图像。输入图像以及它们的基本事实分割可用于训练目的。我们在 80%的训练数据上训练模型,并在剩余的 20%的数据上验证模型的性能。为了训练,我们在网络的前半部分使用具有类似 U-Net 结构的全连接卷积网络,随后通过转置卷积进行上采样。这种网络与 U 型网络的几个不同之处在于,在使用填充进行卷积时,空间维度保持不变。另一个不同之处是,该模型不使用跳过连接来连接从下采样流到上采样流的特征地图。清单 6-4 中提供了详细的实现。

图 6-15a。
Segmentation results on the validation dataset with model trained on
–size images
## Load the required packages
import tensorflow as tf
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
%matplotlib inline
import os
from subprocess import check_output
import numpy as np
from keras.preprocessing.image import array_to_img, img_to_array, load_img, ImageDataGenerator
from scipy.misc import imresize
# Define downsampling - 2 (Conv+ReLU) and 1 Maxpooling
# Maxpooling can be set to False when needed
x = tf.placeholder(tf.float32,[None,128,128,3])
y = tf.placeholder(tf.float32,[None,128,128,1])
def down_sample(x,w1,b1,w2,b2,pool=True):
x = tf.nn.conv2d(x,w1,strides=[1,1,1,1],padding='SAME')
x = tf.nn.bias_add(x,b1)
x = tf.nn.relu(x)
x = tf.nn.conv2d(x,w2,strides=[1,1,1,1],padding='SAME')
x = tf.nn.bias_add(x,b2)
x = tf.nn.relu(x)
if pool:
y = tf.nn.max_pool(x,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
return y,x
else:
return x
# Define upsampling
def up_sample(x,w,b):
output_shape = x.get_shape().as_list()
output_shape[0] = 32
output_shape[1] *= 2
output_shape[2] *= 2
output_shape[1] = np.int(output_shape[1])
output_shape[2] = np.int(output_shape[2])
output_shape[3] = w.get_shape().as_list()[2]
conv_tf = tf.nn.conv2d_transpose(value=x,filter=w,output_shape=output_shape,strides=[1,2,2,1],padding="SAME")
conv_tf = tf.nn.bias_add(conv_tf,b)
return tf.nn.relu(conv_tf)
## Define weights
weights = {
'w11': tf.Variable(tf.random_normal([3,3,3,64],mean=0.0,stddev=0.02)),
'w12': tf.Variable(tf.random_normal([3,3,64,64],mean=0.0,stddev=0.02)),
'w21': tf.Variable(tf.random_normal([3,3,64,128],mean=0.0,stddev=0.02)),
'w22': tf.Variable(tf.random_normal([3,3,128,128],mean=0.0,stddev=0.02)),
'w31': tf.Variable(tf.random_normal([3,3,128,256],mean=0.0,stddev=0.02)),
'w32': tf.Variable(tf.random_normal([3,3,256,256],mean=0.0,stddev=0.02)),
'w41': tf.Variable(tf.random_normal([3,3,256,512],mean=0.0,stddev=0.02)),
'w42': tf.Variable(tf.random_normal([3,3,512,512],mean=0.0,stddev=0.02)),
'w51': tf.Variable(tf.random_normal([3,3,512,1024],mean=0.0,stddev=0.02)),
'w52': tf.Variable(tf.random_normal([3,3,1024,1024],mean=0.0,stddev=0.02)),
'wu1': tf.Variable(tf.random_normal([3,3,1024,1024],mean=0.0,stddev=0.02)),
'wu2': tf.Variable(tf.random_normal([3,3,512,1024],mean=0.0,stddev=0.02)),
'wu3': tf.Variable(tf.random_normal([3,3,256,512],mean=0.0,stddev=0.02)),
'wu4': tf.Variable(tf.random_normal([3,3,128,256],mean=0.0,stddev=0.02)),
'wf': tf.Variable(tf.random_normal([1,1,128,1],mean=0.0,stddev=0.02))
}
biases = {
'b11': tf.Variable(tf.random_normal([64],mean=0.0,stddev=0.02)),
'b12': tf.Variable(tf.random_normal([64],mean=0.0,stddev=0.02)),
'b21': tf.Variable(tf.random_normal([128],mean=0.0,stddev=0.02)),
'b22': tf.Variable(tf.random_normal([128],mean=0.0,stddev=0.02)),
'b31': tf.Variable(tf.random_normal([256],mean=0.0,stddev=0.02)),
'b32': tf.Variable(tf.random_normal([256],mean=0.0,stddev=0.02)),
'b41': tf.Variable(tf.random_normal([512],mean=0.0,stddev=0.02)),
'b42': tf.Variable(tf.random_normal([512],mean=0.0,stddev=0.02)),
'b51': tf.Variable(tf.random_normal([1024],mean=0.0,stddev=0.02)),
'b52': tf.Variable(tf.random_normal([1024],mean=0.0,stddev=0.02)),
'bu1': tf.Variable(tf.random_normal([1024],mean=0.0,stddev=0.02)),
'bu2': tf.Variable(tf.random_normal([512],mean=0.0,stddev=0.02)),
'bu3': tf.Variable(tf.random_normal([256],mean=0.0,stddev=0.02)),
'bu4': tf.Variable(tf.random_normal([128],mean=0.0,stddev=0.02)),
'bf': tf.Variable(tf.random_normal([1],mean=0.0,stddev=0.02))
}
## Create the final model
def unet_basic(x,weights,biases,dropout=1):
## Convolutional 1
out1,res1 = down_sample(x,weights['w11'],biases['b11'],weights['w12'],biases['b12'],pool=True)
out1,res1 = down_sample(out1,weights['w21'],biases['b21'],weights['w22'],biases['b22'],pool=True)
out1,res1 = down_sample(out1,weights['w31'],biases['b31'],weights['w32'],biases['b32'],pool=True)
out1,res1 = down_sample(out1,weights['w41'],biases['b41'],weights['w42'],biases['b42'],pool=True)
out1 = down_sample(out1,weights['w51'],biases['b51'],weights['w52'],biases['b52'],pool=False)
up1 = up_sample(out1,weights['wu1'],biases['bu1'])
up1 = up_sample(up1,weights['wu2'],biases['bu2'])
up1 = up_sample(up1,weights['wu3'],biases['bu3'])
up1 = up_sample(up1,weights['wu4'],biases['bu4'])
out = tf.nn.conv2d(up1,weights['wf'],strides=[1,1,1,1],padding='SAME')
out = tf.nn.bias_add(out,biases['bf'])
return out
## Create generators for pre-processing the images and making a batch available at runtime
## instead of loading all the images and labels in memory
# set the necessary directories
data_dir = "/home/santanu/Downloads/Carvana/train/" # Contains the input training data
mask_dir = "/home/santanu/Downloads/Carvana/train_masks/" # Contains the grouth truth labels
all_images = os.listdir(data_dir)
# pick which images we will use for testing and which for validation
train_images, validation_images = train_test_split(all_images, train_size=0.8, test_size=0.2)
# utility function to convert grayscale images to rgb
def grey2rgb(img):
new_img = []
for i in range(img.shape[0]):
for j in range(img.shape[1]):
new_img.append(list(img[i][j])*3)
new_img = np.array(new_img).reshape(img.shape[0], img.shape[1], 3)
return new_img
# generator that we will use to read the data from the directory
def data_gen_small(data_dir, mask_dir, images, batch_size, dims):
"""
data_dir: where the actual images are kept
mask_dir: where the actual masks are kept
images: the filenames of the images we want to generate batches from
batch_size: self explanatory
dims: the dimensions in which we want to rescale our images
"""
while True:
ix = np.random.choice(np.arange(len(images)), batch_size)
imgs = []
labels = []
for i in ix:
# images
original_img = load_img(data_dir + images[i])
resized_img = imresize(original_img, dims+[3])
array_img = img_to_array(resized_img)/255
imgs.append(array_img)
# masks
original_mask = load_img(mask_dir + images[i].split(".")[0] + '_mask.gif')
resized_mask = imresize(original_mask, dims+[3])
array_mask = img_to_array(resized_mask)/255
labels.append(array_mask[:, :, 0])
imgs = np.array(imgs)
labels = np.array(labels)
yield imgs, labels.reshape(-1, dims[0], dims[1], 1)
train_gen = data_gen_small(data_dir, mask_dir, train_images,32, [128, 128])
validation_gen = data_gen_small(data_dir, mask_dir, validation_images,32, [128, 128])
display_step=10
learning_rate=0.0001
keep_prob = tf.placeholder(tf.float32)
logits = unet_basic(x,weights,biases)
flat_logits = tf.reshape(tensor=logits, shape=(-1, 1))
flat_labels = tf.reshape(tensor=y,shape=(-1, 1))
cross_entropies = tf.nn.sigmoid_cross_entropy_with_logits(logits=flat_logits,
labels=flat_labels)
cost = tf.reduce_mean(cross_entropies)
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
# Evaluate model
## initializing all variables
init = tf.global_variables_initializer()
## Launch the execution Graph
with tf.Session() as sess:
sess.run(init)
for batch in xrange(500):
batch_x,batch_y= next(train_gen)
sess.run(optimizer, feed_dict={x:batch_x,y:batch_y})
loss = sess.run([cost],feed_dict={x:batch_x,y:batch_y})
## Validation loss and store the result for display at the end
val_x,val_y = next(validation_gen)
loss_val = sess.run([cost],feed_dict={x:val_x,y:val_y})
out_x = sess.run(logits,feed_dict={x:val_x})
print('batch:',batch,'train loss:',loss,'validation loss:',loss_val)
## To judge the segmentation quality of the Model we plot the segmentation for couple of the validation images.
## These validation images were evaluated in the last batch of training. Bear in mind that the model hasn’t been
## trained on these validation images.
img = (out_x[1] > 0.5)*1.0
plt.imshow(grey2rgb(img),alpha=0.5)
plt.imshow(val_x[1])
plt.imshow(grey2rgb(val_y[1]), alpha=0.5)
img = (out_x[2] > 0.5)*1.0
plt.imshow(grey2rgb(img),alpha=0.5)
plt.imshow(val_x[2])
plt.imshow(grey2rgb(val_y[2]), alpha=0.5)
-output-
''batch:', 400, 'train loss:', [0.044453222], 'validation loss:', [0.058442257])
('batch:', 401, 'train loss:', [0.049510699], 'validation loss:', [0.055530164])
('batch:', 402, 'train loss:', [0.048047166], 'validation loss:', [0.055518236])
('batch:', 403, 'train loss:', [0.049462996], 'validation loss:', [0.049190756])
('batch:', 404, 'train loss:', [0.047011156], 'validation loss:', [0.051120583])
('batch:', 405, 'train loss:', [0.046235155], 'validation loss:', [0.052921098])
('batch:', 406, 'train loss:', [0.051339123], 'validation loss:', [0.054767497])
('batch:', 407, 'train loss:', [0.050004266], 'validation loss:', [0.052718181])
('batch:', 408, 'train loss:', [0.048425209], 'validation loss:', [0.054115709])
('batch:', 409, 'train loss:', [0.05234601], 'validation loss:', [0.053246532])
('batch:', 410, 'train loss:', [0.054224499], 'validation loss:', [0.05121265])
('batch:', 411, 'train loss:', [0.050268434], 'validation loss:', [0.056970511])
('batch:', 412, 'train loss:', [0.046658799], 'validation loss:', [0.058863375])
('batch:', 413, 'train loss:', [0.048009872], 'validation loss:', [0.049314644])
('batch:', 414, 'train loss:', [0.053399611], 'validation loss:', [0.050949663])
('batch:', 415, 'train loss:', [0.047932044], 'validation loss:', [0.049477436])
('batch:', 416, 'train loss:', [0.054921247], 'validation loss:', [0.059221379])
('batch:', 417, 'train loss:', [0.053222295], 'validation loss:', [0.061699588])
('batch:', 418, 'train loss:', [0.047465689], 'validation loss:', [0.051628478])
('batch:', 419, 'train loss:', [0.055220582], 'validation loss:', [0.056656662])
('batch:', 420, 'train loss:', [0.052862987], 'validation loss:', [0.048487194])
('batch:', 421, 'train loss:', [0.052869596], 'validation loss:', [0.049040388])
('batch:', 422, 'train loss:', [0.050372943], 'validation loss:', [0.052676879])
('batch:', 423, 'train loss:', [0.048104074], 'validation loss:', [0.05687784])
('batch:', 424, 'train loss:', [0.050506901], 'validation loss:', [0.055646997])
('batch:', 425, 'train loss:', [0.042940177], 'validation loss:', [0.047789834])
('batch:', 426, 'train loss:', [0.04780338], 'validation loss:', [0.05711592])
('batch:', 427, 'train loss:', [0.051617432], 'validation loss:', [0.051806655])
('batch:', 428, 'train loss:', [0.047577277], 'validation loss:', [0.052631289])
('batch:', 429, 'train loss:', [0.048690431], 'validation loss:', [0.044696849])
('batch:', 430, 'train loss:', [0.046005826], 'validation loss:', [0.050702494])
('batch:', 431, 'train loss:', [0.05022176], 'validation loss:', [0.053923506])
('batch:', 432, 'train loss:', [0.041961089], 'validation loss:', [0.047880188])
('batch:', 433, 'train loss:', [0.05004932], 'validation loss:', [0.057072558])
('batch:', 434, 'train loss:', [0.04603707], 'validation loss:', [0.049482994])
('batch:', 435, 'train loss:', [0.047554974], 'validation loss:', [0.050586618])
('batch:', 436, 'train loss:', [0.046048313], 'validation loss:', [0.047748547])
('batch:', 437, 'train loss:', [0.047006462], 'validation loss:', [0.059268739])
('batch:', 438, 'train loss:', [0.045432612], 'validation loss:', [0.051733252])
('batch:', 439, 'train loss:', [0.048241541], 'validation loss:', [0.04774794])
('batch:', 440, 'train loss:', [0.046124499], 'validation loss:', [0.048809234])
('batch:', 441, 'train loss:', [0.049743906], 'validation loss:', [0.051254783])
('batch:', 442, 'train loss:', [0.047674596], 'validation loss:', [0.048125759])
('batch:', 443, 'train loss:', [0.048984651], 'validation loss:', [0.04512443])
('batch:', 444, 'train loss:', [0.045365792], 'validation loss:', [0.042732101])
('batch:', 445, 'train loss:', [0.046680171], 'validation loss:', [0.050935686])
('batch:', 446, 'train loss:', [0.04224021], 'validation loss:', [0.052455597])
('batch:', 447, 'train loss:', [0.045161027], 'validation loss:', [0.045499101])
('batch:', 448, 'train loss:', [0.042469904], 'validation loss:', [0.050128322])
('batch:', 449, 'train loss:', [0.047899902], 'validation loss:', [0.050441738])
('batch:', 450, 'train loss:', [0.043648213], 'validation loss:', [0.048811793])
('batch:', 451, 'train loss:', [0.042413067], 'validation loss:', [0.051744446])
('batch:', 452, 'train loss:', [0.047555752], 'validation loss:', [0.04977461])
('batch:', 453, 'train loss:', [0.045962822], 'validation loss:', [0.047307629])
('batch:', 454, 'train loss:', [0.050115541], 'validation loss:', [0.050558448])
('batch:', 455, 'train loss:', [0.045722887], 'validation loss:', [0.049715079])
('batch:', 456, 'train loss:', [0.042583987], 'validation loss:', [0.048713747])
('batch:', 457, 'train loss:', [0.040946022], 'validation loss:', [0.045165032])
('batch:', 458, 'train loss:', [0.045971408], 'validation loss:', [0.046652604])
('batch:', 459, 'train loss:', [0.045015588], 'validation loss:', [0.055410333])
('batch:', 460, 'train loss:', [0.045542594], 'validation loss:', [0.047741935])
('batch:', 461, 'train loss:', [0.04639449], 'validation loss:', [0.046171311])
('batch:', 462, 'train loss:', [0.047501944], 'validation loss:', [0.046123035])
('batch:', 463, 'train loss:', [0.043643478], 'validation loss:', [0.050230302])
('batch:', 464, 'train loss:', [0.040434662], 'validation loss:', [0.046641909])
('batch:', 465, 'train loss:', [0.046465941], 'validation loss:', [0.054901786])
('batch:', 466, 'train loss:', [0.049838047], 'validation loss:', [0.048461676])
('batch:', 467, 'train loss:', [0.043582849], 'validation loss:', [0.052996978])
('batch:', 468, 'train loss:', [0.050299261], 'validation loss:', [0.048585847])
('batch:', 469, 'train loss:', [0.046049926], 'validation loss:', [0.047540378])
('batch:', 470, 'train loss:', [0.042139661], 'validation loss:', [0.047782935])
('batch:', 471, 'train loss:', [0.046433724], 'validation loss:', [0.049313426])
('batch:', 472, 'train loss:', [0.047063917], 'validation loss:', [0.045388222])
('batch:', 473, 'train loss:', [0.045556825], 'validation loss:', [0.044953942])
('batch:', 474, 'train loss:', [0.046181824], 'validation loss:', [0.045763671])
('batch:', 475, 'train loss:', [0.047123503], 'validation loss:', [0.047637179])
('batch:', 476, 'train loss:', [0.046167117], 'validation loss:', [0.051462833])
('batch:', 477, 'train loss:', [0.043556783], 'validation loss:', [0.044357236])
('batch:', 478, 'train loss:', [0.04773742], 'validation loss:', [0.046332739])
('batch:', 479, 'train loss:', [0.04820114], 'validation loss:', [0.045707334])
('batch:', 480, 'train loss:', [0.048089884], 'validation loss:', [0.052449297])
('batch:', 481, 'train loss:', [0.041174423], 'validation loss:', [0.050378591])
('batch:', 482, 'train loss:', [0.049479648], 'validation loss:', [0.047861829])
('batch:', 483, 'train loss:', [0.041197944], 'validation loss:', [0.051383432])
('batch:', 484, 'train loss:', [0.051363751], 'validation loss:', [0.050520841])
('batch:', 485, 'train loss:', [0.047751397], 'validation loss:', [0.046632469])
('batch:', 486, 'train loss:', [0.049832929], 'validation loss:', [0.048640732])
('batch:', 487, 'train loss:', [0.049518026], 'validation loss:', [0.048658002])
('batch:', 488, 'train loss:', [0.051349726], 'validation loss:', [0.051405452])
('batch:', 489, 'train loss:', [0.041912809], 'validation loss:', [0.046458714])
('batch:', 490, 'train loss:', [0.047130216], 'validation loss:', [0.052001398])
('batch:', 491, 'train loss:', [0.041481428], 'validation loss:', [0.046243563])
('batch:', 492, 'train loss:', [0.042776003], 'validation loss:', [0.042228915])
('batch:', 493, 'train loss:', [0.043606419], 'validation loss:', [0.048132997])
('batch:', 494, 'train loss:', [0.047129884], 'validation loss:', [0.046108384])
('batch:', 495, 'train loss:', [0.043634158], 'validation loss:', [0.046292961])
('batch:', 496, 'train loss:', [0.04454672], 'validation loss:', [0.044108659])
('batch:', 497, 'train loss:', [0.048068151], 'validation loss:', [0.044547819])
('batch:', 498, 'train loss:', [0.044967934], 'validation loss:', [0.047069982])
('batch:', 499, 'train loss:', [0.041554678], 'validation loss:', [0.051807735])
Listing 6-4.Semantic Segmentation in TensorFlow with Fully Connected Neural Network
平均训练损失和验证损失几乎相同,这表明该模型没有过拟合,并且泛化能力很好。从图 6-15a 中我们可以看到,基于提供的基本事实,分割结果看起来令人信服。用于该网络的图像的空间维度为
。在将输入图像的空间维度增加到
时,精确度和分割显著增加。由于这是一个完全卷积的网络,没有完全连接的层,因此处理新的图像大小只需要对网络进行很少的更改。图 6-15b 显示了几个验证数据集图像的分割输出,以说明更大的图像尺寸在大多数情况下对图像分割问题是有益的,因为它有助于捕捉更多的上下文。

图 6-15b。
Segmentation results from the validation dataset with model trained on
–size images
图像分类和定位网络
所有的分类模型都预测图像中物体的类别,但并不真正告诉我们物体的位置。边界框可以用来表示图像中对象的位置。如果图像用边界框标注,并且关于它们的信息可通过输出类获得,则可以训练模型来预测这些边界框以及对象的类。这些边界框可以由四个数字表示,两个数字对应于边界框最左边上部的空间坐标,另外两个数字表示边界框的高度和宽度。然后,这四个数字可以通过回归预测。可以使用一个卷积神经网络进行分类,使用另一个卷积神经网络通过回归来预测这些包围盒属性。然而,通常使用相同的卷积神经网络来预测对象的类别以及预测包围盒的位置。直到最后完全连接的密集层的 CNN 将是相同的,但是在输出中,随着对象的不同类,将有四个额外的单元对应于边界框属性。这种预测图像中对象周围边界框的技术称为定位。图 6-16 所示为关于狗和猫的图像的图像分类和定位网络。这种类型的神经网络中的先验假设是图像中只有一个类对象。

图 6-16。
Classification and localization network
该网络的成本函数是不同对象类别的分类损失/成本和与边界框属性预测相关的回归成本的组合。由于优化成本是一个多任务目标函数,因此需要确定每个任务的权重。这一点很重要,因为与这些任务相关的不同成本(比如用于分类的分类交叉熵和用于回归的 RMSE)将具有不同的规模,因此,如果这些成本没有正确加权以形成总成本,可能会导致优化失控。成本需要标准化到一个共同的尺度,然后根据任务的复杂性分配权重。设处理 n 个类的卷积神经网络的参数和由这四个数确定的一个包围盒用θ表示。让输出类由向量
表示,因为每个
。同样,让边界框编号由向量
表示,其中 s 1 和 s 2 表示最左上像素的边界框坐标,而 s 3 和 s 4 表示边界框的高度和宽度。如果类别的预测概率由
表示,而预测边界框属性由
表示,则与图像相关的损失或成本函数可以表示为

上述表达式中的第一项表示 n 个类上 SoftMax 的分类交叉熵,而第二项是与预测边界框属性相关联的回归成本。参数α和β是网络的超参数,应该微调以获得合理的结果。对于超过 m 个数据点的小批量,成本函数可以表示如下:
![$$ C\left(\theta \right)=\frac{1}{m}\left[-\alpha \sum \limits_{i=1}^m\sum \limits_{j=1}n{\mathrm{y}}_j{(i)}\mathit{\log}{\mathrm{p}}_j^{(i)}+\beta \sum \limits_{i=1}^m\sum \limits_{j=1}⁴{\left({\mathrm{s}}_j{(i)}-{\mathrm{t}}_j{(i)}\right)}²\right] $$](A448418_1_En_6_Chapter_Equu.gif)
其中 I 上的后缀代表不同的图像。前面的成本函数可以通过梯度下降来最小化。顺便说一下,当比较具有不同(α,β)超参数值的该网络的不同版本的性能时,不应将与这些网络相关联的成本作为选择最佳网络的标准。相反,对于分类任务,应该使用一些其他度量,例如精度、召回率、F1 分数、曲线下面积等,而对于定位任务,应该使用诸如预测和实际边界框的重叠面积等度量。
目标检测
一幅图像通常不包含一个对象,而是包含几个感兴趣的对象。有许多应用受益于能够检测图像中的多个对象。例如,对象检测可用于统计商店几个区域的人数,以进行人群分析。此外,通过粗略估计通过信号灯的车辆数量,可以立即检测到信号灯上的交通负荷。利用物体检测的另一个领域是工厂的自动监控,以检测事件并在出现安全违规的情况下发出警报。可以在工厂中危险的关键区域捕获连续图像,并且可以基于在图像中检测到的多个对象从这些图像中捕获关键事件。例如,如果工人正在操作要求他戴上安全手套、护目镜和头盔的机器,则可以基于是否在图像中检测到所提到的物体来捕获安全违规。
检测图像中的多个目标是计算机视觉中的一个经典问题。首先,我们不能使用分类和定位网络或它的任何变体,因为图像中可以有不同数量的对象。为了激励我们解决物体检测的问题,让我们从一个非常简单的方法开始。我们可以通过强力滑动窗口技术从现有图像中随机获取图像碎片,然后将其馈送到预先训练的对象分类和定位网络。图 6-17 所示为一种滑动窗口方法,用于检测图像中的多个对象。

图 6-17。
Sliding-window technique to object detection
尽管这种方法可行,但是它在计算上非常昂贵,或者说在计算上难以处理,因为在没有好的区域提议的情况下,人们将不得不在不同的位置和尺度上尝试数千个图像补片。当前先进的对象检测方法提出了几个可以定位对象的区域,然后将这些图像建议区域馈送到分类和定位网络。一种这样的物体探测技术叫做 R-CNN,我们将在下面讨论。
流程图
在 R-CNN 中,R 代表地区提案。区域建议通常是通过一种叫做选择性搜索的算法得到的。对图像的选择性搜索通常提供大约 2000 个感兴趣的区域提议。选择性搜索通常利用传统的图像处理技术来定位图像中的斑点区域,作为可能包含对象的预期区域。以下是广义选择性搜索的处理步骤:
- 在图像中生成许多区域,每个区域只能属于一个类。
- 通过贪婪的方法递归地将较小的区域组合成较大的区域。在每一步,合并的两个区域应该是最相似的。这个过程需要重复,直到只剩下一个区域。这一过程产生了连续更大区域的层次结构,并允许算法为对象检测提出各种可能的区域。这些生成的区域被用作候选区域提议。
然后,这 2000 个感兴趣的区域被馈送到分类和定位网络,以预测对象的类别以及相关联的边界框。分类网络是一个卷积神经网络,后面跟着一个用于最终分类的支持向量机。图 6-18 所示为 R-CNN 的高级架构。

图 6-18。
R-CNN network
以下是与训练 R-CNN 相关的高级步骤:
- 取一个预先训练好的 ImageNet CNN 比如 AlexNet,重新训练最后一个完全连接的层,带有需要检测的对象,以及背景。
- 获取每个图像的所有区域提议(根据选择性搜索,每个图像 2000 个),扭曲它们或调整它们的大小以匹配 CNN 输入大小,通过 CNN 处理它们,然后将特征保存在磁盘上以供进一步处理。通常,池化图层输出地图作为要素保存到磁盘。
- 训练 SVM 根据 CNN 的特征对物体或背景进行分类。对于每一类物体,都应该有一个 SVM 学会区分特定的物体和背景。
- 最后,进行包围盒回归来校正区域提议。
尽管 R-CNN 在目标检测方面做得很好,但它也有一些缺点:
- R-CNN 的一个问题是大量的提议,这使得网络非常慢,因为这 2000 个提议中的每一个都有通过卷积神经网络的独立流。此外,区域建议是固定的;美国有线电视新闻网没有学习他们。
- 预测的定位和边界框来自单独的模型,因此在模型训练期间,我们不会基于训练数据学习任何特定于对象定位的东西。
- 对于分类任务,卷积神经网络生成的特征用于微调支持向量机,导致更高的处理成本。
越来越快的 R-CNN
快速 R-CNN 克服了 R-CNN 的一些计算挑战,它为整个图像提供了一个公共卷积路径,直到达到一定数量的层,在这一点上,区域建议被投影到输出特征图,并提取相关区域,以通过完全连接的层进行进一步处理,然后进行最终分类。通过卷积从输出特征图中提取相关区域建议,并且对于完全连接的层,将它们的大小调整为固定大小是通过被称为 ROI 合并的合并操作来完成的。图 6-19 所示为快速 R-CNN 的架构图。

图 6-19。
Fast R-CNN schematic diagram
快速 R-CNN 节省了与 R-CNN 中的多次卷积运算(每次选择性搜索每幅图像 2000 次)相关的大量成本。然而,区域提议仍然依赖于外部区域提议算法,例如选择性搜索。由于这种对外部区域提议算法的依赖,快速 R-CNN 受到这些区域提议的计算的瓶颈。网络必须等待这些外部建议被提出,然后才能向前推进。更快的 R-CNN 消除了这些瓶颈问题,其中区域提议是在网络本身内完成的,而不是依赖于外部算法。快速 R-CNN 的架构图几乎与快速 R-CNN 的架构图相似,但增加了一个新的内容——区域提议网络,它消除了对外部区域提议方案(如选择性搜索)的依赖。
生成对抗网络
生成对抗网络(GANs)是近年来深度学习领域最大的进步之一。Ian Goodfellow 和他的同事在 2014 年在 NIPS 的一篇题为“生成性对抗网络”的论文中首次介绍了这个网络。论文可以位于 https://arxiv.org/abs/1406.2661 。从那以后,在生成对抗网络中有了很多的兴趣和发展。事实上,最杰出的深度学习专家之一 Yann LeCun 认为,生成对抗网络的引入是近年来深度学习领域最重要的突破。gan 用作生成模型,用于生成合成数据,如由给定分布生成的数据。GAN 在几个领域中具有用途和潜力,例如图像生成、图像修补、抽象推理、语义分割、视频生成、从一个领域到另一个领域的风格转换以及文本到图像生成应用等。
生成对抗网络是基于博弈论中两个主体的零和博弈。一个生成式对抗网络有两个神经网络,生成器(G)和鉴别器(D),它们相互竞争。生成器(G)试图欺骗鉴别器(D ),使得鉴别器不能区分来自分布的真实数据和由生成器(G)生成的虚假数据。类似地,鉴别器(D)学习区分真实数据和生成器(G)生成的虚假数据。经过一段时间后,鉴别器和生成器在相互竞争的同时改进了各自的任务。这个博弈论问题的最优解由纳什均衡给出,其中生成器学习产生具有与原始数据分布相同的分布的虚假数据,同时鉴别器输出真实和虚假数据点的
概率。
现在,最明显的问题是假数据是如何构建的。通过从先验分布 P z 中采样噪声 z,通过生成神经网络模型(G)构建伪数据。如果实际数据 x 遵循分布 P x 并且由发生器产生的假数据 G(z)遵循分布 P g ,那么在平衡 P x (x)应该等于 PG(G(z));即

因为在平衡状态下,假数据的分布几乎与真实数据的分布相同,所以生成器将学习对难以与真实数据区分的假数据进行采样。此外,在平衡时,鉴别器 D 应该输出
作为两个类别的概率——真实数据和虚假数据。在我们研究生成性敌对网络的数学之前,有必要了解一下零和游戏、纳什均衡和极大极小公式。
图 6-20 所示为一个生成式对抗网络,其中有两个神经网络,生成器(G)和鉴别器(D),它们相互竞争。

图 6-20。
Basic illustration of adversarial network
马希民和极大极小问题
在参与者之间的游戏中,每个人都试图最大化他们的收益,增加他们获胜的机会。考虑一个由 N 个竞争对手参与的博弈,候选人 I 的马希民策略是在其他 N–1 个参与者都参与的情况下使他或她的收益最大化的策略。对应于马希民策略的候选人 I 的收益是候选人 I 在不知道其他人的行动的情况下肯定会得到的最大值。于是,马希民策略 s i 和马希民值 L i 可以表示为


解读候选人 I 的马希民策略的一个简单方法是认为我已经知道了他的对手的行动,他们会试图在他的每一次行动中最小化他的最大收益。因此,在这种假设下,
将下一步棋,这一步棋将是他每一步棋中所有最小值中的最大值。
用这种范式来解释极大极小策略比用更专业的术语来解释更容易。在极小极大策略中,候选人 I 会假设其他用-i 表示的候选人会在他们的每一步中都允许最小。在这种情况下,对 I 来说,选择一个能提供他所有最小收益中最大值的策略是合乎逻辑的,而其他候选人在他们的每个策略中都为 I 设定了这个最大值。在极大极小策略下,候选人 I 的收益由

给出
请注意,当所有玩家都采取行动时,最终的收益或损失可能与马希民或极大极小值不同。
让我们用一个直观的例子来解释一个马希民问题,在这个例子中,两个代理人 A 和 B 相互竞争,以最大化他们在游戏中的利润。还有,我们假设 A 可以出三招,L 1 ,L 2 ,L 3 ,而 B 可以出两招,M 1 和 M 2 。这方面的收益表如图 6-21 所示。在每个单元格中,第一个条目对应于 A 的收益,而第二个条目表示 B 的收益。

图 6-21。
Maximin and minimax illustration between two players
我们先假设 A 和 B 都在玩马希民策略;也就是说,他们应该采取行动使他们的收益最大化,同时预期对方会尽可能地使他们的收益最小化。
A 的马希民策略是选择移动 L 1 ,在这种情况下,A 得到的最小值是 44。如果他选择 L 2 ,A 可能会以–20 结束,而如果他选择 L 3 ,他可能会以–201 结束。因此,A 的马希民值是每行中所有可能最小值中的最大值;即 4,对应于策略 L 1 。
B 的马希民策略将是选择 M 1 ,因为沿着 M1B 将得到的最小值是 0.5。如果 B 选择了 M 2 ,那么 B 就冒着以–41 结束的风险。因此,B 的最大最小值是所有可能最小值中的最大值;即 0.5,对应 M 1 。
现在,假设 A 和 B 都采用他们的极大极小策略,即(L 1 ,M 1 ,在这种情况下,A 的收益是 6,B 的收益是 2。所以,我们看到一旦玩家采用他们的马希民策略,马希民值和实际收益值是不同的。
现在,让我们看看两个玩家都想玩极小极大策略的情况。在极小极大策略中,人们选择一种策略来达到最大值,即对手每一步棋中所有可能最大值的最小值。
我们来看看 A 的 minimax 值和策略。如果 B 选择 M 1 ,A 最多能得到 10,而如果 B 选择 M 2 ,A 最多能得到 88。显然,B 会允许 A 在 B 的每一步棋中只取可能的最大值中的最小值,因此,考虑到 B 的心态,A 可以期望的最大最小值是 8,对应于他的棋步 L 2 。
类似地,B 的最大最小值将是 A 的每一步中 B 的所有可能最大值中的最小值;即最小值为 2 和 8。因此,B 的最大最小值是 2。
需要注意的一点是,极大极小值总是大于而不是等于候选人的马希民值,这仅仅是因为马希民和极大极小值是如何定义的。
零和游戏
在博弈论中,零和游戏是一种情况的数学表述,其中每个参与者的收益或损失都被其他参与者的损失或收益相等地抵消。因此,作为一个系统,参与者群体的净收益或净损失为零。考虑两个玩家 A 和 B 之间的零和博弈。零和博弈可以用一个叫做收益矩阵的结构来表示,如图 6-22 所示。

图 6-22。
Payoff matrix for a two-player zero-sum game
图 6-22 是两个玩家的收益矩阵图,矩阵中的每个单元格代表玩家 A 的游戏对于 A 和 B 的每种移动组合的收益。因为这是一个零和游戏,所以没有明确提到 B 的收益;这只是参与人 A 收益的负值,假设 A 参与了一个马希民博弈。它会选择每一行中最小值的最大值,因此会选择策略 L 3 ,其对应的最大值为{-2,-10,6 };即 6 个。6 的收益对应于 b 的移动 M 2 。类似地,如果 A 采取了极大极小策略,A 将被迫获得与每列收益的最大值相等的收益;即 B 的每一步棋。在这种情况下,A 的收益应该是{8,6,10}的最小值,即 6,对应于 L 3 的极大极小策略。同样,这个收益 6 对应于 b 的移动 M 2 ,所以,我们可以看到在零和博弈的情况下,参与者的马希民收益等于最小最大收益。
现在,我们来看看参与人 B 的马希民收益,B 的马希民收益是每次行动中 B 的最小值的最大值;即
=
的最大值,对应移动 M 2 。同样,这个值对应于 A 的移动 L 3 。类似地,B 的最小最大收益是 A 的每个移动中 B 可以具有的最大值的最小值;即
。同样,对于 B,最小值最大值与马希民值相同,并且 B 的对应移动是 M 2 。这个场景中 A 的对应移动也是 L 3 。
因此,零和游戏的经验如下:
- 不管 A 和 B 是采用马希民策略还是极大极小策略,他们都将分别以 L 3 和 M 2 结束,对应于 A 的收益为 6,B 的收益为-6。此外,玩家的极大极小值和马希民值与他们采用极大极小策略时得到的实际收益值一致。
- 前面的观点引出了一个重要的事实:在零和游戏中,如果两个玩家都采用纯极大极小策略或马希民策略,那么一个玩家的极大极小策略将会产生两个玩家的实际策略。因此,两个行动都可以通过考虑 A 或 b 的行动来确定。如果我们考虑 A 的极大极小策略,那么两个玩家的行动都包含在其中。如果 A 的收益效用是 U(S 1 ,S 2 ,那么 A 和 B 的移动——即 S 1 和 S2——可以通过单独应用 A 或 B 的最小最大策略来找出。
极大极小点和鞍点
对于涉及两个参与人 A 和 B 的零和极大极小问题,参与人 A 的收益 U(x,y)可以表示为

其中 x 表示 A 的移动,而 y 表示 b 的移动。
还有,对应于的 x,y 的值分别是 A 和 B 的均衡策略;也就是说,如果他们继续相信极小极大或马希民策略,他们不会改变行动。对于一个零和的两人游戏,极大极小或马希民会产生相同的结果,因此,如果玩家采用极大极小或马希民策略,这个均衡成立。此外,由于极大极小值等于马希民值,极大极小值或马希民的定义顺序并不重要。我们也可以让 A 和 B 分别选择各自的最佳策略,我们会看到,在零和博弈中,其中一种策略组合会重叠。这个重叠条件对于 A 和 B 都是最佳策略,并且与它们的极大极小策略相同。这也是博弈的纳什均衡。
到目前为止,我们使用收益矩阵将策略保持离散,以便于解释,但是它们可以是连续的值。至于 GAN,策略是生成器和鉴别器神经网络的连续参数值,因此在我们深入 GAN 效用函数的细节之前,了解 A 的收益效用函数 f(x,y)是有意义的,它是 x 和 y 中两个连续变量的函数。此外,让 x 是 A 的移动,y 是 b 的移动。我们需要找到平衡点,它也是任一参与者的收益效用函数的最小值或马希民。对应于 A 的极大极小值的收益将提供 A 和 b 的策略。因为对于零和二人游戏,极大极小值和马希民是相同的,极大极小值的顺序并不重要;即

对于一个连续函数,只有当前一个函数的解是一个鞍点时,这才是可能的。鞍点是相对于每个变量的梯度为零的点;然而,它不是局部最小值或最大值。相反,它在输入向量的某些方向上趋于局部最小值,而在输入向量的其他方向上趋于局部最大值。所以,我们可以用多元微积分来寻找鞍点。不失一般性地,对于具有
的多变量函数 f(x ),我们可以通过以下测试来确定鞍点:
- 计算 f(x)相对于向量 x 的梯度,即
,并设置为零。 - 评估函数的 Hessian
,即梯度向量
为零的每个点处的二阶导数矩阵。如果 Hessian 矩阵在评估点既有正特征值又有负特征值,则该点是鞍点。
回到双变量收益效用函数
,对于 A 让我们定义如下来举例说明:

因此,B 的效用函数将自动为
我们现在研究如果两个参与者都采取零和极大极小或马希民策略,效用函数是否提供了一个均衡。这个博弈会有一个均衡,如果函数 f(x,y)有一个鞍点,超过这个均衡,参与者就不能提高他们的收益,因为他们的策略是最优的。均衡条件就是博弈的纳什均衡。
将 f(x,y)的梯度设置为零,我们得到
![$$ \nabla f\left(x,y\right)=\left[\begin{array}{c}\frac{\partial f}{\partial x}\ {}\frac{\partial f}{\partial y}\end{array}\right]=\left[\begin{array}{c}2x\ {}-2y\end{array}\right]=0\kern0.5em =>\left(x,y\right)=\left(0,0\right) $$](A448418_1_En_6_Chapter_Equac.gif)
函数的黑森由
![$$ {\nabla}²\ f\left(x,y\right)=\left[\begin{array}{cc}\frac{\partial²f}{\partial {x}²}& \frac{\partial²f}{\partial x\partial y}\ {}\frac{\partial²f}{\partial y\partial x}& \frac{\partial²f}{\partial {y}²}\end{array}\right]=\left[\begin{array}{cc}2& 0\ {}0& -2\end{array}\right] $$](A448418_1_En_6_Chapter_Equad.gif)
给出
对于包括
在内的任意(x,y)值,函数的 Hessian 为
。由于 Hessian 既有正特征值也有负特征值,即 2 和-2,因此点
是鞍点。A 的均衡策略应该是设定
,而 y 的均衡策略应该是在零和极小极大或马希民博弈中设定
。
GAN 成本函数和训练
在生成敌对网络中,生成者和鉴别者网络都试图通过在零和游戏中玩极大极小策略来胜过对方。这种情况下的移动是网络选择的参数值。为了便于标注,首先让我们用模型标注本身来表示模型参数;即 G 用于发生器,D 用于鉴别器。现在,让我们框出每个网络的收益函数的效用。鉴别器将试图正确地对假的或合成产生的样本和真实数据样本进行分类。换句话说,它会试图最大化效用函数
![$$ U\left(D,G\right)={\mathrm{E}}_{x\sim {P}_x(x)}\left[\log D(x)\right]+{E}_{z\sim {P}_z(z)}\Big[\log \left(1-D\left(G(z)\right)\right] $$](A448418_1_En_6_Chapter_Equaf.gif)
其中 x 表示从概率分布 P x (x)中提取的真实数据样本,z 是从先前噪声分布 P z (z)中提取的噪声。此外,鉴别器试图为真实数据样本 x 输出 1,并为基于噪声样本 z 的生成器创建的假数据或合成数据输出 0。因此,鉴别器会采用一种策略,使 D(x)尽可能接近 1,这将使 logD(x)接近 0 值。D(x)越小于 1,logD(x)的值就越小,因此鉴别器的效用值就越小。类似地,鉴别者希望通过将其概率设置为接近零来捕捉假的或合成的数据;即,将 D(G(z))设置为尽可能接近零,以将其识别为伪图像。当 D(G(z))接近零时,表达式
趋于零。随着 D(G(z))的值偏离零,鉴别器的收益变小,因为
变小。鉴别者希望在 x 和 z 的整个分布上进行,因此在它的收益函数中有期望或均值项。当然,生成器 G 对 D 的收益函数有发言权,以 G(z)的形式表示,即第二项,因此它也会尝试采取一个行动来最小化 D 的收益。D 的收益越多,G 的情况就越糟。因此,我们可以认为 G 与 D 具有相同的效用函数,只是其中有一个负号,这使得这成为一个零和游戏,其中 G 的收益由
![$$ V\left(D,G\right)=-{\mathrm{E}}_{x\sim {P}_x(x)}\left[\log D(x)\right]-{E}_{z\sim {P}_z(z)}\left[\log \left(1-D\left(G(z)\right)\right)\right] $$](A448418_1_En_6_Chapter_Equag.gif)
给出
生成器 G 将尝试选择其参数,使得 V(D,G)最大化;即,它产生虚假的数据样本 G(z ),使得鉴别器被愚弄而用 0 标签对它们进行分类。换句话说,它希望鉴别器认为 G(z)是真实数据,并给它们分配高概率。远离 0 的 D(G(z))的高值将使
成为具有高量值的负值,并且当乘以表达式开始处的负号时,它将产生高值
,从而增加生成器的收益。不幸的是,发生器无法影响涉及实际数据的 V(D,G)中的第一项,因为它不涉及 G 中的参数。
生成器 G 和鉴别器 D 模型通过让它们玩具有极大极小策略的零和游戏来训练。鉴别者会试图最大化它的收益 U(D,G ),并试图达到它的最小值。
![$$ {u}^{\ast }=\underset{D}{\underbrace{\mathit{\min}}}\underset{G}{\underbrace{\mathit{\max}}}{\mathrm{E}}_{x\sim {P}_x(x)}\left[\log D(x)\right]+{E}_{z\sim {P}_z(z)}\left[\log \left(1-D\left(G(z)\right)\right)\right] $$](A448418_1_En_6_Chapter_Equah.gif)
类似地,发电机 G 希望通过选择一个策略来最大化其收益 V(D,G)。
![$$ {v}^{\ast }=\underset{D}{\underbrace{\mathit{\min}}}\underset{G}{\underbrace{\mathit{\max}}}-{\mathrm{E}}_{x\sim {P}_x(x)}\left[\log D(x)\right]-{E}_{z\sim {P}_z(z)}\left[\log \left(1-D\left(G(z)\right)\right)\right] $$](A448418_1_En_6_Chapter_Equai.gif)
既然第一项是不受 G 控制的东西,那么
![$$ {v}^{\ast }=\underset{D}{\underbrace{\mathit{\min}}}\underset{G}{\underbrace{\mathit{\max}}}-{E}_{z\sim {P}_z(z)}\left[\log \left(1-D\left(G(z)\right)\right)\right] $$](A448418_1_En_6_Chapter_Equaj.gif)
正如我们所见,在两个参与者的零和游戏中,不需要考虑单独的极大极小策略,因为两者都可以通过考虑其中一个参与者的支付效用函数的极大极小策略来导出。考虑到鉴别者的极大极小公式,我们得到鉴别者在均衡(或纳什均衡)时的收益为
![$$ {u}^{\ast }=\underset{\begin{array}{l}\kern1em G\ {}\underset{D}{\underbrace{\mathit{\max}}}\end{array}}{\underbrace{\mathit{\min}}}{\mathrm{E}}_{x\sim {P}_x(x)}\left[\log D(x)\right]+{E}_{z\sim {P}_z(z)}\left[\log \left(1-D\left(G(z)\right)\right)\right] $$](A448418_1_En_6_Chapter_Equak.gif)
u*处的
和
的值将是两个网络的优化参数,超过这些参数,它们不能提高它们的分数。另外
给出了 D 的效用函数
的鞍点。
前面的公式可以通过将优化分解成两部分来简化;即,在每次移动中,让 D 最大化其关于其参数的收益效用函数,让 G 最小化 D 关于其参数的收益效用函数。
![$$ \underset{D}{\underbrace{\mathit{\max}}}{\mathrm{E}}_{x\sim {P}_x(x)}\left[\log D(x)\right]+{E}_{z\sim {P}_z(z)}\left[\log \left(1-D\left(G(z)\right)\right)\right] $$](A448418_1_En_6_Chapter_Equal.gif)
![$$ \underset{G}{\underbrace{\mathit{\min}}}{E}_{z\sim {P}_z(z)}\left[\log \left(1-D\left(G(z)\right)\right)\right] $$](A448418_1_En_6_Chapter_Equam.gif)
双方都认为对方的行动是固定的,同时优化自己的成本函数。这种优化的迭代方法只不过是计算鞍点的梯度下降技术。由于机器学习包大多编码为最小化而不是最大化,所以鉴别器的目标可以乘以-1,然后 D 可以将其最小化而不是最大化。
接下来介绍的是基于前面的试探法通常用于训练 GAN 的小批量方法:
-
对于 N 个时期:
-
对于 k 步:
-
从噪声分布
中抽取 m 个样本{ ![$$ {z}{(1)},{z}{(2)},..{z}^{(m)}\Big} $$]()
-
从数据分布
中抽取 m 个样本{ ![$$ {x}{(1)},{x}{(2)},..{x}^{(m)}\Big} $$]()
-
Update the discriminator D parameters by using stochastic gradient descent. If the parameters of the discriminator D are represented by θ D , then update θ D as
![$$ {\theta}_D\to {\theta}_D-{\nabla}_{\theta_D}\left[-\frac{1}{m}\sum \limits_{i=1}^m\left(\log D\left({x}^{(i)}\right)\right)+\log \left(1-D\left(G\left({z}^{(i)}\right)\right)\right)\right] $$]()
-
-
end
![$$ {\theta}_G\to {\theta}_G-{\nabla}_{\theta_G}\left[\frac{1}{m}\sum \limits_{i=1}^m\log \left(1-D\left(G\left({z}^{(i)}\right)\right)\right)\right] $$]()
- 从噪声分布
中抽取 m 个样本{ ![$$ {z}{(1)},{z}{(2)},..{z}^{(m)}\Big} $$]()
- 通过随机梯度下降更新生成器 G。如果发电机 G 的参数用θ G 表示,则更新θ G 为
- 从噪声分布
-
目标
发生器的消失渐变
通常,在训练的初始阶段,生成器生成的样本与原始数据非常不同,因此鉴别器可以很容易地将它们标记为假的。这导致 D(G(z))的值接近于零,因此梯度
饱和,导致 G 的网络参数的梯度消失问题。为了克服这个问题,不是最小化
,而是将函数
最大化,或者遵循梯度下降,将
最小化。这种改变使得训练方法不再是纯粹的极小极大游戏,而是一种合理的近似,有助于克服训练早期阶段的饱和。
GAN 网络的 TensorFlow 实现
在本节中,图示了在 MNIST 图像上训练的 GAN 网络,其中生成器试图创建像 MNIST 那样的假合成图像,而鉴别器试图将这些合成图像标记为假,同时仍然能够将真实数据区分为真实的。一旦训练完成,我们对一些合成图像进行采样,看看它们是否看起来像真的。生成器是一个简单的前馈神经网络,具有三个隐藏层,后面是输出层,输出层由与 MNIST 图像中的 784 个像素相对应的 784 个单元组成。输出单元的激活被认为是 tanh 而不是 sigmoid,因为与 sigmoid 单元相比,tanh 激活单元较少遭受消失梯度问题。双曲正切激活函数输出在
和 1 之间的值,因此真实 MNIST 图像被归一化为具有在
和 11 之间的值,使得合成图像和真实 MNIST 图像在相同的范围内操作。鉴别器网络也是具有 sigmoid 输出单元的三隐层前馈神经网络,以在真实 MNIST 图像和由发生器产生的合成图像之间执行二元分类。发生器的输入是一个 100 维的输入,从在每个维度的
和 1 之间运行的均匀噪声分布中采样。详细实现如清单 6-5 所示。

图 6-23。
Digits synthetically generated by the GAN network
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
## The dimension of the Prior Noise Signal is taken to be 100
## The generator would have 150 and 300 hidden units successively before 784 outputs corresponding
## to 28x28 image size
h1_dim = 150
h2_dim = 300
dim = 100
batch_size = 256
#------------------------------------------------------------------------------------
# Define the generator - take noise and convert them to images
#------------------------------------------------------------------------------------
def generator_(z_noise):
w1 = tf.Variable(tf.truncated_normal([dim,h1_dim], stddev=0.1), name="w1_g", dtype=tf.float32)
b1 = tf.Variable(tf.zeros([h1_dim]), name="b1_g", dtype=tf.float32)
h1 = tf.nn.relu(tf.matmul(z_noise, w1) + b1)
w2 = tf.Variable(tf.truncated_normal([h1_dim,h2_dim], stddev=0.1), name="w2_g", dtype=tf.float32)
b2 = tf.Variable(tf.zeros([h2_dim]), name="b2_g", dtype=tf.float32)
h2 = tf.nn.relu(tf.matmul(h1, w2) + b2)
w3 = tf.Variable(tf.truncated_normal([h2_dim,28*28],stddev=0.1), name="w3_g", dtype=tf.float32)
b3 = tf.Variable(tf.zeros([28*28]), name="b3_g", dtype=tf.float32)
h3 = tf.matmul(h2, w3) + b3
out_gen = tf.nn.tanh(h3)
weights_g = [w1, b1, w2, b2, w3, b3]
return out_gen,weights_g
#-------------------------------------------------------------------------------------------
# Define the Discriminator - take both real images and synthetic fake images
# from Generator and classify the real and fake images properly
#-------------------------------------------------------------------------------------------
def discriminator_(x,out_gen,keep_prob):
x_all = tf.concat([x,out_gen], 0)
w1 = tf.Variable(tf.truncated_normal([28*28, h2_dim], stddev=0.1), name="w1_d", dtype=tf.float32)
b1 = tf.Variable(tf.zeros([h2_dim]), name="b1_d", dtype=tf.float32)
h1 = tf.nn.dropout(tf.nn.relu(tf.matmul(x_all, w1) + b1), keep_prob)
w2 = tf.Variable(tf.truncated_normal([h2_dim, h1_dim], stddev=0.1), name="w2_d", dtype=tf.float32)
b2 = tf.Variable(tf.zeros([h1_dim]), name="b2_d", dtype=tf.float32)
h2 = tf.nn.dropout(tf.nn.relu(tf.matmul(h1,w2) + b2), keep_prob)
w3 = tf.Variable(tf.truncated_normal([h1_dim, 1], stddev=0.1), name="w3_d", dtype=tf.float32)
b3 = tf.Variable(tf.zeros([1]), name="d_b3", dtype=tf.float32)
h3 = tf.matmul(h2, w3) + b3
y_data = tf.nn.sigmoid(tf.slice(h3, [0, 0], [batch_size, -1], name=None))
y_fake = tf.nn.sigmoid(tf.slice(h3, [batch_size, 0], [-1, -1], name=None))
weights_d = [w1, b1, w2, b2, w3, b3]
return y_data,y_fake,weights_d
#----------------------------------------------------------------------------
# Read the MNIST datadet
#----------------------------------------------------------------------------
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
#----------------------------------------------------------------------------
# Define the different Tensorflow ops and variables, cost function, and Optimizer
#----------------------------------------------------------------------------
# Placeholders
x = tf.placeholder(tf.float32, [batch_size, 28*28], name="x_data")
z_noise = tf.placeholder(tf.float32, [batch_size,dim], name="z_prior")
#Dropout probability
keep_prob = tf.placeholder(tf.float32, name="keep_prob")
# generate the output ops for generator and also define the weights.
out_gen,weights_g = generator_(z_noise)
# Define the ops and weights for Discriminator
y_data, y_fake,weights_d = discriminator_(x,out_gen,keep_prob)
# Cost function for Discriminator and Generator
discr_loss = - (tf.log(y_data) + tf.log(1 - y_fake))
gen_loss = - tf.log(y_fake)
optimizer = tf.train.AdamOptimizer(0.0001)
d_trainer = optimizer.minimize(discr_loss,var_list=weights_d)
g_trainer = optimizer.minimize(gen_loss,var_list=weights_g)
init = tf.global_variables_initializer()
saver = tf.train.Saver()
#------------------------------------------------------------------------
# Invoke the TensorFlow graph and begin the training
#--------------------------------------------------------------------------
sess = tf.Session()
sess.run(init)
z_sample = np.random.uniform(-1, 1, size=(batch_size,dim)).astype(np.float32)
for i in range(60000):
batch_x, _ = mnist.train.next_batch(batch_size)
x_value = 2*batch_x.astype(np.float32) - 1
z_value = np.random.uniform(-1, 1, size=(batch_size,dim)).astype(np.float32)
sess.run(d_trainer,feed_dict={x:x_value, z_noise:z_value,keep_prob:0.7})
sess.run(g_trainer,feed_dict={x:x_value, z_noise:z_value,keep_prob:0.7})
[c1,c2] = sess.run([discr_loss,gen_loss],feed_dict={x:x_value, z_noise:z_value,keep_prob:0.7})
print ('iter:',i,'cost of discriminator',c1, 'cost of generator',c2)
#------------------------------------------------------------------------------
# Generate a batch of synthetic fake images
#-------------------------------------------------------------------------------
out_val_img = sess.run(out_gen,feed_dict={z_noise:z_sample})
saver.save(sess, " newgan1",global_step=i)
#-----------------------------------------------------------------------------
# Plot the synthetic images generated
#-----------------------------------------------------------------------------
imgs = 0.5*(out_val_img + 1)
for k in range(36):
plt.subplot(6,6,k+1)
image = np.reshape(imgs[k],(28,28))
plt.imshow(image,cmap='gray')
-- output --
Listing 6-5.Implementation of a Generative Adversarial Network
从图 6-23 中我们可以看到,GAN 发生器能够产生类似于 MNIST 数据集数字的图像。GAN 模型在大小为 256 的 60000 个小批量上进行训练,以获得这种质量的结果。我想强调的一点是,与其他神经网络相比,GANs 相对难以训练。因此,需要进行大量的实验和定制,以达到预期的效果。
TensorFlow 模型在生产中的部署
要将训练好的 TensorFlow 模型导出到生产中,可以利用 TensorFlow Serving 的功能。它旨在简化机器学习模型在生产中的部署。TensorFlow Serving,顾名思义,托管生产中的模型,并为应用程序提供对它的本地访问。以下步骤可用作将 TensorFlow 模型加载到生产中的指南:
- 需要通过在活动会话下激活 TensorFlow 图来训练 TensorFlow 模型。
- 一旦模型训练完毕,TensorFlow 的
SavedModelBuilder模块就可以用来导出模型了。这个SavedModelBuilder将模型的副本保存在一个安全的位置,以便在需要的时候可以轻松地加载它。调用SavedModelBuilder模块时,需要指定导出路径。如果导出路径不存在,TensorFlow 的SavedModelBuilder将创建所需的目录。型号的版本号也可以通过FLAGS.model_version.指定 - TensorFlow 元图定义和其他变量可以通过使用
SavedModelBuilder.add_meta_graph_and_variable()方法与导出的模型绑定。该方法中的选项signature_def_map充当不同用户提供的签名的映射。通过签名,可以指定将输入数据发送到模型进行预测以及从模型接收预测或输出所需的输入和输出张量。例如,可以为模型构建分类签名和预测签名,并将它们与 signature_def_map 联系起来。图像上的多类分类模型的分类签名可以被定义为将图像张量作为输入,并产生概率作为输出。类似地,预测签名可以被定义为将图像张量作为输入,并输出原始类别分数。示例代码由位于https://github.com/tensorflow/serving/blob/master/tensorflow_serving/example/mnist_saved_model.py的 TensorFlow 提供,可作为导出 TensorFlow 模型时的简单参考。 - 模型一旦导出,就可以使用标准 TensorFlow 模型服务器加载,或者可以选择使用本地编译的模型服务器。更详细的细节可以在 TensorFlow 网站提供的链接中找到:
https://www.tensorflow.org/serving/serving_basic。
清单 6-6a 展示了一个基本实现,它保存一个 TensorFlow 模型,然后在测试时重用它进行预测。它与 TensorFlow 模型在生产中的部署方式有很多共同点。
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
batch_size,learning_rate = 256,0.001
epochs = 10
total_iter = 1000
x = tf.placeholder(tf.float32,[None,784],name='x')
y = tf.placeholder(tf.float32,[None,10],name='y')
W = tf.Variable(tf.random_normal([784,10],mean=0,stddev=0.02),name='W')
b = tf.Variable(tf.random_normal([10],mean=0,stddev=0.02),name='b')
logits = tf.add(tf.matmul(x,W),b,name='logits')
pred = tf.nn.softmax(logits,name='pred')
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(pred,1),name='correct_prediction')
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32),name='accuracy')
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
batches = (mnist.train.num_examples//batch_size)
saver = tf.train.Saver()
cost = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=logits,labels=y))
optimizer_ = tf.train.AdamOptimizer(learning_rate).minimize(cost)
init = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init)
for step in range(total_iter):
batch_x,batch_y = mnist.train.next_batch(batch_size)
sess.run(optimizer_,feed_dict={x:batch_x,y:batch_y})
c = sess.run(cost,feed_dict={x:batch_x,y:batch_y})
print ('Loss in iteration ' + str(step) + '= ' + str(c))
if step % 100 == 0 :
saver.save(sess,'/home/santanu/model_basic',global_step=step)
saver.save(sess,'/home/santanu/model_basic',global_step=step)
val_x,val_y = mnist.test.next_batch(batch_size)
print('Accuracy:',sess.run(accuracy,feed_dict={x:val_x,y:val_y}))
--output --
Loss in iteration 991= 0.0870551
Loss in iteration 992= 0.0821354
Loss in iteration 993= 0.0925385
Loss in iteration 994= 0.0902953
Loss in iteration 995= 0.0883076
Loss in iteration 996= 0.0936614
Loss in iteration 997= 0.077705
Loss in iteration 998= 0.0851475
Loss in iteration 999= 0.0802716
('Accuracy:', 0.91796875)
Listing 6-6a.Illustration of How to Save a Model in TensorFlow
前面代码中重要的一点是创建了一个saver = tf.train.Save()类的实例。在 TensorFlow 会话中对实例化的saver对象调用save方法会保存整个会话元图以及指定位置的变量值。这一点很重要,因为 TensorFlow 变量仅在 TensorFlow 会话中有效,因此此方法可用于检索稍后在会话中创建的模型,以用于预测、模型微调等目的。
模型以指定的名称保存在给定位置;即model_basic,它有如下三个组成部分:
- 型号 _ 基本-9999.meta
- 型号 _ 基本-9999 .索引
- 型号 _ 基本-9999 .数据-00000/00001
组件 9999 是附加的步骤号,因为我们添加了global_step选项,它将步骤号附加到保存的模型文件中。这有助于版本化,因为我们可能对不同步骤中模型的多个副本感兴趣。然而,TensorFlow 只维护四个最新版本。
model_basic-9999.meta文件将包含保存的会话图,而model_basic-9999.data-00000-of-00001和model_basic-9999.index文件构成了包含不同变量的所有值的检查点文件。此外,还会有一个公共检查点文件,其中包含关于所有可用检查点文件的信息。
现在,让我们看看如何恢复保存的模型。我们可以通过手动定义所有变量和 ops 来创建网络,就像原始网络一样。然而,那些定义已经存在于model_basic-9999.meta文件中,因此可以通过使用清单 6-6b 中所示的import_meta_graph方法导入到当前会话中。一旦加载了元图,需要加载的只是不同参数的值。这是通过saver实例上的restore方法完成的。完成后,不同的变量可以通过它们的名字直接引用。例如,pred和accuracy张量被它们的名字直接引用,并进一步用于新数据的预测。类似地,占位符也需要通过它们的名称来恢复,以便将数据提供给需要它的不同操作。
清单 6-6b 展示了恢复 TensorFlow 模型并使用它对保存的训练模型进行预测和准确性检查的代码实现。
batch_size = 256
with tf.Session() as sess:
init = tf.global_variables_initializer()
sess.run(init)
new_saver = tf.train.import_meta_graph('/home/santanu/model_basic-999.meta')
new_saver.restore(sess,tf.train.latest_checkpoint('./'))
graph = tf.get_default_graph()
pred = graph.get_tensor_by_name("pred:0")
accuracy = graph.get_tensor_by_name("accuracy:0")
x = graph.get_tensor_by_name("x:0")
y = graph.get_tensor_by_name("y:0")
val_x,val_y = mnist.test.next_batch(batch_size)
pred_out = sess.run(pred,feed_dict={x:val_x})
accuracy_out = sess.run(accuracy,feed_dict={x:val_x,y:val_y})
print 'Accuracy on Test dataset:',accuracy_out
--output--
Accuracy on Test dataset: 0.871094
Listing 6-6b.Illustration of Restoring a Saved Model in TensorFlow
摘要
至此,我们结束了这一章和这本书。尽管本章中阐述的概念和模型更加先进,但是它们使用了在前面章节中学习到的技术。读完这一章后,人们应该对实现书中讨论的各种模型充满信心,并尝试在这个不断发展的深度学习社区中实现其他不同的模型和技术。学习和提出该领域新创新的最佳方式之一是密切关注其他深度学习专家及其工作。还有谁比杰弗里·辛顿、扬·勒村、约舒阿·本吉奥和伊恩·古德菲勒等人更值得关注呢?此外,我觉得一个人应该更接近深度学习的数学和科学,而不是仅仅把它作为一个黑盒子来从中获得适当的好处。我的笔记到此结束。谢谢你。


是满秩的。秩为 n 的方阵意味着所有 n 个列向量甚至 n 个行向量都是线性独立的,因此可以通过对矩阵 A 的 n 个列向量进行线性组合来跨越整个 n 维空间
不是满秩的,那么它就是奇异矩阵;即,它的所有列向量或行向量不是线性独立的。奇异矩阵有一个未定义的逆矩阵和零行列式。
,其中是单位矩阵
表示的一阶导数的导数,需要在一阶导数为零的点上考察。如果二阶导数小于零,那么它是一个最大值点,而如果它大于零,它是一个最小值点。如果二阶导数也是零,那么这个点就叫做拐点。
那么
就是鞍点。
那么
是极值点;即存在最大值或最小值。
,那么 f(x,y)在
具有最大值。
和
,那么 f(x,y)在
处最小。
那么需要更先进的方法来正确地分类静止点。
,如果函数在 x 0 处的海森矩阵既有正负特征值,那么 x 0 就是一个鞍点。如果 Hessian 矩阵的特征值都是正的,则平稳点是局部最小值,如果特征值都是负的,则平稳点是局部最大值。
和 f(x)是相同的。
,
,
。
时,概率
。
时,概率
。




should be the zero vector, which on simplification gives
= 0 和
如果我们考虑正则化,即
,那么
(2)
,则神经元将输出 1。在任何抑制输入激活或
的情况下,输出将为 0。使用这种逻辑,所有布尔逻辑功能可以由一个或多个这样的神经元来实现。这些网络的缺点是它们无法通过训练来学习权重。人们必须手动计算出权重,并组合神经元来实现所需的计算。
对应于超平面,所有位于该超平面上的
将满足该条件。通常,超平面上的点被认为属于负类。
对应正类中的所有点。
对应负类中的所有点。
对应超平面,所有位于超平面上的
都会满足这个条件。
对应正类中的所有点。这意味着分类现在仅由向量 w 和 x 之间的角度决定。如果输入向量 x 与权重参数向量 w 之间的角度在
度到
度之间,则输出分类为正。
对应负类中的点。在不同的分类算法中,相等条件被不同地对待。对于感知器,超平面上的点被视为属于负类。
则预测类
,否则
。对于感知器分类器,超平面上的点通常被认为属于负类。
,则更新权重向量为
,则更新权重向量为
,w 不需要更新。
,预测值为
= 0。由于
,数据点将被归类为 0,这与 1 的实际类别不匹配。因此,根据感知器规则更新的权重向量应该是
。
,预测值为
= 1。由于
,数据点将被正确分类为 1。因此,权重向量没有更新,它停留在
。
,预测值为
= 2。由于
,数据点将被归类为 1,这与 0 的实际类别不匹配。因此,更新后的权重向量应该是
。
,预测值为
= 0。由于
,数据点将被正确分类为 0。因此,没有对权重向量 w 的更新
,因此被错误归类为 0 类。
,因此被错误归类为 0 类。
,因此被正确分类为 0 类。
,因此被正确分类为 0 类。
,因此被错误归类为 0 类。因此,根据感知器规则更新的权重是
。
,因此被错误归类为 0 类。因此,根据感知器规则更新的权重是
。
,因此被错误归类为 1 类。因此,根据感知器规则更新的权重是
。
,因此被错误归类为 1 类。因此,根据感知器规则更新的权重是
。
➤为 0,因此 y ➤为 1。
➤ ∞,因此 y ➤为 0。
= 1 and so
.
➤为 0,所以 y ➤为 1。
= 1,因此
。
。此外,将最大和最小允许重量更新分别设置为δmax和δmin。
—then increase the learning by a factor
. The update rule becomes

—then reduce the learning rate by a factor
. The update rule becomes

, the update rule is as follows:

时,权重由
给出,但是权重不与 x(τ)重叠,因此总和为 0。
的权重由
给出,x(τ)中唯一与权重重叠的元素是
,重叠的权重是 h( 0)。因此,卷积和为
h( 0) = 13 = 3。由此,
时,权重由
给出。元素 x(0)和 x(1)分别与权重 h(1)和 h(0)重叠。因此,卷积和是
权重由
给出时,元素 x(0)、x(1)和 x(2)分别与权重 h(2)、h(1)和 h(0)重叠。因此,卷积和是元素
。图 3-3d 显示了
的两个功能的重叠。
。
个邻域片,并对四个像素的亮度值求和。通过可训练的权重和偏差对总和进行缩放,然后通过 sigmoid 激活函数进行馈送。这与最大池化和平均池化有一点不同。
The output units are radial basis function (RBF) units instead of the SoftMax functions that we generally use. The 84 units of the fully connected layers had 84 connections to each of the classes and hence 84 corresponding weights. The 84 weights/class represent each class’s characteristics. If the inputs to those 84 units are very close to the weights corresponding to a class, then the inputs are more likely to belong to that class. In a SoftMax we look at the dot product of the inputs to each of the class’s weight vectors, while in RBF units we look at the Euclidean distance between the input and the output class representative’s weight vectors. The greater the Euclidean distance , the smaller the chance is of the input belonging to that class. The same can be converted to probability by exponentiating the negative of the distance and then normalizing over the different classes. The Euclidean distances over all the classes for an input record would act as the loss function for that input. Let
be the output vector of the fully connected layer. For each class, there would be 84 weight connections. If the representative class’s weight vector for the ith class is
then the output of the ith class unit can be given by the following:

to produce
, which contains the left singular vectors,
, which contains the singular values, and
, which contains the right singular vectors.![$$ {\left[X\right]}_{m\times n}={\left[U\right]}_{m\times m}{\left[\varSigma \right]}_{m\times n}{\left[{V}^T\right]}_{n\times n} $$](A448418_1_En_4_Chapter_Equad.gif)
after truncation, we would have![$$ {U}^{\hbox{'}}=\left[{u}_1{u}_{2\kern1.75em }{u}_3\dots {u}_k\right],\kern0.5em {\varSigma}^{\prime }=\left[\begin{array}{ccc}{\sigma}_1& \cdots & 0\ {}\vdots & \ddots & \vdots \ {}0& \cdots & {\sigma}_k\end{array}\right],\kern1em V{\hbox{'}}T=\left[\begin{array}{c}\begin{array}{c}{v_1}T\to \ {}{v_2}^T\to \ {}..\end{array}\ {}{v_k}^T\to \end{array}\right] $$](A448418_1_En_4_Chapter_Equae.gif)
被带到单词嵌入向量矩阵。单词向量具有对应于所选的 k 个奇异值的 k 个密集维数。因此,从稀疏的共现矩阵中,我们得到了词向量嵌入的密集表示。将有 m 个单词嵌入对应于处理过的语料库的每个单词。
。同样,如果我们希望预测 V 类,那么输出
和权重矩阵
和将存储状态连接到输出的权重矩阵
, where f is a chosen non-linear activation function.The dimension of
is n; i.e.,
The function f works element-wise on
to produce h t , and hence
and h t have the same dimension.If
, then the following holds true for h t :![$$ {h}_t=\left[\begin{array}{c}f\left({s}_{1t}\right)\ {}f\left({s}_{2t}\right)\ {}.\ {}.\ {}f\left({s}_{nt}\right)\end{array}\right] $$](A448418_1_En_4_Chapter_Equaq.gif)
can be expressed as SoftMax(W ho h t ). Just to keep the notations simple, the biases have not been mentioned. In every unit, we can add bias to the input before it is acted upon by the different functions. So, o t can be represented as
is the bias vector for the output units.Similarly, bias can be introduced in the memory units, and hence h t can be expressed as
is the bias vector at the memory units.


则更新
decide what information to forget from cell state
through the forget-gate layer. The forget gate looks at x t and
and assigns a number between 0 and 1 for each element in the cell state vector
. An output of 0 means totally forget the state while an output of 1 means keep the state completely. The forget gate output is computed as follows:

as input. The candidate cell state
is computed as follows:

are passed through an output gate, which outputs a value between 0 to 1 for each component in the cell state vector C t . The output of this gate is computed as follows:

and current input x t , the reset gate r t and the update gate z t are computed as follows:

is in determining the candidate new hidden state. The update gate determines how much the new candidate state should influence the new hidden state.
is computed as follows:

, which backpropagates the error at sequence step t to sequence step k so that the model learns long-distance dependencies or correlations. The expression for
, as we saw in the vanishing and exploding gradients section, is given by
is large, a vanishing-gradient condition will arise when the gradients of the activations in the hidden state units and/or the weights are less than 1 since the product of
in them would force the overall product to near zero. Sigmoid and tanh gradients are often less than 1 and saturate fast where they have near-zero gradients, thus making the problem of vanishing gradient more severe. Similarly, exploding gradients can happen when the weight connection u ii between the ith hidden to the ithhidden unit is greater than 1 since the product of
in them would make the term
exponentially large for large values of
Now, coming back to the GRU , when the update-gate output units in z t are close to 0, then from the equation (4.4.2),
0.On taking the partial derivative of h t (i) with respect to
in (4.4.3) we get the following:
is also close to 1 since it can be expressed as

良好采样的一个关键决定因素是非常明智地选择方差 S 2 。方差不应该太大,因为在这种情况下,下一个样本 X (2) 停留在当前样本 X (1) 附近的机会较小,在这种情况下,高概率区域可能不会被探索太多,因为下一个样本在大多数时间被选择为远离当前样本。同时,方差不应该太小。在这种情况下,下一个样本将几乎总是停留在当前点附近,因此探索远离当前区域的不同高概率区域的概率将降低。
则接受 X (2) 并将其作为有效样本点。接受的样本成为生成下一个样本的 X (1) 。
被接受;即 U [0,1]。
by sampling it from a probability distribution conditioned on the rest of the variables. In other words, draw
from
variables before x j their values for the
instance are considered since they have already been sampled, while for the rest of the variables their values at instance t are considered since they are yet to be sampled. This step is repeated for all the variables.If each x j is discrete and can take, let’s say, two values 0 and 1, then we need to compute the probability p 1 =
. We can then draw a sample u from a uniform probability distribution between 0 and 1 (i.e., U[0, 1]), and if
set
, else set
. This kind of random heuristics ensure that the higher the probability p 1is, the greater the chances are of
getting selected as 1. However, it still leaves room for 0 getting selected with very low probability, even if p 1 is relatively large, thus ensuring that the Markov Chain doesn’t get stuck in a local region and can explore other potential high-density regions as well. There are the same kind of heuristics that we saw for the Metropolis algorithm as well.
,其中 d 是隐藏层,n 是输入层的维度,则自编码器作为数据压缩网络工作,将数据从高维空间投影到由隐藏层给出的低维空间。这是一种有损数据压缩技术。它还可以用于降低输入信号中的噪声。
和所有的激活函数都是线性的,那么网络学习进行线性 PCA(主成分分析)。
和激活函数是线性的时,网络可以学习一个身份函数,这可能没有任何用处。然而,如果成本函数被正则化以产生稀疏隐藏表示,则网络仍然可以学习数据的有趣表示。
, where i represents the ith image in the training set, then the mean normalized image for x (i) is given by


, but since C is a symmetric matrix,
in this case. U gives the Eigen vectors of the covariance matrix. The Eigen vectors are aligned in a column-wise fashion in U. The variances of the data along the direction of the Eigen vectors are given by the Eigen values housed along the diagonals of D, while the rest of the entries in D are zero since D is the covariance matrix for the uncorrelated directions given by the Eigen vectors.


belong to C 2. The variance between the two segments is determined by the sum of the square deviation of the mean of the clusters with respect to the global mean. The square deviations are weighted by the probability of each cluster.


that maximizes the variance would provide us with the optimal threshold to use for segmentation using Otsu’s method:
, one can evaluate the var(C 1, C 2) at all values of
and then choose the
at which the var(C 1, C 2) is maximum.


,并设置为零。
,即梯度向量
为零的每个点处的二阶导数矩阵。如果 Hessian 矩阵在评估点既有正特征值又有负特征值,则该点是鞍点。
中抽取 m 个样本{ 
中抽取 m 个样本{ 
![$$ {\theta}_D\to {\theta}_D-{\nabla}_{\theta_D}\left[-\frac{1}{m}\sum \limits_{i=1}^m\left(\log D\left({x}^{(i)}\right)\right)+\log \left(1-D\left(G\left({z}^{(i)}\right)\right)\right)\right] $$](A448418_1_En_6_Chapter_Equan.gif)
![$$ {\theta}_G\to {\theta}_G-{\nabla}_{\theta_G}\left[\frac{1}{m}\sum \limits_{i=1}^m\log \left(1-D\left(G\left({z}^{(i)}\right)\right)\right)\right] $$](A448418_1_En_6_Chapter_Equao.gif)
中抽取 m 个样本{ 
浙公网安备 33010602011771号