可以通过在⽹络中加⼊⼀个或多个隐藏层来克服线性模型的限制,使其能处理更普遍的函数关系类型。要做到这⼀点,最简单的⽅法是将许多全连接层堆叠在⼀起。每⼀层都输出到上⾯的层,直到⽣成最后的输出。
上面红框的公式其实换个角度是没错的。实际上,XW和b都是矩阵,所以它们符合矩阵加法规则。具体来说,假设X是一个n×d的矩阵,W是一个d×h的矩阵,b是一个1×h的行向量,那么XW是一个n×h的矩阵,b也可以被扩展为一个n×h的矩阵,使得在矩阵加法时可以直接与XW相加。这种扩展b的方法叫做广播(broadcasting),在深度学习中经常用到。
举个例子,假设X是一个2×3的矩阵:
X = [[1, 2, 3],
[4, 5, 6]]
W是一个3×2的矩阵:
W = [[0.1, 0.2],
[0.3, 0.4],
[0.5, 0.6]]
b是一个1×2的行向量:
b = [0.5, 0.7]
那么XW的结果是一个2×2的矩阵:
XW = [[3.2, 4.3],
[9.8, 12.9]]
现在我们需要把b扩展成一个2×2的矩阵,才能将其与XW相加。具体来说,我们需要让b在第一维(即行)上重复2次,在第二维(即列)上不变。这可以使用NumPy库中的np.newaxis和np.repeat函数实现:
import numpy as np
b = b[np.newaxis, :] # b变为1x2的矩阵
b = np.repeat(b, 2, axis=0) # 在第一维上重复2次,变为2x2的矩阵
现在b是一个2×2的矩阵:
b = [[0.5, 0.7],
[0.5, 0.7]]
最后,我们将XW和b相加,得到:
XW + b = [[3.7, 5.0],
[10.3, 13.6]]
这就是广播的过程。在深度学习中,如果两个矩阵不满足加法规则,那么就可以利用广播来扩展其中一个矩阵,使得它们可以相加。
如果仿射函数本来就是类似XW+b的话,无论多少层这样的仿射函数的符合叠加都是等价于直接的一层线性仿射函数,这样并没有为模型引入新的表示能力。
多层感知机(Multilayer Perceptron,MLP)的结构可以看作是数学中的复合函数。每个隐藏层都可以看作是输入经过仿射变换(线性函数)和激活函数组合而成的函数,而输出层又是对隐藏层输出进行类似的组合。
在多层感知机中,输入信号经过第一层隐藏层的仿射变换和激活函数得到隐藏层的输出,然后该输出又作为下一层隐藏层的输入,以此类推直到最后一个隐藏层。最后一个隐藏层的输出再经过仿射变换和激活函数得到最终的输出结果。
这种堆叠隐藏层的方式使得多层感知机能够逐渐提取输入数据的抽象特征,从而增强模型的表达能力。类似于复合函数,每一层隐藏单元的输出都会作为下一层隐藏单元的输入,通过多层的组合运算,模型能够学习更加复杂和高级的表示。
因此,可以将多层感知机视为一个复合函数,其中每一层隐藏层都相当于复合函数中的一个函数,整个模型的参数通过梯度下降等方法来优化,以使得模型能够捕捉到输入数据中的有效模式和关系。
单隐层网络确实可以理论上表示和学习任何函数,这被称为"万能逼近定理"。然而,在实际应用中,使用更深的网络(多个隐藏层)通常更具优势。
虽然单隐层网络有能力表示任意函数,但它可能需要大量的神经元才能实现复杂函数的逼近。而使用更深的网络结构,可以以更高效的方式逼近复杂函数。多层网络并不一定需要比单层网络使用更少的神经元。
事实上,在某些情况下,多层网络可能需要更多的神经元来处理复杂的函数逼近任务。深层网络之所以能够更好地逼近复杂函数,是因为它可以通过多个非线性变换来建立更高级别的表征和抽象。然而,每个隐藏层都需要足够数量的神经元来有效地表示和处理数据。在实践中,神经网络的层数和每层神经元的数量通常是根据问题的复杂性和规模进行调整的。对于简单的问题,单隐层网络可能足够,而对于更复杂的问题,需要更深的网络结构,并且可能需要更多的神经元来确保网络的表达能力。
因此,并不能简单地说多层网络就比单层网络使用更少的神经元,而是要根据具体问题和任务的需求来确定合适的网络结构和参数设置。深层网络可以通过一系列非线性变换来提取多层次的抽象特征,从而更好地捕捉数据的复杂性。
此外,深层网络还具有梯度传播更容易、参数共享和重复模式的利用等优点,这些都有助于提高网络的训练效率和性能。
在实践中,通常会选择使用更深的网络来解决复杂问题,而不是仅限于单隐层网络。
激活函数(activation function)通过计算加权和并加上偏置来确定神经元是否应该被激活,它们将输⼊信号 转换为输出的可微运算。⼤多数激活函数都是⾮线性的。
ReLU的优点:它求导表现得特别好:要么让参数消失,要么让参数通过。这使得优化表现得更好,并且ReLU减轻了困扰以往神经⽹络的梯度消失问题。
报错RuntimeError: grad can be implicitly created only for scalar outputs
这个报错是因为 torch.relu(x) 返回的 y 是一个张量,而不是一个标量。在使用 backward() 方法计算梯度时,该方法只适用于标量输出。
解决方法是将 y 转换为标量,可以使用聚合操作(如求和、平均值等)来实现。例如,可以使用 torch.sum() 对 y 进行求和操作,然后再调用 backward() 方法。
接着我发现下面的z.backward()和y.backward(torch.ones_like(x))其实效果是等价的。
%matplotlib inline
import torch
import matplotlib.pyplot as plt
from d2l import torch as d2l
x = torch.arange(-8.0, 8.0, 0.1, requires_grad=True)
y = torch.relu(x)
d2l.plot(x.detach(), y.detach(), 'x', 'relu(x)', figsize=(5, 2.5))
z = torch.sum(y)
z.backward() # 效果等价于y.backward(torch.ones_like(x))
d2l.plot(x.detach(), x.grad, 'x', 'grad of relu', figsize=(5, 2.5))
为什么会等价呢?
对于 y.backward(torch.ones_like(x)) 这种写法。
实际上,torch.ones_like(x) 返回的是一个与张量 x 具有相同形状的张量,其中所有元素的值都为 1。在反向传播过程中,torch.ones_like(x) 会被作为梯度权重使用。
具体来说,当调用 y.backward(torch.ones_like(x)) 时,PyTorch会计算关于 y 的梯度,并将这些梯度累积到 x 上。torch.ones_like(x) 的作用是为每个与 x 对应的梯度指定一个权重值,这个权重值是1。通过将这些相等的权重值传递给 backward(),PyTorch 将这些权重与梯度相乘并进行累积计算,从而得到最终的梯度值。
在调用 z.backward() 时,PyTorch会自动计算关于 z 的梯度,并将这些梯度通过计算图向后传播并累积到所有涉及到的叶子节点上,也就是 x。
因此,z.backward() 和 y.backward(torch.ones_like(x)) 的效果是一致的,它们都会计算出关于 x 的梯度。
当调用 z.backward() 时,PyTorch会自动计算关于变量 z 的梯度,并将这些梯度向后传播到所有涉及到的变量(包括向量)上。反向传播就是为了计算每个变量对最终输出的影响程度。
在这种情况下,我们有一个向量 x 和一个向量 y,其中 y 是通过对 x 进行一些操作得到的,这里是使用修正线性单元函数。现在我们想知道对于每个 x 中的元素,它们对最终输出 y 中的对应元素的影响有多大。
通过调用 z.backward(),我们可以得到关于 z 的梯度,并且这些梯度会根据链式法则自动传播回 x。这意味着我们可以获得 ∂z/∂x,即对于 x 中的每个元素,它们对 z 中的对应元素的影响程度。由于 z 是通过对 y 中的元素求和得到的,所以 ∂z/∂x 等于 ∂y/∂x。
嗯…感觉越解释越有点乱,最好还是自己动手跑跑看()
Sigmoid函数是一种常用的激活函数,通常用于二分类问题,将输入的实数映射到一个范围在0到1之间的概率值。它的输出只针对单个输入进行转换。
Softmax函数则是一种多分类问题的激活函数,它将一组实数作为输入,并将它们转换为概率值,使得所有输出的概率之和等于1。Softmax函数适用于具有多个类别的分类问题,可以将每个类别的概率分布表示出来。
当Softmax函数应用于二分类问题时,它会输出两个类别的概率分布,其和为1。而Sigmoid函数会将输入映射到一个范围在0到1之间的单一概率值,表示某个样本属于正例的概率。
因此,Softmax函数在二分类问题中并不能直接得到与Sigmoid函数相同的输出结果。如果只关注两个类别中的一个,可以使用Softmax函数的一个输出值作为正例的概率,并用1减去该值得到负例的概率。
常用激活函数↓
ReLu函数:
函数图像如下:
ReLu函数的导数图像如下,注意在x=0时,ReLu极限不存在,这里默认使用左侧的导数0:
sigmoid函数:
sigmoid函数图像如下:
sigmoid函数的导数图像如下:
tanh函数:
tanh函数图像如下:
tanh函数的导数图像如下,注意在x=0时,ReLu极限不存在,这里默认使用左侧的导数0:
p137习题:
1. 计算pReLU激活函数的导数。
答:
首先pReLU(Parametric ReLU)是一种带参数的修正线性单元激活函数,它的公式如下:
pReLU(x) = max(0, x) + α min(0, x).
其中,α为一个可学习的参数。
现在来计算pReLU函数的导数:
当x > 0时,pReLU(x) = x,此时导数为1。
当x < 0时,pReLU(x) = αx,此时导数为α。
当x = 0时,pReLU(x) = max(0, x)
当x > 0时,pReLU(x) = x
当x < 0时,pReLU(x) = αx
我们可以计算一下在x=0附近的极限:
lim(x->0+) (pReLU(x) - pReLU(0)) / x, lim(x->0+) (x - 0) / x = 1
lim(x->0-) (pReLU(x) - pReLU(0)) / x, lim(x->0-) (αx - 0) / x = α
那α为1就说明在0处可导了
综上所述,pReLU函数的导数可以表示为:
pReLU'(x) = 1, if x > 0
pReLU'(x) = α, if x < 0
x=0的话,看α的情况,如果为1则可导,不为1则不可导
在实际应用中,通常会对导数进行近似处理,比如取左导数或右导数来代替。
2. 证明⼀个仅使⽤ReLU(或pReLU)的多层感知机构造了⼀个连续的分段线性函数。
答:
连续的分段线性函数是指在定义域上具有多个线性段的函数,且在每个线性段上都是连续的。
下面是对于ReLU函数的证明,pReLU函数的证明过程类似。
首先,我们知道ReLU函数定义为:
ReLU(x) = max(0, x)
ReLU函数有以下性质:
对于x < 0,ReLU(x) = 0,该部分的函数图像为一条水平直线;
对于x > 0,ReLU(x) = x,该部分的函数图像为一条斜率为1且通过原点的直线;
在x=0处,ReLU(0) = 0,即函数图像在原点取值为0。
现在考虑一个多层感知机,每一层都只采用ReLU激活函数。假设这个多层感知机有L层,每一层的输出作为下一层的输入,最后一层输出为f(x)。
由于ReLU函数的性质,我们可以得出结论:
在每一层的节点中,所有输入小于0的神经元将激活为零,相当于去掉了那些非零斜率的部分,并保持输出为零;
所有输入大于等于0的神经元将保留其输出值,并传递给下一层。
因此,整个多层感知机实际上是将输入空间划分为一系列超平面的组合。每个ReLU激活函数在相应的超平面上决定输出值。
由于每一层都使用ReLU激活函数,整个多层感知机构成了一系列连续的分段线性函数,其特点是在不同的区域内具有线性性质,通过阈值划分输出值。
所以,一个仅使用ReLU(或pReLU)的多层感知机可以构造一个连续的分段线性函数。
请注意,以上证明是基于ReLU函数的情况,对于pReLU函数也可以进行类似的证明,只是在x < 0部分的线性函数会有斜率α而不是零。
ps:超平面是高维空间中的一个概念。在二维空间中,可以将超平面理解为一条无限延伸的直线。在三维空间中,超平面即是一个二维平面。更一般地,在n维空间中,超平面是一个n-1维的子空间,它将整个空间分割成两个互补的部分。超平面在机器学习和模式识别领域中经常被使用,特别是在分类问题中。通过训练数据,我们可以找到一个超平面,将不同类别的数据样本分隔开。在高维空间中,这个超平面变成了一个用于划分不同类别的决策边界。
另解:
假设我们有一个两层的多层感知机,每一层只有一个神经元。对于输入x,我们的网络可以表示为:
y = f(w2 * f(w1 * x + b1) + b2)
其中,f是ReLU(或pReLU)函数,w1和w2是权重,b1和b2是偏置。
我们可以将其展开为:
h1 = w1 * x + b1
h2 = f(h1)
y = w2 * h2 + b2
这里,h1是第一层的线性变换输出,h2是激活函数ReLU(或pReLU)作用后的输出,y是最终的输出。
现在我们来观察这个网络的行为。ReLU(或pReLU)函数的性质是:如果输入大于0,则保持输入不变,如果输入小于等于0,则将输入变为0。
根据这一性质,我们可以得出以下结论:
当h1 > 0时,h2 = f(h1) = h1 (ReLU函数保持输入不变)
当h1 ≤ 0时,h2 = f(h1) = 0 (ReLU函数将输入变为0)
因此,在不同的区间上,我们可以看到h2与h1线性相关,
在连接处并没有保持严格的连续性。
在每个线性段上,ReLU(或pReLU)函数会将输入划分为两个区域:当输入大于零时,输出与输入成比例;当输入小于等于零时,输出为零。
具体来说,在第一层的线性变换中,h1 = w1 * x + b1,表示输入与权重的线性组合。这样的线性变换将输入空间划分为两个区域:一部分是使h1大于零的输入值,一部分是使h1小于等于零的输入值。
然后,对h1应用ReLU(或pReLU)函数,我们得到h2。在连接处,从小于等于零的区域过渡到大于零的区域时,h2会从零逐渐增加,因此在连接处并不是严格的连续性。但是,在每个线性段内部,都是连续的,也就是说,对于每个线性段,输出在该段上是连续的。
最终,我们还需要对h2进行第二个线性变换,得到输出y。这个线性变换可以写作y = w2 * h2 + b2。这个公式代表了h2与最终输出y之间的线性关系。所以,整个网络可以看作是通过一系列线性变换和ReLU(或pReLU)操作的组合来构建一个连续的分段线性函数。每个线性段内部都是连续的,但在连接处并不是严格的连续性。
总体而言,我们的输出y是由h2通过一个线性变换加上偏置得到的。因此,我们可以得出结论:一个仅使用ReLU(或pReLU)的两层多层感知机构造了一个连续的分段线性函数。
3. 证明tanh(x) + 1 = 2 sigmoid(2x)
tanh(x) = (1-exp(-2x)) /(1+exp(-2x))
sigmoid(x) = 1 / (1 + exp(-x))
答:
首先,我们计算左边的表达式 tanh(x) + 1:
tanh(x) + 1 = (1 - exp(-2x)) / (1 + exp(-2x)) + 1
= (1 - exp(-2x) + 1 + exp(-2x))/(1 + exp(-2x))
= 2 / (1 + exp(-2x))
接下来,我们计算右边的表达式 2 sigmoid(2x):
2 sigmoid(2x) = 2 * (1 / (1 + exp(-2x)))
= 2 / (1 + exp(-2x))
结果显而易见了。
4. 假设我们有⼀个⾮线性单元,将它⼀次应⽤于⼀个⼩批量的数据。这会导致什么样的问题?
答:应用一个非线性单元一次于一个小批量的数据可能会导致以下问题:
维度不匹配:如果小批量中的数据在维度上不一致,即特征数量不同,那么应用非线性单元可能会导致维度不匹配的错误。
梯度失踪或爆炸:非线性函数在某些区域可能具有非常小的梯度,这可以导致梯度消失的问题。相反,非线性函数在某些情况下也可能具有非常大的梯度,导致梯度爆炸的问题。这会影响反向传播算法的稳定性和收敛速度。
过拟合:如果非线性单元具有足够的模型参数,它可能会过度拟合训练数据,导致模型在新样本上的泛化能力下降。
运算量增加:非线性单元通常比线性单元计算量更大,因为它们涉及到复杂的数学运算,如指数、对数等。对于大规模的数据集和复杂的神经网络结构,这可能会导致计算资源需求增加。
要解决这些问题,可以采取一些措施,如使用合适的激活函数、初始化权重参数、正则化技术、批量归一化等,以提高模型的性能和稳定性。