编程实践-Week3
本周课程的主要内容是介绍浅层的神经网络,首先课程介绍了浅层神经网络的基本结构:

在图中,有一个隐藏层和一个输出层,因此我们把这个神经网络称为两层的神经网络。
与逻辑回归(Logistic Regression)类似,我们首先计算前向传播(Forward Propagation)中的输出。
对于神经网络中的每一个神经单元,我们计算其输出为

对于第一个隐藏层,我们可以计算

因此,对于整个的两层神经网络,每一层的输出为:

为了训练多个样本同时不适用低效率的For循环,我们在多个训练样本时,可以采用如下的向量化方式:

获得了每一层的输出后,我们应该选择适当的激活函数,常用的激活函数有Sigmoid函数,ReLu函数,TanH函数,带Leaky的ReLu函数等,其中,在隐藏层的每一层中,我们应该使用ReLu函数或TanH,而在输出层中使用Sigmoid函数。
在计算完正向传播的工作后,我们应进行反向传播的计算,即计算相应的导数及偏导数。
本周的编程作业是构造一个分类器,将不同颜色的数据点进行分类

整个测试的输入为X(x1, x2), Y(red:0, blue:1),X是所有的样本点且每个样本点有两个特征维度x1,x2。Y是每个样本点的标签,其取值为0和1。在Numpy中,可以通过使用X.shape来获得X矩阵的信息,X.shape[0]是每个样本的特征维度,X.shape[1]是样本的个数。
在实验中,首先使用Logistic Regression来对数据进行分类,从而可以与后面的浅层神经网络分类的效果进行对比。
使用Sklearn中带有的逻辑回归函数对数据集进行学习,
# Train the logistic regression classifier clf = sklearn.linear_model.LogisticRegressionCV(); clf.fit(X.T, Y.T); # Plot the decision boundary for logistic regression plot_decision_boundary(lambda x: clf.predict(x), X, Y) plt.title("Logistic Regression") # Print accuracy LR_predictions = clf.predict(X.T) print ('Accuracy of logistic regression: %d ' % float((np.dot(Y,LR_predictions) + np.dot(1-Y,1-LR_predictions))/float(Y.size)*100) + '% ' + "(percentage of correctly labelled datapoints)")
通过使用绘图工具得到如下的结果,可以看出在逻辑回归中,学习得到的准确率只有47%,准确率较差。因此我们采用神经网络的方式来进行分类。

