005_wz_wed_DL_课程一第三周编程题(隐藏层;二分类;)

具有单隐藏层的二类分类神经网络

一、目的

建立一个神经网络,它有一个隐藏层,用于对特定的数据进行二分类

二、训练集与测试集

百度网盘,提取码:1234

三、编程

本次编程所需的所有模块

import numpy as np
import matplotlib.pyplot as plt
import sklearn
import sklearn.datasets
import sklearn.linear_model
from testCases import *
from planar_utils import *

numpy:是用Python进行科学计算的基本软件包
sklearn:为数据挖掘和数据分析提供的简单高效的工具
matplotlib :是一个用于在Python中绘制图表的库
testCases:提供了一些测试示例来评估函数的正确性,参见下载的资料
planar_utils :提供了在这个任务中使用的各种有用的功能,参见下载的资料

3.1 导入原始数据集进行剖析

从planar_utils.py中load_planar_dataset()的加载数据集进来,使用X存放位置信息,Y存放颜色信息,红色:0 , 蓝色 :1

# 导入数据集
X, Y = load_planar_dataset()

我们看一看数据的维度

print(X.shape, Y.shape)

在这里插入图片描述
再看一下具体都存放了写什么,以第一个点为例

print(X[:, 0], Y[:, 0])

在这里插入图片描述
X为两行200列的矩阵,第一列存放x坐标,第二列存放y坐标,共有400个点;Y为1行400列的矩阵,存放每个点对应的颜色信息;我们使用matplotlib可视化数据集,看看我们的数据集是什么样的

# 绘制散点图
plt.scatter(X[0, :], X[1, :], c=np.squeeze(Y), s=40, cmap=plt.cm.Spectral)
plt.show()

在这里插入图片描述

3.2 查看简单的Logistic回归的分类效果

在构建完整的神经网络之前,先让我们看看逻辑回归在这个问题上的表现如何,我们可以使用sklearn的内置函数来做到这一点

# 先看看在逻辑回归上面的表现如何
clf = sklearn.linear_model.LogisticRegressionCV()
clf.fit(X.T, Y.T)

把逻辑回归分类器的分类绘制出来

# 将逻辑回归分类器的分类绘制出来
plot_decision_boundary(lambda x: clf.predict(x), X, Y)  # 绘制决策边界
plt.title("Logistic Regression")  # 图标题
LR_predictions = clf.predict(X.T)  # 预测结果
print ("逻辑回归的准确性: %d " % float((np.dot(Y, LR_predictions) +
                                np.dot(1 - Y,1 - LR_predictions)) / float(Y.size) * 100) +
       "% " + "(正确标记的数据点所占的百分比)")

注1:plot_decision_boundary是planar_utils提供的绘制决策边界的函数
注2:这里这个准确性的计算,是先算预测到1的正确率,再加上预测0的准确率,最后处理总数据量
在这里插入图片描述
在这里插入图片描述
准确性只有47%的原因是数据集不是线性可分的,所以逻辑回归表现不佳

3.3 搭建神经网络

