这是我模式识别的小作业,要求是不利用深度学习框架(tensorflow,pytorch,keras,caffe什么的都不行)实现BP神经网络,还必须每行都有注解,实现过程很难受。PS:转载请注明本文链接
1.数据集下载地址
2.相关资源
我的另一篇博文是利用朴素贝叶斯实现相同任务,如下:
注解超详细的利用朴素贝叶斯实现森林分类python代码
3.代码运行结果
先放一个代码运行完的曲线图,证明这个代码是可以运行的
4.实现代码
# 导入程序依赖包
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import time
from sklearn.decomposition import PCA
from mpl_toolkits.mplot3d import Axes3D
def relu(Z):
"""
功能:
relu激活函数,网络前向传播的非线性计算部分,在网络中的前L-1层使用
输入参数:
Z - 线性运算后的值
返回值:
A - 神经元激活值
cache - 激活函数数据缓存区,为反向传播提供数据
"""
A = np.maximum(0, Z) # 将Z中所有负数置0
cache = Z # 将Z存入缓存区
return A, cache
def relu_backward(dA, cache):
"""
功能:
relu激活函数值的反向传播
输入参数:
dA - 神经元激活值的导数
cache - 数据缓存区,为反向传播提供数据
返回值:
dZ - 线性运算部分的导数,用来更新神经元权重和偏置
"""
Z = cache # 从缓存区中取出Z
dZ = np.array(dA, copy=True) # 将dA进行复制,并转换为array赋值给dZ
dZ[Z <= 0] = 0 # 将dZ中负数部分置0
return dZ
def softmax(Z):
"""
功能:
softmax激活函数,网络前向传播的非线性计算部分,在网络中的第L层,也就是最后一层使用,实现多分类任务
输入参数:
Z - 线性运算后的值
返回值:
A - 神经元激活值
cache - 激活函数数据缓存区,为反向传播提供数据
"""
A = np.exp(Z) / (np.sum(np.exp(Z), axis=0)) # 计算激活值
cache = Z # 将Z存入缓存区
return A, cache
def load_datasets(filepath):
"""
功能:
导入数据,并将标签集进行one-hot编码
输入参数:
filepath - 数据文件路径
返回值:
feature - 特征集,一个n*m矩阵,n为特征维度,m为样本数
label - 标签集,一个4*m的one-hot矩阵,4为标签维度,m为样本数
"""
dataset = pd.read_csv(filepath) # 读取csv文件,数据类型为DataFrame
dataset['class'] = dataset['class'].map({
"s ": 0, "h ": 1, "d ": 2, "o ": 3}) # 将字符转换为数值,注意字符后面的空格不能去掉,这是一个坑
label = dataset.loc[:, ['class']] # 读取标签,数据类型为DataFrame
label = np.array(label) # 转换为array
label = np.eye(4)[label].reshape(dataset.shape[0], 4).T # 将array转换为one-hot
feature = dataset.iloc[:, 1:dataset.shape[1]] # 将特征数据分割出来
feature = np.array(feature).T # 转换为array
return feature, label
def decoding(labels_one_hot):
"""
功能:
进行one_hot解码,将one_hot矩阵还原为原始标签
输入参数:
labels_one_hot - 标签集,一个m*4的one-hot矩阵,m为样本数,4为标签维度
返回值:
label - 标签集,一个m*1的数据帧,m为样本数,元素为字符类别
"""
labels = labels_one_hot.argmax(axis=1).reshape(labels_one_hot.shape[0], -1) # 按行提取矩阵中最大的元素的索引
labels = pd.DataFrame(labels) # 将array转换为dataframe
labels.columns = ['class'] # 设置帧头
labels['class'] = labels['class'].map({
0: "s ", 1: "h ", 2: "d ", 3: "o "}) # 将数字类别还原为原始的字符类别
return labels
def random_dataset(train_x, train_y, test_x, test_y, seed):
"""
功能:
打乱训练集和测试集数据,使数据同分布
输入参数:
train_x - 训练特征集,一个n*m1矩阵,n为特征维度,m1为训练样本数
train_y - 训练标签集,一个4*m1的one-hot矩阵,4为标签维度,m1为训练样本数
test_x - 测试特征集,一个n*m2矩阵,n为特征维度,m2为测试样本数
test_y - 测试标签集,一个4*m2的one-hot矩阵,4为标签维度,m2为测试样本数
seed - 程序随机种子
返回值:
new_train_x.T - 打乱后的训练特征集,一个n*m1矩阵,n为特征维度,m1为训练样本数
new_train_y.T - 打乱后的训练标签集,一个4*m1的one-hot矩阵,4为标签维度,m1为训练样本数
new_test_x.T - 打乱后的测试特征集,一个n*m2矩阵,n为特征维度,m2为测试样本数
new_test_y.T - 打乱后的测试标签集,一个4*m2的one-hot矩阵,4为标签维度,m2为测试样本数
"""
if seed > 0:
np.random.seed(seed) # 随机种子大于0,就打乱数据集
m = train_x.shape[1] # 训练样本数
new_x = np.concatenate((train_x.T, test_x.T), axis=0) # 将特征集进行拼接
new_y = np.concatenate((train_y.T, test_y.T), axis=0) # 将标签集进行拼接
per = np.random.permutation(new_x.shape[0]) # 打乱特征集行号
new_x = new_x[per, :] # 获取打乱后的特征数据
new_y = new_y[per, :] # 获取打乱后的标签数据
new_train_x, new_test_x = np.vsplit(new_x, [m]) # 按原训练样本数进行分割
new_train_y, new_test_y = np.vsplit(new_y, [m]) # 按原训练样本数进行分割
else:
new_train_x, new_train_y, new_test_x, new_test_y = train_x, train_y, test_x, test_y # 随机种子小于零就不打乱数据集
return new_train_x.T, new_train_y.T, new_test_x.T, new_test_y.T
def data_preprocessing(features):
"""
功能:
进行特征集预处理,对特征集进行归一化,返回均值和方差
输入参数:
features - 特征集,一个n*m的矩阵,n为特征维度,m为样本数
返回值:
features - 经过归一化的特征集,一个n*m的矩阵,n为特征维度,m为样本数
mu - 特征集均值
sigma2 - 特征集方差
"""
mu = np