Deep Learning

1 神经网络和深度学习

1.1 神经网络概论

ReLu: rectified linear unit ,修正线性单元;修正指的是取不小于 0 的值。

每个神经元类似一个乐高积木(Lego brick) ,将许多神经元堆叠在一起就形成了一个较大的神经网络。而且并不会人为决定每个神经元 的作用,而是由神经网络自己决定每个神经元的作用。如果给神经网络足够多的训练数据,其非常擅长计算从输入到输出的精确映射。神 经网络在监督学习中效果很好很强大。

  • 结构化数据(structured data):每个特征都有清晰、明确有意义的定义;比如房屋的面积,人的身高等
  • 非结构化数据(unstructured data):特征无法精确定义;比如图像的像素点,音频,文字

人类很擅长处理结构化的数据,但机器很不擅长。而归功于深度学习,使得机器在非结构化数据的处理有了明显的提高;但是现在比较挣 钱的仍然是让机器处理结构化数据,如广告投放、理解处理公司的海量数据并进行预测等。吴恩达希望设计的网络可以处理结构化数据也 可以处理非结构化的数据。

神经网络有不同的种类,有用于处理图像的 CNN(Convolution Neural Network)、处理一维序列的 RNN(Recurrent Neural Network)、以 及自动驾驶中用于处理雷达数据的混合神经网络(Hybrid Neural Network)[对于复杂的问题,需要自行构建网络的架构;和机器学习中的 算法一样,针对具体的问题,需要去做具体的优化,而不是一成不变的使用基本的算法]

scale 使得神经网络在最近流行起来,这里的 scale 并不单单指神经网络的规模,还包括数据的规模。当训练样本不是很大的时候,神 经网络与传统的机器学习算法之间的优劣并不明显,此时主要取决有人为设计算法的技巧和能力以及算法处理的细节,可能一个设计良好 的 SVM 算法结果要优于一个神经网络的效果;但是随着样本量不断变大,传统的机器学习算法的性能会在达到一定的性能之后效果变无 法继续提升,而神经网络此时的效果将明显领先与传统的算法[需要很大的样本,且网络的规模越大,性能越好]。数据、计算能力、算法 都促使了深度学习的发展;算法的主要改进都在加快算法的速度,比如使用 ReLU 函数替代 sigmoid 函数就大大加快了算法的训练速度, 因为 sigmoid 函数在自变量趋向于正负无穷大的时候,导数趋向于 0,而使用梯度下降法,梯度的减小将使得参数的变化变得缓慢,从 而学习将变得缓慢;而 ReLU 函数右侧的斜率始终为 1,由于斜率不会逐渐趋向于 0,使得算法训练速度大大提高。速度的提升使得我们 可以训练大型的网络或者在一定的时间内完成网络的训练。而且训练神经网络的过程一般是 idea - code - experiment - idea 不断循 环,迭代的更快使得验证自己的想法更加快速得到验证,将有机会取验证更多的想法,从而更有可能找到合适的结果。

1989 年 Robert Hecht-Nielsen 证明了万能逼近定理:对于任何闭区间的一个连续函数都可以用一个隐含层的 BP 网络来逼近(完成任 意m 维到 n 维的映射)。

1.2 神经网络基础

一张彩色图像像素点由 RGB 三个通道组成,作为神经网络的输入时,将三个矩阵都转换成向量并拼接起来组成一个列向量 \(x^{(i)} \in \mathbb{R}^{n_x}\),列向量中先是红色通道的所有像素点,然后是绿色通道的所有像素点,最后是蓝色通道的所有像素点。m 个训 练样本 \(\{ (x^{(1)},y^{(1)}), (x^{(2)},y^{(2)}), \cdots, (x^{(i)},y^{(i)}) \}\) ;同时使用 \(X \in \mathbb{R}^{n_x \times m} \) 表示所有的训练样本\[X= \left[ \begin{array}{cccc} | & | & & | \\ x^{(1)} & x^{(2)} & \cdots & x^{(m)} \\ | & | & & | \end{array} \right] \] 相比于让每个样本按行向量堆叠,在神经网络中构建过程会简单很多。\(y^{(i)} \in \{0,1\}\)同 时将所有的标签组成一个行向量 \(Y \in \mathbb{R}^{1 \times m}\) \[ Y = [ y^{(1)}, y^{(2)}, \cdots, y^{(m)} ]\] 在Python 中使用 (n,m) = X.shape 和 (1,m) = Y.shape 得到向量的维数(使用的是 numpy 库中的array)。并且在神经网络中,使用权重 w 和 基 b 来表示参数,而不使用 \(\theta\) 来整体表示参数。且在程序中使用变量 dw 和 db 来表示 cost function 对 w 和 b的导数。

loss(error) function 定义的是单个样本的误差;cost function 衡量的是在所有训练样本上的性能,定义为所有样本的 loss function 之和的平均。算法通过 cost function 来求解参数 w 和 b 。

梯度下降法几乎对任何初始化方法都有效,如初始化为 0 或者使用随机值进行初始化。但深度学习中必须采用随机初始化来打破不同节 点的对称性。

梯度 - 导数,也就是斜率,定义为自变量在某一点产生一点变化,导致因变量变化值相对于自变量变化值的倍数。导数在不同的点可能 会有不同的值,由此组成了导函数。

计算图(computation graph) 用于精确的描述反向传播算法。图中每个节点表示一个变量(变量可以是标量、向量、矩阵、张量或者其他 类型的变量),操作(operation)指一个或多个变量的简单函数[深度学习书中定义一个操作仅返回单个输出变量]。

由于计算反向传播的过程中很多都在求解最终的损失函数(cost function)对中间某个变量的导数,如 \(\frac{d}{dw_1}J\) ,所以一般 会将该表达式简写成 \(dw_1\) 。

向量化可以大大提高运算速度。 CPU 和 GPU 都有并行化的指令,称为 SIMD(single instruction multiple data)[单指令流多数据流]。 CPU 也有并行化指令,只是没有 GPU 那么擅长。使用 Python 的 numpy 库函数能够充分利用并行化操作来提高计算速度,这些库函数都 进行了很好的运用了并行化指令。 原则,如果有其他的方法,就不要使用 for 循环。

