基于BP神经网络实现鸢尾花的分类

首先了解下Iris鸢尾花数据集:

       Iris数据集(https://en.wikipedia.org/wiki/Iris_flower_data_set)是常用的分类实验数据集,由Fisher,1936收集整理。Iris也称鸢尾花卉数据集,是一类多重变量分析的数据集。数据集包含150个数据集,分为3类,每类50个数据,每个数据包含4个属性。可通过花萼长度,花萼宽度,花瓣长度,花瓣宽度4个属性预测鸢尾花卉属于(Setosa,Versicolour,Virginica)三个种类中的哪一类。
iris以鸢尾花的特征作为数据来源,常用在分类操作中。该数据集由3种不同类型的鸢尾花的50个样本数据构成。其中的一个种类与另外两个种类是线性可分离的,后两个种类是非线性可分离的。

该数据集包含了4个属性:
        Sepal.Length(花萼长度),单位是cm;
        Sepal.Width(花萼宽度),单位是cm;
        Petal.Length(花瓣长度),单位是cm;
        Petal.Width(花瓣宽度),单位是cm;
种类:Iris Setosa(1.山鸢尾)、Iris Versicolour(2.杂色鸢尾),以及Iris Virginica(3.维吉尼亚鸢尾)。

一、读取数据

1. 读取训练数据

training_data = pd.read_csv('iris_training.csv', header=None)

2. 获取标签特征

X_train = training_data.iloc[:, :4].values.T
    Y_train = training_data.iloc[:, 4:].values.T
    Y_train = Y_train.astype('uint8')

二、模型训练和输出

1. 训练模型

 start_time = datetime.datetime.now()
    parameters, print_cost, cost_history = nn_model(X_train, Y_train, n_h=10, n_input=4, n_output=3,
                                                    num_iterations=10000,
                                                    print_cost=True)
    end_time = datetime.datetime.now()

2. 输出训练用时

    print("训练用时:" + str((end_time - start_time).seconds) + 's' + str(
        round((end_time - start_time).microseconds / 1000)) + 'ms')

三、模型测试

    test_data = pd.read_csv('iris_test.csv', header=None)
    X_test = test_data.iloc[:, :4].values.T
    Y_test = test_data.iloc[:, 4:].values.T
    Y_test = Y_test.astype('uint8')

四、结果展示

1. 不同学习率下预测结果展示

lr=0.5

lr=0.1

lr=0.01

lr=0.001

2. 结果可视化

五、 全部代码

import pandas as pd
import numpy as np
import datetime
import matplotlib.pyplot as plt
 
plt.rcParams['font.sans-serif'] = ['SimHei']  # 设置中文字体
plt.rcParams['axes.unicode_minus'] = False  # 解决负号无法显示的问题
from pandas.plotting import radviz
 
'''
    构建一个具有1个隐藏层的神经网络,隐层的大小为10
    输入层为4个特征,输出层为3个分类
    (1,0,0)为第一类,(0,1,0)为第二类,(0,0,1)为第三类
'''
 
 
# 初始化参数
def initialize_parameters(n_x, n_h, n_y):
    """
    :param n_x:输入层的节点数
    :param n_h:隐层的节点数
    :param n_y:输出层的节点数
    :return:parameters
    """
    # seed用于指定随机数生成器的种子,也称为随机数生成器的“状态”,其目的是为了使随机数的生成可重复。
    # 如果不指定随机数生成器的种子,每次生成的随机数序列都会不同。
    # 指定了种子为2,这意味着每次运行该代码,生成的随机数序列将始终相同,
    # 这是为了确保结果的可重复性,方便调试和验证算法的正确性。
    np.random.seed(2)
 
    # 初始化权重和偏置矩阵,使用Xavier初始化
    # Xavier初始化可以更好地使得网络中每一层的梯度都有相同的方差,有助于提高网络的训练效果
 
    # 使用Xavier初始化方法初始化权重矩阵w1和w2
    w1 = np.random.randn(n_h, n_x) * np.sqrt(2 / n_x)
    w2 = np.random.randn(n_y, n_h) * np.sqrt(2 / n_h)
    # 初始化偏置向量b1和b2为零向量
    b1 = np.zeros((n_h, 1))
    b2 = np.zeros((n_y, 1))
    # 将初始化好的参数存储到字典中
    parameters = {'w1': w1, 'b1': b1, 'w2': w2, 'b2': b2}
    # 返回参数字典
    return parameters
 
 
# 将X和参数进行前向传播计算,得到预测值和缓存的中间结果
def forward_propagation(X, parameters):
    """
    :param X:数据集X
    :param parameters:包含神经网络参数的字典parameters
    :return:数组a2,包含神经网络的输出结果;字典cache,包含前向传播时计算过程中的一些中间变量,在反向传播时使用。
    """
    # 从参数中提取出w1、b1、w2和b2
    w1, b1, w2, b2 = parameters['w1'], parameters['b1'], parameters['w2'], parameters['b2']
    # 计算z1、a1、z2和a2
    z1 = np.dot(w1, X) + b1
    # 使用tanh作为第一层的激活函数
    a1 = np.tanh(z1)
    z2 = np.dot(w2, a1) + b2
    # 使用sigmoid作为第二层的激活函数
    a2 = 1 / (1 + np.exp(-z2))
    # 将中间结果存储在缓存中
    cache = {'z1': z1, 'a1': a1, 'z2': z2, 'a2': a2}
    return a2, cache
 
 
# 计算代价函数
def compute_cost(a2, Y, parameters, lambd=0.3):
    """
    :param a2:预测值,shape为(1, m),其中m为样本数
    :param Y:实际标签,shape为(1, m)
    :param parameters:包含权重矩阵w1和w2的字典
    :param lambd: L2正则化超参数
    :return:cost: 代价函数的值
    """
    # 样本数
    m = Y.shape[1]
    # 计算交叉熵代价函数
    log_probs = np.multiply(np.log(a2), Y) + np.multiply((1 - Y), np.log(1 - a2))
    cross_entropy_cost = - np.sum(log_probs) / m
    # 从参数字典中获取权重矩阵
    w1, w2 = parameters['w1'], parameters['w2']
    # 添加L2正则化
    l2_regularization_cost = (lambd / (2 * m)) * (np.sum(np.square(w1)) + np.sum(np.square(w2)))
    # 总代价函数
    cost = cross_entropy_cost + l2_regularization_cost
    # 返回代价函数的值
    return cost
 
 
# 反向传播(计算神经网络的梯度值)
def backward_propagation(parameters, cache, X, Y, lambd=0.3):
    """
    :param parameters:包含模型参数 w1, b1, w2, b2 的字典
    :param cache:包含中间结果的字典,包括 a1, a2
    :param X:输入特征矩阵
    :param Y:标签向量
    :param lambd:L2正则化系数
    :return:grads:包含梯度 dw1, db1, dw2, db2 的字典
    """
    # 样本数量
    m = Y.shape[1]
    # 从参数字典和缓存中获取权重和中间结果
    w1, w2, a1, a2 = parameters['w1'], parameters['w2'], cache['a1'], cache['a2']
    # 反向传播,计算dw1、db1、dw2、db2
    dz2 = a2 - Y
    dw2 = np.dot(dz2, a1.T) / m + (lambd / m) * w2
    db2 = np.mean(dz2, axis=1, keepdims=True)
    dz1 = np.dot(w2.T, dz2) * (1 - np.power(a1, 2))
    dw1 = np.dot(dz1, X.T) / m + (lambd / m) * w1
    db1 = np.mean(dz1, axis=1, keepdims=True)
    # 将梯度存入字典
    grads = {'dw1': dw1, 'db1': db1, 'dw2': dw2, 'db2': db2}
    return grads
 
 
# 使用Adam优化算法来更新神经网络的参数,参数包括权重和偏置项
def update_parameters_with_adam(parameters, grads, learning_rate=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8):
    """
    :param parameters:包含神经网络所有参数的字典,其中键是参数名,值是参数的numpy数组
    :param grads:包含神经网络所有参数梯度的字典,其中键是参数名,值是参数的梯度numpy数组
    :param learning_rate:学习率,控制参数更新的速度,默认值为0.01
    :param beta1:第一次指数加权平均值的权重,控制动量项的权重,默认值为0.9
    :param beta2:第二次指数加权平均值的权重,控制RMSProp项的权重,默认值为0.999
    :param epsilon:避免除零错误的小值,默认值为1e-8
    :return:parameters: 更新后的参数字典,其中键是参数名,值是参数的numpy数组
    """
    # 从参数字典中获取权重和偏置项
    w1, b1, w2, b2 = parameters.values()
    # 从梯度字典中获取梯度
    dw1, db1, dw2, db2 = grads.values()
    # 初始化动量向量和RMSProp指数加权平均值
    vdW1, vdW2 = np.zeros_like(w1), np.zeros_like(w2)
    sdW1, sdW2 = np.zeros_like(w1), np.zeros_like(w2)
    vdb1, vdb2 = np.zeros_like(b1), np.zeros_like(b2)
    sdb1, sdb2 = np.zeros_like(b1), np.zeros_like(b2)
    # 计算动量向量和RMSProp指数加权平均值
    vdW1 = beta1 * vdW1 + (1 - beta1) * dw1
    vdb1 = beta1 * vdb1 + (1 - beta1) * db1
    vdW2 = beta1 * vdW2 + (1 - beta1) * dw2
    vdb2 = beta1 * vdb2 + (1 - beta1) * db2
    sdW1 = beta2 * sdW1 + (1 - beta2) * np.square(dw1)
    sdb1 = beta2 * sdb1 + (1 - beta2) * np.square(db1)
    sdW2 = beta2 * sdW2 + (1 - beta2) * np.square(dw2)
    sdb2 = beta2 * sdb2 + (1 - beta2) * np.square(db2)
    # 根据动量向量和RMSProp指数加权平均值来更新权重和偏置项
    w1 -= (learning_rate * vdW1) / (np.sqrt(sdW1) + epsilon)
    b1 -= (learning_rate * vdb1) / (np.sqrt(sdb1) + epsilon)
    w2 -= (learning_rate * vdW2) / (np.sqrt(sdW2) + epsilon)
    b2 -= (learning_rate * vdb2) / (np.sqrt(sdb2) + epsilon)
    # 将更新后的参数存入字典中
    parameters = {'w1': w1, 'b1': b1, 'w2': w2, 'b2': b2}
    return parameters
 
 
# 模型评估
def predict(parameters, x_test, y_test):
    """
    :param parameters: 包含训练好的神经网络参数的字典
    :param x_test:测试集特征矩阵,大小为(n_x, m)
    :param y_test:测试集标签矩阵,大小为(n_y, m)
    :return:output为预测结果,大小为(1, m)
    """
    w1 = parameters['w1']  # 第一层神经网络的权重矩阵,大小为(n_h, n_x)
    b1 = parameters['b1']  # 第一层神经网络的偏置矩阵,大小为(n_h, 1)
    w2 = parameters['w2']  # 第二层神经网络的权重矩阵,大小为(n_y, n_h)
    b2 = parameters['b2']  # 第二层神经网络的偏置矩阵,大小为(n_y, 1)
    z1 = np.dot(w1, x_test) + b1  # 第一层神经网络的加权输入,大小为(n_h, m)
    a1 = np.tanh(z1)  # 第一层神经网络的输出,大小为(n_h, m)
    z2 = np.dot(w2, a1) + b2  # 第二层神经网络的加权输入,大小为(n_y, m)
    a2 = 1 / (1 + np.exp(-z2))  # 第二层神经网络的输出,大小为(n_y, m)
    # 预测结果,大小为(1, m)
    output = np.where(a2 > 0.5, 1, 0)  # 使用numpy中的where函数替代for循环
    print('预测结果:')
    print(output)
    print("\n")
    print('真实结果:')
    print(y_test)
    # 预测准确率,单位为百分比
    accuracy = np.mean(np.all(output == y_test, axis=0)) * 100  # 使用numpy中的mean和all函数计算准确率
    print('准确率:%.2f%%' % accuracy)
    return output
 
 
# 建立神经网络
def nn_model(X, Y, n_h, n_input, n_output, num_iterations=10000, print_cost=False):
    """
    :param X:接收输入数据
    :param Y:标签
    :param n_h:隐层节点数
    :param n_input:输入层节点数
    :param n_output:输出层节点数
    :param num_iterations:迭代次数
    :param print_cost:是否输出代价函数的标志
    :return:更新后的参数parameters、是否输出代价函数的标志print_cost和代价函数历史值cost_history
    """
    # 设置随机数种子为3,以保证可复现性
    np.random.seed(3)
    # 获取输入层、隐层和输出层节点数
    n_x = n_input  # 输入层节点数
    n_y = n_output  # 输出层节点数
    # 初始化神经网络的参数,包括权重和偏置,其中隐层节点数为n_h
    parameters = initialize_parameters(n_x, n_h, n_y)
    # 初始化代价函数历史值
    cost_history = []
    # 梯度下降循环迭代神经网络模型,共进行num_iterations次迭代
    for i in range(1, num_iterations + 1):
        # 进行前向传播,得到输出层的输出a2和存储中间结果的cache
        a2, cache = forward_propagation(X, parameters)
        # 计算代价函数
        cost = compute_cost(a2, Y, parameters)
        # 进行反向传播,得到梯度信息
        grads = backward_propagation(parameters, cache, X, Y)
        # 根据梯度信息更新参数
        parameters = update_parameters_with_adam(parameters, grads)
        # 保存代价函数历史值
        if i % 100 == 0:
            cost_history.append(cost)
        # 每1000次迭代,输出一次代价函数
        if print_cost and i % 1000 == 0:
            print('迭代第%i次     代价函数:%f' % (i, cost))
            print("-----------------------------------------------")
    return parameters, print_cost, cost_history
 
 
# 绘制神经网络模型的代价函数历史值随迭代次数变化的曲线图
def plot_cost_history(cost_history):
    """
    :param cost_history:包含每次迭代的代价函数历史值的列表
    :return:NULL
    """
    plt.figure('代价函数')
    plt.plot(cost_history)
    plt.title('Cost Function')
    plt.xlabel('Iterations (per 100)')
    plt.ylabel('Cost')
    plt.show()
 
 
# 结果可视化
# 特征有4个维度,类别有1个维度,一共5个维度,故采用了RadViz图
def result_visualization(x_test, y_test, result):
    """
    :param x_test:测试集特征矩阵,是一个numpy数组。
    :param y_test:测试集标签独热编码矩阵,是一个numpy数组。
    :param result:模型预测结果独热编码矩阵,是一个numpy数组。
    :return:
    """
    cols = y_test.shape[1]  # 获取测试集的列数,即样本数
    y = []  # 存储反转换后的真实分类
    pre = []  # 存储反转换后的预测分类
    # 反转换类别的独热编码
    # 定义标签的名称
    labels = ['setosa', 'versicolor', 'virginica']
    # 反转换测试集
    y = [labels[np.argmax(y_test[:, i])] for i in range(y_test.shape[1])]
    # 反转换预测结果
    pre = [labels[np.argmax(result[:, i])] if np.max(result[:, i]) > 0.5 else 'unknown' for i in range(result.shape[1])]
    # 将y和pre转换为pandas的Series对象
    y = pd.Series(y)
    pre = pd.Series(pre)
    # 特征矩阵拼接
    # 将特征和类别矩阵拼接起来
    real = np.concatenate((x_test.T, np.array(y).reshape(-1, 1)), axis=1)
    prediction = np.concatenate((x_test.T, np.array(pre).reshape(-1, 1)), axis=1)
    # 转换成DataFrame类型,并添加columns
    df_real = pd.DataFrame(real, columns=['Sepal Length', 'Sepal Width', 'Petal Length', 'Petal Width', 'Species'])
    df_prediction = pd.DataFrame(prediction,
                                 columns=['Sepal Length', 'Sepal Width', 'Petal Length', 'Petal Width', 'Species'])
    # 将特征列转换为float类型,否则radviz会报错
    df_real[['Sepal Length', 'Sepal Width', 'Petal Length', 'Petal Width']] = df_real[
        ['Sepal Length', 'Sepal Width', 'Petal Length', 'Petal Width']].astype(float)
    df_prediction[['Sepal Length', 'Sepal Width', 'Petal Length', 'Petal Width']] = df_prediction[
        ['Sepal Length', 'Sepal Width', 'Petal Length', 'Petal Width']].astype(float)
    # 绘图
    fig, axes = plt.subplots(1, 2, figsize=(10, 5))
    # 绘制真实分类
    radviz(df_real, 'Species', color=['blue', 'green', 'red', 'yellow'], ax=axes[0])
    axes[0].set_title('真实分类')
    # 绘制预测分类
    radviz(df_prediction, 'Species', color=['blue', 'green', 'red', 'yellow'], ax=axes[1])
    axes[1].set_title('预测分类')
    # 调整子图之间的间距和外边距
    plt.tight_layout()
    # 显示图像
    plt.show()
 
 
if __name__ == "__main__":
    # 读取训练数据
    training_data = pd.read_csv('iris_training.csv', header=None)
    # 获取特征和标签
    X_train = training_data.iloc[:, :4].values.T
    Y_train = training_data.iloc[:, 4:].values.T
    Y_train = Y_train.astype('uint8')
    # 训练模型
    start_time = datetime.datetime.now()
    parameters, print_cost, cost_history = nn_model(X_train, Y_train, n_h=10, n_input=4, n_output=3,
                                                    num_iterations=10000,
                                                    print_cost=True)
    end_time = datetime.datetime.now()
    # 输出训练用时
    print("训练用时:" + str((end_time - start_time).seconds) + 's' + str(
        round((end_time - start_time).microseconds / 1000)) + 'ms')
    # 绘制代价函数曲线
    if print_cost:
        plot_cost_history(cost_history)
    # 对模型进行测试
    test_data = pd.read_csv('iris_test.csv', header=None)
    X_test = test_data.iloc[:, :4].values.T
    Y_test = test_data.iloc[:, 4:].values.T
    Y_test = Y_test.astype('uint8')
    # 预测结果
    result = predict(parameters, X_test, Y_test)
    # 分类结果可视化
    result_visualization(X_test, Y_test, result)

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值