吴恩达神经网络与深度学习 Week-2 学习记录

文章目录
  1. 1. 二分分类
  2. 2. Logistic 回归
  3. 3. Logistic回归损失函数
  4. 4. 梯度下降法
  5. 5. 导数
  6. 6. 更多导数的例子
  7. 7. 计算图
  8. 8. 计算图的导数计算
  9. 9. Logistic回归中的梯度下降算法
  10. 10. m个样本梯度下降
  11. 11. 向量化
  12. 12. 向量化的更多例子
  13. 13. 向量化Logistic回归
  14. 14. 向量化Logistic回归的梯度输出
  15. 15. Python中的广播
  16. 16. 关于Python\/numpy向量的说明
  17. 17. 实现一个梯度下降算法

写在最前,Week-1的内容我认为相对简单,看得比较粗略,所以没有做记录。
其次,在学习了一段时间神经网络的NLP的应用之后,在搭建网络和相关基础知识上我存在很多欠缺,对论文中的一些细节把握得不够好,所以准备弥补一下各项短板来提升一下NLP方面的能力。吴恩达老师的课程讲得非常好,解决了我听cs224n课程中遇到的一些理解不了的问题,很有帮助,后续的博客中会记录吴老师神经网络与深度学习的所有课程记录。

本博客中记录了该课程第二周的全部学习记录。

二分分类

假如给定一张图片,任何图片都由三原色组成,也就是通道数为3,假如将一张大小为64x64的图片输入神经网络,则输入矩阵维度是(64,64,3)

其中,三色通道完整的矩阵表示如下:

在二分类的任务中,用一个元组来进行表示 (x,y),x表示输入(即一个n_x维数据,为输入数据),y表示输出的结果(即用0 和 1 来表示图片中是否有猫)。

其中,x是大小为Nx的矩阵,y作为标签只有0和1,m个样本组成下面m个元组的训练集。
将对应的训练集构成一个矩阵X输入神经网络进行训练,其中,训练样本数作为col相较于训练样本作为row要便于训练,X的大小为(Nx,m)

同理,对于Y,为了方便训练,也将样本数作为col,如下图所示,大小为(1,m):

Logistic 回归

在二分类中,给定一张图片,需要识别出来是不是猫,即给定x,预测得到 y_hat,其中 y_hat 是一个概率,y_hat=p(y=1|x),也就是,当输入特征 x 满足条件时,y就是1。

在上述线性回归中一共有两个参数,w 和 b,通过output中的式子计算出来最后的 y_hat。
However,这不是一个好的线性回归算法,前提已经预设了y=1,所以y_hat应该介于0和1之间,但是这其中的值并不容易实现,它的值可能比1大得多,甚至是负值。
所以,可以在计算y_hat的时候加上一个sigmoid函数,sigmoid函数图像如下:

添加sigmoid函数后的ioutput函数结果:

在做逻辑回归时,要做的就是学习参数w和b,所以,y_hat就会成为较好的估计。

在对神经网络进行编程时,通常会将w和参数b分开。

Logistic回归损失函数

为了计算2-2中提到的 w 和 b,需要定义一个代价函数(Cost Function)和一个损失函数(Cost Function)。
损失函数(Loss Function)的定义:
(因为这里是一个二分类任务,所以y的值只有0和1(个人理解))

对上述两个公式的详解,当y=1 or 0,要求L(y,y_hat)的值尽量接近 1 or 0。

其中第一种求平方的损失函数的 定义方法得到的结果是非凸的(如下),有多个最优解:

第二种求解方式可以得到一个凸函数,有利于求出最优解:

所以,常用的损失函数计算方式为第二种。

Finally,损失函数(Loss Function)在单个训练样本中衡量了单个样本上的表现,反之,代价函数(Cost Function)衡量的是在全体样本上的表现。
代价函数(Cost Function)的定义:

从形式上来看,代价函数有点类似于针对损失函数的每个样本值求和之后取平均。

梯度下降法

2-2中提到需要计算出时J(w,b)尽可能小的w和b,这就涉及到了使用梯度下降法。
以下图为例:

根据J(w,b)的定义,要在以w轴和b轴组成的界面上找到一个使得J(w,b)值最小的点,可以看出,成本函数J是一个凸函数。
为了找到最合适的w和b,要做的就是初始化w和b(上图中小红点所示的值),对于Logistic回归而言,几乎是任意的初始化方法都有效,通常使用0来初始化。
梯度下降法所做的是,从初始点开始,朝最抖的下坡方向走一步,以最快的速度往下走,通过不断的迭代到达目的位置。

(绿色部分:学习率(Learning Rate),学习率可以控制每一次迭代或者梯度下降法中的步长)
上图所示为省略了b的梯度下降模拟,这里只需要更新w参数,这里要通过J(w)对w求导,重复这个更新操作。


