深度学习(1-3节)
深度学习介绍
线性整流函数(ReLU函数)
通常意义下,线性整流函数指代数学中的斜坡函数,即
$$
f(x)=max(0,x)
$$
而在神经网络中,线性整流作为神经元的激活函数,定义了该神经元在线性变换$W^Tx+b$之后的非线性输出结果。换言之,对于进入神经元的来自上一层神经网络的输入向量$x$,使用线性整流激活函数的神经元会输出
$$
max(0,W^Tx+b)
$$
到下一层神经元或作为整个神经网络的输出。
神经网络介绍
神经网络的基本模型是神经元,由输入层,隐藏层,输出层组成。最基本的神经网络是计算映射的,输入层为$x$,在实际上一般表现为特征,输出层为y,一般为结果,隐藏层其实就是上面所说的权向量$W^t$。
监督学习
监督学习也称为带标签的学习方式。监督学习是从标记的训练数据
来推断一个功能的机器学习任务。训练数据包括一套训练示例。在监督学习中,每个实例都是由一个输入对象(通常为矢量)和一个期望的输出值(也称为监督信号)组成。
结构化数据vs非结构化数据
结构化数据指传统数据库中的数据,非结构化数据库是指音频,图片,文本等数据。
深度学习的准确率
取决于你的神经网络复杂度以及训练集的大小,一般来说神经网络越复杂时,需要的训练数据也越多,这样训练出来的模型效果也更好。
Sigmoid函数
sigmoid函数也叫Logistic
函数,用于隐层神经元输出,取值范围为(0,1),它可以将一个实数映射到(0,1)的区间,可以用来做二分类。在特征相差比较复杂或是相差不是特别大时效果比较好。Sigmoid作为激活函数有以下优缺点:
优点:平滑、易于求导。
缺点:激活函数计算量大,反向传播求误差梯度时,求导涉及除法;反向传播时,很容易就会出现梯度消失
的情况,从而无法完成深层网络的训练。
Sigmoid函数的公式如下:
$$
S(x)=\frac{1}{1+e^{-x}}
$$
函数图形如下:
深度学习基础
为了方便学习:
1.使用$(x,y)$来表示一个单独的样本
2.$x\in \R^{n_x}$代表$x$是$n_x$维的特征向量,$y\in {0,1}$代表标签$y$值为0或1
3.训练集由m个训练样本构成,$(x^{(1)},y^{(1)})$代表样本一,$(x^{(m)},y^{(m)})$代表最后一个样本m
4.$m=m_{train}+m_{test}$
5.构建神经网络时使用矩阵$X=\left[ \begin{matrix}|&|&&|\ x^{\left( 1\right) }&x^{\left( 2\right) }&\cdots &x^{\left( m\right) }\ |&|&&|\end{matrix} \right] $,$m$是训练集样本的个数。
6.输出标签时,为了方便,也将y标签放入列中,$Y=\left[ \begin{matrix} y^{\left( 1\right) }&y^{\left( 2\right) }&\cdots &y^{\left( m\right) }\end{matrix} \right] $,$Y\in\R^{1\times m}$
Logistic回归
Logistic回归通常用于二元分类问题。
它通常的做法是将sigmoid函数
作用于线性回归:
$$
\hat{y} =\sigma\left( W^{T}x+b\right)\quad \quad \text{其中} \sigma(z)=\frac{1}{1+e^{-z}}
$$
这会使得$\hat{y}$的范围在0~1之间
梯度下降法中的损失函数
如下:
$$
L(\hat{y},y)=\frac12(\hat{y}-y)^2
$$
Logistic回归中使用的损失函数
如下:
$$
L(\hat{y},y)=-(y\log\hat{y}+(1-y)\log(1-\hat{y}))
$$
当$y=1$时,$L(\hat{y},y)=-y\log\hat{y}$,为了使损失函数较小,$\hat{y}$必须比较大,而$\hat{y}$的取值范围在0~1之间,所以$\hat{y}$要接近于1;当$y=0$,$L(\hat{y},y)=-\log(1-\hat{y})$,$\hat{y}$必须比较小,而$\hat{y}$的取值范围在0~1之间,所以$\hat{y}$要接近于0。
损失函数是在单个训练样本中定义的,在全体训练样本上的表现是由代价函数来定义的。
代价函数的定义:
$$
\begin{split}
J(W,b)&=\frac{1}{m}\sum^{m}{i\ =\ 1} L\left( \hat{y}^{\left( i\right) } ,y^{\left( i\right) }\right) \&=-\frac{1}{m}\sum^m{i=1}[y^{(i)}\log\hat{y}^{(i)}+(1-y^{(i)})\log(1-\hat{y}^{(i)})]\quad \quad \quad
\text{其中}\hat{y}^{(i)}\text{代表的是预测值},y^{(i)}代表的是真实值
\end{split}
$$
梯度下降法
我们可以将梯度下降法用下图来表示:
梯度下降法所做的事就是从初始点开始让$J(W,b)$朝最陡的下坡方向
走一步,迭代次数不定。
其中$W$的迭代更新公式如下:
$$
W:=W-\alpha\frac{\partial J(W,b)}{\partial W} \quad \quad 其中\alpha 代表学习率,\frac{\partial J(W,b)}{\partial W} 代表该点的W对应的导数
\b:=b-\alpha\frac{\partial J(W,b)}{\partial b}\quad \quad 其中\alpha 代表学习率,\frac{\partial J(W,b)}{\partial b} 代表该点的b对应的导数
$$
这样会使$W$和$b$一步一步得接近使得$J(W,b)$最小的值。
那么m个样本的梯度下降如何来表示呢?
其实就是对$J(W,b)$函数分别对$W$和$b$求偏导,得到全局的梯度值。
$W$和$b$的迭代过程:
1.对$W$和$b$设定初值,计算$J(W,b)$
2.通过$J(W,b)$对参数求偏导
3.使用$W$和$b$的原值减去学习率乘以偏导来迭代更新值
4.重复1~3步骤
向量化技术
如果不使用向量化技术,在面对巨大的数据集时,你会用非常多的循环去解决迭代的问题,这往往会降低代码运行的速度。向量化技术使得这种计算过程变得更加快速。
比如$f=W^T$,如果$W$有n个维度,不使用向量化一般需要用长度为n的for循环遍历求解,向量化之后则用矩阵来求解,看一段Python
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
import numpy as np
import time
a = np.random.rand(1000000)
b = np.random.rand(1000000)
# 非向量化使用循环
c = 0
tic = time.time()
for i in range(1000000):
c += a[i]*b[i]
toc = time.time()
print("使用循环做法花费的时间为" + str(1000*(toc - tic)) + "ms")
# 使用向量化技术
tic = time.time()
c = np.dot(a, b)
toc = time.time()
print("使用向量化技术花费的时间为" + str(1000*(toc - tic)) + "ms")
# 运行结果如下:(保留一位小数)
# 使用循环做法花费时间为474.3ms
# 使用向量化技术花费的时间为1.5ms
|
我们在编写神经网络的时候,尽量要避免使用for循环
再举个例子:
$$
v=\left[ \begin{matrix}v_{1}\ \vdots \ v_{n}\end{matrix} \right] ==>u=\left[ \begin{matrix}v_{1}\ \vdots \ v_{n}\end{matrix} \right]
$$
可以这样编程(尽量使用numpy):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import numpy as np
# np.random.randint(a, b, size=(c, d)):
# 注:a-b表示生成[a,b]数的范围,后面size表示生成矩阵的大小
n = 10000
v = np.random.randint(10,11,(1,n))
# 原始方法(for循环)
u = np.zero((n,1))
for i in range(n):
u[i]=math.exp(v[i])
# 使用numpy的内置函数,能比原来快很多
u = np.exp(v)
# 同样还有np.log() np.abs() np.maximum(v,0) v**2 1/v
|
使用numpy简易表示Logistic回归的一轮迭代:
1
2
3
4
5
6
7
8
9
10
11
|
# Z = w^T*X+b
Z = np.dot(w.T,X)+b
# A = sigmoid(Z)
def sigmoid_func(Z):
return 1/(1+np.exp(-z))
A = sigmoid_func(Z)
dZ = A - Y
dw = 1/m * dZ
db = 1/m * np.sum(dZ)
w = w - a * dw # a代表学习率
b = b - a * db
|
Python中的广播
|
苹果 |
牛肉 |
鸡蛋 |
土豆 |
碳水化合物 |
56.0 |
0.0 |
4.4 |
68.0 |
蛋白质 |
1.2 |
104.0 |
52.0 |
8.0 |
脂肪 |
1.8 |
135.0 |
99.0 |
0.9 |
求每种食物的每项指标占比:
1
2
3
4
5
6
7
8
9
10
11
|
import numpy as np
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]])
# 对矩阵进行竖直方向求和
cal = A.sum(axis=0)
# reshape是O(1)操作,放心使用
# 这里的广播是将3*4的矩阵除以1*4的矩阵,然后进行自动广播
percentage = 100*A/cal.reshape(1,4)
|
再举一个特殊的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import numpy as np
A = np.array([[1],
[2],
[3],
[4]])
# 广播
A = A + 100
print(A)
# 结果:
# [[101]
# [102]
# [103]
# [104]]
|
如上所示,广播的规则如下:
一个$m\times n$的矩阵加减乘除
一个$1\times n$的矩阵,python就会自动把它复制成$m\times n$的矩阵
一个$m\times n$的矩阵加减乘除
一个$m\times 1$的矩阵,python就会自动把它复制成$m\times n$的矩阵
一个$m\times 1$的矩阵加减乘除
一个常数,python就会自动把它复制成$m\times 1$的矩阵
一个$1\times m$的矩阵加减乘除
一个常数,python就会自动把它复制成$1\times m$的矩阵
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
28
|
import numpy as np
# 并不是一个向量,而是一个秩为1的数组
a = np.random.randn(5)
print(a)
# [-1.20936449 0.67825543 1.92816046 -0.55383946 -0.53203701]
print(a.shape)
# (5,)
print(a.T)
# [-1.20936449 0.67825543 1.92816046 -0.55383946 -0.53203701]
print(np.dot(a,a.T))
# 6.23019719213342
b = np.random.randn(5,1)
print(b)
# [[ 1.83847239]
# [ 0.43958321]
# [-0.87437944]
# [ 0.70296355]
# [-0.1833722 ]]
print(b.T)
# [[ 1.83847239 0.43958321 -0.87437944 0.70296355 -0.1833722 ]]
print(np.dot(b,b.T))
# [[ 3.37998075 0.8081616 -1.60752246 1.29237908 -0.33712473]
# [ 0.8081616 0.1932334 -0.38436252 0.30901097 -0.08060734]
# [-1.60752246 -0.38436252 0.7645394 -0.61465687 0.16033688]
# [ 1.29237908 0.30901097 -0.61465687 0.49415775 -0.12890397]
# [-0.33712473 -0.08060734 0.16033688 -0.12890397 0.03362536]]
|
从上面的例子可以看出,我们构建向量时尽量构建b这种类型的向量,不要使用数组,可以避免不必要的错误。
为了我们程序的运行正确,少点bug,可以使用assert声明函数。Python assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常。断言可以在条件不满足程序运行的情况下直接返回错误,而不必等待程序运行后出现崩溃的情况。语法为assert (表达式)
。
其中np.squeeze()
可以将数组变成一个向量。
作业一
使用numpy手写Logistic回归,这里只写回归部分,数据处理部分略过:
两个偏导数公式如下:
$$ \frac{\partial J}{\partial w} = \frac{1}{m}X(A-Y)^T\tag{7}$$
$$ \frac{\partial J}{\partial b} = \frac{1}{m} \sum_{i=1}^m (a^{(i)}-y^{(i)})\tag{8}$$
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
|
import numpy as np
# ----------------------------------
def sigmoid(z):
"""
sigmoid激活函数
"""
s = 1.0 / (1.0 + np.exp(-1.0 * z))
return s
# ----------------------------------
def initialize_with_zeros(dim):
"""
Argument:
dim -- 输入数据的维度
Returns:
w -- 初始化维度为(dim, 1)的向量
b -- 初始化标量
"""
w = np.zeros((dim,1))
b = 0
assert(w.shape == (dim, 1))
assert(isinstance(b, float) or isinstance(b, int))
return w, b
# ----------------------------------
def propagate(w, b, X, Y):
"""
代价函数
Argument:
w -- 权重
b -- 偏移量
X -- (num_px*num_px*3, 1)维度的数据
Y -- 维度为(1, 样本数量)标签
Returns:
cost -- 代价
dw -- 损失相对于 w 的梯度,因此维度与 w 相同
db -- 损失相对于 b 的梯度,因此维度与 b 相同
"""
m = X.shape[1] # 样本数量
A = sigmoid(np.dot(w.T, X) + b) # 预测值
cost = -(1.0/m) * np.sum(Y * np.log(A) + (1 - Y) * np.log(1 - A))
dw = (1.0/m) * np.dot(X, (A - Y).T)
db = (1.0/m) * np.sum(A - Y)
assert(dw.shape == w.shape)
assert(db.shape == b.shape)
cost = np.squeeze(cost) # 将数组转化为向量(这里为防止bug)
assert(cost.shape == ())
grads = {"dw": dw,
"db": db}
return grads, cost
# ----------------------------------
def optimize(w, b, X, Y, num_iterations, learning_rate, print_cost = False):
"""
w和b的迭代优化
Argument:
w -- 权重
b -- 偏移量
X -- (num_px*num_px*3, 1)维度的数据
Y -- 维度为(1, 样本数量)标签
num_iterations -- 优化迭代的次数
learning_rate -- 学习率
print_cost -- 如果为true,每迭代100次打印一次损失
Returns:
params -- 包含权重 w 和偏差 b 的字典
grads -- 包含权重梯度和相对于成本函数的偏差梯度的字典
costs -- 优化期间计算的所有成本的列表,这将用于绘制学习曲线。
"""
costs = []
for i in range(num_iterations):
grads, cost = propagate(w, b, X, Y)
dw = grads["dw"]
db = grads["db"]
w = w - learning_rate * dw
b = b - learning_rate * db
if i % 100:
costs.append(cost)
if print_cost and i % 100 == 0:
print("Cost after iteration %i:%f" %(i, cost))
params = {"w": w,
"b": b}
grads = {"dw": dw,
"db": db}
return params, grads
# ----------------------------------
def predict(w, b, X):
"""
使用学习的逻辑回归参数 (w, b) 预测标签是 0 还是 1
Arguments:
w -- 权重
b -- 偏移量
X -- (num_px*num_px*3, 样本数量)维度的数据
Returns:
Y_prediction -- 一个numpy数组(向量),包含X中示例的所有预测(0/1)
"""
m = X.shape[1]
Y_prediction = np.zeros((1,m))
w = w.reshape(X.shape[0], 1)
A = sigmoid(np.dot(w.T, X) + b)
for i in range(A.shape[1]):
if A[0, i] > 0.5:
Y_prediction[0, i] = 1
else:
Y_prediction[0, i] = 0
assert(Y_prediction.shape == (1, m))
return Y_prediction
# ----------------------------------
def model(X_train, Y_train, X_test, Y_test, num_iterations = 2000, learning_rate = 0.5, print_cost = False):
"""
构建逻辑回归模型
Arguments:
X_train -- 训练样本 shape:(num_px * num_px * 3, m_train)
Y_train -- 训练标签 shape:(1, m_train)
X_test -- 测试样本 shape:(num_px * num_px * 3, m_test)
Y_test -- 测试标签 shape:(1, m_test)
num_iterations -- 迭代次数 默认为2000
learning_rate -- 学习率 默认为0.5
print_cost -- 是否打印代价
Returns:
d -- 包含模型信息的字典
"""
w, b = initialize_with_zeros(X_train.shape[0])
# 训练
parameter, grads, costs = optimize(w, b, X_train, Y_train, num_iterations, learning_rate, print_cost)
# 训练结果
w = parameter["w"]
b = parameter["b"]
# 预测
Y_prediction_test = predict(w, b, X_test)
Y_prediction_train = predict(w, b, X_train)
# 打印预测结果
print("训练集 预测准确率:{} %".format(100 - np.mean(np.abs(Y_prediction_train - Y_train)) * 100))
print("测试集 预测准确率:{} %".format(100 - np.mean(np.abs(Y_prediction_test - Y_test)) * 100))
d = {"cost": costs,
"测试集预测正确个数": Y_prediction_test,
"训练集预测正确个数": Y_prediction_train,
"w": w,
"b": b,
"学习率": learning_rate,
"迭代轮数": num_iterations}
return d
## 最后就可以使用model函数对已经经过数据处理的训练集和测试集进行训练和预测了
|
神经网络编写
神经网络表示
如上图所示,这是一个双层神经网络
(一般输入层不作为层数)。最左边为输入层,代表单个样本的输入特征数;中间为隐藏层;最右边为输出层,一般代表预测值。中间的隐藏层是代表特征与预测值关系的一些表达式,类似于机器学习中的$W$和$b$。在这个图中,$W$是一个$4\times 3$的矩阵,$b$是一个$4\times 1$的矩阵,4代表隐藏层的个数,3代表输入的特征。
*需要注意的是这里的W和Logistic中讲的W是不一样的:因为这里的W是指整个隐藏层的W,计算时不用转置(4$\times$
3);而Logostic中的W相当于只有一个节点的W,且计算时需要转置(3$\times$1)。*
神经网络的计算
将每个隐藏层分开单独和左边的输入层结合在一起看,神经网络其实就是多个类似于Logistic回归的结构。
所以上图隐藏层的计算过程如下:
$$
z^{[1]}_1 = {w^{[1]}_1}^{T}x+b^{[1]}_1,a^{[1]}_1=\sigma(z^{[1]}_1)\
z^{[1]}_2 = {w^{[1]}_2}^{T}x+b^{[1]}_2,a^{[1]}_2=\sigma(z^{[1]}_2)\
z^{[1]}_3 = {w^{[1]}_3}^{T}x+b^{[1]}_3,a^{[1]}_3=\sigma(z^{[1]}_3)\
z^{[1]}_4 = {w^{[1]}_4}^{T}x+b^{[1]}_4,a^{[1]}_4=\sigma(z^{[1]}_4)\
其中[]里代表的数字是第几层,这里是从隐藏层算起\下标的值代表的是该层的第几个节点\
\sigma(z)代表激活函数
$$
所以将上面的双层神经网络整个计算过程合并起来就变成了:
$$
第一层:隐藏层\
z^{[1]}=W^{[1]}x+b^{[1]}\
a^{[1]}=\sigma(z^{[1]})\
第二层:输出层\
z^{[2]}=W^{[2]}a^{[1]}+b^{[2]}\
a^{[2]}=\sigma(z^{[2]})
$$
多个样本的向量化
上面讲的计算过程是单个样本的计算过程,如果是多个样本,就要使用一个循环来计算。但是前面讲过样本的遍历可以使用向量化技术来加快运算速度。
所以我们把z和a关于样本的多个列合并在一起:
$$
Z^{[1]}=[z^{1},z^{1},\dots z^{1}]\
A^{[1]}=[a^{1},a^{1},\dots a^{1}]\
Z^{[2]}=[z^{2},z^{2},\dots z^{2}]\
A^{[2]}=[a^{2},a^{2},\dots a^{2}]\
m代表样本的数量,[]的值代表不同的层,行代表不同的节点(也叫隐藏单元),列代表不同的样本
$$
所以计算过程变为了:
$$
Z^{[1]}=W^{[1]}X+b^{[1]}\
A^{[1]}=\sigma(Z^{[1]})\
Z^{[2]}=W^{[2]}A^{[1]}+b^{[2]}\
A^{[2]}=\sigma(Z^{[2]})\
这里的b不需要变是因为python自带的广播技术
$$
多种激活函数
上面我们使用的激活函数为$\sigma(z)$也就是sigmoid函数
。现在我们要介绍多种激活函数来进行对比:
$$
a=tanh(z)=\frac{e^z-e^{-z}}{e^z+e^{-z}}\quad \quad 它的取值范围在[-1,1]\
$$
图像如下:
该函数的特点是所有的数据平均值接近0,如果需要进行该种数据中心化可以使用该函数。
通常来说激活函数选取$tanh(z)$都比使用sigmoid
函数更好。但有一个例外是输出层,输出层经常使用sigmoid函数
,或者使用二元分类时,使用sigmoid函数
。为了表示不同的层之间使用不同的激活函数,我们通常会将激活函数用$g$来表示,使用$g^{[i]}$表示第i层的激活函数。
sigmoid函数
和$tanh(z)$函数共同的缺点是当$z$的值很大或者很小的时候,函数的斜率很接近0,也就是我们经常会说的梯度消失,拖慢梯度下降算法。
$$
a=max(0,z)
$$
图像如下:
-
Leaky ReLU:
$$
a=max(cz,z)\quad \quad c在这里是一个常数,通常取一个比较小的数,比如0.01或者0.001
$$
图像如下:
ReLU的缺点是,当$z$的值为负数的时候,它没有导数值。而Leaky ReLU解决了这个问题。
激活函数如何选择?
激活函数的选择经验:
1.如果你在做二元分类时,输出一般为0或1,那么该网络的输出层激活函数选择sigmoid函数较好,其他所有单元都使用ReLU函数。使用ReLU函数最大的好处就是梯度下降比较快,也就是收敛的比较快。
2.有时候特定情况下会使用tanh(z)函数。
3.ReLU函数是最常用的激活函数
为什么神经网络需要使用激活函数?
我们来做一个公式推导:
$$
如果不使用激活函数:\
a^{[1]}=z^{[1]}=W^{[1]}x+b^{[1]}\
a^{[2]}=z^{[2]}=W^{[2]}a^{[1]}+b^{[2]}=(W^{[2]}W^{[1]})x+(W^{[2]}b^{[1]}+b^{[2]})\
=W^{’}x+b^{’}
$$
可以看到如果不使用激活函数,无论你使用多庞大的神经网络,都始终在做线性激活函数,这就退化成了线形回归的内容。
激活函数的导数
当你使用神经网络进行反向传播时,需要计算激活函数的斜率或者导数。
$$
a=g(z)=\frac{1}{1+e^{-z}}\
g^{’}(z)=\frac{dg(z)}{dz}=\frac{1}{1+e^{-z}}(1-\frac{1}{1+e^{-z}})=g(z)(1-g(z))=a(1-a)
$$
$$
a=g(z)=tanh(z)=\frac{e^z-e^{-z}}{e^z+e^{-z}}\
g^{’}(z)=\frac{dg(z)}{dz}=1-(\frac{e^z-e^{-z}}{e^z+e^{-z}})^2=1-g(z)^2=1-a^2
$$
$$
a=g(z)=max(0,z)\
g^{’}(z)=\begin{cases}
0,& \text{如果}z<0\
1,& \text{如果}z>0\
undefined,&\text{如果}z=0
\end{cases}
$$
$$
g(z)=max(0.01z,z)\
g^{’}(z)=\begin{cases}
0.01& \text{如果}z<0\
1& \text{如果}z>0
\end{cases}
$$
神经网络的梯度下降法
以单隐藏层为例,写出它们的正向传播和反向传播的过程:
$$
Z^{[1]}=W^{[1]}X+b^{[1]}\
A^{[1]}=\sigma(Z^{[1]})\
Z^{[2]}=W^{[2]}A^{[1]}+b^{[2]}\
A^{[2]}=\sigma(Z^{[2]})
$$
$$
dZ^{[2]}=A^{[2]}-Y\
dW^{[2]}=\frac{1}{m}dZ^{[2]}{A^{[1]}}^T\
db^{[2]}=\frac{1}{m}np.sum(dZ^{[2]},axis=1,keepdims=True)\
dZ^{[1]}={W^{[2]}}^TdZ^{[2]}{g^{[1]}}^{’}(Z^{[1]})\
这里的代表逐个元素乘积\
dW^{[1]}=\frac{1}{m}dZ^{[1]}X^T\
db^{[1]}=\frac{1}{m}np.sum(dZ^{[1]},axis=1,keepdims=True)
$$
np.sum的axis不同时:
1.np.sum(axis = 0)代表矩阵最外维度相加(如果最外维度为n,可以理解为n个二维矩阵直接相加)
2.np.sum(axis = 1)代表矩阵中间维度相加(相当于是二维矩阵内部对每列求和)
3.np.sum(axis = 2)代表矩阵最内维度相加(相当于是二维矩阵内部对每行求和)
keepdims=True是为了保证不输出秩为1的数组
随机初始化
在Logistic回归中,我们把$w$和$b$都初始化为0向量,在神经网络中$W$不能这么初始化为0矩阵。因为这样会导致第一层在做计算时,每个隐藏单元所做的计算都是一模一样的,在反向传播时,不同隐藏单元激活函数的导数$dz^{[1]}_1$和$dz^{[1]}_2$是一样的。
我们的做法一般是对$W$随机初始化:
$$
W^{[1]}=np.random.randn((x,y))\times 0.01\
b^{[2]}=np.zeros((y,1))\
0.01代表权重,一般取比较小的值,这样能使梯度下降更快一些\这在使用sigmoid作为激活函数的网络更为明显(z值太大,导数接近0);\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
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
|
import numpy as np
import matplotlib.pyplot as plt
# -----------------------------------------
def sigmoid(z):
"""
sigmoid激活函数
"""
s = 1.0 / (1.0 + np.exp(-1.0 * z))
return s
# -----------------------------------------
def layer_sizes(X, Y):
"""
Arguments:
X -- 输入数据 (输入层大小, 样本数量)
Y -- 标签 (输出层大小, 样本数量)
Returns:
n_x -- 输入层的大小
n_h -- 隐藏层的大小
n_y -- 输出层的大小
"""
n_x = X.shape[0] # 输入层的大小
n_h = 4
n_y = Y.shape[0] # 输出层的大小
return (n_x, n_h, n_y)
# -----------------------------------------
def initialize_parameters(n_x, n_h, n_y):
"""
Arguments:
n_x -- 输入层的大小
n_h -- 隐藏层的大小
n_y -- 输出层的大小
Returns:
params -- 初始化参数的字典:
W1 -- weight matrix of shape (n_h, n_x)
b1 -- bias vector of shape (n_h, 1)
W2 -- weight matrix of shape (n_y, n_h)
b2 -- bias vector of shape (n_y, 1)
"""
np.random.seed(2) # 设置随机种子
W1 = np.random.randn((n_h, n_x))
b1 = np.zeros((n_h, 1))
W2 = np.random.rand((n_y, n_h))
b2 = np.zeros((n_y, 1))
assert(W1.shape == (n_h, n_x))
assert(b1.shape == (n_h, 1))
assert(W2.shape == (n_y, n_h))
assert(b2.shape == (n_y, 1))
parameters = {"W1": W1,
"b1": b1,
"W2": W2,
"b2": b2}
return parameters
# -----------------------------------------
def forward_propagation(X, parameters):
"""
前向传播计算
Argument:
X -- 输入数据 (n_x, m)
parameters -- 初始化参数的字典 (output of initialization function)
Returns:
A2 -- 第二层sigmoid激活函数输出的结果
cache -- 中间权向量的字典 "Z1", "A1", "Z2" and "A2"
"""
W1 = parameters["W1"]
b1 = parameters["b1"]
W2 = parameters["W2"]
b2 = parameters["b2"]
Z1 = np.dot(W1, X) + b1 # b1使用广播技术自动扩充
A1 = np.tanh(Z1) # 隐藏层使用tanh激活函数
Z2 = np.dot(W2, A1) + b2 # b2使用广播技术自动扩充
A2 = sigmoid(Z2) # 输出层使用sigmoid激活函数
assert(A2.shape == (1, X.shape[1])) # X.shape[1]代表样本数量
cache = {"Z1": Z1,
"A1": A1,
"Z2": Z2,
"A2": A2}
return A2, cache
# -----------------------------------------
def compute_cost(A2, Y, parameters):
"""
计算代价函数 (13)
Arguments:
A2 -- 第二层sigmoid激活函数输出的结果 维度(1, number of examples)
Y -- 正确的标签 维度(1, number of examples)
parameters -- 初始化参数的字典 W1, b1, W2 and b2
Returns:
cost -- 代价函数结果
"""
m = Y.shape[1] # 样本数量
# 计算代价函数
# np.multiply(X, Y)是指X和Y对应位置两两相乘
logprobs = np.multiply(np.log(A2), Y) + np.multiply(np.log(1 - A2), 1 - Y)
cost = -np.sum(logprobs) / m
cost = np.squeeze(cost) # 确保代价为我们期望的维度
# isinstance() 函数来判断一个对象是否是一个已知的类型
assert(isinstance(cost, float))
return cost
# -----------------------------------------
def backward_propagation(parameters, cache, X, Y):
"""
后向传播
Arguments:
parameters -- 参数初始化字典
cache -- 中间权向量的字典 "Z1", "A1", "Z2" and "A2"
X -- 输入数据 维度(2, number of examples)
Y -- 正确标签 维度(1, number of examples)
Returns:
grads -- 参数渐变的字典
"""
m = X.shape[1]
W1 = parameters["W1"]
W2 = parameters["W2"]
A1 = cache["A1"]
A2 = cache["A2"]
# 后向传播计算
# tanh()函数的导数为 g'(a) = 1 - a^2
dZ2 = A2 - Y
dW2 = np.dot(dZ2, A1.T) / m
db2 = np.sum(dZ2, axis=1, keepdims=True)
dZ1 = np.multiply(np.dot(W2.T, dZ2), 1 - np.power(A1, 2)) # 1-np.power(A1, 2)为tanh的导数
dW1 = np.dot(dZ1, X.T) / m
db1 = np.sum(dZ1, axis=1, keepdims=True)/ m
grads = {"dW1": dW1,
"db1": db1,
"dW2": dW2,
"db2": db2}
return grads
# -----------------------------------------
def update_parameters(parameters, grads, learning_rate = 1.2):
"""
中间权重向量更新
Arguments:
parameters -- 更新前的参数
grads -- 用于参数更新的逆向传播参数
Returns:
parameters -- 更新后的参数
"""
W1 = parameters["W1"]
b1 = parameters["b1"]
W2 = parameters["W2"]
b2 = parameters["b2"]
dW1 = grads["dW1"]
db1 = grads["db1"]
dW2 = grads["dW2"]
db2 = grads["db2"]
# 权重向量更新
W1 = W1 - dW1 * learning_rate
b1 = b1 - db1 * learning_rate
W2 = W2 - dW2 * learning_rate
b2 = b2 - db2 * learning_rate
parameters = {"W1": W1,
"b1": b1,
"W2": W2,
"b2": b2}
return parameters
# -----------------------------------------
def nn_model(X, Y, n_h, num_iterations = 10000, print_cost=False):
"""
Arguments:
X -- dataset of shape (2, number of examples)
Y -- labels of shape (1, number of examples)
n_h -- size of the hidden layer
num_iterations -- 循环迭代的次数
print_cost -- 如果为True,每1000次打印一次代价
Returns:
parameters -- 训练好的参数,用于预测
"""
np.random.seed(3)
n_x = layer_sizes(X, Y)[0]
n_y = layer_sizes(X, Y)[2]
parameters = initialize_parameters(n_x, n_h, n_y)
W1 = parameters["W1"]
b1 = parameters["b1"]
W2 = parameters["W2"]
b2 = parameters["b2"]
# 循环
for i in range(0, num_iterations):
# 计算前向传播
A2, cache = forward_propagation(X, parameters)
# 计算代价
cost = compute_cost(X, parameters)
# 计算后向传播
grads = backward_propagation(parameters, cache, X, Y)
# 更新权向量
parameters = update_parameters(parameters, grads) # 学习率直接设置为默认值1.2
if print_cost and (i % 1000):
print("第i次迭代之后的代价为:" + str(cost))
return parameters
# -----------------------------------------
def predict(parameters, X):
"""
通过训练好的权重来预测X的类型
Arguments:
parameters -- python dictionary containing your parameters
X -- 输入数据 维度 (n_x, m)
Returns
predictions -- 模型预测的结果 (red: 0 / blue: 1)
"""
A2, cache = forward_propagation(X, parameters)
prediction = (A2 > 0.5) # sigmoid函数的判别方式
return prediction
# -----------------------------------------
# 使用:
# 构建双层神经网络模型
parameters = nn_model(X, Y, n_h=4, num_iterations=10000, print_cost=True)
# 绘制决策边界
plot_decision_boundary(lambda x: predict(parameters, x.T), X, Y[0, :])
plt.title("Decision Boundary for hidden layer size " + str(4))
|
因为有部分公式可能因为博客插件不支持的原因,完整的笔记请看:
https://github.com/caixiongjiang/deep-learning-computer-vision
最后修改于 2022-07-12
本作品采用
知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。