整个神经网络的设计包括以下几个部分:
1.定义神经网络的结构(有多少层,每层有多少个逻辑单元,即超参数的设置)
2.初始化参数(在神经网络的参数初始化中,不能使用逻辑回归中全0的初始化方式,否则同一层每个单元的学习数值均相等,失去了神经网络的意义,因此应该采用随机化的初始化方式)
3.循环执行以下操作:(1)前向传播 (2)计算损失函数 (3)反向传播得到梯度 (4)更新参数(梯度下降)
具体的代码实现如下:
1.定义神经网络结构
上面提到,我们可以通过Numpy中的shape函数来获得输入的维度,在这里我们定义三个变量:
n_x: 输入层的大小。
n_h: 隐藏层的大小(设置为4)。
n_y: 输出层的大小。
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 """ n_x = X.shape[0] # size of input layer n_h = 4 n_y = Y.shape[0] # size of output layer return (n_x, n_h, n_y) X_assess, Y_assess = layer_sizes_test_case() (n_x, n_h, n_y) = layer_sizes(X_assess, Y_assess)
2. 初始化参数
神经网络中参数的初始化与逻辑回归不同,为了避免每个单元学习到相同的参数,在初始化时使用随机的初始化方式,代码如下:
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: 在整个深度学习的过程中,时刻要注意矩阵的参数,大部分Bug都是因为矩阵参数的设置问题,掌握W和b的参数意义。 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. W1 = np.random.randn(n_h,n_x) * 0.01 # 在对W进行初始化时,我们通常选择较小的W1,因为这样得到Z较小,具有较好的梯度,若Z过大过小,则收敛的速度会很慢 b1 = np.zeros((n_h,1),dtype=float) # 使用np.zeros(a,b)时,不能忘记指定相应的数据类型 dtype = int or float W2 = np.random.randn(n_y,n_h) * 0.01 b2 = np.zeros((n_y,1),dtype=float) 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来对神经网络中的参数进行缓存,在以后的调用中十分方便,这种方法一定要掌握。
parameters = {"W1": W1, "b1": b1, "W2": W2, "b2": b2} return parameters n_x, n_h, n_y = initialize_parameters_test_case() parameters = initialize_parameters(n_x, n_h, n_y)
3. 循环
(1)前向传播
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" 从parameters恢复相应数据的方式(parameters类似C++中的结构体数组,但访问起来更加方便)
W1 = parameters["W1"] b1 = parameters["b1"] W2 = parameters["W2"] b2 = parameters["b2"] # Implement Forward Propagation to calculate A2 (probabilities) # 前向传播的计算:向量化的计算方式,直接计算矩阵。注意np.dot(A,B)和A*B的区别:前者是矩阵的乘法运算,而后者是对应元素相乘,对应np.multiply(A,B). Z1 = np.dot(W1,X)+b1 A1 = np.tanh(Z1) Z2 = np.dot(W2,A1)+b2 A2 = sigmoid(Z2) #本神经网络只有一个隐藏层,因此隐藏层的激活函数为TanH,而输出层的激活函数仍为sigmoid assert(A2.shape == (1, X.shape[1])) # 与parameters类似,将前向传播中的参数存储在cache中 cache = {"Z1": Z1, "A1": A1, "Z2": Z2, "A2": A2} return A2, cache X_assess, parameters = forward_propagation_test_case() A2, cache = forward_propagation(X_assess, parameters)
(2) 计算损失函数
计算损失函数的公式为:

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,每一列都是一个样本,所以直接使用sum函数即可实现累加操作,不需要使用For循环。 logprobs = np.multiply(np.log(A2),Y)+np.multiply(np.log(1-A2),1-Y) cost = -np.sum(logprobs)/m cost = np.squeeze(cost) # makes sure cost is the dimension we expect. # E.g., turns [[17]] into 17 assert(isinstance(cost, float)) return cost A2, Y_assess, parameters = compute_cost_test_case() compute_cost(A2, Y_assess, parameters)
(3)反向传播
反向传播的所需要计算偏导数的方程为

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] #m是样本个数 # First, retrieve W1 and W2 from the dictionary "parameters". W1 = parameters["W1"] W2 = parameters["W2"] # Retrieve also A1 and A2 from dictionary "cache". A1 = cache["A1"] A2 = cache["A2"] # Backward propagation: calculate dW1, db1, dW2, db2. dZ2 = A2 - Y dW2 = 1/m * np.dot(dZ2, A1.T) print("logprobs:"+str(dZ2.shape)) print("logprobs:"+str(dZ2)) db2 = 1/m * np.sum(dZ2) dZ1 = np.multiply(np.dot(W2.T, dZ2), (1 - np.power(A1, 2))) dW1 = 1/m * np.dot(dZ1, X.T) db1 = 1/m * np.sum(dZ1, axis=1, keepdims=True) grads = {"dW1": dW1, "db1": db1, "dW2": dW2, "db2": db2} return grads parameters, cache, X_assess, Y_assess = backward_propagation_test_case() grads = backward_propagation(parameters, cache, X_assess, Y_assess)
(4) 参数更新(梯度下降)
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" W1 = parameters["W1"] b1 = parameters["b1"] W2 = parameters["W2"] b2 = parameters["b2"] # Retrieve each gradient from the dictionary "grads" dW1 = grads["dW1"] db1 = grads["db1"] dW2 = grads["dW2"] db2 = grads["db2"] # Update rule for each parameter W1 = W1-learning_rate*dW1 b1 = b1-learning_rate*db1 W2 = W2-learning_rate*dW2 b2 = b2-learning_rate*db2 parameters = {"W1": W1, "b1": b1, "W2": W2, "b2": b2} return parameters
parameters, grads = update_parameters_test_case()
parameters = update_parameters(parameters, grads)
编写完成以上所有的独立函数后,可以将以上函数整合起来完成一个完整的神经网络模型。
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 -- 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". parameters = initialize_parameters(X.shape[0], n_h, Y.shape[0]) W1 = parameters["W1"] b1 = parameters["b1"] W2 = parameters["W2"] b2 = parameters["b2"] # Loop (gradient descent) for i in range(0, num_iterations): # 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) # Print the cost every 1000 iterations if print_cost and i % 1000 == 0: print ("Cost after iteration %i: %f" %(i, cost)) return parameters
X_assess, Y_assess = nn_model_test_case()
parameters = nn_model(X_assess, Y_assess, 4, num_iterations=10000, print_cost=False)
经过num_iterations=10000次迭代后,使用该模型对结果进行预测,其中预测函数为:
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 = A2 > 0.5 #使用该函数亦可实现对整个矩阵的判断 ### END CODE HERE ### return predictions parameters, X_assess = predict_test_case() predictions = predict(parameters, X_assess) print("predictions mean = " + str(np.mean(predictions))) #mean函数可以输出相应的平均值
总结: 经过完整神经网络的实践,在整个过程中感觉最重要的就是对微积分和线性代数的要求还是比较高,在整个算法中对矩阵维度的把握要求准备,不然会遇到很多BUG。

浙公网安备 33010602011771号