专栏首页用户2133719的专栏Deeplearning.ai 课程笔记第一部分:神经网络与深度学习

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 构建神经网络

构建一个神经网络一般包含以下步骤:

  1. 定义神经网络的结构
  2. 初始化模型参数
  3. 重复以下循环直至收敛:
    • 计算当前的代价函数(前向传播)
    • 计算当前的梯度(反向传播)
    • 更新参数(梯度下降)

对于神经网络的训练,数据集的预处理与超参数(如学习速率)的调整十分重要。

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)
  • z1z1 = W1*X + b 的计算结果,其形状为 (noOfHiddenNeurons, 1)
  • a1a1 = sigmoid(z1) 的计算结果,其形状为 (noOfHiddenNeurons, 1)
  • W2 是输出层的参数矩阵,其形状为 (1, noOfHiddenNeurons)
  • b2 是输出层的参数矩阵,其形状为 (1, 1)
  • z2z2 = W2*a1 + b 的计算结果,其形状为 (1, 1)
  • a2a2 = 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 表示向量化后的线性输出
  • wb 表示每一层线性输出的对应参数
    • WB 表示向量化后的参数
  • 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 参数与超参数

在神经网络中,参数主要指 wb。而超参数指影响参数选择的参数,例如:

  • 学习速率
  • 迭代次数
  • 隐藏层层数
  • 隐藏层单元数
  • 激励函数的选择

深度学习是一个经验主义的过程,随着外界条件的不断变化,需要进行多次的实验来确定最佳的超参数与参数。

4.5 深层神经网络与大脑的关系

神经网络的单个逻辑单元与实际的神经元在结构上有一些相似,但大脑的工作原理目前还是未知的,所以无法进行进一步比较。

4.6 思维导图

本文分享自微信公众号 - 口仆(roito33),作者:口仆

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-11-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • CS229 课程笔记之十五:强化学习与控制

    本章将开始介绍「强化学习」与适应性控制。在监督学习中,对于训练集我们均有明确的标签,算法只需要模仿训练集中的标签来给出预测即可。但对于某些情况,例如序列性的决策...

    口仆
  • CS229 课程笔记之九:EM 算法与聚类

    为了证明 k-means 算法能否保证收敛,我们定义「失真函数」(distortion function)为:

    口仆
  • CS229 课程笔记之十四:隐马尔可夫模型基础

    将会是任意数量变量的函数,将难以建模。因此,我们会提出两个「马尔可夫假设」来便于我们建模。第一个假设是「有限地平线假设」(limited horizon as...

    口仆
  • 改造typecho上传地址URL

    typecho附件上传我是一直看着难受 usr/uploads/2020/04/xxxxx.jpg 太长了于是我直接修改了上传部分 将其规范为 usr/upl...

    乔千
  • OJ刷题记录:银行业务队列简单模拟 题目编号:1023

    题目要求: 设某银行有A、B两个业务窗口,且处理业务的速度不一样,其中A窗口处理速度是B窗口的2倍,即当A窗口每处理完2个顾客时,B窗口处理完1个顾客。给定到...

    英雄爱吃土豆片
  • 用Python制作恋爱日志

    最近一直在学习Python,就想到编写一个程序每天早上自动给女朋友发送微信,内容是我俩相恋时间,每日一句以及一句早安。

    stormwen
  • 好雨云资深架构师祁世垚参加Qcon演讲,现场反响热烈

    Rainbond开源
  • LeetCode 168. Excel表列名称

    Michael阿明
  • VBA到底有多厉害?VBA公众号推荐

    Sam Gor
  • 初识VBA

    把菜单开发工具显示出来方便以后打开VBA编辑器(点“Visual Basic”打开的那个界面)、设置宏安全性是为了能够打开文件就执行程序(这一步设置后,一...

    xyj

扫码关注云+社区

领取腾讯云代金券