带有一个隐藏层的平面数据分类
解释一下,就是一个二维平面有两种不同的花色,我们通过花色将这个平面进行划分
预备知识
我们先来看一下我们需要的包
import numpy as np import matplotlib.pyplot as plt from testCases import * import sklearn import sklearn.datasets import sklearn.linear_model from planar_utils import plot_decision_boundary, sigmoid, load_planar_dataset, load_extra_datasets np.random.seed(1) #设置一个固定的随机种子,以保证接下来的步骤中我们的结果是一致的。
前两个不需要解释,在上一次的作业中,我们已经使用过了
第三个包,是课后作业自带的代码,看名字就知道,这个是为我们提供测试用例的,我们先来看一看,了解一个大概的样子
def layer_sizes_test_case(): np.random.seed(1) X_assess = np.random.randn(5, 3) Y_assess = np.random.randn(2, 3) return X_assess, Y_assess def initialize_parameters_test_case(): n_x, n_h, n_y = 2, 4, 1 return n_x, n_h, n_y def forward_propagation_test_case(): np.random.seed(1) X_assess = np.random.randn(2, 3) parameters = {'W1': np.array([[-0.00416758, -0.00056267], [-0.02136196, 0.01640271], [-0.01793436, -0.00841747], [ 0.00502881, -0.01245288]]), 'W2': np.array([[-0.01057952, -0.00909008, 0.00551454, 0.02292208]]), 'b1': np.array([[ 0.], [ 0.], [ 0.], [ 0.]]), 'b2': np.array([[ 0.]])} return X_assess, parameters
我们每编写完一段代码,这个库就会为我们提供测试的用例,一会正式编写的时候,我来具体解释
import sklearn import sklearn.datasets import sklearn.linear_model from planar_utils import plot_decision_boundary, sigmoid, load_planar_dataset, load_extra_datasets
前三行为我们提供数据分析的工具,我们一会正式开始在来分析,最后一行也是,我们用到了在进行分析
分析数据集
我们来看一下我们的数据集
X, Y = load_planar_dataset()
执行了这个函数,我们就获取到了我们的数据集,这个数据就是我们上面说的
from planar_utils import plot_decision_boundary, sigmoid, load_planar_dataset, load_extra_datasets
给出来的,我们使用到了其中的load_planar_dataset这个函数,进入其中,我们看看大概的内容
def load_planar_dataset(): np.random.seed(1) m = 400 # number of examples N = int(m/2) # number of points per class D = 2 # dimensionality X = np.zeros((m,D)) # data matrix where each row is a single example Y = np.zeros((m,1), dtype='uint8') # labels vector (0 for red, 1 for blue) a = 4 # maximum ray of the flower for j in range(2): ix = range(N*j,N*(j+1)) t = np.linspace(j*3.12,(j+1)*3.12,N) + np.random.randn(N)*0.2 # theta r = a*np.sin(4*t) + np.random.randn(N)*0.2 # radius X[ix] = np.c_[r*np.sin(t), r*np.cos(t)] Y[ix] = j X = X.T Y = Y.T return X, Y
具体代码的意思大概就是创建一个二维的平面,然后给其中填上不同颜色的小点点,最后我们返回的X和Y并不是这个二维平面的坐标,我们先来验证一下
shape_X = X.shape shape_Y = Y.shape m = Y.shape[1] # 训练集里面的数量 print ("X的维度为: " + str(shape_X)) print ("Y的维度为: " + str(shape_Y)) print ("数据集里面的数据有:" + str(m) + " 个")
根据前两行的意思,我们可以推断出X和Y就是两个矩阵,根据第三行我们可以知道Y的第二维表示的数据集的数量,我们来看一下运行结果
X的维度为: (2, 400) Y的维度为: (1, 400) 数据集里面的数据有:400 个
我们发现了,X和Y的第二维表示就是数据集的数量,然后X的一维一直都是两个数,表示的是这个小点点在二维平面图的具***置,Y的第一维1,也就是0和1分别表示不同的颜色,这样说起来还不是太过的直观,我们通过绘图工具来直观的看一下,并重新分析
plt.scatter(X[0, :], X[1, :], c=Y, s=40, cmap=plt.cm.Spectral) #绘制散点图 plt.show()
和我们预想的一样,X表示的就是图片(2,400),通俗的解释一下有400个数据集,每一个数据集都用两个属性,也就是x和y,进一步说明,有400个(x,y),400个坐标
然后就是Y(1,400),400个坐标对应这400个不同的结果,每一个结果只用两种情况,也就是0或者是1,也就是红色和蓝色,介绍完了数据集我们就开始正式的编写代码了
编写代码
定义神经网络的结构
定义神经网络层数
首先就是确定它的层数,我们将X和Y传入进来,先来确定它的层数
def layer_sizes(X , Y): """ 参数: X - 输入数据集,维度为(输入的数量,训练/测试的数量) Y - 标签,维度为(输出的数量,训练/测试数量) 返回: n_x - 输入层的数量 n_h - 隐藏层的数量 n_y - 输出层的数量 """ n_x = X.shape[0] #输入层 n_h = 4 #,隐藏层,硬编码为4 n_y = Y.shape[0] #输出层 return (n_x,n_h,n_y)
虽然有400个数据集,但是设定神经网络的层数,我们还是要以单一样本为准,以一个小点点为例子,它的特征就是x和y,这里的x和y表示的是它的坐标,宏观的想一下,我们输入它的坐标,经过神经网络的处理,就可以知道它是红还是蓝了,所以它的输入特征有两个,也就是第0层是2,第2层是0,其中隐藏层我们定义的是4,然后我们将这三层返回,测试一下,看一下测试代码
#测试layer_sizes print("=========================测试layer_sizes=========================") X_asses , Y_asses = layer_sizes_test_case() (n_x,n_h,n_y) = layer_sizes(X_asses,Y_asses) print("输入层的节点数量为: n_x = " + str(n_x)) print("隐藏层的节点数量为: n_h = " + str(n_h)) print("输出层的节点数量为: n_y = " + str(n_y))
这里我们使用到了layer_sizes_test_case(),这个就是我们定义库给出的函数,我们看一下
def layer_sizes_test_case(): np.random.seed(1) X_assess = np.random.randn(5, 3) Y_assess = np.random.randn(2, 3) return X_assess, Y_assess
我们定义了两个随机矩阵,也分别对应了输出和输入,如同输入为5,样本是3,输出为2,样本也是3,而在开头我们已经说了,神经网络是针对单一样本的,只需要看它的行,所以结果分别如下
=========================测试layer_sizes========================= 输入层的节点数量为: n_x = 5 隐藏层的节点数量为: n_h = 4 输出层的节点数量为: n_y = 2
初始化参数
我们已经定义了神经网络的层数,既然有两层的话,那么就会用两对w和b,我们将上面用到的三层分别传入
def initialize_parameters( n_x , n_h ,n_y): np.random.seed(2) #指定一个随机种子,以便你的输出与我们的一样。 W1 = np.random.randn(n_h,n_x) * 0.01 b1 = np.zeros(shape=(n_h, 1)) W2 = np.random.randn(n_y,n_h) * 0.01 b2 = np.zeros(shape=(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
n_x是第一层,n_h是第二层,n_y是第三层
分析我们可以知道w[1]的规模如下[第一层神经元的数量,上一层神经元的数量],b[1]的规模是[第一层神经元的数量,1],同理w[2]的规模如下[第二层神经元的数量,和上一层神经元的数量],b[2]的规模是[第二层神经元的数量,1]
其中w和b中的内容,都是随机定义的,否则的话,不同的神经元的运算结果会是相同的,在吴恩达老师的课程中解释过,而且我们都要乘一个0.01,防止梯度过缓,但时候迭代差距不大,总结一下三个注意的问题
w和b的维数问题 一定要是随机值 乘0.01
然后我们测试一下我们的代码
#测试initialize_parameters print("=========================测试initialize_parameters=========================") n_x , n_h , n_y = initialize_parameters_test_case() parameters = initialize_parameters(n_x , n_h , n_y) print("W1 = " + str(parameters["W1"])) print("b1 = " + str(parameters["b1"])) print("W2 = " + str(parameters["W2"])) print("b2 = " + str(parameters["b2"]))
再去自定义的库中去看一下initialize_parameters_test_case
def initialize_parameters_test_case(): n_x, n_h, n_y = 2, 4, 1 return n_x, n_h, n_y
我们就是定义了矩阵的规模,没什么要注意的,最后测试的结果
=========================测试initialize_parameters========================= W1 = [[-0.00416758 -0.00056267] [-0.02136196 0.01640271] [-0.01793436 -0.00841747] [ 0.00502881 -0.01245288]] b1 = [[ 0.] [ 0.] [ 0.] [ 0.]] W2 = [[-0.01057952 -0.00909008 0.00551454 0.02292208]] b2 = [[ 0.]]
向前传播
def forward_propagation( X , parameters ): """ 参数: X - 维度为(n_x,m)的输入数据。 parameters - 初始化函数(initialize_parameters)的输出 返回: A2 - 使用sigmoid()函数计算的第二次激活后的数值 cache - 包含“Z1”,“A1”,“Z2”和“A2”的字典类型变量 """ W1 = parameters["W1"] b1 = parameters["b1"] W2 = parameters["W2"] b2 = parameters["b2"] #前向传播计算A2 Z1 = np.dot(W1 , X) + b1 A1 = np.tanh(Z1) Z2 = np.dot(W2 , A1) + b2 A2 = sigmoid(Z2) #使用断言确保我的数据格式是正确的 assert(A2.shape == (1,X.shape[1])) cache = {"Z1": Z1, "A1": A1, "Z2": Z2, "A2": A2} return (A2, cache)
我们传入两对w和b,然后传入我们的x(2,400)的数据集,向前传播的公式就不仔细解释了,我们来分析一下我们矩阵维度的变化,W1的规模是[这一层的神经元,上一层的神经元(或者是输入特征值)],X就是(特征属性,样本的数量),b的规模就是[当前层的神经元的数量,1],因此我们的Z1就是[当前层的神经元的数量,样本数量],同理A1[当前层的神经元的数量,样本数量],Z2和A2也出来了[当前神经元的数量,样本数量],返回的值就是Z1,A1,Z2,A2
我们给出我们的测试数据,也是从自定义的库中去取的
def forward_propagation_test_case(): np.random.seed(1) X_assess = np.random.randn(2, 3) parameters = {'W1': np.array([[-0.00416758, -0.00056267], [-0.02136196, 0.01640271], [-0.01793436, -0.00841747], [ 0.00502881, -0.01245288]]), 'W2': np.array([[-0.01057952, -0.00909008, 0.00551454, 0.02292208]]), 'b1': np.array([[ 0.], [ 0.], [ 0.], [ 0.]]), 'b2': np.array([[ 0.]])} return X_assess, parameters
#测试forward_propagation print("=========================测试forward_propagation=========================") X_assess, parameters = forward_propagation_test_case() A2, cache = forward_propagation(X_assess, parameters) print(cache["Z1"], cache["A1"], cache["Z2"], cache["A2"])
运算结果如下
=========================测试forward_propagation========================= [[-0.00616586 0.0020626 0.0034962 ] [-0.05229879 0.02726335 -0.02646869] [-0.02009991 0.00368692 0.02884556] [ 0.02153007 -0.01385322 0.02600471]] [[-0.00616578 0.0020626 0.00349619] [-0.05225116 0.02725659 -0.02646251] [-0.02009721 0.0036869 0.02883756] [ 0.02152675 -0.01385234 0.02599885]] [[ 0.00092281 -0.00056678 0.00095853]] [[0.5002307 0.49985831 0.50023963]]
损失函数的计算
先看代码
def compute_cost(A2,Y,parameters): """ 计算方程(6)中给出的交叉熵成本, 参数: A2 - 使用sigmoid()函数计算的第二次激活后的数值 Y - "True"标签向量,维度为(1,数量) parameters - 一个包含W1,B1,W2和B2的字典类型的变量 返回: 成本 - 交叉熵成本给出方程(13) """ m = Y.shape[1] W1 = parameters["W1"] W2 = parameters["W2"] #计算成本 logprobs = logprobs = np.multiply(np.log(A2), Y) + np.multiply((1 - Y), np.log(1 - A2)) cost = - np.sum(logprobs) / m cost = float(np.squeeze(cost)) assert(isinstance(cost,float)) return cost
然后就是测试,这里的测试和上面道理是相同的
#测试compute_cost print("=========================测试compute_cost=========================") A2 , Y_assess , parameters = compute_cost_test_case() print("cost = " + str(compute_cost(A2,Y_assess,parameters)))
=========================测试compute_cost========================= cost = 0.6929198937761266
正向的传播搞定了,我们在来看看反向传播
反向传播
首先是两层神经网络传播的公式,然后继续
def backward_propagation(parameters,cache,X,Y): """ 使用上述说明搭建反向传播函数。 参数: parameters - 包含我们的参数的一个字典类型的变量。 cache - 包含“Z1”,“A1”,“Z2”和“A2”的字典类型的变量。 X - 输入数据,维度为(2,数量) Y - “True”标签,维度为(1,数量) 返回: grads - 包含W和b的导数一个字典类型的变量。 """ m = X.shape[1] W1 = parameters["W1"] W2 = parameters["W2"] A1 = cache["A1"] A2 = cache["A2"] dZ2= A2 - Y dW2 = (1 / m) * np.dot(dZ2, A1.T) db2 = (1 / m) * np.sum(dZ2, axis=1, keepdims=True) 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
然后我们逐层进行分析
dZ2= A2 - Y dW2 = (1 / m) * np.dot(dZ2, A1.T)
分析一下规模的问题,dz2是(1,样本数),A1是(第一层神经元个数,样本数),A1*T是(样本数,第一层神经元个数),然后dw2是(1,第一层神经元个数)
db2 = (1 / m) * np.sum(dZ2, axis=1, keepdims=True)
db2是(1,1)
dZ1 = np.multiply(np.dot(W2.T, dZ2), 1 - np.power(A1, 2))
1-np.power(A1,2)这个就是tanh的导数,multiply前后两个值是相同的维度
dW1 = (1 / m) * np.dot(dZ1, X.T) db1 = (1 / m) * np.sum(dZ1, axis=1, keepdims=True)
dW1(当前神经元,输入特征值)
db1(当前神经元个数,1)
然后就是测试
#测试backward_propagation print("=========================测试backward_propagation=========================") parameters, cache, X_assess, Y_assess = backward_propagation_test_case() grads = backward_propagation(parameters, cache, X_assess, Y_assess) print ("dW1 = "+ str(grads["dW1"])) print ("db1 = "+ str(grads["db1"])) print ("dW2 = "+ str(grads["dW2"])) print ("db2 = "+ str(grads["db2"]))
修改参数
def update_parameters(parameters,grads,learning_rate=1.2): """ 使用上面给出的梯度下降更新规则更新参数 参数: parameters - 包含参数的字典类型的变量。 grads - 包含导数值的字典类型的变量。 learning_rate - 学习速率 返回: parameters - 包含更新参数的字典类型的变量。 """ W1,W2 = parameters["W1"],parameters["W2"] b1,b2 = parameters["b1"],parameters["b2"] dW1,dW2 = grads["dW1"],grads["dW2"] db1,db2 = grads["db1"],grads["db2"] 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
根据上面的几个函数,我们已经知道了W1,W2,b1,b2,dW1,dW2,db1,db2,然后我们通过梯度下降的方法来更新参数来获取到更好的参数
代码方面没什么要讲的
然后就是给出测试值,然后进行测试,先来看给出测试值的函数,这个是自定义库带有的
def update_parameters_test_case(): parameters = {'W1': np.array([[-0.00615039, 0.0169021 ], [-0.02311792, 0.03137121], [-0.0169217 , -0.01752545], [ 0.00935436, -0.05018221]]), 'W2': np.array([[-0.0104319 , -0.04019007, 0.01607211, 0.04440255]]), 'b1': np.array([[ -8.97523455e-07], [ 8.15562092e-06], [ 6.04810633e-07], [ -2.54560700e-06]]), 'b2': np.array([[ 9.14954378e-05]])} grads = {'dW1': np.array([[ 0.00023322, -0.00205423], [ 0.00082222, -0.00700776], [-0.00031831, 0.0028636 ], [-0.00092857, 0.00809933]]), 'dW2': np.array([[ -1.75740039e-05, 3.70231337e-03, -1.25683095e-03, -2.55715317e-03]]), 'db1': np.array([[ 1.05570087e-07], [ -3.81814487e-06], [ -1.90155145e-07], [ 5.46467802e-07]]), 'db2': np.array([[ -1.08923140e-05]])} return parameters, grads
然后就是测试和测试结果
#测试update_parameters print("=========================测试update_parameters=========================") parameters, grads = update_parameters_test_case() parameters = update_parameters(parameters, grads) print("W1 = " + str(parameters["W1"])) print("b1 = " + str(parameters["b1"])) print("W2 = " + str(parameters["W2"])) print("b2 = " + str(parameters["b2"])) 测试结果如下: =========================测试update_parameters========================= W1 = [[-0.00643025 0.01936718] [-0.02410458 0.03978052] [-0.01653973 -0.02096177] [ 0.01046864 -0.05990141]] b1 = [[-1.02420756e-06] [ 1.27373948e-05] [ 8.32996807e-07] [-3.20136836e-06]] W2 = [[-0.01041081 -0.04463285 0.01758031 0.04747113]] b2 = [[0.00010457]]
整合到一起
def nn_model(X,Y,n_h,num_iterations,print_cost=False): """ 参数: X - 数据集,维度为(2,示例数) Y - 标签,维度为(1,示例数) n_h - 隐藏层的数量 num_iterations - 梯度下降循环中的迭代次数 print_cost - 如果为True,则每1000次迭代打印一次成本数值 返回: 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(num_iterations): A2 , cache = forward_propagation(X,parameters) cost = compute_cost(A2,Y,parameters) grads = backward_propagation(parameters,cache,X,Y) parameters = update_parameters(parameters,grads,learning_rate = 0.5) if print_cost: if i%1000 == 0: print("第 ",i," 次循环,成本为:"+str(cost)) return parameters
给出测试和测试集
测试函数
def predict(parameters,X): """ 使用学习的参数,为X中的每个示例预测一个类 参数: parameters - 包含参数的字典类型的变量。 X - 输入数据(n_x,m) 返回 predictions - 我们模型预测的向量(红色:0 /蓝色:1) """ A2 , cache = forward_propagation(X,parameters) predictions = np.round(A2) return predictions
运行的函数
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) plt.title("Decision Boundary for hidden layer size " + str(4)) predictions = predict(parameters, X) print ('准确率: %d' % float((np.dot(Y, predictions.T) + np.dot(1 - Y, 1 - predictions.T)) / float(Y.size) * 100) + '%')