本文为《动手学深度学习》一书的第二章部分,书籍地址
机器学习通常需要处理大型数据集。我们可以将数据集视为表,其中表的行对应样本,列对应属性。线性代数为我们提供了一些用来处理表格数据的方法。
深度学习是关于优化的。我们有一个带有参数的模型,想要找到其中能拟合数据的最好模型。在算法的每个步骤中,决定以何种方式调整参数需要一点微积分知识。
机器学习还涉及如何做出预测:给定我们观察到的信息,某些未知属性可能的值是多少?要在不确定的情况下进行严格的推理,我们需要借用概率语言。
为了能够完成各种操作,我们需要某种方法来存储和操作数据。一般来说需要做两件事情:
(1)获取数据;
(2)在将数据读入计算机后对其进行处理。
首先,我们介绍 维数组,也称为张量(tensor)。
张量表示由一个数值组成的数组,这个数组可能有多个维度。具有一个轴的张量对应数学上的向量(vector)。具有两个轴的张量对应数学上的 矩阵(matrix)。具有两个轴以上的张量没有特殊的数学名称。
x = tf.range(a)
:创建一个行向量x,包含从0开始的a个整数。除非额外指定,否则新的张量将存储在内存中,并采用基于CPU的计算。
x.shape:可以通过张量的 shape 属性来访问张量的形状
tf.size(x)
:如果只想知道张量中元素的总数,即形状的所有元素乘积,可以检查它的大小(size)。
X = tf.reshape(x, (3,4))
:要改变一个张量的形状而不改变元素数量和元素值,可以调用 reshape 函数。
tf.zeros((2,3,4,)); tf.ones((2, 3, 4))
:有时,我们希望使用全0、全1、其他常量或者从特定分布中随机采样的数字来初始化矩阵
tf.random.normal(shape=[3, 4])
:通过从某个特定的概率分布中随机采样来得到张量中每个元素的值。例如,当我们构造数组来作为神经网络中的参数时,我们通常会随机初始化参数的值。以下代码创建一个形状为 (3, 4) 的张量。其中的每个元素都从均值为0、标准差为1的标准高斯(正态)分布中随机采样。
我们想在这些数组上执行数学运算。一些最简单且最有用的操作是 按元素(elementwise) 操作。它们将标准标量运算符应用于数组的每个元素。对于将两个数组作为输入的函数,按元素运算将二元运算符应用于两个数组中的每对位置对应的元素。我们可以基于任何从标量到标量的函数来创建按元素函数。
给定同一形状的任意两个向量 和 和二元运算符 ,我们可以得到向量 =(,) 。具体计算方法是 ←(,) ,其中 、 和 分别是向量 、 和 中的元素。
x + y, x - y, x * y, x / y, x ** y
对于任意具有相同形状的张量,[常见的标准算术运算符(+、-、* 、/ 和 )都可以被升级为按元素运算]。我们可以在同一形状的任意两个张量上调用按元素操作。
tf.concat([X, Y], axis=0), tf.concat([X, Y], axis=1)
我们也可以把多个张量 连结(concatenate) 在一起,把它们端对端地叠起来形成一个更大的张量。 我们只需要提供张量列表,并给出沿哪个轴连结。可以沿行(轴-0,形状的第一个元素)和按列(轴-1,形状的第二个元素)连结两个矩阵。
X == Y
通过 逻辑运算符构建二元张量
tf.reduce_sum(X)
对张量中的所有元素进行求和会产生一个只有一个元素的张量。
在上面的部分中,我们看到了如何在相同形状的两个张量上执行按元素操作。在某些情况下,即使形状不同,我们仍然可以通过调用 广播机制 (broadcasting mechanism) 来执行按元素操作。这种机制的工作方式如下:首先,通过适当复制元素来扩展一个或两个数组,以便在转换之后,两个张量具有相同的形状。其次,对生成的数组执行按元素操作。
就像在任何其他 Python 数组中一样,张量中的元素可以通过索引访问。与任何 Python 数组一样:第一个元素的索引是 0;可以指定范围以包含第一个元素和最后一个之前的元素。与标准 Python 列表一样,我们可以通过使用负索引根据元素到列表尾部的相对位置访问元素。
X[-1], X[1:3]
:可以用 [-1] 选择最后一个元素,可以用 [1:3] 选择第二个和第三个元素
TensorFlow中的 Tensors 是不可变的,也不能被赋值。TensorFlow中的 Variables 是支持赋值的可变容器。请记住,TensorFlow中的梯度不会通过 Variable 反向传播。
X_var[1, 2].assign(9)
:可以通过索引来写入 Variable 的元素
X_var[0:2, :].assign(tf.ones(X_var[0:2,:].shape, dtype = tf.float32) * 12)
:为多个元素赋值相同的值,我们只需要索引所有元素,然后为它们赋值。
运行一些操作可能会导致为新结果分配内存。例如,如果我们用 Y = X + Y,我们将取消引用 Y 指向的张量,而是指向新分配的内存处的张量。
Variables 是TensorFlow中的可变容器。它们提供了一种存储模型参数的方法。我们可以通过assign将一个操作的结果分配给一个 Variable。
a.item(), float(a), int(a)
:将大小为1的张量转换为 Python 标量。大小非1的张量无法转换为 Python 标量
深度学习存储和操作数据的主要接口是张量( 维数组)。它提供了各种功能,包括基本数学运算、广播、索引、切片、内存节省和转换其他 Python 对象。
为了能用深度学习来解决现实世界的问题,我们经常从预处理原始数据开始,而不是从那些准备好的张量格式数据开始。在Python中常用的数据分析工具中,通常使用pandas软件包。像庞大的Python生态系统中的许多其他扩展包一样,pandas可以与张量兼容。
此部分知识可以去看Pandas教学部分内容,需要经常的练习才能有良好的使用体验,下面只介绍几种简单的使用
pd.read_csv(data_file)
:从CSV文件中加载原始数据集
为了处理缺失的数据,典型的方法包括插值和删除,其中插值用替代值代替缺失值。而删除则忽略缺失值。
现在所有条目都是数值类型,它们可以转换为张量格式。
仅包含一个数值的叫标量(scalar),标量由只有一个元素的张量表示
可以将向量视为标量值组成的列表,我们将这些标量值称为向量的元素(elements)或分量(components)。当我们的向量表示数据集中的样本时,它们的值具有一定的现实意义。例如,如果我们正在训练一个模型来预测贷款违约风险,我们可能会将每个申请人与一个向量相关联,其分量与其收入、工作年限、过往违约次数和其他因素相对应。
可以通过一维张量处理向量。一般来说,张量可以具有任意长度,取决于机器的内存限制。
x[i]
:通过张量的索引来访问任一元素
长度、纬度、形状
len(x)
:我们可以通过调用Python的内置len()函数来[访问张量的长度]
x.shape
:我们也可以通过.shape属性访问向量的长度。形状(shape)是一个元组,列出了张量沿每个轴的长度(维数)。对于(只有一个轴的张量,形状只有一个元素。)
A = tf.reshape(tf.range(20), (5, 4))
:当调用函数来实例化张量时,我们可以通过指定两个分量 和 来创建一个形状为 × 的矩阵。
tf.transpose(A)
:矩阵的转置)
A == tf.transpose(A)
:对称矩阵(symmetric matrix) 等于其转置: =⊤
就像向量是标量的推广,矩阵是向量的推广一样,我们可以构建具有更多轴的数据结构
当我们开始处理图像时,张量将变得更加重要,图像以 维数组形式出现,其中3个轴对应于高度、宽度,以及一个通道(channel)轴,用于堆叠颜色通道(红色、绿色和蓝色)。
给定具有相同形状的任意两个张量,任何按元素二元运算的结果都将是相同形状的张量
A + B
A * B : 两个矩阵的按元素乘法称为哈达玛积(Hadamard product)(数学符号 ⊙ )
a = 2, a + X;a * X: 将张量乘以或加上一个标量不会改变张量的形状,其中张量的每个元素都将与标量相加或相乘。
我们可以对任意张量进行的一个有用的操作是[计算其元素的和]
tf.reduce_sum(x)
:求和函数,可以表示任意形状张量的元素和。
A_sum_axis0 = tf.reduce_sum(A, axis=0)
:指定张量沿哪一个轴来通过求和降低维度。以矩阵为例,为了通过求和所有行的元素来降维(轴0),我们可以在调用函数时指定axis=0。将参数改为1即为对所有行求和,也就是汇总所有列的元素降维
reduce_sum(A, axis=[0, 1])
:沿着行和列对矩阵求和,等价于对矩阵的所有元素进行求和
tf.reduce_mean(A), tf.reduce_sum(A) / tf.size(A).numpy()
:平均值(mean或average),可以调用函数来计算任意形状张量的平均值
tf.reduce_mean(A, axis=0), tf.reduce_sum(A, axis=0) / A.shape[0]
:计算平均值的函数也可以沿指定轴降低张量的维度。
有时在调用函数来计算总和或均值时保持轴数不变会很有用,可以使用tf.reduce_sum(A,axis=1,keepdims=True)
来保证轴数不变,从而通过广播将A除以求和后的张量,就可以进行计算。
tf.cumsum(A, axis=0)
:如果我们想沿[某个轴计算A元素的累积总和],比如axis=0(按行计算),我们可以调用cumsum函数。此函数不会沿任何轴降低输入张量的维度。
给定两个向量 ,∈ℝ ,它们的点积(dotproduct) ⊤ (或 ⟨,⟩ )是相同位置的按元素乘积的和。
tf.tensordot(x, y, axes=1)
:这里需要加上axes=1这个参数,否则可能会因为两个都是行向量而报错。我们也可以通过执行按元素乘法,然后进行求和来表示两个向量的点积
如图所示,向量积即为矩阵中每一行向量与列向量x的点积:tf.linalg.matvec(A, x)
我们可以将矩阵-矩阵乘法 看作是简单地执行 次矩阵-向量积,并将结果拼接在一起,形成一个 × 矩阵:tf.matmul(A, B)
线性代数中最有用的一些运算符是范数(norms)。非正式地说,一个向量的范数告诉我们一个向量有多大。 这里考虑的大小(size)概念不涉及维度,而是分量的大小。
在线性代数中,向量范数是将向量映射到标量的函数 。向量范数要满足一些属性。 给定任意向量 x \mathbf{x} x ,第一个性质说,如果我们按常数因子 缩放向量的所有元素,其范数也会按相同常数因子的绝对值缩放:
f ( α x ) = ∣ α ∣ f ( x ) . f(\alpha \mathbf{x}) = |\alpha| f(\mathbf{x}). f(αx)=∣α∣f(x).
第二个性质是我们熟悉的三角不等式:
f ( x + y ) ≤ f ( x ) + f ( y ) . f(\mathbf{x} + \mathbf{y}) \leq f(\mathbf{x}) + f(\mathbf{y}). f(x+y)≤f(x)+f(y).
第三个性质简单地说范数必须是非负的:
f ( x ) ≥ 0. f(\mathbf{x}) \geq 0. f(x)≥0.
因为在大多数情况下,任何东西的最小的大小是0。最后一个性质要求范数最小为0,当且仅当向量全由0组成。
∀ i , [ x ] i = 0 ⇔ f ( x ) = 0. \forall i, [\mathbf{x}]_i = 0 \Leftrightarrow f(\mathbf{x})=0. ∀i,[x]i=0⇔f(x)=0.
此处为数学中二范数、1范数、p范数相关的知识,也是在学习中使用最多的。2 范数是向量元素平方和的平方根。1 范数,它表示为向量元素的绝对值之和。2 范数和1范数都是更一般的范数的特例
∥ x ∥ p = ( ∑ i = 1 n ∣ x i ∣ p ) 1 / p . \|\mathbf{x}\|_p = \left(\sum_{i=1}^n \left|x_i \right|^p \right)^{1/p}. ∥x∥p=(i=1∑n∣xi∣p)1/p.
以及弗罗贝尼乌斯范数(Frobenius norm)是矩阵元素平方和的平方根:
∥ X ∥ F = ∑ i = 1 m ∑ j = 1 n x i j 2 . \|\mathbf{X}\|_F = \sqrt{\sum_{i=1}^m \sum_{j=1}^n x_{ij}^2}. ∥X∥F=i=1∑mj=1∑nxij2.
在深度学习中,我们经常试图解决优化问题: 最大化分配给观测数据的概率; 最小化预测和真实观测之间的距离。 用向量表示物品(如单词、产品或新闻文章),以便最小化相似项目之间的距离,最大化不同项目之间的距离。 通常,目标,或许是深度学习算法最重要的组成部分(除了数据),被表达为范数。
在深度学习中,我们“训练”模型,不断更新它们,使它们在看到越来越多的数据时变得越来越好。通常情况下,变得更好意味着最小化一个损失函数(loss function),即一个衡量“我们的模型有多糟糕”这个问题的分数。这个问题比看上去要微妙得多。最终,我们真正关心的是生成一个能够在我们从未见过的数据上表现良好的模型。但我们只能将模型与我们实际能看到的数据相拟合。因此,我们可以将拟合模型的任务分解为两个关键问题:
(1)优化(optimization):用模型拟合观测数据的过程;
(2)泛化(generalization):数学原理和实践者的智慧,能够指导我们生成出有效性超出用于训练的数据集本身的模型。
给定 y = f ( x ) y=f(x) y=f(x),其中 x x x和 y y y分别是函数 f f f的自变量和因变量。以下表达式是等价的:
f ′ ( x ) = y ′ = d y d x = d f d x = d d x f ( x ) = D f ( x ) = D x f ( x ) , f'(x) = y' = \frac{dy}{dx} = \frac{df}{dx} = \frac{d}{dx} f(x) = Df(x) = D_x f(x), f′(x)=y′=dxdy=dxdf=dxdf(x)=Df(x)=Dxf(x),
其中符号 d d x \frac{d}{dx} dxd和 D D D是微分运算符,表示微分操作。我们可以使用以下规则来对常见函数求微分:
为了微分一个由一些简单函数(如上面的常见函数)组成的函数,下面的法则使用起来很方便。
假设函数 f f f和 g g g都是可微的, C C C是一个常数,我们有:
常数相乘法则
d d x [ C f ( x ) ] = C d d x f ( x ) , \frac{d}{dx} [Cf(x)] = C \frac{d}{dx} f(x), dxd[Cf(x)]=Cdxdf(x),
加法法则
d d x [ f ( x ) + g ( x ) ] = d d x f ( x ) + d d x g ( x ) , \frac{d}{dx} [f(x) + g(x)] = \frac{d}{dx} f(x) + \frac{d}{dx} g(x), dxd[f(x)+g(x)]=dxdf(x)+dxdg(x),
乘法法则
d d x [ f ( x ) g ( x ) ] = f ( x ) d d x [ g ( x ) ] + g ( x ) d d x [ f ( x ) ] , \frac{d}{dx} [f(x)g(x)] = f(x) \frac{d}{dx} [g(x)] + g(x) \frac{d}{dx} [f(x)], dxd[f(x)g(x)]=f(x)dxd[g(x)]+g(x)dxd[f(x)],
除法法则
d d x [ f ( x ) g ( x ) ] = g ( x ) d d x [ f ( x ) ] − f ( x ) d d x [ g ( x ) ] [ g ( x ) ] 2 . \frac{d}{dx} \left[\frac{f(x)}{g(x)}\right] = \frac{g(x) \frac{d}{dx} [f(x)] - f(x) \frac{d}{dx} [g(x)]}{[g(x)]^2}. dxd[g(x)f(x)]=[g(x)]2g(x)dxd[f(x)]−f(x)dxd[g(x)].
现在我们可以应用上述几个法则来计算 u ′ = f ′ ( x ) = 3 d d x x 2 − 4 d d x x = 6 x − 4 u'=f'(x)=3\frac{d}{dx}x^2-4\frac{d}{dx}x=6x-4 u′=f′(x)=3dxdx2−4dxdx=6x−4。因此,通过令 x = 1 x=1 x=1,我们有 u ′ = 2 u'=2 u′=2:这一点得到了我们在本节前面的实验的支持,在这个实验中,数值结果接近 2 2 2。当 x = 1 x=1 x=1时,此导数也是曲线 u = f ( x ) u=f(x) u=f(x)切线的斜率。
∂ y ∂ x i = lim h → 0 f ( x 1 , … , x i − 1 , x i + h , x i + 1 , … , x n ) − f ( x 1 , … , x i , … , x n ) h . \frac{\partial y}{\partial x_i} = \lim_{h \rightarrow 0} \frac{f(x_1, \ldots, x_{i-1}, x_i+h, x_{i+1}, \ldots, x_n) - f(x_1, \ldots, x_i, \ldots, x_n)}{h}. ∂xi∂y=h→0limhf(x1,…,xi−1,xi+h,xi+1,…,xn)−f(x1,…,xi,…,xn).
连结一个多元函数对其所有变量的偏导数,可以得到该函数的梯度(gradient)向量
∇ x f ( x ) = [ ∂ f ( x ) ∂ x 1 , ∂ f ( x ) ∂ x 2 , … , ∂ f ( x ) ∂ x n ] ⊤ , \nabla_{\mathbf{x}} f(\mathbf{x}) = \bigg[\frac{\partial f(\mathbf{x})}{\partial x_1}, \frac{\partial f(\mathbf{x})}{\partial x_2}, \ldots, \frac{\partial f(\mathbf{x})}{\partial x_n}\bigg]^\top, ∇xf(x)=[∂x1∂f(x),∂x2∂f(x),…,∂xn∂f(x)]⊤,
在深度学习中,多元函数通常是复合(composite)的,所以我们可能没法应用上述任何规则来微分这些函数。 但是,链式法则使我们能够微分复合函数。
深度学习框架通过自动计算导数,即自动求导(automatic differentiation),来加快这项工作。实际中,根据我们设计的模型,系统会构建一个计算图(computational graph),来跟踪计算是哪些数据通过哪些操作组合起来产生输出。自动求导使系统能够随后反向传播梯度。 这里,反向传播(backpropagate)只是意味着跟踪整个计算图,填充关于每个参数的偏导数。
在计算y关于x的梯度之前,我们要设置一个地方来存储梯度,为了在每次对一个参数求导时都不用分配新的内存而导致内存耗尽的情况。
注1:标量函数关于向量x的梯度是向量,并且和x具有相同的形状
注2:分配了新的存储梯度的空间之后,自动求导才能出来结果,不然没有out
t.gradient(y,x)
:自动求导
有时,我们希望将某些计算移动到记录的计算图之外。 例如,假设y是作为x的函数计算的,而z则是作为y和x的函数计算的。 现在,我们想计算z关于x的梯度,但由于某种原因,我们希望将y视为一个常数,并且只考虑到x在y被计算后发挥的作用。
在这里,我们可以分离y来返回一个新变量u,该变量与y具有相同的值,但丢弃计算图中如何计算y的任何信息。这样子梯度不会向后流经u到x。
机器学习从某种形式上来说,就是做预测,预测也是有难度的,尤其是在低分辨率的预测。概率的作用就是给了我们一种正式的途径来说明确定性水平,比如无法百分百确定一个图片是猫,那么就可以说这张图是猫的概率有P(y = 猫) = p,其中0<=p<1
古典概型,最暴力的方法就是将其在索引i处的值为采样结果中i出现的次数,用i除以总次数即可得到大概的概率。当采样的次数足够多时,即可将其作为真实概率的估计值。
离散随机变量
连续随机变量:将其化为a到b之间更有意义,一个区间内的密度值。
P ( A = a , B = b ) P(A=a,B=b) P(A=a,B=b), A = a A=a A=a和 B = b B=b B=b同时满足的概率是多少
P ( B = b ∣ A = a ) P(B=b \mid A=a) P(B=b∣A=a)
P ( A ∣ B ) = P ( B ∣ A ) P ( A ) P ( B ) . P(A \mid B) = \frac{P(B \mid A) P(A)}{P(B)}. P(A∣B)=P(B)P(B∣A)P(A).
也叫做求和规则,用来从另一件事中推断一件事而做,比如A和B两个事件,我们只知道B的而不知道A的属性,即可用求和的方法,把所有的概率压缩到A上。
P ( B ) = ∑ A P ( A , B ) , P(B) = \sum_{A} P(A, B), P(B)=A∑P(A,B),
AB两事件独立,二者概率不互相影响,所以在A发生条件下的B事件的发生概率跟B自己的发生概率相同。
本书的第二部分是预备知识的部分,包括pandas的基本使用、微分、线性代数、概率学的部分知识,其中数学部分复习一下之前学过的东西,基本都能够想起一些。pandas如果觉得用起来不熟悉的话可以找一两个数据集自己操作一下,一些常用的命令和方法会了之后,有需要时查阅工具书即可。