当我打算直接使用numpy设计一个bp神经网络时,发现不仅运行时间长,而且准确率无法达到百分之八十以上(无论怎么去调整参数),我的代价函数使用的是交叉熵函数,隐藏层激活函数为sigmoid,输出层激活函数为softmax,训练集为MNIST,总共有一个隐藏层,而且为了简化问题我将MNIST数据集的文件转化为了只包含0和1的csv文件
代码有点杂乱,实在是无力再去整理:
"""main.py"""
import numpy as np
from neural_network import ANN
if __name__ == "__main__":
# 实例化
n1 = 784
n2 = 8
n3 = 10
my_ann = ANN(n1, n2, n3)
# 载入学习数据
my_ann.load_train_file("数据集/train.csv")
# eta_func 表示学习率的调整函数,这里略去
# 训练
eta = 0.001 #学习率
max_n = 100000 # 学习轮数
appro = 0.00001 # 达到设定的梯度精度时退出
batch_size = 20 # 分批处理
circle, time = my_ann.train(max_n, appro, eta, batch_size, eta_func=None, SGD=False, initial=False)
# 测试网络
rate = my_ann.test("数据集/test.csv")
rate_train = my_ann.test("数据集/train.csv")
print(f"测试集通过率为: {rate}\t训练集通过率为{rate_train}")
"""代价函数使用交叉熵,激活函数使用softmax"""
import numpy as np
import matplotlib.pyplot as plt
import time
import random
class ANN:
def __init__(self, n1, n2, n3):
# 神经网络每层的神经元数量
self.n1 = n1
self.n2 = n2
self.n3 = n3
# 权重
self.w21 = None
self.w32 = None
# 输入
self.input = []
# 每层输出
self.output2 = None
self.output3 = None
# 偏置
self.b21 = None
self.b32 = None
# 加权输入
self.z2 = None
self.z3 = None
# 神经单元偏差
self.delta2 = None
self.delta_l = None
# 计算中间变量
# cost_function
self.cost = None
self.cost_functions = []
# 训练数据的大小
self.data_num = None
def load_train_file(self, train_filename):
"""
预处理训练文件
:param train_filename: 训练文件名称
:return:
"""
i = 0
with open(train_filename) as f:
for line in f:
line = line.split(",")
self.input.append(np.array(line))
i += 1
print("数据处理完成")
self.data_num = i
return self.input
def train(self, max_n, approximate, eta=0.01, batch_size=None, eta_func=None, SGD=False, initial=True):
"""
训练模型
使用softmax激活函数搭配交叉熵cross_entropy代价函数
:param initial: 是否进行参数初始化,False可以导入数据后接着训练
:param SGD: 是否使用SGD算法(是否随机取样)
:param eta_func: 学习率调整策略
:param batch_size: 分批次
:param max_n: 最大循环次数
:param approximate: 精度
:param eta: 学习率
:return: 返回学习迭代次数,时间
"""
# 分批次训练
batch_index = 0
# SGD算法
start = time.perf_counter()
batch_num = self.data_num // batch_size
# 初始化参数值
if initial:
self.w21 = np.random.randn(self.n2, self.n1)*np.sqrt(2./self.n1)
self.w32 = np.random.randn(self.n3, self.n2)*np.sqrt(2./self.n2)
self.b21 = np.zeros(self.n2)
self.b32 = np.zeros(self.n3)
# 开始训练
N = 1
I = np.identity(10)
nabala_max = np.inf
while N <= max_n and nabala_max > approximate:
# 预处理初始化梯度
nabala_w21 = np.zeros((self.n2, self.n1))
nabala_w32 = np.zeros((self.n3, self.n2))
nabala_b21 = np.zeros(self.n2)
nabala_b32 = np.zeros(self.n3)
# SGD
if SGD:
batch = [random.choice(self.input) for _ in range(batch_size)]
elif batch_size:
batch = self.input[batch_index:(batch_index+1)*batch_size]
else:
batch = self.input
# 正向顺序计算
temple = np.array([])
for x in batch:
# 预处理
real_datas = int(x[0])
x = x.astype("int")[1:]
# 计算每层输入输出
self.z2 = self.w21 @ x + self.b21
self.output2 =1. / (1. - np.exp(self.z2))
self.z3 = self.w32 @ self.output2 + self.b32
max_z3 = np.max(self.z3)
self.temple2 = np.exp(self.z3 - max_z3)
temple2_sum = np.sum(self.temple2)
self.output3 = self.temple2 / temple2_sum
# 计算代价函数
# I[:, real_datas]表示数据真实标签
self.cost = -np.sum(I[:, real_datas] * np.log(self.output3), dtype="float64") # 注意numpy数组中的特殊索引方法
# 计算delta
# 此计算公式参见https://www.jianshu.com/p/c02a1fbffad6多分类时计算稍稍复杂因为值和同层的其他单元也有关
self.delta_l = self.output3 - I[:, real_datas]
# 误差反向传播
self.delta2 = self.delta_l @ self.w32 * self.output2*(1-self.output2)
# 计算梯度
nabala_w21 += np.outer(self.delta2, x) # 使用外积来得到矩阵
nabala_w32 += np.outer(self.delta_l, self.output2)
nabala_b21 += self.delta2
nabala_b32 += self.delta_l
# 存储每幅图像的计算结果
temple = np.append(temple, self.cost)
# 将nabala合起来
Nabala = np.concatenate([nabala_w21.flatten(), nabala_w32.flatten(), nabala_b32, nabala_b21]) / batch_size
# 计算梯度误差
nabala_norm = np.linalg.norm(Nabala)
# 最大绝对梯度
nabala_max = np.max(np.abs(Nabala))
# 更新参数,梯度下降
# 可以做单位化处理一下
self.w32 += -eta * nabala_w32 / batch_size
self.w21 += -eta * nabala_w21 / batch_size
self.b21 += -eta * nabala_b21 / batch_size
self.b32 += -eta * nabala_b32 / batch_size
# 计算和
# 代价函数
cost_function = np.array(temple).mean
self.cost_functions.append(cost_function)
print(f"第{N}次迭代完成\t代价函数:{cost_function}\t梯度误差:{nabala_norm}\t最大梯度{nabala_max}\t当前学习率:{eta}")
# 更新学习率
# 这里的η是局部变量(形参中定义)
if eta_func:
eta = eta_func(N)
N += 1
# 普通批次更新
batch_index += 1
# 保存计算出来的数据
np.savez('data', self.w21, self.w32, self.b21, self.b32)
end = time.perf_counter()
print(f"训练完成! 耗时: {end - start}\t迭代总次数为: {N-1}\n代价函数收敛于: {self.cost_functions[-1]}")
return N, end - start
def judge(self, input_data):
"""
判断图片是什么数字
:param input_data: ndarray: 输入图片数据
:return: 数字, 相似度
"""
# 计算每层输入输出
# 这里就是train部分的向前传播代码,其实可以写个函数同构一下,这里省略
max_probability = np.max(self.output3)
r = np.where(self.output3 == max_probability)
return int(r[0]), max_probability
def test(self, test_filename):
"""
测试神经网络
:param test_filename: 测试数据文件名(csv)
:return: 通过率
"""
i = 1
rate = 0
with open(test_filename) as f:
for line in f:
flag = False
r = int(line[0])
line = line.split(",")
x = line[1:]
x = np.array(x).astype("int32")
number, p = self.judge(x)
if number == r:
rate += 1
flag = True
print(f"测试第{i}条数据: 预测值:{number}\t真实值:{r}\t{flag}")
i += 1
return rate / (i - 1)
运行起来非常慢,而且准确率不高
贴上来的代码的算法细节已经经历过多次修改,使用tf不需要使用更先进的算法就能轻松达到90%正确率,可这个我无论如何去改变(包括将激活函数全部使用sigmoid,loss使用均方和计算)都对结果影响不大
计算效率提高,最重要的就是提高准确率,至少要到百分之九十。
所以试问一下代码部分是哪里出了问题
你好,我是有问必答小助手,非常抱歉,本次您提出的有问必答问题,技术专家团超时未为您做出解答
本次提问扣除的有问必答次数,将会以问答VIP体验卡(1次有问必答机会、商城购买实体图书享受95折优惠)的形式为您补发到账户。
因为有问必答VIP体验卡有效期仅有1天,您在需要使用的时候【私信】联系我,我会为您补发。
你好,我是有问必答小助手,非常抱歉,本次您提出的有问必答问题,技术专家团超时未为您做出解答
本次提问扣除的有问必答次数,将会以问答VIP体验卡(1次有问必答机会、商城购买实体图书享受95折优惠)的形式为您补发到账户。
因为有问必答VIP体验卡有效期仅有1天,您在需要使用的时候【私信】联系我,我会为您补发。