本文将用Numpy实现简单BP神经网络完成对手写数字图片的识别,数据集为42000张带标签的28x28像素手写数字图像。在计算机完成对手写数字图片的识别过程中,代表图片的28x28=764个像素的特征数据值将会被作为神经网络的输入,经过网络的正向传播,得到可以粗略作为0~9每个数字的概率的输出(输出层第一个神经元节点的输出看成是图片数字是0的概率,其余9个神经元节点以此类推),取概率最大的数字即为识别结果。神经网络的输出神经元节点有10个,假设待识别数字为1,就可以定义label为[0,1,0,0,0,0,0,0,0,0],将网络的10个节点的输出组成的向量(预测值)与label(真实值)进行比较,定义均方误差损失E衡量网络预测值和真实值的差距程度,根据梯度下降法完成对网络权值和阈值的参数更新,即对网络的训练。在对模型的评估中,简单地使用测试样本中预测正确的样本数占全部测试样本总数的比例作为模型的识别正确率。
网络隐层的层数以及神经元的个数是可以人为*调整的,但需注意合理设置。文中设置的隐层有两层,每层神经元的节点数都设置为50,在对数据集进行训练集、测试集约为1:1划分,训练轮数为5轮的情况下,达到了92%以上不错的正确预测率。需要提醒的是,训练轮数过大以及隐层节点数设置过大,极有可能发生过拟合的情况,导致正确率不如人意。
数据集地址:
链接:https://pan.baidu.com/s/1XzexzXGYhUdFpM4UaIb3yA
提取码:9wjm
实现代码如下:
import numpy as np
import csv
from tqdm import trange
def sigmoid(x):
return 1.0 / (1.0 + np.exp(-x))
def sigmoid_derivative(x):
return sigmoid(x) * (1 - sigmoid(x))
class Net:
def __init__(self, layers):
self.active = sigmoid # 激活函数
self.active_d = sigmoid_derivative # 激活函数的求导
self.weights = [] # 权值参数
self.bias = [] # 阈值参数
for i in range(1, len(layers)): # 参数初始化
self.weights.append(np.random.randn(layers[i - 1], layers[i]))
self.bias.append(np.random.randn(layers[i]))
def core(self, x, label, learning_rate):
y = [x] # 保存每层激活后的输出值
# 正向传播
for i in range(len(self.weights)):
y.append(self.active(np.dot(y[-1], self.weights[i]) + self.bias[i]))
# 反向传播
e = y[-1] - label
deltas = [e * y[-1] * (1 - y[-1])] # 输出层Delta值
# 各隐藏层Delta值
for i in range(1, len(self.weights)):
deltas.append(np.dot(deltas[-1], self.weights[-i].T) * y[-i - 1] * (1 - y[-i - 1]))
# 误差项倒置
deltas.reverse()
# 更新参数w和b
for i in range(len(self.weights)):
y_2d = np.atleast_2d(y[i])
deltas_2d = np.atleast_2d(deltas[i])
self.weights[i] -= learning_rate * np.dot(y_2d.T, deltas_2d)
self.bias[i] -= learning_rate * deltas[i]
def predict(self, x):
# 正向传播预测输出值
y = x
for i in range(len(self.weights)):
y = self.active(np.dot(y, self.weights[i]) + self.bias[i])
_ = [str(round((i / y.sum()) * 100, 2)) + "%" for i in y] # 将输出结果以概率的形式展现
out = list(y)
result = out.index(max(out))
return result, _
if __name__ == '__main__':
Net = Net([784, 50, 50, 10])
# 读取数据
file_name = "data/train.csv" # 数据集为42000张带标签的28x28手写数字图像
x_train = [] # 训练集样本数据特征
y_train = [] # 训练集样本标签
'''
测试集用来测试模型质量必不可少。
验证集用来调节超参数,如神经网络层数,各层神经节点数、学习率等人为设置而不是模型通过学习得到的参数,可省略,仅用训练集即可
'''
x_test = [] # 测试集样本数据特征
y_test = [] # 测试集样本数据标签
with open(file_name, 'r') as f:
reader = csv.reader(f)
header_row = next(reader)
for row in reader:
data = np.array(row[1:], dtype=np.float_)
if np.random.random() < 0.5: # 划分数据集 训练集:测试集 ≈ 1:1
x_train.append(data / ((data ** 2).sum() ** 0.5)) # 每个样本对应的784个特征数值归一化
y_train.append(int(row[0]))
else:
x_test.append(data / ((data ** 2).sum() ** 0.5))
y_test.append(int(row[0]))
print("训练样本数:{},测试样本数:{}".format(len(y_train), len(y_test)))
# 训练
learning_rate = 0.5 # 初始学习率
epochs = 5 # 训练轮数
for i in trange(len(y_train) * epochs):
if (i + 1) % len(y_test) == 0:
learning_rate *= 0.5 # 每训练一轮 学习率减半
label = np.zeros(10)
label[y_train[i % len(y_train)]] = 1 # 设置label
Net.core(x_train[i % len(y_train)], label, learning_rate) # 更新网络参数
# 预测
count = 0
for i in trange(len(y_test)):
print("-------第{}个测试样本-----------".format(i + 1))
predict, _ = Net.predict(x_test[i])
print("预测值:{},真实值:{}".format(predict, y_test[i]))
if predict == y_test[i]:
count += 1
print("正确率:{}".format(count / len(y_test)))
运行结果: