第3周-浅层神经网络

下面进入第三周的学习内容。

神经网络概述

image.png

这里将神经网络与Logistic回归进行了一个简单的对比,即神经网络也需要前向传播和后向传播。另外,每一层的参数用右上标的一个方括号表示,即方括号用来区分不同的层;如果右上标是圆括号,则指具体的一个样本。

神经网络表示

下图是一个简单的双层神经网络的表示(即单隐层网络),其中也包括了一些通用的符号标记方法。
image.png

计算神经网络的输出

图中给出了第一层隐藏层的输入和输出过程,我们可以通过将参数w进行堆叠化,再与X向量相乘,与b向量相加,得到对应的输出Z^[1]和a^[1]。
image.png

更简单的一个解释如下图,可以看到W,a,b,z的维度的相同点与不同点。我们固定输入特征为列向量,则隐藏层参数的维数则为(隐藏层结点个数,特征总数)。
image.png

多个样本的向量化

如果没有进行向量化,那么我们要用如下的一个for循环来实现神经网络的前向传播:
image.png

如果进行向量化,就像下图红框中标出的,这是就不需要使用for循环了,而是一次性就可以计算所有样本。即我们的关键是,将X向量化,这样也能使得Z,A向量化。
image.png

下面我们横向看A矩阵,矩阵A会扫过不同的训练样本,而竖向则经过当前隐藏层的不同单元;再以X矩阵为例,横向对应不同的训练样本,竖向则对应同一样本的不同输入特征(相当于输入层的不同单元)。

形象解释

接下来,对于Z^[1] = W^[1]X + b^[1]这样的向量化做了一个形象的解释。
image.png

简单的回顾,可以看到右下角的公式可以一次性计算所有的样本,而不需要使用for循环。但是我们会注意到,仍然有地方需要使用到for循环,即遍历所有的隐藏层来计算对应的输出。
image.png

激活函数-Activation functions

神经网络常见的几种激活函数:
image.png

这里有几点需要注意的,以及来自经验者的一些经验法则:

  1. 如果是做二元分类,即输出值为0或1,则Sigmoid函数可以用作输出层的激活函数,其他情况下都不要使用Sigmoid函数。
  2. 一般的默认激活函数为ReLu(线性修正单元),不过它的一个小缺点是当z小于0时,导数为0,因此有人提出了Leaky ReLu,不过挺少人使用的。
  3. 搭建神经网络时面临很多选择,如隐藏层单元数、激活函数、如何初始化权重等,当我们不确定哪些选择更有效时,可以使用交叉验证集尝试不同的选择,然后再找到最合适的来使用。

为什么神经网络需要非线性激活函数

image.png

如图,如果我们的隐藏层激活函数都使用恒等激活函数(线性激活函数),那么我们最终得到的只是输入的线性组合,即线性隐藏层毫无作用,因为两个线性函数的组合就是线性组合。因此,我们需要使用非线性激活函数才能使神经网络有效。当然,如果我们需要的真实输出y属于实数,那么我们可以在输出层使用线性激活函数,但是隐藏层则绝对不可以(例外是有关于一些压缩机制)。

激活函数的导数

image.png

神经网络的梯度下降公式

image.png

推导

省略

向量化写法

image.png

随机初始化

对于Logistic回归来说,我们可以将权重全部初始化为0,但是如果将神经网络的权重都初始化为0,则是行不通的。从图中可以知道,如果权重均为0,那么同一隐藏层中的两个隐藏层结点完全对称,即完全相同,因此并没有起到神经网络隐藏层的作用。
image.png

解决方法是进行随机初始化,而且应该初始化成小的参数。
image.png

我们使用np.random.randn(a,b)函数来进行随机初始化,注意到有个0.01的常数。对于浅层神经网络而言,常数设置为0.01也可以;但对于深层神经网络,则应该设置得更小。

本周作业

这周作业是创建一个单隐层神经网络,遇到了两个大坑!!!这两个大坑都是由于遇到了类似(100,)这样的向量,导致程序无法正常运行。今后在遇到bug的时候,第一个要想到这个问题!接下来记录一下整个的创建过程。

数据集

image.png

这里就遇到了坑一,我们需要通过reshape函数来确认向量的维度才行。

Logistic Regression

这里不再是上周作业那样自己从头到尾创建,而是直接调用sklearn库中的函数。

1
2
clf = sklearn.linear_model.LogisticRegressionCV();
clf.fit(X.T, Y.T);

其测试结果如下:
image.png

Neural Network model

我们的神经网络模型图形如下:
image.png

接下来是整个的网络构建过程。

确定神经网络结构-Defining the neural network structure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def layer_sizes(X, Y):
"""
Arguments:
X -- input dataset of shape (input size, number of examples)
Y -- labels of shape (output size, number of examples)

Returns:
n_x -- the size of the input layer
n_h -- the size of the hidden layer
n_y -- the size of the output layer
"""
### START CODE HERE ### (≈ 3 lines of code)
n_x = X.shape[0] # size of input layer
n_h = 4
n_y = Y.shape[0] # size of output layer
### END CODE HERE ###
return (n_x, n_h, n_y)

初始化模型参数-Initialize the model’s parameters

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
def initialize_parameters(n_x, n_h, n_y):
"""
Argument:
n_x -- size of the input layer
n_h -- size of the hidden layer
n_y -- size of the output layer

Returns:
params -- python dictionary containing your parameters:
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) # we set up a seed so that your output matches ours although the initialization is random.

### START CODE HERE ### (≈ 4 lines of code)
W1 = np.random.randn(n_h,n_x)*0.01
b1 = np.zeros((n_h,1))
W2 = np.random.randn(n_y,n_h)*0.01
b2 = np.zeros((n_y,1))
### END CODE HERE ###

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

循环过程-The Loop

前向传播过程:

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
def forward_propagation(X, parameters):
"""
Argument:
X -- input data of size (n_x, m)
parameters -- python dictionary containing your parameters (output of initialization function)

Returns:
A2 -- The sigmoid output of the second activation
cache -- a dictionary containing "Z1", "A1", "Z2" and "A2"
"""
# Retrieve each parameter from the dictionary "parameters"
### START CODE HERE ### (≈ 4 lines of code)
W1 = parameters["W1"]
b1 = parameters["b1"]
W2 = parameters["W2"]
b2 = parameters["b2"]
### END CODE HERE ###

# Implement Forward Propagation to calculate A2 (probabilities)
### START CODE HERE ### (≈ 4 lines of code)
Z1 = np.dot(W1, X) + b1
A1 = np.tanh(Z1)
Z2 = np.dot(W2, A1) + b2
A2 = sigmoid(Z2)
### END CODE HERE ###
assert(A2.shape == (1, X.shape[1]))

cache = {"Z1": Z1,
"A1": A1,
"Z2": Z2,
"A2": A2}

return A2, cache

计算成本函数:

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
def compute_cost(A2, Y, parameters):
"""
Computes the cross-entropy cost given in equation (13)

Arguments:
A2 -- The sigmoid output of the second activation, of shape (1, number of examples)
Y -- "true" labels vector of shape (1, number of examples)
parameters -- python dictionary containing your parameters W1, b1, W2 and b2

Returns:
cost -- cross-entropy cost given equation (13)
"""

m = Y.shape[1] # number of example

# Compute the cross-entropy cost
### START CODE HERE ### (≈ 2 lines of code)
logprobs = np.multiply(np.log(A2),Y) + np.multiply(np.log(1-A2),1-Y)
cost = -np.sum(logprobs) / m
### END CODE HERE ###

cost = np.squeeze(cost) # makes sure cost is the dimension we expect.
# E.g., turns [[17]] into 17
assert(isinstance(cost, float))

return cost

后向传播:

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
def backward_propagation(parameters, cache, X, Y):
"""
Implement the backward propagation using the instructions above.

Arguments:
parameters -- python dictionary containing our parameters
cache -- a dictionary containing "Z1", "A1", "Z2" and "A2".
X -- input data of shape (2, number of examples)
Y -- "true" labels vector of shape (1, number of examples)

Returns:
grads -- python dictionary containing your gradients with respect to different parameters
"""
m = X.shape[1]

# First, retrieve W1 and W2 from the dictionary "parameters".
### START CODE HERE ### (≈ 2 lines of code)
W1 = parameters["W1"]
W2 = parameters["W2"]
### END CODE HERE ###

# Retrieve also A1 and A2 from dictionary "cache".
### START CODE HERE ### (≈ 2 lines of code)
A1 = cache["A1"]
A2 = cache["A2"]
### END CODE HERE ###

# Backward propagation: calculate dW1, db1, dW2, db2.
### START CODE HERE ### (≈ 6 lines of code, corresponding to 6 equations on slide above)
dZ2 = A2 - Y
dW2 = np.dot(dZ2, A1.T) / m
db2 = np.sum(dZ2, axis=1, keepdims = True) / m
dZ1 = np.multiply(np.dot(W2.T, dZ2), 1-np.power(A1,2))
dW1 = np.dot(dZ1, X.T) / m
db1 = np.sum(dZ1, axis=1, keepdims = True) / m
### END CODE HERE ###

grads = {"dW1": dW1,
"db1": db1,
"dW2": dW2,
"db2": db2}

return grads

参数更新:

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
def update_parameters(parameters, grads, learning_rate = 1.2):
"""
Updates parameters using the gradient descent update rule given above

