动手实现深度神经网络3 增加误差反向传播计算梯度&完成MNIST数据集手写数字识别

动手实现深度神经网络3 增加误差反向传播计算梯度

在这一部分中我们利用误差反向传播来计算梯度,误差反向传播计算梯度的速度大大超过了之前采用的数值微分发法。经过这次改进,我们的神经网络就能以很快的速度和较高的准确率完成MNIST数据集手写数字识别啦!

1.理解误差反向传播

关于误差反向传播的理论和使用计算图推导理解的过程,我之前的文章:Python深度学习入门笔记 2

理解误差反向传播&用python实现自动微分中已经介绍地很详细了啦,如果你对误差反向传播还不了解,就先去看看我这两篇文章吧。

2.代码实现

在我们的两层神将网络类中添加一个方法,如下。

def gradient_BP(self,x,t):
    w1, w2 = self.params['w1'], self.params['w2']
    b1, b2 = self.params['b1'], self.params['b2']
    grads = {}

    batch_num = x.shape[0]

    # 正向传播 forward
    a1=np.dot(x,w1)+b1
    z1=sigmoid(a1)
    a2=np.dot(z1,w2)+b2
    y=softmax(a2)


    # 反向传播 backward
    #一
    dy=(y-t)/batch_num
    #二
    grads['b2']=np.sum(dy,axis=0)
    #三
    grads['w2']=np.dot(z1.T,dy)
    #四
    da1=np.dot(dy,w2.T)
    #五
    dz1=sigmoid_grad(a1) * da1
    #六
    grads['b1'] = np.sum(dz1, axis=0)
    #七
    grads['w1'] = np.dot(x.T,dz1)

    return grads

代码中反向传播的每一步都可以在下面的图中找到对应。

QQ图片20220301165307

需要说明的有两点:

  1. dy=(y-t)/batch_num 在我之前的文章神经网络中的激活函数与损失函数&深入理解推导softmax交叉熵有详细的解释和推导过程,这里只说一下结论:如果神经网络输出层使用softmax激活函数,并且使用交叉熵误差,那么反向传播时,经过交叉熵和softmax后流出的值是(y-t)如下图

之后除以batch_num是得到平均值。


  1. sigmoid_grad

    sigmoid函数长这样:

    使用计算图推导它的导函数:

    image-20220102174403600-1645754919514

    然后,对它的导函数进行化简:

    image-20220301154645092

下面是实现的代码:

def sigmoid_grad(x):
    return (1.0 - sigmoid(x)) * sigmoid(x)

3.使用神经网络训练MNIST数据集手写数字识别

使用这个神经网络的代码和上篇文章的几乎一样,只有计算梯度的那一行做了替换。

(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)

network=Myself_Two_Layer_Net(input_size=784, hidden_size=100, output_size=10,weight_init_std=0.01)

train_size = x_train.shape[0]
test_size = x_test.shape[0]
learning_rate = 0.1  # 学习率


iters_num = 10000  # 这次终于可以火力全开了
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1

train_loss_list = []
train_acc_list = []
test_acc_list = []

iter_per_epoch = max(train_size / batch_size, 1)


for i in range(iters_num):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]

    # 计算梯度 这里使用了误差反向传播的高效方法!!!!!!!!!!!!!!!!!
    # grad = network.gradient_numerical(x_batch, t_batch)
    grad= network.gradient_BP(x_batch,t_batch)

    # 更新参数
    for key in ('w1', 'b1', 'w2', 'b2'):
        key
        network.params[key] -= learning_rate * grad[key]

    if i % iter_per_epoch == 0:
        train_acc = network.accuracy(x_train, t_train)
        test_acc = network.accuracy(x_test, t_test)
        train_acc_list.append(train_acc)
        test_acc_list.append(test_acc)
        print("train acc, test acc | " + str(train_acc) + ", " + str(test_acc))


# 绘制图形
markers = {'train': 'o', 'test': 's'}
x = np.arange(len(train_acc_list))
plt.plot(x, train_acc_list, label='train acc')
plt.plot(x, test_acc_list, label='test acc', linestyle='--')
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()

同时,我们现在可以把循环次数设置为10000次,然后来训练MNIST数据集手写数字识别

执行代码:

image-20220301163102889

Figure_1

在我的电脑上,只用了不到一分钟就训练完成了(在没有实现误差反向传播的代码,即使把循环次数设置成5,1分钟也跑不完)而且可以看到准确率在94%以上!

46124862724)]

[外链图片转存中…(img-WVB5y6gq-1646124862725)]

