这一章告诉你如何用TensorFlow构建简单的机器学习系统。第一部分回顾构建机器学习系统的基础特别是讲函数,连续性,可微性。接着我们介绍损失函数,然后讨论机器学习归根于找到复杂的损失函数最小化的点的能力。我们然后讲梯度下降,解释它如何使损失最小。然后简单的讨论自动微分的算法思想。第二节侧重于介绍基于这些数学思想的TensorFlow概念。包括placeholders, scopes, optimizers,和TensorBoard, 并实际构建和分析学习系统。最后一节研究如何用TensorFlow训练线性和逻辑回归模型。这一章较长,并介绍许多新的思想。如果你不想了解每一个细节,你可以继续往下读然后在必要时返回来。在后面我们将重复这些基础。
2.神经元
这一章我们讨论神经元和它的组成。我将澄清我们需要的数学标记并覆盖现今神经网络使用的许多激活函数。详细的讨论梯度下降优化,学习速率的概念,并介绍它的异常行为。为了让事情更有趣,我们用一个神经元来进行线性回归和逻辑回归,使用实际数据集。然后讨论和解释如何用tensorflow实现这两种算法。
为了学习更有效率,我去掉了一些东西。例如,我们不分割数据集到训练集和测试集。我们只简单的使用所有的数据。使用这两个数据集会使我们做一些分析,这会偏离这一章的目的,让这一章变得很长。本书后面我们进行几个数据集的适当的分析,并看何何才合适,特别是对于深度学习上下文。这是一个需要它自已成章的主题。你可以用深度学习进行很有趣的事情。我们开始吧!
神经元的结构
深度学习基于大量的复杂的由大量的简单的计算单元构成的网络。最前沿的研究公司处理 160 billion个参数的神经网络。这个数量可以是银河系的星星的数量的一半,或者是地球上人口总数的1.5倍。本质上,神经网络是大量不同的相互连接的单元,每个单元进行具体的计算。 它们称为乐高玩具,你可以用基本单元构建非常复杂的东西。 神经网络非常相似,用相对简单的计算单元,你可以构建 非常复杂的系统。我们可以变更基本单元,改变它们的计算方法,改变它们之间的连接关系,改变它们如何使用输入,等等。 简单的公式化一下,所有的这些方面定义神经网络的架构。改变它就会改变它的学习方式,预测的准确度,等等。
对于这些单元称为神经元,平行于生物大脑。基本上,神经元进行非常简单的事情:取一定数量的输入 (实数)然后计算输出 (也是实数)。我们的输入记为 xi ∈ ℝ (实数),其中 i = 1, 2, …, nx, 其中 i ∈ ℕ 是整数且 nx是输入属性 (通常称为特征)。作为输入特征的例子,你可以想像一下人的年龄和体重 (所以我们有nx = 2)。 x1可以是年龄, x2可以是体重。在现实中,特征的数量可以很大。在后面的逻辑回归的例子里,我们有nx = 784。
有多种神经元被广泛的研究。本书我们关注最常见的几种。我们感兴趣的神经元简单的应用函数到所有输入的线性组合。更数学的形式,给定 nx, 实数参数wi ∈ ℝ ( i = 1, 2, …, nx), 和常数b ∈ ℝ (通常称为偏置), 神经元将计算文献里所标记的z。
然后它将函数 f作用于 z, 得到输出 yˆ。
注意 实践者使用下面的标记: wi 指权重, b指偏置 , xi 是输入特征, f是激活函数.
出于生物学平行,函数 f称为神经元激活函数 (有时称为这换函数), 我们在下一节讨论。
我们再次总结一下神经元的计算步骤.
- 线性组合所有的输入 xi, 计算
2.应用函数f到 z, 得到输出
你可能记得在第2章, 我讨论的计算图。在图 3-3, 你可以看到前面描述的神经元的计算图。
图 3-3. 神经元的计算图
这不是你通常在博客、书本或教程里看到的。当你想画很多神经元的网络时,通常很复杂和不是很实用。在文献里,你可以找到神经元的多种表示。本书,我们使用图 3-4的表示,因为它使用广容易理解。
图 3-4. 实践者使用最多的神经元的表示方法
图3-4必须用下面的方法解释:
- 输入不放在泡泡里,这是为了与进行实际计算的节点相区别。
- 权重的名称与箭头放在一起。这意味着传递输入到中心泡泡(或节点)之前,输入必须与相关的箭头所示的权重相乘。第一个输入 x1,乘于 w1, x2乘以w2, 等等。
- 中心泡泡 (或节点)同时进行多个计算。首先,它求和输入( xiwi 对于 i = 1, 2, …, nx), 然后将结果与偏置 b相加,最后将结果应用到激活函数.
本书的所有神经元都有这种相同的结构。通常更简单的表示如图 3-3.这种情况,除非另有说明,理解它的输出为
图 3-5. 下面是 3-4的简化表示。除非另有说明,通常理解输出为
,权重通常不在神经元里显式的表示。
矩阵标记
当处理大数据集时,特征的数量很大 (nx 可能很大),所以最好用向量表示特征和权重:
为了与后面的公式一致,我们为了 x 乘 w, 我们使用矩阵乘标记,因此我们写作
其中 wT 表示 w的转置。
神经元的输出 yˆ 为
yˆ = f (z ) = f (wT x + b) #(3)
我们总结一下神经元的不同组份以及我们在本书使用的标记。
- yˆ → 神经元的输出
- f(z) → 应用到z的激活函数 (或者变换函数)
- w → 权重 (有 nx 个元素的向量)
- b → 偏置
Python实现: 循环和NumPy
要等式 (3)可以用Python标准库和循环进行计算,但随着变量和观察数的增加这会很慢。一个很好的经验是避免使用循环,而是尽可能的使用 NumPy (或TensorFlow, 后面看到)方法。
很容易的得到NumPy是多么的快。我们从创建两个随机数的标准列表开始,每个列表有 107 个元素.
#List3-1
import random
lst1 = random.sample(range(1, 10**8),10**7)
lst2 = random.sample(range(1,10**8),10**7)
实际值与我们的目的无关。我们只感兴趣于Python乘两个列表有多快,按元素乘。我们2017年记录的时间,用 Microsoft surface笔记本电脑,变化很大,取决于硬件。我们不感兴趣于绝对值,只是NumPy与标准循环相比快多少倍。要记录Python 代码的运行时间,在Jupyter notebook里,我们可以使用魔法命令。通常,在Jupyter notebook里,这些命令以 %% 或 %开始。很好的办法是检查官方文档 Built-in magic commands — IPython 9.3.0 documentation以更好的理解这是如何工作的。
回到我们的测试,我们测试一下笔记本电脑元素级的乘两个列表要多长时间,使用标准的循环。代码如下:
%%timeit
ab = [lst1[i]*lst2[i] for i in range(len(lst1))]
得到如下结果 (注意,你的电脑可能不一样的结果):
2.06 s ± 326 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
经过7次运行,代码需要约2秒钟。现在我们进行相同的乘操作,但是这次使用NumPy,我们首先转换两个列表到 NumPy数组,用下面的代码:
#List3-2
import numpy as np list1_np =np.array(lst1)
list2_np = np.array(lst2)
%%timeit
Out2 = np.multiply(list1_np, list2_np)
这次,我们得到下面的结果:
20.8 ms ± 2.5 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
numpy 代码只需要21 ms,换句话,大约是标准循环的100倍速率。 NumPy更快的原因是:例程用C书写, 尽可能的使用向量化代码以加速大数据计算。
注意 向量化代码指对向量里的多个元素的乘操作是在同一时间(同一语句)进行的。传递矩阵到NumPy函数是很好的向量化例子。 NumPy可以同时进行很大的数据块的操作,相对标准循环有更好的性能,标准循环一次操作一个元素。注意,另一个原因是 NumPy的底层用C书写。
当你训练深度学习模型时,你会发现你不断重复这样的操作,这种速率差别会使你可以得到训练的模型和根本得不到结果。