带有一个隐藏层的平面数据分类

解释一下,就是一个二维平面有两种不同的花色,我们通过花色将这个平面进行划分

预备知识

我们先来看一下我们需要的包

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) + '%')