上图右边部分的J(w)的导数值为正(斜率为正),乘以学习率,新的w值计算为:w=w-αw,导数是正的,从w中减去这个乘积,使得w不断减小,然后朝左边一步,不断循环这个过程,不断迭代,最终到达最低点。
上图左边部分的J(w)的导数值为负(斜率为负),乘以学习率,新的w值计算为:w=w-αw,不过这里的w是负数,慢慢地使得参数w增加,再往右边移动,循环这个过程来达到最低点。

导数

这个..,大概放一张图就行了,这节的内容很简单。

更多导数的例子

其他导数的栗子

计算图

计算图表示的是整个式子的计算流程,比较简单,如图所示:

从左到右的计算中可以得到J的最终值,反向箭头(红色)的计算详情见下节。

计算图的导数计算

Q:如何利用计算图计算出J的导数?
假设存在如下一张流程图:

需要J对v的导数,需要怎么计算?

J=3v,J随v的改变而改变,完成dJ\/dv,也就完成了第一步反向求导。
下一步,求dJ\/da,变化的过程是:改变a→改变v→改变J,即链式求导法则, 所以:
dJ\/da=dJ\/dv * dv\/da= 3
完成流程示意图如下:

注:当你想编程实现反向传播时,通常需要关心一个最终的输出值(中间底部红色部分标注)。

计算 dJ\/du=dJ\/dv * dv\/du=3 的流程图如下:

同理,dJ\/db=dJ\/dv * dv\/du * du\/db=dJ\/du * du\/db
从上述计算中可知,dJ\/du=3
根据变量(或是直接求偏导)可得du\/db=2
所以,dJ\/db=6.

Finally,dJ\/dc * dJ\/du * du\/dc=9.
总结:一个计算流程图,从左往右计算代价函数(cost function)J,你可能需要优化的函数,然后反向从右到左计算导数。

Logistic回归中的梯度下降算法

首先回顾一下逻辑回归(Logistic Regression)的公式,其中,a是逻辑回归计算后得到的输出结果,y是样本的基本真值标签值。

假设样本只有两个特征值 x1 和 x2,为了计算 z ,需要引入参数 w1 和 w2 和 b,通过这些值可以得到 z 的求值公式:z=w1x1+w2x2+b,然后计算y_hat=a=sigmoid(z),最后计算L(a,y)=-(yloga+(1-y)log(1-a)),整个流程如下图所示:

在逻辑回归中,我们需要做的是变换参数 w 和 b 的值来最小化损失函数。
上述描述是一个正向传播的过程,现在来讨论反向传播的过程。

想要计算损失函数L的导数,要先往前一步,计算dL/da,通过偏导计算,可得:
dL/da=-y/a + (1+y)/1-a
下一步计算dL/dz,这个步骤有点繁琐,所以写在纸上,利用的是链式法则,推导过程如下:

吴恩达老师的推导示意图如下:

上述过程即一次梯度下降更新的结果,逻辑回归算法中不仅有一个样本,而是有m个样本的整个训练集。

m个样本梯度下降

前面提到了单个样本的计算,现在需要对m个样本进行计算,回顾一边Cost Function定义:

其中,求导的过程是类似的:

求m个样本的梯度下降法,即先正向传播,再反向传播:
使用 dw1,dw1 和 db 作为累加器。

当前示例只包含两个特征,假如存在多个特征,则必须dw3,dw4…这样一直计算下去,最后计算平均值,这样就完成了整个计算步骤。

后续需要对 w1,w2,b 等参数进行更新(α是学习率):

注:在编程实现过程中,假如存在多个特征,就必须从 dw1,dw2,…,dwn,遍历所有的n个特征,所以不使用显式for循环,可以有效提高使用效率。有一门向量化技术可以帮助解决这个问题,向量化可以用来加速运算。在深度学习时代,使用向量化来摆脱for循环已经变得非常重要。

向量化

向量是消除代码中显式for循环语句的艺术。
左半部分需要不断循环计算,而右边部分仅需要一个矩阵就能完成。

这里用一个小例子来说明一下向量化和循环之间计算耗时的差距,和吴恩达老师有点点差别,也就是Jupyter和Pycharm的区别,个人更习惯后者。

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
#!/usr/bin/env python
#coding:utf-8

import numpy as np
import time

'''
# 生成一个np类型的array
a=np.array([1,2,3,4])
print(a)
'''

if __name__=="__main__":
a=np.random.rand(1000000)
b=np.random.rand(1000000)

tic=time.time()
c=np.dot(a,b)
toc=time.time()

print("Vectorized version:",1000*(toc-tic),'ms')

c=0
tic = time.time()
for i in range(1000000):
c+=a[i]*b[i]
toc = time.time()
print("For Loop:", 1000 * (toc - tic), 'ms')

结果一目了然,毫无疑问向量化的匀速速度更快,快了接近300倍。

向量化的更多例子