在我的电脑上,只用了不到一分钟就训练完成了(在没有实现误差反向传播的代码,即使把循环次数设置成5,1分钟也跑不完)而且可以看到准确率在94%以上!

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
非常感谢您的提问。以下是实现您所描述的任务的代码: ```python import struct import numpy as np # 读取MNIST数据集 def load_mnist_data(path): with open(path, 'rb') as file: # 读取文件头部信息,魔数和数据集/标签数目 magic, num = struct.unpack(">II", file.read(8)) # 读取每个数据集/标签的行列数 rows, cols = struct.unpack(">II", file.read(8)) # 读取所有数据集/标签 data = np.fromfile(file, dtype=np.dtype('uint8').newbyteorder('>')) data = np.reshape(data, (num, rows * cols)) # 归一化数据集 data = data / 255 return data # 设计神经网络参数 input_size = 784 hidden_size_1 = 256 hidden_size_2 = 128 hidden_size_3 = 64 output_size = 10 # 初始化权重矩阵和偏置向量 W1 = np.random.randn(input_size, hidden_size_1) * 0.01 b1 = np.zeros((1, hidden_size_1)) W2 = np.random.randn(hidden_size_1, hidden_size_2) * 0.01 b2 = np.zeros((1, hidden_size_2)) W3 = np.random.randn(hidden_size_2, hidden_size_3) * 0.01 b3 = np.zeros((1, hidden_size_3)) W4 = np.random.randn(hidden_size_3, output_size) * 0.01 b4 = np.zeros((1, output_size)) # 定义激活函数(这里使用ReLU) def relu(Z): return np.maximum(0, Z) # 定义softmax函数 def softmax(Z): exp_Z = np.exp(Z) return exp_Z / np.sum(exp_Z, axis=1, keepdims=True) # 前向传播算法 def forward(X): Z1 = np.dot(X, W1) + b1 A1 = relu(Z1) Z2 = np.dot(A1, W2) + b2 A2 = relu(Z2) Z3 = np.dot(A2, W3) + b3 A3 = relu(Z3) Z4 = np.dot(A3, W4) + b4 A4 = softmax(Z4) cache = [Z1, A1, Z2, A2, Z3, A3, Z4, A4] return A4, cache # 反向传播算法 def backward(X, Y, cache): Z1, A1, Z2, A2, Z3, A3, Z4, A4 = cache dZ4 = A4 - Y dW4 = np.dot(A3.T, dZ4) db4 = np.sum(dZ4, axis=0, keepdims=True) dA3 = np.dot(dZ4, W4.T) dZ3 = np.multiply(dA3, np.int64(A3 > 0)) dW3 = np.dot(A2.T, dZ3) db3 = np.sum(dZ3, axis=0, keepdims=True) dA2 = np.dot(dZ3, W3.T) dZ2 = np.multiply(dA2, np.int64(A2 > 0)) dW2 = np.dot(A1.T, dZ2) db2 = np.sum(dZ2, axis=0, keepdims=True) dA1 = np.dot(dZ2, W2.T) dZ1 = np.multiply(dA1, np.int64(A1 > 0)) dW1 = np.dot(X.T, dZ1) db1 = np.sum(dZ1, axis=0, keepdims=True) return dW1, db1, dW2, db2, dW3, db3, dW4, db4 # 定义训练函数 def train(X, Y, learning_rate, epochs, batch_size): num_samples, _ = X.shape num_batches = num_samples // batch_size loss_list = [] for epoch in range(epochs): epoch_loss = 0 for batch_index in range(num_batches): # 从数据集中随机选取一个batch batch_start = batch_index * batch_size batch_end = (batch_index + 1) * batch_size X_batch = X[batch_start:batch_end] Y_batch = Y[batch_start:batch_end] # 前向传播 Y_pred, cache = forward(X_batch) # 计算损失函数 loss = -np.mean(Y_batch * np.log(Y_pred)) epoch_loss += loss # 反向传播 dW1, db1, dW2, db2, dW3, db3, dW4, db4 = backward(X_batch, Y_batch, cache) # 更新权重和偏置 W1 -= learning_rate * dW1 b1 -= learning_rate * db1 W2 -= learning_rate * dW2 b2 -= learning_rate * db2 W3 -= learning_rate * dW3 b3 -= learning_rate * db3 W4 -= learning_rate * dW4 b4 -= learning_rate * db4 epoch_loss /= num_batches loss_list.append(epoch_loss) print("Epoch %d: loss = %.4f" % (epoch, epoch_loss)) return loss_list # 主函数 if __name__ == '__main__': # 读取MNIST数据集 X_train = load_mnist_data("train-images-idx3-ubyte") Y_train = load_mnist_data("train-labels-idx1-ubyte") X_test = load_mnist_data("t10k-images-idx3-ubyte") Y_test = load_mnist_data("t10k-labels-idx1-ubyte") # 将标签转为one-hot向量形式 Y_train_one_hot = np.zeros((Y_train.shape[0], output_size)) Y_train_one_hot[np.arange(Y_train.shape[0]), Y_train.astype(int)] = 1 Y_test_one_hot = np.zeros((Y_test.shape[0], output_size)) Y_test_one_hot[np.arange(Y_test.shape[0]), Y_test.astype(int)] = 1 # 训练神经网络 loss_list = train(X_train, Y_train_one_hot, learning_rate=0.1, epochs=10, batch_size=100) # 测试神经网络 Y_pred, _ = forward(X_test) accuracy = np.mean(np.argmax(Y_test_one_hot, axis=1) == np.argmax(Y_pred, axis=1)) print("Accuracy on test set: %.4f" % accuracy) ``` 该代码实现了一个基本的三层神经网络,可以用于识别MNIST手写数字集。其中,第一层和第二层使用ReLU激活函数,第三层使用softmax激活函数。训练过程中使用了随机梯度下降算法,且每个batch的数据集大小为100。最后,我们将MNIST测试集输入到该神经网络中,并计算其准确率,以测试该神经网络的性能。 请注意,该代码实现的是基本的三层神经网络模型,因此准确率可能并不高。对于该模型,我们希望通过调整超参数或修改网络结构等手段进一步优化其性能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值