Deeplearning.ai 课程笔记第一部分:神经网络与深度学习
1 深度学习引言
1.1 什么是神经网络?
神经网络就是由若干神经元组合而成的网络结构,其包含输入层、隐藏层和输出层。而含有多层隐藏层的神经网络即为深度神经网络。下图给出了一个深度神经网络的示意图。
1.2 神经网络的监督学习
在深度学习领域,目前为止几乎所有的经济价值都是由基于监督学习的神经网络所创造的。
对于不同的应用领域,我们需要不同类型的神经网络:
- 对于房价预测和在线广告等应用,采用的是相对标准的神经网络
- 对于图像领域的应用,常常使用卷积神经网络(CNN)
- 对于序列数据,例如音频、语言等,常常使用循环神经网络(RNN)(注意不要与递归神经网络混淆)
监督学习所处理的数据可以分为结构化与非结构化两种:
- 结构化数据:数据以类似关系型数据库的表结构的形式展示
- 非结构化数据:音频、图像或文本等特征无法直接展示的数据
1.3 为什么深度学习会兴起?
深度学习兴起的原因主要有三点:
- 信息化社会带来的数据量的巨大提升
- 硬件更新带来更快的计算速度
- 神经网络算法的不断发展
1.4 思维导图
2 神经网络的编程基础
经典的神经网络可以理解为逻辑回归的叠加。本节将介绍逻辑回归的基本原理及其程序实现。
2.1 符号定义
2.2 逻辑回归
2.2.1 逻辑回归的代价函数
2.3 梯度下降
2.3.1 逻辑回归中的梯度下降
在逻辑回归中,梯度下降涉及到复合求导,需要基于链式法则求解。课程中介绍了计算图的方法,能够更加直观地求解复合导数,而这其实可以看做一种简单的反向传播。
下面给出求解含有 m 个样本的逻辑回归的梯度下降的伪代码
变量名如下:
X1 Feature
X2 Feature
W1 Weight of the first feature.
W2 Weight of the second feature.
B Logistic Regression parameter.
M Number of training examples
Y(i) Expected output of i
基于复合求导得出的导数如下:
d(a) = d(l)/d(a) = -(y/a) + ((1-y)/(1-a))
d(z) = d(l)/d(z) = a - y
d(W1) = X1 * d(z)
d(W2) = X2 * d(z)
d(B) = d(z)
伪代码如下:
J = 0; dW1 = 0; dW2 =0; dB = 0; # Devs
W1 = 0; W2 = 0; B=0; # Weights
for i = 1 to m
# Forward pass
z(i) = W1*X1(i) + W2*X2(i) + b
a(i) = Sigmoid(z(i))
J += (Y(i)*log(a(i)) + (1-Y(i))*log(1-a(i)))
# Backward pass
dz(i) = a(i) - Y(i)
dW1 += dz(i) * X1(i)
dW2 += dz(i) * X2(i)
dB += dz(i)
J /= m
dW1/= m
dW2/= m
dB/= m
# Gradient descent
W1 = W1 - alpha * dW1
W2 = W2 - alpha * dW2
B = B - alpha * dB
上述伪代码实际上存在两组循环(迭代循环没有写出),会影响计算的效率,我们可以使用向量化来减少循环。
2.4 向量化
向量化可以避免循环,减少运算时间,Numpy 的函数库基本都是向量化版本。向量化可以在 CPU 或 GPU 上实现(通过 SIMD 操作),GPU 上速度会更快。
2.4.1 向量化逻辑回归
下面将仅使用一组循环来实现逻辑回归:
输入变量为:
X Input Feature, X shape is [Nx,m]
Y Expect Output, Y shape is [Ny,m]
W Weight, W shape is [Nx,1]
b Parameter, b shape is [1,1]
向量化后的伪代码如下:
W = np.zeros((Nx, 1))
b = 0
dW = np.zeros((Nx, 1))
db = 0
for iter in range(1000):
Z = np.dot(W.T, X) + b # Vectorization, then broadcasting, Z shape is (1, m)
A = 1 / (1 + np.exp(-Z)) # Vectorization, A shape is (1, m)
dZ = A - Y # Vectorization, dZ shape is (1, m)
dW = np.dot(X, dZ.T) / m # Vectorization, dW shape is (Nx, 1)
db = np.sum(dZ) / m # Vectorization, db shape is (1, 1)
W = W - alpha * dW
b = b - alpha * db
2.5 Python/Numpy 使用笔记
下面介绍课程中提到的一些 python/numpy 的使用tips。
Tip1: 在 Numpy 中,obj.sum(axis = 0)
按列求和,obj.sum(axis = 1)
按行求和,默认将所有元素求和。
Tip2: 在 Numpy中,obj.reshape(1, 4)
将通过广播机制(broadcasting)重组矩阵。reshape 操作的调用代价极低,可以放在任何位置。广播机制的原理参考下图:
Tip3: 关于矩阵 shape 的问题:如果不指定一个矩阵的 shape,将生成 "rank 1 array",会导致其 shape 为 (m, )
,无法进行转置。对于这种情况,需要进行 reshape。可以使用 assert(a.shape == (5,1))
来判断矩阵的 shape 是否正确
Tip4: 计算 Sigmoid 函数的导数:
s = sigmoid(x)
ds = s * (1 - s)
Tip5: 如何将三维图片重组为一个向量:
v = image.reshape(image.shape[0]*image.shape[1]*image.shape[2],1)
Tip6: 归一化输入矩阵后,梯度下降将收敛得更快。
2.6 构建神经网络
构建一个神经网络一般包含以下步骤:
- 定义神经网络的结构
- 初始化模型参数
- 重复以下循环直至收敛:
- 计算当前的代价函数(前向传播)
- 计算当前的梯度(反向传播)
- 更新参数(梯度下降)
对于神经网络的训练,数据集的预处理与超参数(如学习速率)的调整十分重要。
2.7 思维导图
3 浅层神经网络
3.1 神经网络概述
3.1.1 与逻辑回归的对比
逻辑回归的结构如下:
X1
X2 ==> z = XW + B ==> a = Sigmoid(z) ==> l(a,Y)
X3 /
而一个单层神经网络的结构如下:
X1
X2 => z1 = XW1 + B1 => a1 = Sig(z1) => z2 = a1W2 + B2 => a2 = Sig(z2) => l(a2,Y)
X3 /
因此,我们可以将神经网络简单理解为逻辑回归的叠加。
3.1.2 表示与计算
本节将定义含有一层隐藏层的神经网络
-
a0 = x
表示输入层 -
a1
表示隐藏层的激活值 -
a2
表示输出层的激活值
计算神经网络的层数时,我们一般不考虑输入层(即本节讨论的是两层神经网络)。下图给出了一个神经网络的前向传播计算公式:
在该网络中,隐藏层的神经元数量(noOfHiddenNeurons
)为 4,输入的维数(nx
)为 3。计算中涉及到的各个变量及其大小如下:
-
W1
是隐藏层的参数矩阵, 其形状为(noOfHiddenNeurons, nx)
-
b1
是隐藏层的参数矩阵, 其形状为(noOfHiddenNeurons, 1)
-
z1
是z1 = W1*X + b
的计算结果,其形状为(noOfHiddenNeurons, 1)
-
a1
是a1 = sigmoid(z1)
的计算结果,其形状为(noOfHiddenNeurons, 1)
-
W2
是输出层的参数矩阵,其形状为(1, noOfHiddenNeurons)
-
b2
是输出层的参数矩阵,其形状为(1, 1)
-
z2
是z2 = W2*a1 + b
的计算结果,其形状为(1, 1)
-
a2
是a2 = sigmoid(z2)
的计算结果,其形状为(1, 1)
3.1.3 代码实现
两层神经网络前向传播的伪代码如下:
for i = 1 to m
z[1, i] = W1*x[i] + b1 # shape of z[1, i] is (noOfHiddenNeurons,1)
a[1, i] = sigmoid(z[1, i]) # shape of a[1, i] is (noOfHiddenNeurons,1)
z[2, i] = W2*a[1, i] + b2 # shape of z[2, i] is (1,1)
a[2, i] = sigmoid(z[2, i]) # shape of a[2, i] is (1,1)
如果对整个训练集进行向量化,得到新的 X
形状为 (Nx, m)
,则新的伪代码如下:
Z1 = W1X + b1 # shape of Z1 (noOfHiddenNeurons,m)
A1 = sigmoid(Z1) # shape of A1 (noOfHiddenNeurons,m)
Z2 = W2A1 + b2 # shape of Z2 is (1,m)
A2 = sigmoid(Z2) # shape of A2 is (1,m)
其中样本数量 m 始终表示列的维数,X
可以写为 A0
。
3.2 激活函数
3.2.1 常见激活函数
sigmoid
sigmoid 激活函数的取值范围是 [0,1] 。
注意 sigmoid 可能会导致梯度下降时更新速度较慢。其代码实现如下:
sigmoid = 1 / (1 + np.exp(-z)) # Where z is the input matrix
tanh
tanh 激活函数的取值范围是 [-1,1](sigmoid 函数的偏移版本)。
对隐藏层来说,tanh 比 sigmoid 的效果更好,因为其输出的平均值更接近0,这使得下一层数据更加靠近中心(便于梯度下降)。而 tanh 与 sigmoid 存在同样的缺点,即如果输入过大或过小,则斜率会趋近于0,导致梯度下降出现问题。
代码实现如下:
tanh = (np.exp(z) - np.exp(-z)) / (np.exp(z) + np.exp(-z)) # Where z is the input matrix
tanh = np.tanh(z) # Where z is the input matrix
ReLU
ReLU 函数可以解决梯度下降慢的问题(针对正数)。如果你的问题是二元分类(0或1),那么输出层使用 sigmoid,隐藏层使用 ReLU。
代码实现如下:
ReLU = np.maximum(0,z) # so if z is negative the slope is 0 and if z is positive the slope remains linear.
leaky ReLU
leaky RELU 与 ReLU 的区别在于当输入为负值时,斜率会较小(不为0)。它和 ReLU 同样有效,但大部分人使用 ReLU。
代码实现如下:
leaky_ReLU = np.maximum(0.01*z,z) #the 0.01 can be a parameter for your algorithm.
目前激活函数的选择并没有普适性的准则,需要尝试各种激活函数(也可以参考前人的经验)
3.2.2 激活函数的非线性
线性激活函数会输出线性的激活值,因此无论你有多少层隐藏层,激活都将是线性的(类似逻辑回归),这会使隐藏层会失去意义,无法处理复杂的问题。因此我们需要非线性的激活函数。
注意当输出是实数时,可能需要使用线性激活函数,但即便如此如果输出非负,那么使用 ReLU 函数更加合理。
3.2.3 激活函数的导数
sigmoid 函数:
A = 1 / (1 + np.exp(-z))
dA = (1 / (1 + np.exp(-z))) * (1 - (1 / (1 + np.exp(-z))))
dA = A * (1 - A)
tanh 函数:
A = (np.exp(z) - np.exp(-z)) / (np.exp(z) + np.exp(-z))
dA = 1 - np.tanh(z)^2 = 1 - A^2
ReLU 函数:
A = np.maximum(0,z)
dA = { 0 if z < 0
1 if z >= 0 }
leaky ReLU 函数:
A = np.maximum(0.01*z,z)
dA = { 0 if z < 0
1 if z >= 0 }
3.3 神经网络的梯度下降
反向传播的公式与伪代码如下:
3.4 随机初始化
在逻辑回归中随机初始化权重并不重要,而在神经网络中我们需要进行随机初始化。
如果在神经网络中将所有权重初始化为0,那么神经网络将不能正常工作:所有隐藏层会完全同步变化(计算同一个函数),每次梯度下降迭代所有隐藏层会进行相同的更新。注意 bias 初始化为0是可以的。
为了解决这个问题我们将 W 初始化为一个小的随机数:
W1 = np.random.randn((2,2)) * 0.01 # 0.01 to make it small enough
b1 = np.zeros((2,1)) # its ok to have b as zero
对于 sigmoid 或 tanh 来说,我们需要随机数较小,因为较大的值会导致在训练初期线性激活输出过大,从而使激活函数趋向饱和,导致学习速度下降。而如果没有使用 sigmoid 或 tanh 作为激活函数,就不会有很大影响。
常数 0.01 对单层隐藏层来说是合适的,但对于更深的神经网络来说,这个参数会发生改变来保证线性计算得出的值不会过大。
3.5 思维导图
4 深层神经网络
4.1 深层神经网络概述
深层神经网络是指隐藏层超过两层的神经网络:
4.1.2 符号定义
- 我们使用
L
来定义神经网络的层数(不包含输入层) -
n
表示每一层的神经元数量集合-
n[0]
表示输入层的维数 -
n[L]
表示输出层的维数
-
-
g
表示每一层的激活函数 -
z
表示每一层的线性输出-
Z
表示向量化后的线性输出
-
-
w
和b
表示每一层线性输出的对应参数-
W
和B
表示向量化后的参数
-
-
a
表示每一层的激活输出-
a[0]
表示输出,a[L]
表示输出 -
A
表示向量化后的激活输出
-
4.1.3 深层网络中的前向传播
对于单个输入,前向传播的伪代码如下:
z[l] = W[l]a[l-1] + b[l]
a[l] = g[l](z[l])
对于 m
个输入(向量化),前向传播的伪代码如下:
Z[l] = W[l]A[l-1] + B[l]
A[l] = g[l](Z[l])
我们无法对整个前向传播使用向量化,需要使用 for 循环(即每一层要分开计算)。
4.1.4 维数的确认
我们需要确保各个向量的维数能够匹配,这里用 l
表示当前是第几层。各向量的具体维数如下:
-
w[l]
和dw[l]
的维数:(n[l], n[l-1])
-
W[l]
和dW[l]
的维数:(n[l], n[l-1])
-
-
b[l]
和db[l]
的维数:(n[l], 1)
-
B[l]
和dB[l]
的维数:(n[l], m)
-
-
z[l]
和a[l]
的维数:(n[l], 1)
-
Z[l]
和A[l]
的维数:(n[l], m)
-
dZ[l]
和dA[l]
的维数:(n[l], m)
-
4.2 为什么要进行深层表示?
我们可以从两个角度解释为什么使用多个隐藏层:
- 多个隐藏层可以将问题从简单到复杂进行拆分,先考虑简单的特征,再逐步变得复杂,最终实现预期的效果;
- 电路理论表明越少的层数需要的单元数呈指数级上升,对神经网络来说也是如此。对于一个复杂任务来说,层数越少每一层所要包含的神经元数量会爆炸式增长。
4.3 深层神经网络的模块
深层神经网络一般包含前向传播与反向传播两个模块:前向传播模块得到代价函数,后向传播模块计算各层参数的梯度,最后通过梯度下降来更新参数,进行学习。
在实际实现中,我们需要通过缓存将前向传播中的某些参数传递到反向传播中,帮助进行梯度的计算。
4.3.1 前向传播模块
向量化后的伪代码如下:
Input A[l-1]
Z[l] = W[l]A[l-1] + B[l]
A[l] = g[l](Z[l])
Output A[l], cache(Z[l], W[l], B[l])
4.3.2 反向传播模块
向量化后的伪代码如下:
Input dA[l], Caches
dZ[l] = dA[l] * g'[l](Z[l])
dW[l] = (1/m) * np.dot(dZ[l], A[l-1].T)
dB[l] = (1/m) * np.sum(dZ[l], axis=1, keepdims=True)
dA[l-1] = np.dot(W[l].T, dZ[l])
Output dA[l-1], dW[l], dB[l]
最后一层 dA
的求解基于代价函数得出,注意计算时应去除 1/m
这一项,防止重复计算。
4.4 参数与超参数
在神经网络中,参数主要指 w
和 b
。而超参数指影响参数选择的参数,例如:
- 学习速率
- 迭代次数
- 隐藏层层数
- 隐藏层单元数
- 激励函数的选择
深度学习是一个经验主义的过程,随着外界条件的不断变化,需要进行多次的实验来确定最佳的超参数与参数。
4.5 深层神经网络与大脑的关系
神经网络的单个逻辑单元与实际的神经元在结构上有一些相似,但大脑的工作原理目前还是未知的,所以无法进行进一步比较。
4.6 思维导图
- Java多线程详解4【面试+工作】
- Java多线程详解5【面试+工作】
- Java多线程详解6【面试+工作】
- MySQL备份恢复第二篇(r5笔记第6天)
- SpringMVC 中配置 Swagger 插件.
- MySQL和Oracle对比学习之事务(r5笔记第4天)
- 【面试宝典】Java如何打印数组
- MySQL数据导入导出牛刀小试(r5笔记第3天)
- SpringMVC 异常处理.
- 一条简单的sql在11g和12c中的不同(r5笔记第2天)
- 浅析 SpringMVC 原理和配置.
- 使用impdp不当导致的数据丢失问题(r5笔记第1天)
- MySQL数据库 Event 定时执行任务.
- Tomcat 日志分割.
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Android 使用fast-verification实现验证码填写功能的实例代码
- android studio 3.6.0 绑定视图新特性的方法
- Android ListView UI组件使用说明
- 解决laravel 出现ajax请求419(unknown status)的问题
- php求斐波那契数的两种实现方式【递归与递推】
- Android 自定义日期段选择控件功能(开始时间-结束时间)
- Yii框架日志操作图文与实例详解
- Flutter 实现虎牙/斗鱼 弹幕功能
- Yii框架中用response保存cookie,用request读取cookie的原理解析
- Yii框架操作cookie与session的方法实例详解
- php中钩子(hook)的原理与简单应用demo示例
- flutter仿微信底部图标渐变功能的实现代码
- Flutter 插件url_launcher简介
- Laravel框架查询构造器 CURD操作示例
- androidx下的fragment的lazy懒加载问题详解