我们要搭建的神经网络模型如下:
在这里插入图片描述
当然还有我们的理论基础:
对于 x ( i ) x^{(i)} x(i)而言:
z [ 1 ] ( i ) = W [ 1 ] ( i ) x ( i ) + b [ 1 ] ( i ) z^{[1](i)}=W^{[1](i)}x^{(i)}+b^{[1](i)} z[1](i)=W[1](i)x(i)+b[1](i) a [ 1 ] ( i ) = σ ( z [ 1 ] ( i ) ) a^{[1](i)} = \sigma(z^{[1](i)}) a[1](i)=σ(z[1](i)) z [ 2 ] ( i ) = W [ 2 ] ( i ) a [ 1 ] ( i ) + b [ 2 ] ( i ) z^{[2](i)}=W^{[2](i)a^{[1](i)}}+b^{[2](i)} z[2](i)=W[2](i)a[1](i)+b[2](i) a [ 2 ] ( i ) = σ ( z [ 2 ] ( i ) a^{[2](i)}=\sigma(z^{[2](i)} a[2](i)=σ(z[2](i)
成本函数J:
J = − 1 m ∑ i = 0 m [ y ( i ) ∗ log ⁡ ( a [ 2 ] ( i ) ) + ( 1 − y ( i ) ) ∗ log ⁡ ( 1 − a [ 2 ] ( i ) ) ] J=-\frac{1}{m}\sum^{m}_{i=0}[y^{(i)}*\log(a^{[2](i)})+(1-y^{(i)})*\log(1-a^{[2](i)})] J=m1i=0m[y(i)log(a[2](i))+(1y(i))log(1a[2](i))]
构建神经网络的一般方法是:

  1. 定义神经网络结构(输入单元的数量,隐藏单元的数量等)
  2. 初始化模型的参数
  3. 循环
    3.1 实施前向传播
    3.2 计算损失
    3.3 实现向后传播
    3.4 更新参数(梯度下降)

最后把它们合并到一个model函数中,当我们构建好了model并学习了正确的参数,我们就可以预测新的数据

3.4 定义神经网络结构

在构建之前,我们要先把神经网络的结构给定义好:
n_x: 输入层的数量
n_h: 隐藏层的数量(这里暂时设置为4)
n_y: 输出层的数量

def layer_sizes(X, Y):
    """
    定义神经网络结构
    :param X: 输入数据集,维度为(输入的数量,训练/测试的数量)
    :param Y: 标签,维度为(输出的数量,训练/测试的数量)
    :return: 返回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

测试一下

# 测试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))

在这里插入图片描述

3.5 初始化模型的参数

是用随机值(高斯分布)初始化权重W矩阵,将偏向量b置为0

def initialize_parameters(n_x, n_h, n_y):
    """
    初始化模型的参数
    :param n_x: 输出层数量
    :param n_h: 隐藏层数量
    :param n_y: 输出层数量
    :return: 字典parameters:权重矩阵W1(n_h, n_x),W2(n_y, n_h);偏向量b1(n_h, 1),b2(n_y, 1);
    """
    # 定义随机种子确保我们的值一样
    np.random.seed(2)

    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))

    # 使用断言确保我的数据格式是正确的
    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,
        "W2": W2,
        "b1": b1,
        "b2": b2
    }
    return parameters

测试一下

# 测试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"]))

在这里插入图片描述

3.6 循环

3.6.1 前向传播

使用字典类型的parameters(它是initialize_parameters() 的输出)检索每个参数
实现向前传播,计算 Z 1 , A 1 , Z 2 , A 2 Z1,A1,Z2,A2 Z1,A1,Z2,A2 A 2 A2 A2为训练集里面所有例子的预测向量)
反向传播所需的值存储在cache中,cache将作为反向传播函数的输入

def forward_propagation(X, parameters):
    """
    前向传播
    :param X: 维度为(n_x, m)的输入数据
    :param parameters: 包含初始化W1,W2,b1,b2的字典,是initialize_parameters函数的输出
    :return:A2:通过sigmoid函数计算的第二次激活的数值
            cache:包含Z1,A1,Z2,A2的字典值,后面用于反向传播
    """
    W1 = parameters["W1"]
    W2 = parameters["W2"]
    b1 = parameters["b1"]
    b2 = parameters["b2"]

    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

测试一下

# 测试forward_propagation
print("=========================测试forward_propagation=========================")
X_assess, parameters = forward_propagation_test_case()
A2, cache = forward_propagation(X_assess, parameters)
print(np.mean(cache["Z1"]), np.mean(cache["A1"]), np.mean(cache["Z2"]), np.mean(cache["A2"]))

在这里插入图片描述
现在我们已经计算了 A 2 , a [ 2 ] ( i ) A2,a^{[2](i)} A2,a[2](i)包含了训练集里每个数值,现在我们就可以构建成本函数了

3.6.2 损失函数

计算成本的函数如下:
J = − 1 m ∑ i = 0 m [ y ( i ) ∗ log ⁡ ( a [ 2 ] ( i ) ) + ( 1 − y ( i ) ) ∗ log ⁡ ( 1 − a [ 2 ] ( i ) ) ] J=-\frac{1}{m}\sum^{m}_{i=0}[y^{(i)}*\log(a^{[2](i)})+(1-y^{(i)})*\log(1-a^{[2](i)})] J=m1i=0m[y(i)log(a[2](i))+(1y(i))log(1a[2](i))]

def compute_cost(A2, Y):
    """
    损失函数
    :param A2: 通过sigmoid函数计算的第二次激活的数值
    :param Y: 标签,维度(1, m)
    :return: 交叉熵成本函数
    """
    m = Y.shape[1]
    cost = - 1 / m * np.sum(Y * np.log(A2) + (1 - Y) * np.log(1 - A2))
    cost = float(np.squeeze(cost))
    return cost

测试一下

# 测试compute_cost
print("=========================测试compute_cost=========================")
A2, Y_assess, parameters= compute_cost_test_case()
print("cost = " + str(compute_cost(A2, Y_assess)))

在这里插入图片描述

3.6.3 反向传播

计算dz2,dw2,db2,dz1,dw1,db1,使用以下公式计算:
在这里插入图片描述
其中 g [ 1 ] ( . . . ) s g^{[1]}(...)s g[1](...)s是tanh函数:
g [ 1 ] ( z ) = a = t a n h ( z ) = e z − e − z e z + e − z g^{[1]}(z)=a=tanh(z)=\frac{e^z-e^{-z}}{e^z+e^{-z}} g[1](z)=a=tanh(z)=ez+ezezez
故:
g [ 1 ] ′ ( Z [ 1 ] ) = 1 − a 2 g^{[1]'}(Z^{[1]})=1-a^2 g[1](Z[1])=1a2

def backward_propagation(X, Y, cache, parameters):
    """
    反向传播
    :param X: 维度为(n_x, m)的输入数据
    :param Y: 标签,维度(1, m)
    :param cache: 包含Z1,A1,Z2,A2的字典值
    :param parameters: 包含初始化W1,W2,b1,b2的字典值
    :return: 字典grads包含W和b的导数
    """
    m = Y.shape[1]
    A1 = cache["A1"]
    A2 = cache["A2"]
    W2 = parameters["W2"]

    dz2 = A2 - Y
    dW2 = 1 / m * np.dot(dz2, A1.T)
    db2 = 1 / m * np.sum(dz2, axis=1, keepdims=True)
    dz1 = 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 = {
        "dz2": dz2,
        "dW2": dW2,
        "db2": db2,
        "dz1": dz1,
        "dW1": dW1,
        "db1": db1
    }
    return grads

测试一下

# 测试backward_propagation
print("=========================测试backward_propagation=========================")
parameters, cache, X_assess, Y_assess = backward_propagation_test_case()
grads = backward_propagation(X_assess, Y_assess, cache, parameters)
print("dW1 = " + str(grads["dW1"]))
print("db1 = " + str(grads["db1"]))
print("dW2 = " + str(grads["dW2"]))
print("db2 = " + str(grads["db2"]))

在这里插入图片描述
反向传播完成,,开始对参数进行更新

3.6.4 更新参数

我们需要使用(dW1, db1, dW2, db2)来更新(W1, b1, W2, b2)。
更新算法如下:
θ = θ − α ∂ J ∂ θ \theta = \theta - \alpha \frac{\partial J }{ \partial \theta } θ=θαθJ
其中 θ \theta θ为参数, α \alpha α为学习率

def update_parameters(parameters, grads, alpha):
    """
    更新参数
    :param parameters: 包含初始化W1,W2,b1,b2的字典值
    :param grads: 包含W和b的导数的字典值
    :param alpha: 学习率
    :return: 字典parameters包含更新后的W1,W2,b1,b2
    """
    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 - alpha*dW1
    b1 = b1 - alpha * db1
    W2 = W2 - alpha*dW2
    b2 = b2 - alpha*db2

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

测试一下:

# 测试update_parameters
print("=========================测试update_parameters=========================")
parameters, grads = update_parameters_test_case()
parameters = update_parameters(parameters, grads, alpha=1.2)

print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))

在这里插入图片描述

3.7 模型整合

我们现在把上面的东西整合到model中,神经网络模型必须以正确的顺序使用先前的函数

def model(X, Y, n_h, n_iters, alpha, print_cost):
    """
    模型整合
    :param X: 维度为(n_x, m)的输入数据
    :param Y: 标签,维度(1, m)
    :param n_h: 隐藏层数量
    :param n_iters: 迭代次数
    :param alpha: 学习率
    :param print_cost: 控制打印的布尔值
    :return: 字典parameters包含更新后的最佳参数W和b,列表costs包含每一步的迭代成本数据,后面用于绘图
    """
    costs = []
    n_x, n_y = layer_sizes(X, Y)[0], 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(n_iters):
        A2, cache = forward_propagation(X, parameters)
        cost = compute_cost(A2, Y)
        costs.append(cost)
        grads = backward_propagation(X, Y, cache, parameters)
        parameters = update_parameters(parameters, grads, alpha)
        if i % 1000 == 0:
            if print_cost:
                print("第", i,"次循环,成本为", cost)
    return parameters, costs

测试一下

# 测试model
print("=========================测试nn_model=========================")
X_assess, Y_assess = nn_model_test_case()

parameters, costs = model(X_assess, Y_assess, n_h=4, n_iters=10000, alpha=1.2, print_cost=False)
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))

在这里插入图片描述

:这里的警告是因为sigmoid函数进行exp(-z)运算时,因为输入得z值太大(正值)或太小(负值),产生了内存溢出,最终得到的结果是nan,所以在cost函数中的log计算引发此警告,但是该警告不影响梯度的计算

3.8 预测

构建predict来使用模型进行预测, 使用向前传播来预测结果
A 2 = { < 0.5 , y = 0 ⩾ 0.5 , y = 1 A2=\left\{\begin{matrix} <0.5,y=0\\ \geqslant 0.5,y=1\\ \end{matrix}\right. A2={<0.5,y=00.5,y=1

def predict(X, parameters):
    """
    预测函数
    :param X: 维度为(n_x, m)的输入数据
    :param parameters: 包含更新后的最佳参数W和b的字典值
    :return: 预测值
    """
    A2, cache = forward_propagation(X, parameters)
    predictions = np.round(A2)
    return predictions

测试一下

# 测试predict
print("=========================测试predict=========================")
parameters, X_assess = predict_test_case()
predictions = predict(X_assess, parameters)
print("预测的平均值 = " + str(np.mean(predictions)))

在这里插入图片描述

3.9 运行与结果分析

parameters, costs = model(X, Y, n_h=4, n_iters=10000, alpha=1.2, print_cost=True)
# 绘制边界
plt.figure(1)
plot_decision_boundary(lambda x: predict(x.T, parameters), X, Y)
plt.title("Decision Boundary for hidden layer size " + str(4))
predictions = predict(X, parameters)
print('准确率: %d' % float((np.dot(Y, predictions.T) + np.dot(1 - Y, 1 - predictions.T)) / float(Y.size) * 100) + '%')
plt.figure(2)
plt.plot(costs)
plt.xlabel("per thousand iters")
plt.ylabel("cost")
plt.legend()
plt.show()

结果:
在这里插入图片描述

在这里插入图片描述在这里插入图片描述
在此模型中我们可以改变的因素有:

  1. 激活函数tanh
  2. 隐藏层神经元数量
  3. 迭代次数n_iters
  4. 学习率 α \alpha α
  5. 更换一个数据集
3.9.1 改变隐藏层神经元数量
n_h = [1, 2, 3, 4, 5, 10, 20, 50]
for n_h in n_h:
    parameters, costs = model(X, Y, n_h=n_h, n_iters=10000, alpha=1.2, print_cost=False)
    # 绘制边界
    plt.figure(n_h)
    plot_decision_boundary(lambda x: predict(x.T, parameters), X, Y)
    plt.title("Decision Boundary for hidden layer size " + str(n_h))
    predictions = predict(X, parameters)
    print('隐藏层神经元为', n_h, '准确率: %d' % float((np.dot(Y, predictions.T) + np.dot(1 - Y, 1 - predictions.T)) / float(Y.size) * 100) + '%')
plt.show()

测试一下
在这里插入图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
可以看到在隐藏层神经元数量为3-5之间,数据分类的效果不错,往后随着神经元的增多,数据过度拟合了

3.9.2 改变学习速率

此处可以参照我的另一篇文章:003_wz_wed_DL_课程一第二周编程题

3.9.3 更换数据集

在planner_utils中还提供了额外的4个数据集load_extra_datasets,我们可以用来测试一下

noisy_circles, noisy_moons, blobs, gaussian_quantiles, no_structure = load_extra_datasets()
datasets = {"noisy_circles": noisy_circles,
            "noisy_moons": noisy_moons,
            "blobs": blobs,
            "gaussian_quantiles": gaussian_quantiles}
dataset = "noisy_circles"
X, Y = datasets[dataset]
X, Y = X.T, Y.reshape(1, Y.shape[0])
if dataset == "blobs":
    Y = Y % 2

在这里插入图片描述
在这里插入图片描述
其余大家自行测试

3.10 源码

源码在此

四、总结

整个神经网络架构如下:
在这里插入图片描述
我们需要注意的是

  1. 正确认识到原始数据集的维度、数据等信息
  2. 把控好每一层的数据维度
  3. 梯度下降法的正确应用
  4. 对结果的分析与思考

五、参考文章

【中文】【吴恩达课后编程作业】Course 1 - 神经网络和深度学习 - 第三周作业

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值