经验法则:当构建法则或是做回归时,要尽量避免for循环。
举个栗子:如果你想计算一个向量u作为矩阵A和另一个向量v的乘积,矩阵乘法的定义就是u_i等于A_ij乘以v_j对j求和。

下图,左边部分使用循环计算,右边部分使用点乘。

可以看出,使用向量的版本,消除了两个循环。
在逻辑回归中使用向量化:

向量化Logistic回归

假如有m个样本,则需要对m个样本分别进行下面的运算(图示只到3,注:#盖里奇不会数到3,#全世界最不好的盖里奇)。

下面通过一个例子来展示如何使用向量将上述过程写在一个向量里:

需要转置的w可以是一个行向量(row vector)。

向量化Logistic回归的梯度输出

这节的主要目的是学会使用向量来同时计算所有样本的下降梯度,为了避免忘掉前面的知识,这里补一张之前讲过的计算图(主要方式:链式求导):

假如有m个样本需要计算,对m个训练样本做计算过程都是相同的:

所以,仅需要一行代码,就能完成所有的计算,但是仍然需要一个for循环来计算各个dw和db的值:

接着,可以通过向量的方式再消掉基于样本的循环,一个循环修改为两行向量计算的代码即可,如下图所示:

下面给出完整的,只需要使用一个for循环的Logistic梯度下降描述,这个循环是必须的,因为要更新不同的dw和db来确定最终的w和b:

Python中的广播

下面的矩阵中列出了100g不同的食物中碳水化合物,蛋白质,脂肪的占比。

现在的目标是,计算四种食物中,各种含量占卡路里有多少百分比?
以Apple为例,卡路里=56+1.2+1.8=59(btw,I don’t like this number.)
∴碳水化合物的占比是:56\/59≈94.9%

Q:可以不使用for循环来计算各成分的占比吗?

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env python
#coding:utf-8

import numpy as np

if __name__=="__main__":
a=np.array([[56.0,0.0,4.4,68.0],[1.2,104.0,52.0,8.0],[1.8,135.0,99.0,0.9]],dtype=np.float32)
col=np.sum(a,axis=0)
# print(col)
percentage=100*a/col.reshape(1,4)
print(percentage)

运算结果如下:

Python 矩阵计算中其他栗子,numpy会根据计算矩阵的大小自动调整对应值。

具体运算规则如下:

关于Python\/numpy向量的说明

这一节中上半部分的内容主要看下面的代码即可:

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
#!/usr/bin/env python
#coding:utf-8

import numpy as np

if __name__=="__main__":
# 生成5个高斯变量
a=np.random.randn(5)
# 输出 a 的结果
print(a)
# 输出 a 的形状
# 注:这种生成的结果既不是行向量也不是列向量
print(a.shape)
# 输出 a 的转置
print(a.T)
# 输出 a 的点积
print(np.dot(a,a.T))

'''
上述写法中,生成a的随机高斯变量方式不合适,无法确定一个向量,按照下面的方式写要更合适
'''

a=np.random.randn(5,1)
print(a)
print(a.T)
print(np.dot(a,a.T))
# 可以看出结果和先前的写法完全不同

运行结果如下:

后续吴老师讲到的一些小技巧:
当代码较多时,不确定Matrix的大小是否和预期一样,可以使用assert:

1
assert(a.shape==(5,1))

实现一个梯度下降算法

这里以Rosenbrock在1960年提出的山谷函数为例,Rosenbrock函数定义如下:

函数 f 分别对 x,y 求导得到:

在实现的过程中可以给出x, y初始值(例如设置为 0, 0) 然后计算函数在这个点的梯度,并按照梯度方向更新x, y的值。
这里给出通过梯度下降法计算上述函数的最小值对应的x 和 y。

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
#!/usr/bin/env python
#coding:utf-8


import numpy as np

def cal_rosenbrock(x,y):
# 计算 Rosenbrock 的值
return pow((1-x),2)+100*pow((y-pow(x,2)),2)

def cal_rosenbrock_praX(x,y):
# 对 x 求偏导
return -2*(1-x)-2*100*(y-pow(x,2))*2*x

def cal_rosenbrock_praY(x,y):
# 对 y 求偏导
return 2*100*(y-pow(x,2))

def rosenbrock(max_iter_count=100000,step_size=0.0001):
preX=np.zeros((2,),dtype=np.float32)
# 初始值
loss=10
iter_count=0
while loss>0.001 and iter_count<max_iter_count:
error=np.zeros((2,),dtype=np.float32)
error[0]=cal_rosenbrock_praX(preX[0],preX[1])
error[1]=cal_rosenbrock_praY(preX[0],preX[1])

for j in range(2):
preX[j]-=step_size*error[j]
# 迭代计算

loss=cal_rosenbrock(preX[0],preX[1])

print("iter count:",iter_count," the loss is :",loss)
iter_count+=1

return preX

if __name__=="__main__":
w=rosenbrock()
print(w)

参考博客:https://blog.csdn.net/pengjian444/article/details/71075544