Arguments:
parameters -- python dictionary containing your parameters
grads -- python dictionary containing your gradients

Returns:
parameters -- python dictionary containing your updated parameters
"""
# Retrieve each parameter from the dictionary "parameters"
### START CODE HERE ### (≈ 4 lines of code)
W1 = parameters["W1"]
b1 = parameters["b1"]
W2 = parameters["W2"]
b2 = parameters["b2"]
### END CODE HERE ###

# Retrieve each gradient from the dictionary "grads"
### START CODE HERE ### (≈ 4 lines of code)
dW1 = grads["dW1"]
db1 = grads["db1"]
dW2 = grads["dW2"]
db2 = grads["db2"]
## END CODE HERE ###

# Update rule for each parameter
### START CODE HERE ### (≈ 4 lines of code)
W1 -= learning_rate * dW1
b1 -= learning_rate * db1
W2 -= learning_rate * dW2
b2 -= learning_rate * db2
### END CODE HERE ###

parameters = {"W1": W1,
"b1": b1,
"W2": W2,
"b2": b2}

return parameters

整合最终模型-nn_model()

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
def nn_model(X, Y, n_h, num_iterations = 10000, print_cost=False):
Y = Y.reshape((1,-1))
"""
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 -- Number of iterations in gradient descent loop
print_cost -- if True, print the cost every 1000 iterations

Returns:
parameters -- parameters learnt by the model. They can then be used to predict.
"""

np.random.seed(3)
n_x = layer_sizes(X, Y)[0]
n_y = layer_sizes(X, Y)[2]

# Initialize parameters, then retrieve W1, b1, W2, b2. Inputs: "n_x, n_h, n_y". Outputs = "W1, b1, W2, b2, parameters".
### START CODE HERE ### (≈ 5 lines of code)
parameters = initialize_parameters(n_x, n_h, n_y)
W1 = parameters["W1"]
b1 = parameters["b1"]
W2 = parameters["W2"]
b2 = parameters["b2"]
### END CODE HERE ###

# Loop (gradient descent)

for i in range(0, num_iterations):

### START CODE HERE ### (≈ 4 lines of code)
# Forward propagation. Inputs: "X, parameters". Outputs: "A2, cache".
A2, cache = forward_propagation(X, parameters)

# Cost function. Inputs: "A2, Y, parameters". Outputs: "cost".
cost = compute_cost(A2, Y, parameters)

# Backpropagation. Inputs: "parameters, cache, X, Y". Outputs: "grads".
grads = backward_propagation(parameters, cache, X, Y)

# Gradient descent parameter update. Inputs: "parameters, grads". Outputs: "parameters".
parameters = update_parameters(parameters, grads, learning_rate=1.2)

### END CODE HERE ###

# Print the cost every 1000 iterations
if print_cost and i % 1000 == 0:
print ("Cost after iteration %i: %f" %(i, cost))

return parameters

预测-Predictions

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
def predict(parameters, X):
"""
Using the learned parameters, predicts a class for each example in X

Arguments:
parameters -- python dictionary containing your parameters
X -- input data of size (n_x, m)

Returns
predictions -- vector of predictions of our model (red: 0 / blue: 1)
"""

# Computes probabilities using forward propagation, and classifies to 0/1 using 0.5 as the threshold.
### START CODE HERE ### (≈ 2 lines of code)
A2, cache = forward_propagation(X, parameters)
# predictions = [y>0.5 for y in A2] # 这里要多学习!!
predictions = (A2 > 0.5)
### END CODE HERE ###

return predictions

parameters = nn_model(X, Y, n_h = 4, num_iterations = 10000, print_cost=True)

# Plot the decision boundary
plot_decision_boundary(lambda x: predict(parameters, x.T), X, Y)
plt.title("Decision Boundary for hidden layer size " + str(4))

而具体的计算公式可以参考前面的内容。