使用Numpy实现简单二层神经网络

前言

本文基础

用numpy构建多种损失函数
结合numpy及mnist库的简单神经网络演练

友情链接

python实现梯度下降优化算法
实现二层神经网络反向传播

正题

本文通过对前向神经网络的基本学习,来完成一个简单的、效率并不高的mnist库的二层神经网络训练模型,方便记录神经网络的基本学习。

基本思路

  1. 初始化二层神经网络权重
  2. 通过predict()函数完成神经网络前向预测功能(包括sigmoid函数以及softmax函数编写)
  3. 编写loss()函数来计算一次训练的损失(包括cross_entropy_error函数的编写)
  4. 编写numerical_gradient()函数计算本次训练各层参数的更新梯度
  5. 编写gradient_decent()更新参数权重
  6. 编写get_accuracy()函数来计算预测正确率
  7. 通过mini_batch方式进行训练

下面由此循序渐进展开

一.初始化二层神经网络权重

首先二层神经网络指的是由一层输入层、一层隐藏层以及一层输出层构成的简单神经网络,我们依旧使用mnist库,则输入层大小为784,定义隐藏层大小为100,输出层大小为10(有10种状态),那么权重W1输入层以及隐藏层之间权重矩阵,大小为784*100W2则为隐藏层输出层之间权重矩阵,大小为100*10,偏置b1大小为100*1,偏置b2大小为10*1

    def __init__(self, input_layer_size, hidden_layer_size, output_layer_size, weight_std=0.01):
        self.params = {"W1": weight_std * np.random.randn(input_layer_size, hidden_layer_size),
                       "W2": weight_std * np.random.randn(hidden_layer_size, output_layer_size),
                       "b1": np.zeros(hidden_layer_size),
                       "b2": np.zeros(output_layer_size)}

weight_std表征初始化权重数量级,可以不添加。

二.前向预测函数predict

在编写predict函数之前我们需要两个函数:sigmoid()softmax,具体编写理解在前面博客中有,这里只是贴一下:

@classmethod
def sigmoid(cls,X):
	return 1/(1+np.exp(-X))
	
@classmethod
def softmax(cls, X):
	if X.ndim == 1:
		X = X.reshape(1, X.shape[0])
	c = np.max(X, axis=1)
	exp_a = np.exp(X - np.tile(c, (X.shape[1],1)).T)
	exp_sum = np.sum(exp_a, axis=1)
	return exp_a/np.tile(exp_sum, (exp_a.shape[1],1)).T

那么就可以实现predict()函数,基本思路即通过sigmoid层间神经元迭代,最终经过softmax计算结果输出。

def predict(self, X):
	y = X
	for W, b in zip(["W1","W2"], ["b1","b2"]):
		y = self.sigmoid(np.dot(X, self.params[W])+ np.tile(self.params[b], (X.shape[0], 1)))
		return self.softmax(y)

这样就可以完成神经网络最基本的功能:预测功能,当然这种预测是不会更新权重的。

三.损失函数构造

这一步主要是构造一个损失函数loss,用来计算最终预测结果的损失是多少。
首先损失函数选择的是交叉熵损失函数,构造如下(如果想更深入了解请看上篇博文):

@classmethod
def cross_entropy_error(cls, Y, labels):
	if Y.ndim == 1:
		Y = Y.reshape(1, Y.shape[0])
	delta = 1e-7
	batch_size = Y.shape[0]
	# 如果两个数组维数一致,则说明是one-hot编码,需要转换为label形式
	if Y.ndim == labels.ndim:
		labels = np.argmax(labels, axis=1)
	return -np.sum(np.log(Y[np.arange(batch_size), labels]+delta))/batch_size
		
		

从而完成loss函数定义:

def loss(self, X, labels):
	# 首先对X进行预测
	Y = self.predict(X)
	# 而后返回交叉熵损失函数计算后的结果
	return self.cross_entropy_error(X, labels)
四. 实现梯度计算

这一步主要是为了训练神经网络而做的反馈,通过梯度更新的方式来使得神经网络真正“活”起来。
简单原理即通过对权重或偏重的每一个参数求导数,得到这个参数的梯度更新矩阵。
当然本神经网络中最慢、最值得优化的部分就是这个部分,因为这样更新梯度会非常冗余。
因为每一次求导的过程就伴随着调用loss函数一次,意味着要重新将输入数据重新预测一遍,从而得到这个参数对整个神经网络的影响,我的下篇博客大概会从这个地方说起。
当然这种方式是神经网络最初的构型,也应该去实现一下:

def numerical_gradient(self, X, labels):
	loss_w = lambda w:self.loss(X, labels)
	gradient = {}
	for key in ["W1", "W2", "b1", "b2"]:
		gradient[key] = self.numerical_gradient_for_param(loss_w, self.params[key])
	return gradient

# 分别计算参数的梯度情况
def numerical_gradient_for_param(self, f, param):
	h = 1e-4
	temp_grad = np.zero_like(param)
	it = np.nditer(param, flags=['multi-index'], op_flags=['readwrite'])
	while not it.finished:
		idx = it.multi-index
		temp = param[idx]
		param[idx]+=h
		f1 = f(param)
		param[idx]-=2*h
		f2 = f(param)
		param[idx] = temp
		temp_grad[idx] = (f1 -f2)/2*h
		it.iternext()
	return temp_grad
		
		