将每个样本 \(x^{(i)}\) 看成一个列向量,然后按列把所有样本堆叠起来组成一个大的矩阵 X 。权重 \(W^T\) 视为行向量,可以让两 者直接相乘得到 \(W^T X\) ,而不再是逐个样本去计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np
A = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12] ])
print(A.shape)
cal = A.sum(axis=0) # 按列求和
row = A.sum(axis=1) # 按行求和
# numpy 中有广播机制,可以自动扩展向量(按行或者列复制 n 次)
percentage = 100 * A / (cal.reshape(1,4) # 最好使用 reshape 函数确保
# 矩阵的维数正确,该函数调用成本很低 O(1)

a = np.random.randn(5) #
print(a.shape) # (5,) 是一个秩为 1 的数组,但即不是行向量,也不是列向量;永远不要使用,否则会产生一些很奇怪的 bug

a = np.random.randn(5,1) # 列向量
a.shape # (5,1)
a = np.random.randn(1,5) # 行向量
a.shape # (1,5)

assert(a.shape == (5,1)) # 多多验证
a = a.reshape((5,1))

db = np.sum(dz, axis = 1, keepdims = True) # keepdims 用于阻止 numpy 生成秩为 1 的数组

1.3 两层神经元网络

\(z^{[i]}\) 用于表示网络的第 i 层。

输入层、隐层、输出层。约定俗成, 计算网络的层数的时候,不算输入层,输入层称为第 0 层。 \(a^{[0]} = X \) 表示输入层 (a 是 activation), \(a^{[1]}\) 表示第一个隐层 \[a^{[1]} = \left[ \begin{array}{c} a_1^{[1]} \\ a_2^{[1]} \\ \ldots \\ a_{m1}^{[1]} \end{array} \right] \]

\[ z_1^{[1]} = {(w_1^{[1]})}^T x + b_1^{[1]}, \ a_1^{[1]} = sigmoid(z_1^{[1]}) \] \[ z_2^{[1]} = {w_2^{[1]}}^T x + b_2^{[1]}, \ a_2^{[1]} = sigmoid(z_2^{[1]}) \] \[ z_3^{[1]} = {w_3^{[1]}}^T x + b_3^{[1]}, \ a_3^{[1]} = sigmoid(z_3^{[1]}) \] \[ z_4^{[1]} = {w_4^{[1]}}^T x + b_4^{[1]}, \ a_4^{[1]} = sigmoid(z_4^{[1]}) \]

将网络中每一层的相同变量按行堆叠起来组成一个列向量,如 w, b, z, a ,便可以使用向量化计算来提高速度。

\[ W^{[i]} = \left[ \begin{array}{ccc} -- & {w_1^{[i]}}^T & -- \\ -- & {w_2^{[i]}}^T & -- \\ & \vdots & \\ -- & {w_l^{[i]}}^T & -- \end{array} \right] \] \[ b^{[i]} = \left[ \begin{array}{c} b_1^{[i]} \\ b_2^{[i]} \\ \vdots \\ b_l^{[i]} \end{array} \right] \] \[ z^{[i]} = \left[ \begin{array}{c} z_1^{[i]} \\ z_2^{[i]} \\ \vdots \\ z_l^{[i]} \end{array} \right] \] \[ a^{[i]} = \left[ \begin{array}{c} a_1^{[i]} \\ a_2^{[i]} \\ \vdots \\ a_l^{[i]} \end{array} \right] \]

得到向量化公式

\[ z^{[i]} = W^{[i]} a^{[i-1]} + b^{[i]} \] \[ a^{[i]} = np.sigmoid(z^{[i]}) \]

多个训练样本 \[ z^{[i](l)} = W^{[i]} a^{[i-1](l)} + b^{[i]} \] \[ a^{[i](l)} = sigmoid(z^{[i](l)}) \]

不同训练样本的值按列堆叠

\[X= \left[ \begin{array}{cccc} | & | & & | \\ x^{(1)} & x^{(2)} & \cdots & x^{(m)} \\ | & | & & | \end{array} \right] \]

\[Z^{[i]} = \left[ \begin{array}{cccc} | & | & & | \\ z^{[i](1)} & z^{[i](2)} & \cdots & z^{[i](m)} \\ | & | & & | \end{array} \right] \]

\[A^{[i]} = \left[ \begin{array}{cccc} | & | & & | \\ a^{[i](1)} & a^{[i](2)} & \cdots & a^{[i](m)} \\ | & | & & | \end{array} \right] \]

A 、Z 的水平方向表示的是不同的样本,垂直方向表示的不同网络某一层中的不同节点。

\[ Z^{[i]} = W[i]A[i-1] + b^{[i]}\] \[ A^{[i]} = sigmoid(Z^{[i]}) \]

如果把输入按列堆叠,输出也将按列堆叠。

1.3.1 Activation function

激活函数,不同网络层的激活函数可以不同。

\begin{align*} sigmoid(z) & = \frac{1}{ 1-e^{-z} } \\ tanh(z) & = \frac{ e^z - e^{-z} }{ e^z + e^{-z} }, \quad \text{a shifted version of sigmoid} \\ ReLU(z) & = \max(0,z) \\ leaky ReLU(z) & = \max(0.01z,z) \\ & = \left\{ \begin{array}{} 0.01z, & z < 0 \\ z, & z \geq 0 \end{array} \right. \end{align*}

tanh 函数几乎总是比 死规模的 sigmoid 函数的效果要好,因为其均值为 0 ?更有利于后面网络层的学习,类似于将输入样本的均值归 一化到 0 一样。网络的输出层(二分类)可以使用 sigmoid 函数,其他的时候几乎不要使用。但是两者当 z 很大或者很小的时候,两 者的梯度变得很小,将减慢网络的学习速度。深度神经网络中,一般都只使用 ReLU 激活函数。另外 leaky ReLU 理论上效果会更好,不 会一半的导数为 0 ,但实际上很少使用。

为什么需要激活函数: 如果没有激活函数或者激活函数是线性的,那么无论网络层数有多少,其实际上都只是在做线性回归。两个或 者多个线性函数的叠加仍然是线性函数。所以使用非线性的激活函数非常重要。

激活函数求导:

\begin{align*} g(z) & = \frac{1}{1 + e^{-z}} \\ g'(z) & = g(z)(1-g(z)), \quad \text{compute quickly when g(z) is know} \\ g(z) & = tanh(z) \\ g'(z) & = 1-(tanh(z))^2 \\ g(z) & = max(0,z) \\ g'(z) & = \left\{ \begin{array}{} 0 & if \ z < 0 \\ 1 & if \ z > 0 \\ undefined & if \ z = 0 \end{array} \right. \\ g(z) & = max(0.01z,z) \\ g'(z) & = \left\{ \begin{array}{} 0.01 & if \ z < 0 \\ 1 & if \ z > 0 \\ undefined & if \ z = 0 \end{array} \right. \end{align*}

实际中使用 ReLU 或者 leaky ReLU 函数时,z 为 0 的概率很小很小,所以使用时,让激活函数的导数在 0 点等于 0 或者 1 都可以, 并不会影响结果。

1.3.2 反向传播

利用计算图(computation graph)表示前向传播和反向传播。前向传播时,需要计算网络的输出,每经过一个节点,都需要乘以该节点的 函数表达式来得到该神经元的输出值,然后继续向前传播;反向传播时,需要计算的是参数的导数,根据导数的链式法则,每经过一个神 经元,都需要乘以该神经元函数表达式对需要求导变量的偏导数,然后继续反向传播。无论前向传播还是反向传播,都是一层一层的计算, 根据前一层的结果来求得本层节点的值,只是前向传播时乘以的是节点的函数表达式,而反向传播时乘以的是偏导数。

比如求取导数 dz ,并且已知 \(a = g(z)\) \[ dz = da \cdot g'(z) \]

前向传播过程中计算激活函数时,是将矩阵中的每个元素都乘以激活函数的表达式,也就是逐元素相乘;反向传播乘以激活函数的导数时 也要逐元素相乘。

无论前向传播还是反向传播,计算过程中确保矩阵的维数相同,将避免很多问题。

权重的维数 \[ W^{[l]}.shape = (n^{[l]},n^{[l-1]}) \] \[ Z^{[l]}.shape = (n^{[l]}) \]

某个向量 v 和其导数 dv 的维数必定总是相同的。

1.3.3 随机初始化权重

在深度学习中,必须使用随机初始化的方式来初始化权重。假如将所有的权重都初始化为 0,那么前向传播时,由于对称性每个神经元节 点的值都会相同,反向传播时,得到的每个神经元节点权重的导数也相同,从而导致所有神经元的权重都是相同的。而实际上我们希望不 同的神经元使用不同的权重,来计算不同的特征。这将导致神经网络无法工作。

1
2
3
# 通常把权重初始化称非常小的随机数;防止直接达到 sigmoid 函数梯度很小的地方
W1 = np.random.randn(2,2) * 0.01 #
b1 = np.zeros(2,1) # 无需随机初始化

1.4 深层神经元网络

算法的复杂性来自于数据而不是代码,所以很多时候会惊讶,这么简单的代码居然实现了这么 6 的功能。

发现一些问题只有深层网络可以求解,浅层网络无法解决。下面有两个解释使用深层网络的原因:

  1. 使用深层网络检测人脸,开始的网络层检测的是脸部的边缘(横线、竖线、不同角度的斜线),之后的网络层检测的是五官(由浅层 网络组合成眼睛、鼻子、嘴巴等部位),随后的网络层组合不同的五官来组成整张脸从而识别身份。语音类似
  2. 复杂的数学函数,如果使用多层网络来学习表示,那么每个节点只需要是一个很简单的函数,将这些简单的函数组成一个深层网络, 就可以很好的表示该复杂函数;而如果只使用一个隐层,那么隐层函数将会非常复杂,可能需要指数级个数的节点。

逐样本计算公式:

\begin{align*} z^{[l]} & = W^{[l]} a^{[l-1]} + b^{[l]} \\ a^{[l]} & = g^{[l]} (z^{[l]}) \\ dz^{[l]} & = da^{[l]} * {g^{[l]}}' (z^{[l]}) \quad \text{element wise product} \\ dW^{[l]} & = dz^{[l]} \cdot a^{[l-1]} \\ db^{[l]} & = dz^{[l]} \\ da^{[l-1]} & = {W^{[l]}}^T \cdot dz^{[l]} \end{align*}

向量化计算所有样本公式:

\begin{align*} Z^{[l]} & = W^{[l]} A^{[l-1]} + b^{[l]} \\ A^{[l]} & = g^{[l]} (Z^{[l]}) \\ dZ^{[l]} & = dA^{[l]} * {g^{[l]}}' (Z^{[l]}) \quad \text{element wise} \\ dW^{[l]} & = dZ^{[l]} \cdot {A^{[l-1]}}^T \\ dB^{[l]} & = \frac{1}{m} np.sum(dZ^{[l]}, axis=1, keepdims=True) \\ dA^{[l-1]} & = {W^{[l]}}^T \cdot dZ^{[l]} \end{align*}
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# Talk is cheap, show me the code
import numpy as np
# ReLU 激活函数
def ReLU(Z):
# ReLU(z) = max(0,z)

# numpy broadcoast
res = np.maximum(Z, 0)

return res

# ReLU 激活函数的导数
def dReLU(Z):
# 所有小于 0 的值导数为 0
dZ = np.maximum(Z, 0)
# 所有大于 0 的值导数为 1
dZ[Z > 0] = 1

return dZ

def forword(Apl, Wl, bl, g):
'''
Apl: 上一层的节点的输出
Wl: 本层节点的权重
bl: 本层节点的偏移
g: 本层网络的激活函数

'''


# Apl.shape[1] = bl.shape[1] = minibatch size
assert(bl.shape[1] == 1 or Apl.shape[1] == bl.shape[1])
# Wl.shape = (Al.shape[0], Apl.shape[0])
assert(Apl.shape[0] == Wl.shape[1])
# A 的行数等于 W 的行数,列数等于 Apl 的列数

# Z^{[l]} & = W^{[l]} A^{[l-1]} + b^{[l]}
# A^{[l]} & = g^{[l]} (Z^{[l]})
Zl = np.dot(Wl, Apl) + bl
Al = g(Zl)

return Al,Zl


def backword(dAl, Wl, Zl, dgl, Apl):
'''
dAl: 本层节点的导数
Wl: 本层节点的权重
Zl: 本层节点激活函数的输入
dgl: 本层网络激活函数的导函数
Apl: 本层节点的输出
'''


assert(dAl.shape == Zl.shape)
assert(Wl.shape == (dAl.shape[0], Apl.shape[0]))
assert(dAl.shape[1] == Apl.shape[1])

# dZ^{[l]} & = dA^{[l]} * {g^{[l]}}' (Z^{[l]}) \quad \text{element wise product} \\
# dW^{[l]} & = dZ^{[l]} \cdot {A^{[l-1]}}^T \\
# dB^{[l]} & = \frac{1}{m} np.sum(dZ^{[l]}, axis=1, keepdims=True) \\
# dA^{[l-1]} & = {W^{[l]}}^T \cdot dZ^{[l]}
dZl = np.multiply(dAl, dgl(Zl)) # element wise product
dWl = np.dot(dZl, Apl.transpose())
dbl = np.sum(dZl, axis = 1, keepdims = True) / dAl.shape[1]
dApl = np.dot(Wl.transpose(), dZl)

return dApl,dWl,dbl


Apl = np.random.randn(5,8)
Wl = np.random.randn(9,5) * 0.01
bl = np.random.randn(9,1)

Al,Zl = forword(Apl, Wl, bl, ReLU)
print("Al")
print(Al)

print("Zl")
print(Zl)

dAl = np.random.randn(9,8)
dApl,dWl,dbl = backword(dAl, Wl, Zl, dReLU, Apl)

print("dApl")
print(dApl)
print("dWl")
print(dWl)
print("dbl")
print(dbl)

1.4.1 核对矩阵的维数

拿出纸和笔,手算一下每个矩阵的维数,可以大大减小网络的 bug 。

参数 维数
\(W^{[l]}\) \((n^{[l]}, n^{[l-1]})\)
\(dW^{[l]}\) \((n^{[l]}, n^{[l-1]})\)
\(b^{[l]}\) \((n^{[l]}, 1)\)
\(B^{[l]}\) \((n^{[l]}, m)\)
\(db^{[l]}\) \((n^{[l]}, 1)\)
\(dB^{[l]}\) \((n^{[l]}, m)\)
\(z^{[l]}\) \((n^{[l]}, 1)\)
\(Z^{[l]}\) \((n^{[l]}, m)\)
\(a^{[l]}\) \((n^{[l]}, 1)\)
\(A^{[l]}\) \((n^{[l]}, m)\)

无论是否向量化同时计算多个样本,权重 W 的维数都是一样的。

1.4.2 Hyperparamter

超参: 学习速率、迭代次数、隐层数、每一层节点的个数、激活函数、minibatch size、momentum、regularization parameters

这些超参需要手动设置,并且这些参数经影响你参数的最终结果。而预先很难知道最优的超参是什么,所以必须尝试各种参数 (依据 idea->code->experiment 循环),观察模式是否成功。并且可能由于电脑环境 CPU GPU 老化或者其他原因,最优超参也是会不 断变化,每隔一段时间需要重新调节超参。

凭经验的过程通俗的来说就是不断尝试直到找到合适的数值。 empirical process is maybe a fancy way of saying you just have to try a lot of things and see what works.

深度学习用于计算机视觉、语音、自然语言处理、广告投放、搜索、数据分析等。深度学习应用到了很多结构化的数据分析中。

2 提升深度神经元网络:超参调节、正则化、最优化

深度学习中有很多的超参,我们不可能一开始就是知道这些超参的最优解。应用机器学习的过程是一个高度迭代的过程:在项目启动的时 候,我们有一个初步想法(对超参的一个设置),然后运行代码进行实验,根据结果去改变策略或者完善想法,从而不断找到更加优化的 网络。深度学习已经应用到了各个领域,经常有某个领域的专家投身到其他领域中去,然而不同领域对超参设置的直觉、经验通常并不适 合其他的领域。最佳的选择通常依赖于你的数据量、输入特征的数量、计算机的配置(GPU群、单GPU、CPU)。所以即使是专家也通常无 法开始就知道超参的准确值,深度学习是一个典型的迭代的过程,通过不断的验证来提高网络的性能。所以项目的进度直接依赖于每一个 迭代的时间,设置高质量的训练、验证、测试集可以提高迭代的效率。

2.1 Training - Development - Test Data Set

正确选择训练集、验证集[Hold-out cross validation]、测试集可以很大程度上帮助我们创建一个高效的神经网络。

在样本较少的机器学习时候,普遍认为最好的比例为 70/30 的训练集和交叉验证集,或者 60/20/20 的训练集、交叉验证集、测试集。 在深度学习中,一般都有海量的数据,此时验证集和测试集的比例会变得很小。因为验证集目的是验证不同算法的优劣,所以验证集只需 要拥有能够验证那个算法更好的个数的样本就可以。测试集的目的是评估分类器的性能,同样并不需要 20% 的数据去评估。并且可以没 有测试集,因为测试集是为了得到网络性能的无偏估计,当不需要网络的无偏估计的时候可以不需要测试集。

100万 : 98/1/1, 数据量更大时:99.5/0.25/0.25, 99.5.0.4/0.1

训练集和测试集分布不同: 确保验证集和测试来自相同的分布。 利用爬虫等从网络上获取训练图片,可能使得网络的训练集和测试集 分布不同,但是一定要让验证集和测试集的分布相同,这样可以让机器学习算法收敛的更快。

2.2 Bias and Variance

偏差和方差两个概念很容易学,但很难理解(Easy to learn but hard to master)。即使你认为已经学会了两者的基本概念,不过总是有 一些意想不到的新东西出现。

在深度学习中,不再需要权衡(trade-off)偏差和方差。因为现在有方法可以只较小偏差而对方差的影响很小,或者只减小方差而对偏差 的影响很小,不像原来那样减小其中一个势必增大另一个。

在二维时可以通过画图达到可视化的效果来观察偏差和方差;在高维空间中可以通过训练误差和验证误差两者来观察偏差和方差。

训练集误差 验证集误差 偏差-方差[贝叶斯误差接近 0%,训练样本和验证样本同分布]
1% 11% 高方差(过拟合)
15% 16% 高偏差(欠拟合)
15% 30% 高偏差和高方差
0.5% 1% 低偏差和低方差

同时高偏差和高方差的情况:在高维空间中,有些区域偏差高、有些区域方差高。

偏差比较高的时候,如果去寻找更多的训练样本来训练网络,通常帮助不大,且会浪费时间。所以一定要清楚系统现在是高偏差还是高方 差,从而使用更加精确的方法来改善系统。

调试系统的基本方法:

  1. 首先查看系统是否是高偏差。根据人眼的识别率来近似估计贝叶斯误差;如果系统的偏差较大,可以通过训练更大的网络(增加网络 的层数或者隐层节点的个数)、增长训练时间、改善系统的网络架构等方法来减小偏差,直到将偏差降低到一个合理的范围。
  2. 然后依据偏差的大小查看系统的是否是高方差。如果系统是高偏差,可以通过使用更多的训练样本、正则化、不同的网络架构等方法 来改善偏差。
  3. 如果需要再进入第一步,直到训练出一个合理的系统。

训练一个正则化的更大的网络几乎没有任何负面影响,只是会增长训练时间,需要更大的训练样本。

2.3 正则化

如果怀疑网络出现了过拟合,首先应该考虑正则化,当然使用更多的训练样本同样可以减小过拟合,但有时候可能不现实。

square Euclidean norm 欧几里德范数的平方

正则化的时候只考虑权重 w ,而不考虑 b ,是因为 w 包含了绝大多数参数,而 b 只有很少的参数,影响不大。当然如果需要同样可以 在正则化项中增加 b 。

\begin{align*} J(W^{[1]}, b^{[1]}, \cdots , W^{[L]}, b^{[L]}) & = \frac{1}{m} \sum_{i=1}^m L({\hat{y}}^{(i)} - y^{(i)}) \color{red}{ + \frac{\lambda}{2m} \sum_{l=1}^{L} ||W^{[l]}||_F^2 } \\ dW^{[l]} & = dZ^{[l]} \cdot {A^{[l-1]}}^T \color{red}{ + \frac{\lambda}{m} W^{[l]} } \\ W^{[l]} & := W^{[l]} + \alpha dW^{[l]} \\ & = W^{[l]} - \alpha ( dZ^{[l]} \cdot {A^{[l-1]}}^T \color{red}{ + \frac{\lambda}{m} W^{[l]} } ) \\ & = (\color{green}{1 - \frac{\alpha \lambda}{m}}) W^{[l]} - \alpha ( dZ^{[l]} \cdot {A^{[l-1]}}^T ) \end{align*}

由于 \(1 - \frac{\alpha \lambda}{m} < 0\) ,L2 正则化也称为权重衰减(weight decay)。 L2 正则化使用较广泛。Frobenius norm

\[ ||W^{[l]}||_F^2 = \sum_{i=1}^{n_l} \sum_{j=1}^{n_{l-1}} w_{ij}^{[l]} \]

L1 正则化可以使权重变得稀疏,也就是会使权重中存在较多的 0 。吴恩达认为虽然有较多的权重参数为 0,但是对减少存储空间没有太 大的贡献。

lambda 是正则化参数,通过交叉验证来选择,从而使得训练误差和权重参数之和最小,来减小过拟合的风险。

lambda 是 Python 的一个保留关键字,编程时使用 lambd 来代替表示正则化参数。

直观理解正则化可以减小方差: 增加正则化项,假如正则化参数 labda 很大,那么将有很多的权重参数变得几乎为 0,从而消除或者 减小了中间网络层节点对结果的影响,从而使得网络变得简单。从而不容易产生过拟合。逐渐减小正则化参数,可以找到一个合适的值使 得网络偏差和方差都不是很大。

Dropout(随机失活)是一个非常有效的正则化方法。常用 inverted dropout,只在训练阶段使用 dropout,在测试阶段不使用 dropout。

1
2
3
4
5
6
7
# 每次训练的时候 dropout 的网络节点不相同,都是随机的
# inverted dropout
keep_prob = 0.5
# 反向传播的时候仍然使用该矩阵
dropout3 = np.random.randn(a3.shape) < keep_prob
a3 = np.multiply(a3, dropout3)
a3 /= keep_prob

直观理解 Dropout : Dropout 使得网络结构变得简单,从而减少了过拟合;由于会随机丢弃一些节点,所以一个神经元就不能够依赖 其某一个或者某几个固定的输入节点,而是会将权重分散开来到每一个输入节点,相当于 shrink 了权重,所以使得权重参数的 F 范数 变少,达到了类似 L2 正则化的效果。

可以在不同的网络层使用不同大小的 keepprob ,在含有较多权重参数的网络层,使用较小的 keepprob (如 0.5),从而预防该层网 络过拟合;在含有较少权重参数的网络层,使用较大的 keepprob (如 0.7 、0.9),因为不用太担心该层网络会过拟合。输入层一般 不使用 Dropout ,即让 keepprob 等于 1 ,或者很接近 1 的某个值。当然让不同的网络层有不同的 keepprob 增加了超参的个数, 需要使用交叉验证来选择参数。

Dropout 使得代价函数 J 的定义变得不明确,因为每次都会随机丢弃一些节点。所以在最开始训练的时候可以先关掉 Dropout ,使网络 所有层的 keepprob = 1 ,观察代价函数 J 是否会随着迭代次数的增加而减小,从而减小因为引入 Dropout 而导致的 bug 。然后再打 开 Dropout 开始训练网络。

记住:Dropout 是为了防止网络过拟合的一种正则化方法,除非确认网络会过拟合,否则不要使用。当然 Dropout 在图像中使用很频繁, 因为有太多的参数,以至于总是没有足够数量的样本,所以才会默认都使用 Dropout。

data argumentation: 通过水平翻转(flipping horizontally)、随机裁剪(random crops)[原图随意旋转放大后再裁剪]等方法来扩大数 据集。这样数据集会有冗余,虽然不如使用全新样本效果好,但是节省了寻找新样本的时间。注意:需要经过处理后的样本仍然保持基本 模样,如可以将一张猫的图片水平翻转,但是不要上下翻转,那样猫将上下颠倒(我怎么感觉也需要上下翻转,因为有时候很有可能看到 的就是一个上下颠倒的猫)。对于光学字符识别,可以通过任意的旋转和扭曲来扩张数据。

early stopping:通过不断的迭代,训练误差不不断减小,但是验证误差会在减小到一定值之后开始增加,early stopping 就是希望在 验证误差比较小的时候停止。另外由于权重初始化为很小的值,随着迭代次数的不断增加,权重变得越来越大,early stopping 在权重 不是很大的时候停下了,就类似与 L2 正则化的效果。缺点:early stopping 会同时调节损失函数和正则化两者,不符合正交化的规则, 可能使得两者调节的都不好。使用 L2 正则化则可以让网络的迭代次数尽可能多,而无需考虑过拟合,只是需要多次验证最优的正则化参 数。

正则化输入: 将输入样本的均值和方差归一化将有助于提高网络的训练速度。因为假如样本的不同特征的范围差别很大(特征 1 的范 围是 0-1 ,特征 2 的范围是 1-1000)将会让损失函数的形状类似与一个细长形状,contour 是细长的椭圆。必须使用很小的学习速率 来反复学习(否则将远离最优解),势必需要耗费很多时间。而将特征归一化处理之后,损失函数将是一个圆碗的形状,其 contour 是 圆形,可以快速收敛。样本归一化只有在样本的不同特征范围差别很大的时候才会生效,但使用不会有什么坏处,所以可以总是使用。样 本正则化共需要两步:均值转换成 0 ,方差转换成 1 。

  1. 让 \(\mu = \frac{1}{m} \sum_{i=1}^m x^{(i)}\)
  2. 使用 \(x^{(i)} - \mu\) 逐一替换 \(x^{(i)}\) ;这两步用于将均值变换成 0,若已知均值为 0 ,可跳过此步骤
  3. 让 \(\sigma_j^2 = \frac{1}{m} \sum_i (x_j^{(i)})^2 \) ;逐元素求平方
  4. 使用 \(x_j^{(i)} / \sigma_j\) 逐一替换 \(x_j^{(i)}\) ;将协方差变为单位阵,方差归一化使得不同的属性拥有相同的尺度。

在测试集中仍然需要使用训练集的 \(\mu\) \(\sigma\) 参数,不可以让测试集去使用自己的参数

Batch Normalization 是很好的正则化方法。

2.4 vanishing / exploding gradient

梯度消失/爆炸:当网络的层数很深的时候,如果所有的权重都大于 1 ,那么最终节点的输出值将变得很大;如果所有权重都小于 1 , 那么最终的输出值将变得很小。从而出现梯度爆炸或者消失的问题。可以通过合适的选择权重初始化的值来缓解这个问题,让所有节点的 输出值都在 1 的附近,从而不会很快的爆炸或者消失。同样是一个加速训练网络的方法。

1
2
3
# hurd 论文公式,适用 ReLU 激活函数
Wl = np.random.randon(nl, npl) * np.sqrt(2 / npl)
# 可以将 2 视为一个超参来调节,但其优先级较低

2.5 Gradient check

使用梯度检查有利于查找代码中的 bug 。方法:将所有的权重参数 \(W^{[1]},b^{[1]},\cdots,W^{[L]},b^{[l]}\) 都变换成列向量, 然后串接成一个大向量 \(\theta\) ,其中每一个列向量记为 \(\theta_1,\theta_2,\cdots,\theta_{2L}\) 。同时将所有的梯度向量 \(dW^{[1]},db^{[1]},\cdots,dW^{[L]},db^{[l]}\) 转换成列向量并串接成一个向量 \(d\theta\) 。使用 for 循环变量大向量 \(\theta\) 的每一个小向量 \(\theta_i\) ,计算 \[ d\theta_{approx} [i] = \frac{J(\theta_1,\theta_2,\cdots,\theta_i + \varepsilon, \cdots, \theta_{2L}) - J(\theta_1,\theta_2,\cdots,\theta_i - \varepsilon, \cdots, \theta_{2L})}{2\varepsilon} \] 然后比较两个向量 \(d\theta_{approx},d\theta\) 的相似度 \[ \frac{||d\theta_{approx} - d\theta||_2}{||d\theta_{approx}||_2 + ||d\theta||_2} \] 通常选取 \(\varepsilon = 10^{-7}\) ,查看两个向量的相似度如果也 在 \(10^{-7}\) 表明没有问题,若在 \(10^{-5}\) 则可能有问题,更大的话则肯定有问题。这里使用的是双边检查,相比于单边检查更 加精确。另外有几点需要注意:

  • 只在 debug 的时候使用双边检查。训练网络的时候不要使用,否则会减慢训练速度
  • 如果检查有问题,可以通过比较两个大向量的差别比较大的 i 来进一步定位问题的位置
  • 如果使用了正则化,记得在代价函数和梯度中都有相应的增加项
  • 不使用 Dropout
  • 在网络训练过一段时候后,再次检查一下;可能网络只在权重比较小的时候是正确的
\begin{gather*} f'(\theta) = \lim_{\varepsilon \to 0} \frac{f(\theta + \varepsilon) - f(\theta - \varepsilon)}{2\varepsilon}. \quad error \ O(\varepsilon^2) \\ f'(\theta) = \lim_{\varepsilon \to 0} \frac{f(\theta + \varepsilon) - f(\theta)}{\varepsilon}. \quad error \ O(\varepsilon) \\ \end{gather*}

O(n): on the Order of 。表示常数乘以括号中的项

2.6 mini-batch gradient descent

当训练数据即非常大的时候(比如有 500 万个样本),使用批量梯度下降法将非常的耗时,因为必须要计算所有的样本后才可以调节参 数。因此使用 mini-batch 梯度下降法结合了随机梯度下降法和批量梯度下降法两者的优点:可以使用向量话计算同时避免必须计算完所 有样本后才能更新参数。

将样本特征和标记都分成许多等个数的小段,记为 \(X^{\{t\}}, Y^{\{t\}}\) 。计算过程同批量梯度下降法类似,将所有训练样本迭代 一次称为一个 epoch 。

mini-batch size 是一个很重的超参,使用时需要快速选择。使用较大的是 64-512 之间的某个 2 的次方数。并且确保 mini-batch size fit in 你的 CPU / GPU 的内存。

2.7 Momentum

通常选取 momentum 参数 \(\beta = 0.9\) ,这是一个比较鲁棒的值。

\begin{align*} V_{dW} & = \beta V_{dW} + (1 - \beta)dW \\ V_{db} & = \beta V_{db} + (1 - \beta)db \\ W & = W - \alpha V_{dW} \\ b & = b - \alpha V_{db} \end{align*}
1
2
3
4
5
6
7
8
VdW = np.zeros(dW.shape)
Vdb = np.zeros(db.shape)
# 第 t 次迭代,计算 mini-batch 的 dW db
VdW = beta * VdW + (1 - beta)dW
Vdb = beta * Vdb + (1 - beta)db

W = W - alpha * VdW
b = b - alpha * Vdb

计算得到本次的导数值之后,使用指数加权移动平均来估计过去 10 次迭代的平均值,然后使用平均后的权重值来更新权重。这样将使得 迭代左右来回摆动得到抑制(细长型的代价函数,每次迭代都会左右摆动,通过计算过去 10 次的平均值,正负得到抵消,左右摆动将会 被消除很多,相当于给其增加了摩擦力,估计也是称为 momentum 的原因),而使用向最优解移动的方向则不会被抑制。由于仅需经过 10 次迭代之后就可以消除因为初始化为 0 带来的偏差,通常不需要偏差修正。

2.7.1 Exponentially Weighted Moving Averages

\[ V_t = \beta V_{t-1} + (1 - \beta) \theta_t \] 指数加权移动平均,大约相当于求取了 \(\frac{1}{1-\beta}\) 个数的平均值 \( (1-\varepsilon)^{\frac{1}{\varepsilon}} = \frac{1}{\varepsilon}\) 。当 \(\beta\) 较小的时候(比如等于 0.5),平均的数 量较小,会对当前值有快速的响应,但也会有较大的震荡;当 \(\beta\) 较大的时候(比如等于 0.99),求取了太多数量的平均值,导 致对当前数值不敏感,最终的曲线会有延后。使用时作为超参来调节。

实际使用时只需要先将 V 初始化成 0,然后有新的值时使用公式 \(V := \beta V + (1-\beta) \theta_i\) 更新 V 即可。这样只需要 占用一个内存,也很高效;虽然计算并不精确,如果时刻记录过去 50 个的值,然后求和在求平均计算更精确,但比较繁琐,且耗内存。 Bias correction,由于将 V 初始化成 0,导致最初的估计会存在偏差。可以在求得 V 之后再除以一个修正变量 \(\frac{V}{1-\beta^t}\) 来代替 \(V\) ,就可以修正因初始值估计不准确而导致的偏差。

\[ ( 1 - \varepsilon)^{ \frac{1}{\varepsilon} } = \frac{1}{e} \]

2.8 RMSprop

root mean square prop :让每个权重参数都除以自己的过去 \(\frac{1}{1-\beta_2}\) 个绝对值的平均,来消除较大的变化

\begin{align*} S_{dW} & = \beta_2 S_{dW} + (1 - \beta_2) dW^2 \\ S_{db} & = \beta_2 S_{db} + (1 - \beta_2) db^2 \\ W & := W - \alpha \frac{dW}{\sqrt{S_{dW} + \varepsilon}} \\ b & := b - \alpha \frac{db}{\sqrt{S_{db} + \varepsilon}} \end{align*}
1
2
3
4
5
6
# 第 t 次迭代,计算出 dW db 后
SdW = beta2*Sdw + (1 - beta2)dW**2 # 逐元素求平凡
Sdb = beta2*Sdb + (1 - beta2)db**2

W = W - alpha * dW / np.sqrt(SdW + epsilon) # epsilon 是一个很小的值,防止除以 0
b = b - alpha * db / np.sqrt(Sdb + epsilon) # epsilon 可取 10^(-8)

2.9 Adam

Adaptive momentum estimation 结合了 Momentum 和 RMSProp 两个算法,需要偏差修正。

\begin{align*} & V_{dW} = 0 \\ & V_{db} = 0 \\ & S_{dW} = 0 \\ & S_{db} = 0 \\ & \text{mini-batch gradient descent to compute dW and db on iter t} \\ & V_{dW} = \beta_1 V_{dW} + (1-\beta_1)dW \\ & V_{db} = \beta_1 V_{db} + (1-\beta_1)db \\ & S_{dW} = \beta_2 S_{dW} + (1-\beta_2)dW^2 \\ & S_{db} = \beta_2 S_{db} + (1-\beta_2)db^2 \\ & V_{dW}^{corrected} = \frac{V_{dW}}{1-\beta_1^t} \\ & V_{db}^{corrected} = \frac{V_{db}}{1-\beta_1^t} \\ & S_{dW}^{corrected} = \frac{S_{dW}}{1-\beta_2^t} \\ & S_{db}^{corrected} = \frac{S_{db}}{1-\beta_2^t} \\ & W := W - \alpha \frac{V_{dW}^{corrected}}{\sqrt{S_{dW} + \varepsilon}} \\ & b := b - \alpha \frac{V_{db}^{corrected}}{\sqrt{S_{db} + \varepsilon}} \\ \end{align*}

超参选择:

  • \(\alpha\) 需要调节
  • \(\beta1 = 0.9\)
  • \(\beta2 = 0.999\)
  • \(\varepsilon = 10^{-8}\)

2.10 Learning Rate Decay

由于不断的迭代,参数将不断趋向于最优解,但由于使用了 moni-batch ,所以算法最终无法收敛到最优解,这时需要不断减小学习速率, 使得求得的参数可以在最优解的较小的周围徘徊。

\begin{align*} & \alpha = \frac{1}{1 + decay-rate * epoch-num} \alpha_0. \quad \text{decay-rate haper-paramter} \\ & \alpha = 0.95^{epoch-num} \alpha_0. \quad \text{expontrally decay} \\ & \alpha = \frac{k}{\sqrt{epoch-num}} \ or \ \alpha = \frac{k}{\sqrt{t}} \\ & \text{Discrete decay} \\ & \text{manual decay} \end{align*}

2.11 局部最优解

当参数非常多的时候,不同于以往的对三维空间理解,很难碰到局部最优解(所有的参数变化都导致损失函数增大或减小),很有可能只 是鞍点(saddle points)。在鞍点会有一个较长的停滞期(plateaus),这个时候梯度变化很小,几乎为 0,导致会有长时间在该段停留。 而 Adam 最优化方法有助于加速停滞期的迭代??为什么??

超参的重要级别:学习速率 \(\alpha\) ;mini-batch size 、 隐层节点个数 hidden units 、Momentum 参数 \(\beta\) ;网络的层 数 layers 、学习衰减率 learnning rate decay ;Adam 算法参数。

寻找超参时,在某个范围内随机采样(random value) ,而不是使用将区域等分的网格值(grid) 。在参数维的空间内进行随机采样(其实 是在每个参数的范围内单独随机采样,然后组合起来),这样可能有更多的数值被使用来训练(使用 grid 的时候会多次使用重复的参数 值),更容易找到最优参数,会提高搜索效率。;使用由粗到细的搜索方法(coarse to fine) ,即先在比较大的范围内所有超参的最优 解,找到一些比较好的区域后,在该区域内重点搜索,采样更多的样本,从而更精确的找到超参的最优值。

2.12 超参调试

选择合适的尺度有利于加快超参的搜索速度。有些超参,比如网络的层数、隐层节点的个数、输入特征的维数等都可以使用 uniform 采 样;但是项学习速率 \(\alpha\) 和 Momentum 参数 \(\beta\) 使用 uniform 采样就不太合理,比如学习速率范围选择成 0.0001-0.1 范围,如果在该区间 uniform 采样,那么将有 90% 的概率落在 0.001-0.1 之间,显然不太合理。此时可以考虑使用对数坐标,将取值 范围表示成 10 的多少次方到 10 的多少次方,先在两个次方的范围内使用 uniform 采样,再将 10 为底,采样得到的值为指数,并将 该值作为超参使用。Momentum 参数选取范围 0.9-0.999 ,同样不应该使用 uniform 采样,此时希望调节的是想要平均的个数,因此应 该让该个数得到 uniform 采样。

1
2
3
4
5
6
# r ~ [-4,-1]
r = np.random.randn() * (-3) - 1
alpha = 10^r

r = np.random.randn() *(-2) -1
beta = 1 - 10^r

由于更换了服务器或者 GPU 等原因,需要 Re-test hyperparameters occasionally ,每几个月都要重新测试调节一次。当计算资源充 足的时候,可以同时使用不同的参数训练多个网络,从而可以快速找到最优的超参;当没有足够的计算资源,没有只可以训练一个网络的 额时候,需要每天不断的观察网络训练的结果,依据误差曲线走势等来不断调节超参。

现在深度学习应用的已经相当广泛,不同领域的一些想法可以应用到其他领域。

2.13 Batch Normalization

batch normalization 是优化深度神经网络中最激动人心的创新之一。类似于将样本进行归一化有助于加快网络的训练,batch norm 的 目的是让网络的每一层输出都进行归一化,使得每一层网络的输出值都是归一化后的值,更加有利于后面层网络参数的学习,从而进一步 加速网络的训练。 网络输入的稳定使得每一层神经元可以单独训练,而不受前面层输出的影响。 另外并不希望所有网络层的输出都是 0 均值、方差为 1 ,所以 batch norm 为每个节点增加了均值和方差两个参数来调节归一化结果的分布,这两个参数由网络学习得到。 又由于增加了均值这个参数使得节点原来的偏移参数 b 不再有意义,可以去掉。

可以有两种不同的使用方法:在求取激活函数之前进行归一化,然后再利用激活函数得到该层网络的输出;也可以先计算激活函数的输出, 然后再进行归一化。第一种方法较为常用。为什么?

\begin{align*} \mu = \frac{1}{m} \sum_i Z^{[l](i)} \\ \sigma^2 = \frac{1}{m} \sum_i (Z^{[l](i)} - \mu)^2 \\ Z_{norm}^{[l](i)} = \frac{Z^{[l](i)} - \mu}{\sqrt{\sigma^2+\varepsilon}} \\ {\widetilde{Z}}^{[l](i)} = \gamma Z_{norm}^{[l](i)} + \beta \end{align*}

使用 mini-batch 前向传播的时候在计算激活函数之前先使用 batch norm ,然后计算激活函数,继续传播;反向传播时使用和求取权重 参数 W 一样的方法来求取均值和方差参数 \(d\gamma, \ d\beta\) 。

batch norm 使得网络每一层的输出值都得到归一化,归一化到某个分布。这将减小前面层网络参数的变化对后面层权重的影响,因为不 论前面层如何变化,都始终服从某个固定的分布,当前面层的输入变化时,其输出变化不会很大,所以后面的网络层的输入不会变化很大, 从而前面输入的变化对后面层网络权重参数的训练的影响减小,类似 达到了让每层网络参数独立训练的效果 。另外 batch norm 还有 一点正则化的效果,由于使用 mini-batch 只是所有训练样本的一小部分,所以其均值和方法都含有一定的噪声,每次使用 mini-batch 的样本去训练网络,并用含有噪声的均值和方法去归一化每一层的输出,就类似于 Dropout 随机丢弃网络中神经元节点一样,达到了轻 微的正则化的效果。

测试时一般一次只输入一个样本,而不是像训练时那样,每次使用 mini-batch size 数量的样本。需要使用训练样本来估计网络每一层 输出的均值和方差,并用于测试时使用。一般使用不同的 mini-batch 的各个层输出值的指数加权平均来估计

\begin{align*} {\mu_{mean}}^{[l]} & = \beta {\mu_{mean}}^{[l]} + (1-\beta) {\mu}^{\{i\}[l]} \\ {\sigma_{mean}}^{2[l]} & = \beta {\sigma_{mean}}^{2[l]} + (1-\beta) {\sigma}^{2\{i\}[l]} \\ \end{align*}

疑问:这里求取平均值只是穿越了不同的 mini-batch ,那么不用关系 epoch 吗?是不是取最后一个 epoch 的所有 mini-batch 的平均 效果更好?感觉这个好像就是训练好网络之后,又重新将所有训练样本训练一般一样。吴恩达说两者的效果都不错。这里的平均值次数是 不是应该选的比较大一点?0.9999

Sometimes it has some extra intended or unintended effect on your learning algorothm.

2.14 Softmax

Softmax 是一个激活函数,不同于 ReLU 或者 sigmoid 函数, Softmax 的输入和输出都是一个向量,用于得到输入分到不同类别的概率。 可以看做是罗杰斯特回归的推广。

\begin{align*} \phi_i = \frac{e^{\eta_i}}{\sum_{j=1}^C e^{\eta_j}} \end{align*}

单使用一个网络层,并使用 Softmax 作为激活函数,可以做到多分类,每个类别之间都是不同的线性分类器的效果。

损失函数,试图使得样本分到与标签相同类别的概率尽可能的大。

\begin{align*} loss(\hat{y},y) = \sum_{c=1}^C y_c ln \hat{y}_c \\ dZ^{[l]} = \frac{\partial J}{\partial Z^{[l]}} = \hat{y} - y \end{align*}

2.15 Tensorflow

选择深度学习框架的标准:

  • 易于编程、迭代和最终产品的部署 deployment
  • 运行速度快
  • 真正的能够开源很久;一些公司会逐步关闭曾经开源的软件

3 深度学习策略

Deeplearning strategy :诊断系统的瓶颈和 debug 的能力。

虽然很多深度学习研究人员说,他们只是将数据输入系统,然后系统自己去学习相应的知识,中间没有人为干预。但是搭建一个系统时, 是需要很多很多人为干预的,需要人类的经验来搭建一个可以自动学习的系统。

3.1 Orthogonalization

老式电视机设计者花费很多时间来设计,使得每个旋钮都有明确的功能,每个旋钮只能调节一个功能选项,每个旋钮互相不影响,从而单 独调节需要的选项,使得调节更加容易。达到了相互 正交化 的功能。

机器学习假设链:

  1. fit training set well on cost function
    • 如果系统在训练集上表现不好:训练更大的网络(模型不够复杂来拟合映射函数)、增加训练时间、更好的优化算法(Adam 等)、 调节超参或者更改网络架构
  2. fit development set well on cost function
    • 算法在在训练集上表现良好,但在验证集上表现较差:正则化、更大的训练集(用更多的样本学得更多的知识,更好的泛化到验证 集)、调节超参或者更改网络的架构
  3. fit test set well on cost function
    • 在训练集和验证集上表现良好,但在测试集上表现较差:更大的验证集(此时可能对验证集过拟合了)
  4. perform well in realWord
    • 算法在训练集、验证集、和测试集上表现都可以,但最终使用上表现较差:改变验证集或代价函数(在测试集上表现良好时,却并 没有在真实使用时表现良好,说明要么验证集和测试集的分布不合理,要么代价函数指标不对 is not measuring the right thing)

3.2 Single number evaluation metic

单一评价指标:集可以使用 \(F_1 = \frac{2}{1/P + 1/R}\) (Harmanic mean)来评估指示算法的性能。这样当有很多的识别器,不同的识别器又有很多 的性能指标时,可以快速的知道哪个分类器的性能更优。

准确率和召回率需要权衡

  • precision: the examples that your classifier recognize as cats,what percentage actually are cats?正确率表明,如果识 别器说这是一只猫,那么有 95% 的可能性表明这是一只猫。
  • recall: of all the images that really are cats,what percentage were correctly recognized by your classifier? 召回率即 实际上是猫的图片中,有多少被分类器识别出来。

但有时候很难将很多要求的指标综合到一个单一的实数上,此时可以采取的策略是选择一个需要最优化的指标 optimizing metic ,让其 越小越好;其他的指标只要满足一定的阈值就好 satisficing metic (如运行时间小于 100ms ,24 小时内误唤醒次数小于 1),这些 指标只要达到要求的范围便不在乎有多好(运行时间 1ms 或者 99ms 都不关心),但一定要满足这些指标的阈值。

3.3 Train-development-test set

训练接、验证集、测试集的选取对训练迭代的效率有很大的影响。设定 development set 和 evaluation metric 就表明设定了需要瞄准 的目标靶心,设定了靶心后,不断的调节优化算法,使得算法能够逐渐靠近靶心。如果验证集和测试集输入不同的分布,相当于在测试时 更换了靶心目标,准确率肯定无法保证。

指导原则:选择的验证集和测试集一定能够反应你最终希望使用的场景。验证集和测试集从样本集中随机选取。

验证集和测试集并不再需要像机器学习中那样继续使用 20% 的样本(在样本数量比较小的时候可以)。当用于大量的样本的时候,可能 只需要分别有 10000 或者 100000 个样本来作为验证集和测试集即可,而这个比例将远远小于 20% 。当确信验证集足够大, 算法不会 在验证集上过拟合的时候, 可以省略测试集(但强烈不建议这么做)。Set your test set to be big enough to give high confidence in overall performance of your system. And the development set has to be big enough to evaluate different ideas.

当评价指标无法区分哪一个算法更好的时候,需要更改评价指标 (或者验证集和测试集)。因为评价指标的作用就是评价算法的优劣。 例如要训练一个猫脸识别器,最终两个分类器比另一个分类器的识别率高,但是该分类器会将一些色情图片误分称猫,这是绝对不能容忍 的。此时需要在代价函数中增大色情图片误分的权重。或者使用验证集和测试集都是高清的图片,但最终用户使用的都是一些低分辨率的 图像,使得一个算法 A 在验证集的测试集上表现比另一个算法 B 好,但是在用户使用时却没有算法 B 表现好,此时需要修正验证集和 测试集。

即使最初无法定义一个完美的评价指标或者验证集和测试集,先使用其进行快速迭代,等发现问题再去修改;但不建议在没有评价指标和 验证集和测试集是长时间训练,那样会减缓进度。同时将定义评价指标和优化指标看成是相互正交的,两者可以分别单独调节。

如果只有少量的最终使用的场景样本,而有大量其他从网络上下载或者花钱购买的与实际应用分布不相同分布的样本:绝对不可以将所有 的样本混合,然后从中按比例随机选择训练集以及验证集测试集,这样会让验证集测试集中含有太多的不符合最终场景的样本,相当于放 错了靶心。 验证集和测试集必须全部使用最终使用场景样本, 因为这两者是为了设定算法的靶心;如果有剩余可以放入训练样本中。 当然缺点是这会让训练接和验证集测试集的分布不同,从会有数据不匹配误差。

此时应该在训练样本中保留一部分样本作为训练-验证集( train-development set ),这一部分样本和训练集的样本分布相同,但不用 于训练模型,而用于估计模型在训练集上的泛化误差。这样有助于估计模型在验证集测试集上增大的误差,是由于模型的泛化能力较差, 还是由于训练集和验证集测试集因为分布不同而导致的数据不匹配(data mismatch)所引起的。

human error(train set) 0% 0% 0% 0%  
          available bias
train error 1% 1% 10% 10%  
          variance
train-development error 9% 1.5% 11% 11%  
          data mismatch
development error 10% 10% 12% 20%  
test error 10% 15% 12% 20%  
  large variance data mismatch and overfitting on development set large available bias large available bias and data mismatch  

当然如果验证集测试集比较简单,则可能出现在验证集测试集上的误差小于在 train-development 上的误差。

处理 data mismatch: 人工分析训练集与验证集之间的差异(只分析训练集与验证集的差异,不分析训练集与测试集的差异,因为那 样可能会导致在测试集上过拟合),比如验证集中大多数图片都比较模糊、有较大的噪音、包含较多的街道数字、人脸有较大的旋转等等; 找到不同的特征后,尝试将训练集变得更像验证集(通过认为合成噪声)或者收集更多同验证集分布相同的样本来训练算法。

Data mismatch 并没有系统的解决办法,但上述方法通常可以对问题的解决有很大的帮助。

Using artificial data synthesis, be cautious and bear in mind whether or not you might be accidentally simulating data from a tiny subset of the space of all possible examples. 比如将 1 小时的噪声加到 10000 小时的训练样本中,或者从游戏中 截图很多很多的汽车的图片(实际上游戏中可能只有 20 种车型),虽然在人类看来这些合成的声音或者图片都相当的好,可是这些只是 需要处理的问题集中很小很小的一部分,算法很有可能只会对这 1 小时的噪声或者 20 中车型过拟合。

吴恩达说通过人工合成的方法使得已经很好的语音识别系统又有了很大的提高。所以人工合成的方法还是可以使用的。

3.4 最优分类器

贝叶斯最优误差: 在人类擅长的领域(如图像识别、语音识别),人类的表现已经很好了,很接近贝叶斯最优误差,所以经常使用人类 的表现来近似估计贝叶斯误差。

算法达到人类表现后很难继续优化的原因: 当算法超越人类的表现的时候,提升空间已经不大了;并且当算法表现性能不如人类的时候, 可以通过使用更对人为标记的样本、人工错误分析、更容易分析 bias/variance,但是当算法的性能高于人类的时候,这些方法就很难实 施,所以导致缺少调试方法来使得算法的性能进一步提升(用另一个算法来提升该算法???)。

原来一直默认贝叶斯最优误差约为 0,但是有些问题并不是(比如在噪音特别大的环境中辨别一句话)。此时应该清晰的了解贝叶斯最优 误差的大致范围,才能指导训练算法。当模型的训练误差和验证误差相同时,由于贝叶斯误差的不同,需要采取不同的方法来调节算法。 而在人类非常擅长的领域,使用人类的表现来近似估计贝叶斯误差。由于贝叶斯误差是理论上限,所以不管是一个特别擅长的人的表现还 是一个团队共同决定后的表现,总是取误差最小的那个值最为贝叶斯误差的估计。当算法很接近人类的表现的时候,如果继续减小训练集 的误差很可能只会过拟合。

吴恩达将算法的训练误差与人类的表现误差之差称为可避免误差(avoidable bias)。

人类的表现 1% 7.5%
模型训练误差 8% 8%
验证误差 10% 10%
结论 高偏差 高方差

当一个算法在训练集上的误差为 0.3% ,验证集上的误差为 0.4% ,而一个团队的决定表现的误差为 0.5% 。此时就很难知道贝叶斯误差 是多少(可能是 0.1% 也肯能是 0.4% ),算法是过拟合了还是仍有提升空间将难以使用原来的方法进行判断。

在线广告投放、产品推荐、逻辑推理、贷款评估等方面,计算机早已经远远超越人类;同时在某些特定的语音识别、图像识别领域,机器 也有一些超越了人类的表现。

监督学习的两个基础:算法在训练集上表现良好,有较小的可避免误差;在验证集上表现良好,模型泛化到没有经过训练的验证集测试集 时误差不会增加很多。

3.5 误差分析

在验证集或者测试集找到一些分类错误的样本(100 个,或者更多),统计 (false positive 和 false negative) 不同类型错误的个数, 如果在统计的过程中发现了错误分类样本新的共性,可以随时添加新的一类重新统计,最终得到不同错误类型占总错误的百分比,从而帮 助我们找到系统最需要解决的问题,并大致了解各个改进后的结构对系统性能的提升空间。

图片 大型猫科 模糊 滤镜 comments
1 1 0 1 0 柯基
2 0 0 1 1  
3 0 1 0 0 豹子
4 0 0 1 0 非常模糊
         
百分比 8% 43% 61% 12%  

样本标记错误: 深度学习算法对随机错误非常鲁棒。所以如果训练样本中有些许样本由于某些随机因素而标记错误,并不会对算法产 生影响;但是如果是系统误差,即将所有白色的小狗都标记成了猫,那么算法将会受到影响。如果在验证集和测试集中有标记错误的样本, 则需要评估这些标记错误的样本对最终正确率的影响,如果标记错误的样本占最终错误率已经影响到了对不同算法优劣的评估,则需要修 正这些标记错误的样本,如果标记错误的样本只是占很小的比例,那么先处理其他更重要的事情,暂不修改标记。同时要 注意 ,如果 要修正标记错误的样本,必须对验证集和测试集做同样的操作,以确保两者的分布相同;并且应该同时处理那些分类正确和分类错误的样 本,否则会引入偏差(不过实际上,很少有人这么做,一般只修正分类错误的样本,因为分类正确的样本数量太大);修正标记后,训练 集和验证集测试集的分布会稍微有些不同。

3.6 Build first system quickly

语音识别系统: 可能有 50 个不同的改进方向,每一个都可以改善系统。 But the challenge is how do you pick which of those to focus on.

  • 嘈杂的环境: cafe noise、car noise
  • 口音
  • 远场语音识别
  • 儿童语言
  • 口吃

Guideline: Build your first system quickly and dirty, then iterate.

  1. 设置验证集、测试集和评价指标。就是先设定目标靶心
  2. 快速搭建一个原始系统。原始系统可能很差劲,不过无所谓。当然如果有一些可能参考的文献,那么可以借鉴
  3. 使用 bias/variance 分析、误差分析等方法来分析下一步的方向。

吴恩达说有些系统搭建的太简单,但更多的是很多团队搭建了一些过于复杂的系统。所以应该从简单开始,然后逐步去分析处理需要解决 的问题,慢慢让系统变复杂,等达到要求就可以终止。当然这些处理流程都是在解决实际问题,严重不适合去发明一个新的算法。

3.7 Transfer learning

迁移学习: 如果任务 A 和任务 B 使用拥有的输入,且任务 A 有远多于任务 B 的样本,那么任务 A 学得的 low level features 将 有助于任务 B的训练。可以根据任务 B 的样本数量,将任务 A 的最后一层或者几层权重使用随机初始化的方法来重新赋值(也可以更改 后面基层网络的结构,如增加新的网络层或者删除某些节点),然后使用任务 B 的样本只训练最后几个重新初始化权重的网络层。

如果有较充足的样本,可以先使用其他相似模型对权重进行初始化,也称为预训练( pre-training ) ;然后再使用样本对网络进行参 数进行训练,只是不再采用随机初始化的方法来初始化权重,而是使用前面模型的权重来初始化模型,这样的训练称为调优( fine-tunning ) 。

3.8 Mutilate task learning

多任务学习在计算机视觉中运用较多,比如同时去识别图像中很多不同的物体。多个不同的任务会共享 low level features 。此时的代 价函是所有不同任务的代价函数的总和。并且当一些图片中并没有标记某些类别的物体是否存在的情况下,仍然可以使用多任务学习来训 练。

\begin{equation*} J = \frac{1}{m} \sum_{i=1}^m \sum_{j=1}^c \mathcal{L} (\hat{y}_i^{(i)}, y_j^{(i)}) \end{equation*}

只要网络足够大,多任务学习的效果通常优于多个单任务网络的效果。一般用于需要同时处理几个相近的任务,且各个任务的样本数量相 差不太大,通过组合这些样本来训练一个更大的网络。

3.9 End-to-end learnning

端到端的学习:直接从输入中间经过一个网络然后得到输出。当有非常多的样本的时候,使用端到端的学习效果会很好;但是如果样本量 比较小的时候,传统的人工设计 pipeline 的方法往往效果更好。

比如人脸识别系统,如果直接将一张包含人脸的图像作为输入,希望得到这个人的身份信息,往往效果不好,同时也没有很多这样的样本。 实际中一般分成两步,先检查出人脸的位置,然后只将包含人脸的方框用于网络的输入。这样每一步的任务都比较容易实现。(训练人脸 识别时,使用的人脸库,需要先将这些人脸进行裁剪,然后再送入网络进行训练)

优点:

  • Let the data speak :无论从输入到输出的映射函数是什么,让网络自己去学习数据中的信息,而不引入任何人为观点
  • Less hand-designing of components needed :简化了设计流程

缺点:

  • Need large amount of data
  • Excludes potentially useful hand-designed components :无法将人类的经验注入算法

只在样本的数量足以训练从输入 x 到输出 y 的映射的时候才使用端到端的学习。并且要 carefully choose what types of x to y mappings you want to learn depending on what task you can get data for. 可以让端到端的学习只是系统的一个组件,或者整个系 统就是一个端到端的系统。但不应该一味的不切实际的追求端到端。

4 卷积神经网络

计算机视觉用于处理图像分类、目标识别、图片风格转换等问题。

4.1 Convolution

卷积数学上定义:先翻转然后再相乘,为了让卷积具有结合律;但卷积神经网络中并没有将卷积核进行水平翻转和垂直翻转,卷积操作仅 仅执行了 加权求和 。其实是数学上的 coss-correlation

边缘检测:滤波器 filter 检测水平边缘和垂直边缘;

1 1 1
0 0 0
-1 -1 -1
1 0 -1
1 0 -1
1 0 -1

上面滤波器中的权重未必是比较好的值,在卷积神经网络中,让网络自己去学习滤波器中的权重参数。但卷积核的尺寸需要人工设计,通 常使用奇数大小的卷积核,常用卷积核大小 3 、5

立体卷积:卷积操作可以同时在多个通道上进行,如输入的图像有 RGB 3 个通道,那么卷积核同样有 3 个通道,从而可以在不同的通道 上进行不同的滤波操作以提取不同的特征。此时需要将所有通道的值求和得到输出,此时仅仅是增加了一个卷积核的通道数,但多个通道 的卷积核仍然是一个卷积核,所做的卷积操作类似于一个通道的卷积核。卷积网络后面的网络层有多个通道时也是一样, 一定要确保输 入的通道数和卷积核的通道数相等 ,而卷积的输出只有一个通道。当然这只是对应一个卷积核,可以同时使用多个卷积核来提取不同的 特征,每个卷积核都和输入有相同的通道个数,输出的通道数等于卷积核的个数。输出通常称为映射(maps)。一般随着卷积层数的增加, 图像的尺寸将逐渐变小,通道数逐渐增加。

parameter sharing and sparsity of connections : 这里的卷积操作实现了局部感受野和权值共享:局部感受野指的是一个卷积核只关注其所对应的区域的输入,而不关心其他区域的输入; 权值共享指的是输入的不同区域使用相同的权重来提取特征;这样无论输入有多大,参数的个数可以始终保持不变,有效减小了过拟合。 当然这样将只能提取一个特征,如果想要提取多个特征,可以使用多个卷积核。

最终计算 Softmax 层时,将前一层的节点展开称一个向量作为 Softmax 层的输入。

一个卷积神经网络中通常有三种不同的网络层:卷积层(convolution, CONV)、池化层(poolling, POOL)、全连接层(full connected, FC) 。通常一个典型的卷积神经网络为一个或几个卷积层后面接一个池化层,这样的样式重复多次,然后是一个或多个全连接层最后是一 个 Softmax 层。组合这些基本的模块需要深入的理解和感觉,需要大量阅读别人的案例。

随着网络层数的增加,激活单元的个数将逐渐变少,而如果减小的速度太快,可能会影响网络的性能。

4.2 Padding

使用卷积核对图像进行卷积操作后图像的尺寸会变小,同时边缘的像素点被使用的次数小于中间像素点被使用的次数(对最终结果的将有 不同的影响)。所以一般会对图像的四周进行填充操作,填充的值一般为 0 。有两种不同的填充方式

  • Valid :不填充
  • Same : 保证卷积后图像的大小不变

4.3 Stride

卷积的步长不一定非得是 1 ,可以使用其他的步长。如 poolling 层其实就是步长为 2 的卷积。

卷积操作后图像的大小,如果无法得到整数,需要向下取整,也就是卷积操作不能超过包含填充在内的图像。

\[ \lfloor \frac{n+2p-f}{s} + 1 \rfloor \]

卷积层各参数的维数:

\begin{align*} & input: \quad m \times n_H^{[l-1]} \times n_W^{[l-1]} \times n_c^{[l-1]} \\ & output: \quad m \times \left \lfloor \frac{n_H^{[l]} +2p^{[l]} - f^{[l]}}{s^{[l]}} + 1 \right \rfloor \times \left \lfloor \frac{n_W^{[l]} +2p^{[l]} - f^{[l]}}{s^{[l]}} + 1 \right \rfloor \times n_c^{[l]} \\ & weights: \quad f^{[l]} \times f^{[l]} \times n_c^{[l-1]} \times n_c^{[l]} \\ & bias: \quad 1 \times 1 \times 1 \times n_c^{[l]} \\ & \text{each filter is} \quad f^{[l]} \times f^{[l]} \times n_c^{[l-1]} \end{align*}

4.4 Poolling

池化层用于缩减模型以提高计算速度,同时提高提取的特征的鲁棒性。而池化层只有超参卷积核的大小和步长,没有参数需要使用梯度下 降法进行训练。由于池化层没有参数,通常计算网络的层数的时候,并不包含。

max pooling :相当于一个卷积核的尺寸为 2 * 2 ,步长为 2 的卷积。在实际使用中效果很好,所以被广泛使用。可以理解为保留了检 测到的最大的特征值,而该特征值很可能代表某种信息。经过池化层之后,图像的尺寸将减小一半,通道数不变。

average pooling :使用的比较少,通常都使用 max poolling

4.5 Classic network

LeNet-5 : 大约有 6 万个参数;奠定了卷及神经网络的基本架构:卷积-池化-卷积-池化–全连接-Softmax;网络的基本规律:随着网 络层数的增加,图像的尺寸逐渐变小,通道数逐渐增加。

AlexNet : 大约有 6000 万个参数;使用了 ReLU 激活函数;网络架构和 LeNet-5 相似,但使用了更多的隐层和更多的训练样本,从 而使得性能有了很大的提升;但是有大量的超参需要人工调节

VGG : 大约有 1.38 亿个参数;固定卷积核大小为 3 * 3、步长为 1、same padding,池化层 2 * 2、步长为 2 ,没有太多的超参。 虽然有 16 个权重层,但总体结构并不复杂;网络的结构很规整:总是几个卷积层后接一个池化层、滤波器的个数不断更新为原来的 2倍, 从而图像的宽和高每次池化后都缩减到一半、每组卷积的通道数都增加一倍。

ResNet : residual Network 残差网络,在 plain network 中增加 shortcut 或者 skip connection 以构成残差块,将前面某一层的 激活输出直接跨越一些隐层增加到后面某个隐层的激活函数输入,从而形成残差网络。使用残差网络使得训练误差可以随着网络的层数增 加而不断变小。这些跨越的连接要保持维数相等,所以原论文中 shortcut 只跨越了两层,且使用 same padding ;如果维数不同,则需 要增加一个转换矩阵 Wt ,Wt 可以使用网络进行训练或者使用 0 padding 填充都可以。

由于梯度消散和爆炸,很难训练层数很深的网络。使用 shortcut 可以使网络很容易的学到和去掉残差块同样的网络网络结构的参数,这 样无论是否增加残差块,网络的性能都是一样的。当然如果残差块能够学习到一些额外的信息,网络整体的性能将得到提升。而不使用残 差块,仅仅增加网络的层数,深层网络难以学到和浅层网络表现相同的参数,从而使得网络的性能变得更差。

1 * 1 convolutions : 用于改变网络的通道数; 1 * 1 卷积就是将不同通道的同一个位置的节点组成一个切片向量,乘以一个同维数 的权重,然后再经过激活函数得到同位置的输出。就是一个所有通道的全连接操作,这样将得到一个通道,如果需要得到多个通道,只需 要增加 1 * 1 卷积的个数即可。也称为 network in network 。

Inception network : 将 1 * 1 卷积、3 * 3 卷积、5 * 5 卷积、max pooling (需要 same padding ,且步长改为 1 使得图像的高 和宽保持不变)全部在一个网络层中使用,将每一种操作得到的结果堆叠起来得到网络的输出,让网络自己决定这一层网络到底需要什么 操作,而不人工指定该层就是卷积层或者池化层或者全连接层。当然这会大大增加计算量,此时可以先通过 1 * 1 convolution 来减小 网络的通道数,可以大大减小计算量。

inception module : 每一个基本模块都是将前一层的输出,使用一些 1 * 1 卷积得到输出的一些通道、先使用 1 * 1 卷积来缩减通道 数,然后使用 3 * 3 卷积得到输出的某些通道、先使用 1 * 1 卷积缩减通道数,然后使用 5 * 5 卷积得到输出的某些通道、使用 max poolling 然后再使用 1 * 1 卷积来缩减通道数,作为输出的一些通道。这写构成一个基本模块,然后将许多模块串接起来构成整个网络。

网络中还在两个隐层加入了分支来预测最终的结果,使得隐层特征也可以预测结果。

网络的名称来自盗梦空间中 “we need go deeper” 这句话,表明谷歌想要深层网络的决心;另外这个网络也称为 GoogLeNet ,为了向 LeNet 致敬。

4.6 Transfer learning

open source implementations : 使用开源实现,深度网络很复杂或者细致,以至于很难复现。学习衰减速率等超参的调节,会影响网 络的性能。即使是顶尖学习深度学习的博士生也很难仅仅通过阅读他人的论文来复现他人的成果。因此应该使用别人已经训练好的网络权 重参数作为预训练,将大大加快迭代速度。别人的网络是由大量的样本和 GPU ,花费很长时间训练出来的,直接使用将使自己直接跨越 这个过程。另外可以根据自己样本数量的多少,来选择值训练网络的后几层,如果样本很少,可以只替换到 Softmax 层,稍微多一点, 就可以替换掉后 2~3 层或者更多,替换成自己设计的网络层,将前面层的网络权重冻结,然后只训练替换掉的网络层。而如果样本量很 大,则可以重新训练整个网络,将他人的权重作为初始化值开始训练,就是 pre-trainning 和 fine-tune 。

数据集 : ImageNet 、 MS coco 、Pascal

计算机视觉通常需要大量的样本,增加样本数量总能提高性能, data argumentation 是一种常用的技巧。能够保留图像中绝大多数需要 保留的信息就是一个好的 data argumentation 。

方法 : Mirroring 、 Random Cropping 、 Rotation 、 Shearing 、 Local warping 、 Color Shifting (RGB 三个通道分别增加或 减小三个独立的值来改变图像的颜色,使其对颜色的变化更加鲁棒;可以使用 AlexNet 论文中提到的 PCA color argument 方法来改变 图像的颜色,不会改变图像的基本色调) 。

当有较大的样本量的时候,通常使用某个或某几个线程来对样本进行增强,同时将变换后的样本组成一个 mini-batch 送入 GPU 进行训 练,两者可以并行处理。而不是先将样本变化并保存,然后再去训练。 同样 data argumentation 时也会有一些超参,此时同样可以参 考别人的实现,如果不合适在适当调节

算法学得的知识来自两个方面: 带标签的数据; 手工设计的特征、网络架构或者其他组件。

当有大量的样本的时候,可以大大减少手工设计的部分;而只有少量样本的时候,则需要大量的手工设计。

计算机视觉的现状: 目标检测没有充足的样本,所以网络的机构比较复杂;目标识别有一定量的样本。而语音识别有充足的样本。

比赛时可以使用集成(Ensembling)或者 10-crop 的方法来提高 benchmark 的正确率,但很少在实际中使用。

4.7 Object Detection

在最终输出中除了类别的概率,增加目标的边框 bx,by,bh,bw ,分别表示目标的中心点坐标,目标边框的高和宽;以及是否有目标指示 量。 loss function 需要计算所有的预测损失,可以根据需要让不同的输出使用不同的损失函数。

Landmark Detection ; landmark 即标识目标的一些关键位置点的坐标。可以是人脸上一些关键点的坐标,也可以是表示人体姿态的 一些点的集合。但是要确保这些 landmark 点在所有的样本中顺序相同。

Sliding Windows Detection : 滑动窗口检测,使用一个比较小的矩形如 3 * 3 ,称为窗口,重图像的左上角开始,从左到右,从上 到下,以一定的步长遍历整个图像,裁剪每一个窗口位置的图像来进行目标检测;然后将窗口方法一定的比例,如宽和高都变成原来的 1.1 倍,使用扩大后的窗口按照上述方法重新扫描一遍;然后继续增大窗口的大小,指导窗口大小为整张图像后停止。该方法需要相当大 的计算量。

Use convolutional network to detect : 使用卷积神经网络来检测目标将避免滑动窗口中大量的重复检测,只需要对图像进行一次卷 积就可以达到检测所有窗口的目的。由于全连接层限制了输入的维数,需要将网络中的全连接层转化成卷积层,让整个网络全部由卷积层 和池化层组成,这样网络变得对输入不敏感,可以使用任意维数的输入。将全连接层转换成卷积层的步骤是,将全连接层的每个节点看做 是前一层网络经过同等大小的卷积核产生的 1 * 1 的输出,然后使用全连接层节点个数个卷积核对前一层网络进行卷积得到该全连接层, 此时全连接层的每个节点仍然和前一层网络的每一个节点全部连接,和全连接的效果一样。使用改进后的网络对待检测图像进行检测,网 络最终输出的每一个节点将代表一个滑动窗口的检测结果,而使用 max-pooling 为 2 * 2 则代表滑动窗口的步长为 2 。

YOLO : 将图像分成许多小的单元 cell ,比如 19 * 19 个,将目标分配到其中心点所在的 cell ,依据这个 cell 来确定目标的中心 点坐标,同时确定其高和宽。将图像分成足够多的 cells ,使得目标中心点在多个 cell 的可能性很低,即中心点必定在某个 cell 中, 而宽和高可能超过该 cell 。使用 cell 的左上角坐标为 0,0 右下角坐标为 1,1 ,从而确定目标的中心点坐标如 0.3,0.7 ,宽和高为 0.9,0.5 或者 1.3,0.6 等等。当然这是比较简单的标注方法,可以通过使用 sigmoid 函数确保中心点坐标介于 0-1 之间,指数参数化 确保宽和高大于 0 等方法效果更好,可以参考原论文。不过该论文较难,吴恩达表示需要请教别人才能看懂其中的细节。输出 y 包含目 标的矩形框以及不同类别的概率,同时要显示指定是否是背景。

Intersection over Union : IoU 交并比,即两个矩形框交集的大小与并集大小的比值称为交并比。用来衡量两个 bounding boxes 的 相似程度。如果两个矩形框完全重合,那么IoU = 1 ,一般 IoU > 0.5 就认为检测成功,但这个 0.5 只是一个人为规定的值,为了更好 的效果,可以选择成 0.6 或者 0.7 等这些更严格的值。

Non-max Suppression Algorithm : 选择一个概率最大的 bounding box ,然后把与该 bounding box 的 IoU > 0.5 的 bounding box 抑制掉,认为两者检测到的是同一个目标。从而确保一个目标只被检测到一次。可以先把所有 bounding box 概率小于 0.6 的都去掉, 认为这些 bounding box 无效。

Anchor box : 如果在一个 grid cell 中有多个不同的目标的中心点,可以使用多个预定义的组合矩形框,将目标分配到和真实边框具 有最大 IoU 的矩形框中。这样需要增加输出 y 的维数,几个矩形框就需要将原来输出的维数变成几倍。无法处理一个 grid cell 中有 多于定义的矩形框个数的目标,也无法处理一个 grid cell 中有多个同样形状目标的情况。可以人工定义使用组合矩形框的形状,也可 以使用 k-means 算法来对不同的目标进行聚类来得到矩形框的形状。此时需要在样本中每一个 grid cell 所有的 anchor box 中标注目 标的边框和类别。实际使用中可能用 5 、6 个。

最终会在每个 grid cell 中得到 anchor box 个目标边框,去掉概率较小的边框,如类别概率小于 0.6 的边框,然后对不同的类别分别 运行 Non-max suppression algorithm ,得到最终的结果。输出 y 的维数 grid-cells * gird-cells * anchor-boxs * [class+1+4]

R-CNN : Region proposals , 先使用图像分割或者聚类算法,从图像中得到一些候选区域,认为目标只会出现在候选区域中,然后在 候选区域上使用滑动窗口输入卷积神经网络,得到目标的中心点和边框(并不依赖于候选区域的边框,所以会得到目标的精确边框); Fast R-CNN 使用卷及神经网络代替滑动窗口;Faster R-CNN 使用卷积神经网络得到候选区域。但是吴恩达认为相比于这样需要两个步骤 来完成,YOLO 更有优势,速度比 Faster R-CNN 快很多。但候选区域是一个很有意思的想法,影响也很广。

4.8 Face Recognition

face verification
1 对 1 的验证,验证输入头像是否是某一个人
face recognition
1 对 n 的验证,识别输入的头像是数据库中的哪个人
One-shot learning
数据库可能只有每个人的一张头像,实际应用中输入的也仅仅是某人的一张头像。此时如果使用 Softmax 来分 类,表明此人的身份信息,经没有任何扩展性,比如,需要在数据库中增加心得人员时,必须得重新训练网络的权重。解决办法是 仅仅使用网络的到人脸的一个特征向量,然后使用相似函数(similarity function)来度量两个特征向量的相似性,当两者的特征的 距离小于某个阈值时(超参),认为两者是同一个人,反之则认为不是同一个人。这样只需要求取每张头像的特征,然后计算与其 他头像的相似度就可以。
Siamese network
[DeepFace]让同一张人脸得到的特征相似度数值尽可能小、不同人脸得到的尽可能大。可以使用两张人脸输入得 到相应的特征,然后使用二分类。使用罗杰斯特回归 \(\hat{y} = \sigma (\sum_{k=1}^{128} w_i |f(x^{(i)})_k - f(x^{(j)})_k| + b)\\) ,将所有的人脸头像对输入网络训练,最终得到网络的权重。
triplet loss
[FaceNet] 每次训练时需要三张人脸头像 anchor、positive、negative ,anchor 和 positive 是同一个人的不同 头像,anchor和 negative 是不同人的头像,让 d(anchor,positive) 比 d(anchor,negtive) 小,同时可以增加一 个间隔 \(\gamma\) [超参],使得两者的距离差至少等于该间隔的大小(类似 SVM),同时应该选择比较 hard triplets 来训练网络,由于不同的人脸之间差别可能本来就很大,选择比较难的 triplets 才能让网络尽可能学习 更多有用的信息。可以阅读原论文查看怎样更好的选择 triplets 来加快网络的训练。代价函数定义为 \( J = \sum_{i=1}^m \max(||f(A)-f(P)||^2 - ||f(A)-f(N)||^2 + \gamma, 0 ) \)

数据中不应该存储人脸图像,只需要存储人脸的特征,这样就不必每次消耗时间来提取数据库中人脸的特征,只需要计算新输入人脸的特 征,然后逐一和数据库的人脸进行比较即可。

4.9 Neural Style Transfer

[Visualizing and understanding convolutional network] 网络中的每个神经元都用于检测某个固定的特征(虽然很难使用语言来描 述),低层的网络检测的是 low-level 的特征,高层的网络检测 high-level 的特征。网络的第一层检测到的是比较简单的特征,如边 缘或者颜色阴影,其中某个节点可能主要用于检测斜率为 25 度的直线,某个节点检测左下角明亮的物体等等;此时检测的都是图像中很 小的局部特征,随着网络层数的增加,节点检测的范围将逐渐增大。

[A neural algorithm of artistic style] 定义生成图像的代价函数等于生成图像 G 与内容图像 C 的差别和生成图像与风格图像 S 的 差别之和,现利用随机初始化产生生成图像 G ,然后用一个训练好的网络,使用梯度下降法不断迭代来更新生成图像 G 。内容图像 C 和生成图像 G 之间的代价函数定义为网络的某一层 l 在输入为内容图像 C 和输入为生成图像 G 时,激活函数输出值逐元素求差,然后 平方求和,即两个激活函数向量逐元素差的平方和。如果两者的差值很小,说明两者的内容很相似;风格图像 S 和生成图像 G 并不需要 内容相同,而是需要风格相同,使用不同的通道激活输出的相关性来衡量两者的相似性,求解互协方差矩阵 Grim Matrix ,即风格图像 S 在网络某一层的某个通道有一个特定的输出,在该层的其他通道有另一种相关的输出特征,为了让生成图像 G 有相似的风格,生成图 像 G 也应该有相似的特性。计算所有的网络层会得到更好的效果。

\begin{align*} J(G) & = \alpha J_{content} (C,G) + \beta J_{style} (S,G) \\ & = \alpha || a^{[l](C)} - a^{[l](G)} ||^2 + \beta \sum_l \lambda^{[l]} ||G^{[l](S)}-G^{[l](G)}||_F^2 \\ & = \alpha || a^{[l](C)} - a^{[l](G)} ||^2 + \beta \sum_l \lambda^{[l]} \sum_k^{n_c^{[l]}} \sum_{k'}^{n_c^{[l]}} (G_{kk'}^{[l](S)} - G_{kk'}^{[l](G)}) \\ & = \alpha || a^{[l](C)} - a^{[l](G)} ||^2 + \beta \sum_l \lambda^{[l]} \sum_k^{n_c^{[l]}} \sum_{k'}^{n_c^{[l]}} (\sum_{i=1}^{n_H^{[l]}} \sum_{j=1}^{n_W^{[l]}} a_{ijk}^{[l](S)} a_{ijk'}^{[l](S)} - \sum_{i=1}^{n_H^{[l]}} \sum_{j=1}^{n_W^{[l]}} a_{ijk}^{[l](G)} a_{ijk'}^{[l](G)} ) \end{align*}

4.10 1D and 3D convolutional network

卷积神经网络可以推广到 1 维数据和 3 维数据中。只是需要保持卷积核的维数和输入数据的维数相同,当输入是 1 维的时候,卷积核 也必须是 1 维,输入是 3 维时,卷积核也必须是 3 维的。让卷积核依次遍历输入的所有维度,产生相应的输出,输出的维数也始终与 输入的维数相同,即 输入的维数、卷积核的维数、卷积得到输出的维数始终是一致的 。当然卷积核的通道数应随着输入的通道数而变 化。

5 自然语言处理

序列模型可以用于:语音识别、生成音乐、情感分析、DNA 序列分析、翻译机、视频动作分析、Name entry Recognition

使用序列模型需要先构造字典,字典包含你认为比较重要的词汇。字典中需要有一个表示句子结束的标志 <ESC> ,同时还要有一个标识 单词不在字典中的标志 <UNK> 。有些商业应用的字典维数可能是 30,000 、50,000 或者 100,000 ,甚至有 1,000,000 或更多。然后依 据字典,使用 one-hot 向量(只有一个为 1 ,其余全为 0)来表示每个单词。使用 \(x^{(i)\langle t \rangle}\) 标识第 i 个句子 的第 t 个单词。

5.1 Recurrent Neural Network

如果使用标准的神经网络,让一个句子作为输入,产生相应的输出,则无法很好的处理输入维数不确定的问题,同时不同的单词无法实现 权值共享。因此提出了 recurrent neural network ,网络让单个单词作为输入(实现权值共享),经过一些运算产生一个输出,同时产 生的激活值将作为下一个单词的输入,这样将不同的单词在时间上串接了起来(循环网络名字的由来)。前面的单词将影响后面单词的预 测。如果构建双向的 RNN ,将后一时刻的输出作为前一时刻的输入,后面单词同样会对前面的单词产生影响[双向 RNN 必须等待整个句 子输入完成,所以实际的语音识别使用更复杂的机制,先输出一个词汇,然后在一个句子输入完毕后,更新刚才的生成]。RNN 中激活函 数通常使用 tanh 函数,而无需使用 ReLU激活函数,虽然网络由于在时间上进行了串联,而且句子可能很长,相当于网络的层数很深, 很有可能会有梯度爆炸或者消失的问题,因为一般都会使用 GRU 或者 LSTM 单元,而有效的解决了这个问题;而输出则根据需要选择 sigmoid 或者 Softmax 激活函数以产生两个或者多个输出。

RNN 前向传播时,只需要依据输入和前一个单词的输入产生本单词的输出和激活,第一个单词的前一个激活使用 0 向量作为输入;反向 传播时,处理依据本单词输出产生的误差,还要依据后面单词产生的误差来更细权重[backpropagation through time]。

RNN 依据输入和输出的维数有 6 中不同的架构: one to one 、one to many [生成音乐] 、many to one [情感分析] 、many to many [输入输出维数不同,翻译机] 、many to many [输入输出维数相同, name entry recognition]

Language module : 是语音识别和翻译机的基础。第一个单词输入为 0 向量,让其直接生成输出,第二个输入使用句子的第一个单词, 之后依次输入剩余的单词。第一个输出依据字典中所有单词的概率进行输出,第二个输出依据此时的输入为条件,字典中其他单词的条件 概率进行输出,第三个输出依据前面两个输入为条件的概率输出,后面输出道理相同。

此时如果将第一输出作为第二个的输入,第二个输出作为第三个的输入,直到遇到句子结束符或者达到指定的长度,就可以达到从一个训 练的 RNN 中采样一个序列的效果。这样网络自己就可以造句了,使用新闻信息训练的网络,输出的句子就有新闻稿件的意思,而使用莎 士比亚文章训练的网络生成的句子也会有莎士比亚的风格。

当然字典也可以使用 a-z 的字母,而不是单词进行训练,这样将不会出现未在字典中的单词的情况。但是序列将变得很长,而且训练将 变得非常困难,同时不容易捕捉单词间的关联信息。因此一般都使用单词级别的字典,而不是字母级别的字典。只有在大部分都是专有名 词时才会使用字母级别的字典。

5.1.1 GRU

Gated Recurrent Unit : 为了让当前的值经过很长时间之后仍然会影响后面的值。引入 memory cell ,让当前的输入可以影响很久之 后的输出。引入了两个门,决定输入到下一个时间输入的是记忆单元的值还是当前激活值。门的激活函数采用 sigmoid 函数,门的输出 将很接近 0 或者 1 ,实际中直接使用 0 或者 1 代替经更加便于思考。

\begin{gather*} {\widetilde{c}}^{ \langle t \rangle } = tanh(\Gamma_r * W_c [c^{ \langle t-1 \rangle },x^{ \langle t \rangle }]+b_c) \\ \Gamma_u = \sigma(W_u [c^{ \langle t-1 \rangle },x^{ \langle t \rangle }]+b_u) \\ \Gamma_r = \sigma(W_r [c^{ \langle t-1 \rangle },x^{ \langle t \rangle }]+b_r) \\ c^{ \langle t \rangle } = \Gamma_u * {\widetilde{c}}^{ \langle t \rangle } + (1-\Gamma_u) * c^{ \langle t-1 \rangle } \end{gather*}

5.1.2 LSTM

Long Short Term Memory : 引入了 3 个门[forget gate, update gate, output gate] ,其中 forget gate 和 update gate 单独控 制记忆单元和当前的激活值,output gate 控制输出值。此时有前一时间到后一时间的输入变成了两个,一个是记忆单元,另一个是当 前的激活值。此时记忆单元将很容易通过作为后一时刻的输入而有长时间的影响。

\begin{gather*} {\widetilde{c}}^{ \langle t \rangle } = tanh(W_c [a^{ \langle t-1 \rangle },x^{ \langle t \rangle }]+b_c) \\ \Gamma_u = \sigma(W_u [a^{ \langle t-1 \rangle },x^{ \langle t \rangle }]+b_u) \\ \Gamma_f = \sigma(W_f [a^{ \langle t-1 \rangle },x^{ \langle t \rangle }]+b_f) \\ \Gamma_o = \sigma(W_o [a^{ \langle t-1 \rangle },x^{ \langle t \rangle }]+b_o) \\ c^{ \langle t \rangle } = \Gamma_u * {\widetilde{c}}^{ \langle t \rangle } + \Gamma_f * c^{ \langle t-1 \rangle } \\ a^{ \langle t \rangle } = \Gamma_o * c^{ \langle t \rangle } \end{gather*}

5.1.3 Deep RNN

将一层 RNN 单元变成多层连接,第一层的输出作为第二层的输入,第二层的输出作为第三层的输入;同时每一层都有到下一时刻的连接。 由于时间上的连接使得网络变得很复杂,所有通常跨越时间连接的 RNN 不会超过三层。当然可以在三层网络上面继续增加网络来产生输 出。

5.2 word embedding

不再使用 one-hot 向量来表示单词,而是找到某些维度的空间,然后使用其中的一个坐标表示一个单词,就好像将单词嵌入该空间的一 个位置。这样可以度量相似的单词,比如 apple 和 pear 会有很大的相似度,而不像在 one-hot 向量那样完全无关。如果将该空间降维 到二维来表示,可以发现相似的单词簇拥在一起,如 boy 和 girl 距离很近,同时 boy girl mather father 相比于其他的单词如 book bicycle 等,距离也会比较近,因为他们都是人类的称呼。

在网上可以找到很多已经训练好的 Word embedding 模型,直接使用将大大缩短自己的迭代时间。

5.2.1 GloVe

global vectors for word representation

\begin{gather*} minimize \sum_{i=1}^{100000} \sum_{j=1}^{100000} f(X_{ij}) ( \theta_i^T e_j + b_i - b'_j - \ln X_{ij})^2 \end{gather*}

5.2.2 Sentiment classification

可以使用已经训练好的 word embedding ,将训练样本中评价的单词直接用 word embedding 表示,然后将所有的结果求平均然后使用 Softmax 分类器产生 1-5 级别的评价。效果还不错。不过无法区分 good 和 not good 中 good 的不同。如果将产生的结果输入 RNN 将 得到一个更好的结果。

此时就是用别人已经训练好的 word embedding 模型,迁移到自己的模型中。直接得到训练集中一个单词的表示向量。类似于人类识别中, 先得到人脸的特征,然后在计算特征的距离。或者使用迁移学习,利用别人已经训练好的模型,用自己的样本只训练网络的最后几层道理 相似。

5.2.3 Bias in word embedding

有较大的偏见,如性别,医生并不一定是男士,护士也不一定是女士。这些偏见都需要消除。

normalized log likelihood objective

5.3 Sequence to sequence

5.3.1 Image captioning

先使用卷积神经网络得到图像的特征,然后输入 RNN 得到该图片的描述信息:一只猫坐在凳子上。

5.3.2 Beam search

并不使用贪心算法来逐个查找概率最大的单词,而是希望整个句子的概率最大,同时增加候选单词,也就是先选择概率最大的几个单词, 然后在已经选择的几个单词的基础上计算第二个单词的条件概率最大的几个,暂存联合概率最大的几个单词,继续之后的求解。

先一个设定 beam width = 10 (也可能是100 ,商业上 1000 似乎太大,而且达到一定值之后继续增加效果不明显),每次都选择个联 合概率最大的 10 个单词暂存起来,和下一个单词的条件概率(以前面生成的单词为条件)求乘积得到联合概率。继续选择联合概率最大 的 10 个单词求解。当然为了防止数值下溢,使用对数联合概率求解。

算法会倾向于输出较短的句子,因为每个单词的概率都很小,较短的句子的联合概率会比较大,为了避免这种情况,使用平均概率来表示, 即将得到的联合概率除以单词的个数。实际中可能会除以单词个数的 0.8 次方,或者其他的值,可以作为超参调节,效果更好。

Beam search 虽然不像 BFS 或者 DFS 那样确保能找到最大值,但其运行速度更快。

  1. Error analysis on Beam search

    用 \(y^*\) 表示最优的结果,\(\hat{y}\) 表示 RNN 使用 Beam search 算法的到的输出结果,如果两者不一致,怎样判断是由于 RNN 还是由于 Beam search 算法导致结果错误?

    \(p(y^* | x) > p(\hat{y} | x)\)
    最优结果的概率优于输出的概率,说明 RNN 计算的概率值是正确的,但 Beam search 没有搜 索到该最优解
    \(p(y^* | x) > p(\hat{y} | x)\)
    最优结果的概率小于输出的概率,说明 RNN 产生的概率值有问题

5.3.3 Bleu score

BLEU (bilingual evaluation understudy) is an algorithm for evaluating the quality of text which has been machine-translated from one natural language to another.

一个很好的单一实数指标来评价句子翻译的优劣。

遍历机器翻译得到的句子,从所有的单个单词,到所有的连续两个单词,连续三个单词,一直到整个句子,将不同单词个数组合数分别作 为分母,其在真实的句子中出现的次数作为分子,且该分子以该组合在真实句子中出现次数的最大次数截止。最优将不同长度组合得到的 值求和然后平均,然后作为以 e 为底的指数。同样为了惩罚较短的输出,当输出句子的长度短于输入句子的长度的时候,给得到的值乘 以一个小于 1 的值来惩罚。

5.3.4 Attention model

seq2seq 的输出 RNN 在得到输出时,除了输入激活值,同时输入一个对当前输入的关心程度,表明当前的激活值对输出的重要程度。

该注意力值通过前一时刻的隐状态值和激活值经过一个只包含一个隐层的神经网络计算得到,使用梯度下降法来训练相应的参数。

不同时刻的注意力值总和为 1,通过类似 Softmax 激活函数得到。

5.3.5 Speech Recognition

预处理: 需要先将声音片段处理成声谱图 spectrogram

语音识别时,一般让输入和输出的维数相同。假如采样的频率为 100HZ ,那么 10S 中的语音片段将有 1000 个输入,而实际输出可能只 需要 20 个左右,此时输入和输出的维数无法很好的匹配。此时可以通过 CTC [connectionist temporal classification] 损失函数来 求解,即无输出值中间所有重复的输出进行合并,认为是一个输出,从而得到较短的输出。

5.3.6 Trigger Word detection

使用 RNN 当检测到唤醒词之后输出一个 1 ,否则输出 0 。为了平衡较多的 0 输出,可以在下一个变换之前始终输出 1 (no see)。

0%