神经网络就是进行了一系列的线性映射与非线性激活函数交织的运算。本节将讨论更多的算法设计选项,比如数据预处理,权重初始化和损失函数。
数据预处理
关于数据预处理我们有3个常用的符号,数据矩阵X,假设其尺寸是[N x D](N是数据样本的数量,D是数据的维度)。
均值减法(Mean subtraction)
它对数据中每个独立特征减去平均值,从几何上可以理解为在每个维度上都将数据云的中心都迁移到原点。
归一化(Normalization)
是指将数据的所有维度都归一化,使其数值范围都近似相等。有两种常用方法可以实现归一化。第一种是先对数据做零中心化(zero-centered)处理,然后每个维度都除以其标准差,实现代码为
。第二种方法是对每个维度都做归一化,使得每个维度的最大和最小值是1和-1。
协方差(Covariance):定义为:
如果X 与Y 是统计独立的,那么二者之间的协方差就是0,这是因为
但是反过来并不成立,即如果X 与Y 的协方差为0,二者并不一定是统计独立的。
协方差矩阵是一个矩阵,其 i, j 位置的元素是第 i 个与第 j 个随机向量(即随机变量构成的向量)之间的协方差(所以对角线是方差)。
我们可以对数据协方差矩阵进行SVD(奇异值分解)运算。???
|A|:行列式
A*:伴随矩阵(不是共轭矩阵):方阵 的各元素的代数余子式 所构成的如下矩阵 :
PCA:
白化(whitening):
权重初始化
6.1 激活函数(视频)
- 权重太小(eg.0.01) 网络崩溃 梯度为0,所有神经元乘相同的权重,所有激活函数变成0
- 权重太大(eg.1)网络饱和 不再更新了 梯度变成0
改进方法:Xavier初始化
用ReLu激活函数,有一半神经元会失活,所以每次要除以2
6.2 批量归一化 batch normalization
想实现所有的通过深度网络的转发都能够在每一层有很好的高斯分布,需要归一化每一个神经元的均值和方差。
正则化 Regularization
L2正则化可以直观理解为它对于大数值的权重向量进行严厉惩罚,倾向于更加分散的权重向量。(权重不唯一,尽量不选大数值权重)
L2正则化可能是最常用的正则化方法了。可以通过惩罚目标函数中所有参数的平方将其实现。即对于网络中的每个权重w,向目标函数中增加一个
,其
是正则化强度。前面这个
很常见,是因为加上
后,该式子关于
梯度就
而不
了。
在线性分类章节中讨论过,由于输入和权重之间的乘法操作,这样就有了一个优良的特性:使网络更倾向于使用所有输入特征,而不是严重依赖输入特征中某些小部分特征。最后需要注意在梯度下降和参数更新的时候,使用L2正则化意味着所有的权重都以
向着0线性下降。
随机失活(Dropout)是一个简单又极其有效的正则化方法。在训练的时候,随机失活的实现方法是让神经元以超参数 的概率被激活或者被设置为0。在训练过程中,随机失活可以被认为是对完整的神经网络抽样出一些子集,每次基于输入数据只更新子网络的参数(然而,数量巨大的子网络们并不是相互独立的,因为它们都共享参数)。在测试过程中不使用随机失活,可以理解为是对数量巨大的子网络们做了模型集成(model ensemble),以此来计算出一个平均的预测。
""" 普通版随机失活: 不推荐实现 (看下面笔记) """
p = 0.5 # 激活神经元的概率. p值更高 = 随机失活更弱
def train_step(X):
""" X中是输入数据 """
# 3层neural network的前向传播
H1 = np.maximum(0, np.dot(W1, X) + b1)
U1 = np.random.rand(*H1.shape) < p # 第一个随机失活遮罩 U1是bool型变量 true/false
H1 *= U1 # drop!
H2 = np.maximum(0, np.dot(W2, H1) + b2)
U2 = np.random.rand(*H2.shape) < p # 第二个随机失活遮罩
H2 *= U2 # drop!
out = np.dot(W3, H2) + b3
# 反向传播:计算梯度... (略)
# 进行参数更新... (略)
def predict(X):
# 前向传播时模型集成
H1 = np.maximum(0, np.dot(W1, X) + b1) * p # 注意:激活数据要乘以p
H2 = np.maximum(0, np.dot(W2, H1) + b2) * p # 注意:激活数据要乘以p
out = np.dot(W3, H2) + b3
注意:在predict函数中不进行随机失活,但是对于两个隐层的输出都要乘以
,调整其数值范围。这一点非常重要,因为在测试时所有的神经元都能看见它们的输入,因此我们想要神经元的输出与训练时的预期输出是一致的。以
为例,在测试时神经元必须把它们的输出减半,这是因为在训练的时候它们的输出只有一半。为了理解这点,先假设有一个神经元x的输出,那么进行随机失活的时候,该神经元的输出就是
,这是有
的概率神经元的输出为0。在测试时神经元总是激活的,就必须调整
来保持同样的预期输出。
上述操作不好的性质是必须在测试时对激活数据要按照p进行数值范围调整。既然测试性能如此关键,实际更倾向使用反向随机失活(inverted dropout)它是在训练时就进行数值范围调整,从而让前向传播在测试时保持不变。这样做还有一个好处,无论你决定是否使用随机失活,预测方法的代码可以保持不变。
"""
反向随机失活: 推荐实现方式.
在训练的时候drop和调整数值范围,测试时不做任何事.
"""
p = 0.5 # 激活神经元的概率. p值更高 = 随机失活更弱
def train_step(X):
# 3层neural network的前向传播
H1 = np.maximum(0, np.dot(W1, X) + b1)
U1 = (np.random.rand(*H1.shape) < p) / p # 第一个随机失活遮罩. 注意/p!
# 这样 U1 就不是bool型变量了,而是float型
H1 *= U1 # drop!
H2 = np.maximum(0, np.dot(W2, H1) + b2)
U2 = (np.random.rand(*H2.shape) < p) / p # 第二个随机失活遮罩. 注意/p!
H2 *= U2 # drop!
out = np.dot(W3, H2) + b3
# 反向传播:计算梯度... (略)
# 进行参数更新... (略)
def predict(X):
# 前向传播时模型集成
H1 = np.maximum(0, np.dot(W1, X) + b1) # 不用数值范围调整了
H2 = np.maximum(0, np.dot(W2, H1) + b2)
out = np.dot(W3, H2) + b3
。。。。。。。。。。
尽量把你的输出变成二分类
小结
- 推荐的预处理操作是对数据的每个特征都进行零中心化,然后将其数值范围都归一化到[-1,1]范围之内。
- 使用标准差为\sqrt{2/n}的高斯分布来初始化权重,其中n是输入的神经元数。例如用numpy可以写作:
- 使用L2正则化和随机失活的倒置版本。
使用批量归一化。 - 讨论了在实践中可能要面对的不同任务,以及每个任务对应的常用损失函数。
现在,我们预处理了数据,初始化了模型。在下一节中,我们将讨论算法的学习过程及其运作特性。