五.更新梯度,实现神经网络训练功能

这一步是神经网络的灵魂,通过完成梯度更新,神经网络才能不断的迭代训练,最终接近模型的局部最优解。
更新梯度的操作也十分的简单,即对每个参数进行:
x 1 = x 1 − η α f α x 1 x_1 = x_1 - η\frac{αf}{αx_1} x1=x1ηαx1αf
η为学习率,大小决定着最终最优解能否最接近最优解,决定着最终的准确度

def gradient_descent(self, X, labels, lr=0.01):
	grad = self.numerical_gradient(X, labels)
	for key in ["W1", "W2", "b1", "b2"]:
		self.params[key] -=lr*grad[key]
	return self.params
六.准确率预测函数实现

神经网络最终训练结果当然需要和labels进行比对,从而测定当前神经网络的训练情况,方法也非常的简单:

def get_accuracy(self, X, labels):
	y = np.argmax(self.predict(X),axis=1)
	return np.sum(y==labels)/labels.shape[0]
七.使用mini_batch方式进行训练以及预测
(x_train, y_train), (x_test, y_test) = load_mnist()
    train_acc_list = []
    test_acc_list = []
    net = TwoLayerNetwork(x_train.shape[1], 100, 10)
    batch_size = 100
    learning_rate = 0.01
    iter_times = 10000
    epoch = max(x_train.shape[0]/batch_size,1)

    for i in range(iter_times):
        x_batch_num = np.random.choice(x_train.shape[0], batch_size)
        x_batch = x_train[x_batch_num]
        y_batch = y_train[x_batch_num]

        net.gradient_descent(x_batch, y_batch)

        if i % epoch == 0:
            train_acc = net.get_accuracy(x_train, y_train)
            test_acc = net.get_accuracy(x_test, y_test)
            train_acc_list.append(train_acc)
            test_acc_list.append(test_acc)
            print("训练集预测准确度:"+str(train_acc)+" 测试集预测准确度:"+str(test_acc))

这一部分中的batch_size是从训练集中一次迭代抽取的数量,允许重复,增加随机性。
epoch参数是为了计算一轮训练进行的循环次数,在这里面即60000/100=600次,每600次迭代相当于训练集所有数据都训练了一遍,然后输出训练结果。

经过这样一轮流程,一个简单的二层神经网络就完成了,下一篇会分享反向传播的优化,通过对神经网络的梯度计算实现优化,可以非常明显的提高神经网络训练速度。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
以下是使用numpy实现简单二层神经网络的示例代码: ```python import numpy as np class NeuralNetwork: def __init__(self, input_size, hidden_size, output_size): self.W1 = np.random.randn(input_size, hidden_size) # 输入层到隐藏层的权重矩阵 self.b1 = np.zeros((1, hidden_size)) # 隐藏层的偏置向量 self.W2 = np.random.randn(hidden_size, output_size) # 隐藏层到输出层的权重矩阵 self.b2 = np.zeros((1, output_size)) # 输出层的偏置向量 def forward(self, X): # 前向传播计算输出 self.z1 = np.dot(X, self.W1) + self.b1 self.a1 = np.tanh(self.z1) self.z2 = np.dot(self.a1, self.W2) + self.b2 self.a2 = self.sigmoid(self.z2) return self.a2 def backward(self, X, y, learning_rate): m = X.shape[0] # 样本数量 # 反向传播更新权重和偏置 delta2 = self.a2 - y dW2 = (1 / m) * np.dot(self.a1.T, delta2) db2 = (1 / m) * np.sum(delta2, axis=0, keepdims=True) delta1 = np.dot(delta2, self.W2.T) * (1 - np.power(self.a1, 2)) dW1 = (1 / m) * np.dot(X.T, delta1) db1 = (1 / m) * np.sum(delta1, axis=0, keepdims=True) self.W2 -= learning_rate * dW2 self.b2 -= learning_rate * db2 self.W1 -= learning_rate * dW1 self.b1 -= learning_rate * db1 def train(self, X, y, epochs, learning_rate): for epoch in range(epochs): output = self.forward(X) self.backward(X, y, learning_rate) if epoch % 100 == 0: loss = self.calculate_loss(y, output) print(f"Epoch {epoch}, loss: {loss:.4f}") def predict(self, X): return self.forward(X) def calculate_loss(self, y_true, y_pred): return -np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred)) def sigmoid(self, x): return 1 / (1 + np.exp(-x)) ``` 上述代码定义了一个名为NeuralNetwork的类,它具有输入层、隐藏层和输出层。在初始化函数中,随机初始化了权重矩阵和偏置向量。forward函数实现了前向传播,backward函数实现了反向传播和更新权重和偏置。train函数用于训练神经网络,predict函数用于进行预测。calculate_loss函数计算损失函数的值,sigmoid函数实现了激活函数sigmoid。 你可以根据自己的需求修改代码中的参数和结构,比如调整隐藏层的大小、修改激活函数等。然后使用训练集进行训练,最后使用测试集进行预测。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值