Building neural networks from scratch in python: making
作者:禅与计算机程序设计艺术
1.简介
以下是对原文的有效同义改写版本
本文将深入讲解反向传播(Backpropagation)算法,在训练神经网络过程中发挥着核心作用。通过使用Python编程语言,从基础开始帮助读者完成一个完整的基于神经网络的系统框架设计,并最终实现对 handwritten digit recognition 问题的求解。从而深入理解 neural network 的基本工作原理。
文章分为以下七章:
- 研究背景与重要性概述
- 核心术语解析 3. 反向传播算法的作用机制分析 4. 手写数字识别的具体实现路径 5. 多层感知机在神经网络训练中的功能解析 6. 卷积神经网络的深入特征提取架构设计 7. 总结与应用前景展望
2.基本概念术语说明
2.1 概念阐述
首先介绍神经网络的概念时, 我们通常会探讨其基本组成部分及其功能机制. 神经网络系统一般由多个输入层、隐藏层和输出层构成, 其中每个输入层单元负责接收外界信号并通过特定通道传递给下一个层级. 在这一过程中, 输出层单元经过一系列数据处理后, 将最终整合为预测结果或目标值. 中间的各层则由许多相互连接的神经元构成, 通过这样的连接架构, 神经网络得以建立输入与输出之间的映射关系.
那么,在最基础的情况下(即不考虑复杂的优化策略),神经网络的工作机制是怎样的呢?它会将输入数据直接导入模型中进行计算,并通过一系列数学运算生成相应的输出结果。不过,在实际应用中实现这一目标并非易事。由于神经网络模型包含大量参数(权重和偏置项),这些参数之间的关系错综复杂且难以直观理解。因此,在保证模型性能的同时进行精确的参数调优是一项极具挑战性的任务。为了使模型达到预期效果并提高预测准确性,在训练过程中我们不仅需要设计合适的优化算法还需要不断调试模型结构以及相关的超参数设置
为了求解这一问题, 该算法被命名为反向传播算法(简称BP 算法)。该方法通过迭代优化的过程逐步更新网络参数, 并使损失函数达到最小值。在众多领域中占据重要地位的这一方法, 在神经网络训练中得到了广泛应用。
2.2 术语说明
下面,我们来了解一下BP算法涉及到的一些术语。
神经网络中各神经元间的连接关系由权值(weight)和阈值(bias)参数决定。权值大小直接影响信号传递强度;而其阈值设置使得激活值在零点附近达到平衡状态。通过调节这些参数可有效控制信号传递过程中的强弱关系以及系统整体响应特性。
在神经网络架构中,激活函数(activation function)是一个非线性变换过程,在每个节点中执行这一操作以生成最终输出。常用到的激活函數包括 sigmoid、tanh 和 ReLU 等多種類型。sigmoid 激活將輸入值限制在 0 到 1之間,并特别适用于解决二分类问题;而 tanh 激活則具備將數值範圍限定在 -1 到 1之間的能力,并同样适用于回归分析任务;ReLU(Rectified Linear Unit)作為一種替代激勵方式,在實質上是一種變形版本,并且其最大輸出值有限制——避免了神經元「死亡」現象的发生——特别適合處理稀疏輸入數據的情況。
- 损失函数(loss function):在神经网络中被定义为衡量预测结果与真实值之间差异的一种指标。它不仅用于评估模型性能,并且指导着模型优化的方向。
而衡量这种差异大小的标准方法即为此处所指的损失函数(loss function)。
在实际应用中, 常见的是均方误差(MSE)、交叉熵(Cross Entropy)等。
其中一种是均方误差(MSE),计算公式为每个样本预测值与实际值差的平方平均;另一种是交叉熵,在分类任务中表现尤为突出。
Derivatives: In the training of neural networks, the backpropagation (BP) algorithm employs gradient descent to minimize the loss function. The gradient is defined as the derivative of the loss function with respect to a variable, representing the direction in which the loss function increases most rapidly. Derivatives represent the rates of change of these functions.
5.多层感知器(Multi-layer Perceptron, MLP):MLP网络是神经网络体系中的一种核心结构。其由多个隐藏层以及一个输出层共同构成,在数据处理过程中能够实现从低级特征到高级抽象特征的逐步提取。在实际应用中,隐藏层数量及神经元数目可以根据需求进行灵活配置;而输出单元数目则通常固定为1个以确保单一预测目标的生成。
卷积神经网络(Convolutional Neural Network, CNN):作为一种特殊的人工神经网络,在图像识别、语音信号分析等领域表现出色,并被广泛应用于高维数据分类任务中。该模型主要包含卷积块和池化模块两个基本组件:通过卷积操作从输入中提取空间特征并生成中间表示;而池化操作则能够有效降低计算复杂度并提升模型泛化能力
- 前向传播型神经网络(FNN):仅包含输入层与输出层的神经网络
3.理解反向传播算法
BP算法(Backpropagation algorithm)在神经网络训练过程中发挥着至关重要的作用。基于迭代优化机制,BP算法能够持续调整网络参数以使输出结果能够最大限度地逼近真实标签。
我们知道,反向传播算法其核心优势在于能够基于计算当前参数在损失函数上的梯度(gradient)从而实现对模型参数的有效更新与优化。具体而言该算法通过迭代执行以下步骤来进行训练与优化操作:
- 采用正向传播的方式完成输出的计算
- 带入损失函数值并进行偏导数计算以获得损失函数的导数
- 利用其梯度信息按照梯度下降方法更新网络参数
下面,我们用数学公式来详细阐述BP算法的具体操作步骤。
3.1 梯度计算公式
考虑一个具有L层的网络结构,在每一轮前馈传播中,各层依次计算并传递信息。其中每一层的输出变量a_l等于激活函数g作用于其内部状态变量z^l的结果。具体地,每一层内部的状态变量z^l=\sigma (W^{l} a^{l-1}+b^l)表示该层通过加权求和前一层输出并对结果施加非线性激活σ得到的状态信息。而损失函数J的具体形式则由所有这些中间状态变量共同决定。
在该模型中进行训练时,在公式\hat y^{(i)}=h_{\theta}(x^{(i)})中用于生成预测值\hat y^{(i)};其中L对应于损失函数这一指标;而R则是指代正则化项的部分;这里的正则化系数λ起到相当于引入一个调节参数的作用
基于此,损失函数对模型参数θ的梯度能够通过链式法则进行计算。
BP算法利用损失函数的导数信息来优化网络参数。在训练过程中, BP算法被反复更新参数直到损失函数值不再下降。
为了计算损失函数关于权重矩阵w_j^{(l)}的梯度,可以使用链式法则:
根据链式法则,可以得到权重矩阵的梯度:
变化量\Delta w_{j}^{(l)}由学习率\alpha与损失函数关于权重参数\frac{\partial L}{\partial w_{j}^{(l)}}的负方向决定,
同样地,
变化量\Delta b_{j}^{(l)}也由学习率\alpha与损失函数关于偏置参数\frac{\partial L}{\partial b_{j}^{(l)}}的负方向决定。
其中,\alpha为学习率(learning rate)。
3.2 示例:单层感知机(Perceptron)
下面,我们以单层感知机作为案例,来展示反向传播算法的操作步骤。
3.2.1 数据集加载
首先,加载MNIST手写数字数据集。该数据集共有60,000张训练图片,10,000张测试图片,每个图片大小为28x28。
import numpy as np
from keras.datasets import mnist
from sklearn.preprocessing import OneHotEncoder
# Load data
(X_train, Y_train), (X_test, Y_test) = mnist.load_data()
# Data normalization
X_train = X_train / 255.0
X_test = X_test / 255.0
# Reshape input to be [samples][pixels][width*height]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1]*X_train.shape[2]))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1]*X_test.shape[2]))
# Convert target variable into one-hot encoded format
encoder = OneHotEncoder()
Y_train = encoder.fit_transform(np.expand_dims(Y_train, axis=1)).toarray()
Y_test = encoder.transform(np.expand_dims(Y_test, axis=1)).toarray()
num_classes = len(set(Y_train))
代码解读
3.2.2 模型建立
接着搭建一个单层感知机模型。在这里我们采用ReLU作为隐含层所使用的激活函数
from keras.models import Sequential
from keras.layers import Dense
# Create model
model = Sequential([
Dense(units=num_classes, activation='softmax', input_dim=(784,))
])
model.compile(optimizer='adam', loss='categorical_crossentropy')
代码解读
3.2.3 参数初始化
然后,随机初始化模型的参数。
# Initialize parameters
weights = []
biases = []
for layer in model.layers:
weights.append(layer.get_weights()[0].flatten())
biases.append(layer.get_weights()[1].flatten())
weights = np.concatenate(weights)
biases = np.concatenate(biases)
params = np.random.randn(len(weights)+len(biases))/np.sqrt(784)
代码解读
3.2.4 BP算法迭代训练
最后,启动BP算法迭代训练模型。
epochs = 10 # number of epochs
batch_size = 100 # batch size for each iteration
learning_rate = 0.01 # learning rate
for i in range(epochs):
# Shuffle dataset randomly
idx = np.arange(X_train.shape[0])
np.random.shuffle(idx)
X_train = X_train[idx,:]
Y_train = Y_train[idx,:]
for j in range(0, X_train.shape[0]-batch_size+1, batch_size):
# Update parameter gradients
activations = X_train[j:j+batch_size,:].dot(weights) + biases[:-1].reshape((-1,1))
sigmoids = 1/(1+np.exp(-activations))
error = sigmoids - Y_train[j:j+batch_size,:]
delta = error * sigmoids*(1-sigmoids)
gradient_weights = delta.T.dot(X_train[j:j+batch_size,:]).flatten()/batch_size + reg_strength * weights
gradient_biases = np.mean(delta,axis=0).flatten()
params -= learning_rate * np.concatenate([gradient_weights,gradient_biases])
# Evaluate performance on test set every epoch
scores = model.evaluate(X_test, Y_test, verbose=0)
print('Epoch %d/%d, Test accuracy: %.2f%%'%(i+1, epochs, scores[1]*100))
代码解读
3.3 小结
本节阐述了反向传播算法及其相关概念。BP算法的训练原理以及其操作流程已经进行了详细说明。BP算法在深度神经网络的训练过程中发挥着极其重要的作用。
4.具体应用场景:实现手写数字识别
到目前为止,我们已掌握反向传播算法的核心内容与理论基础。接下来计划利用BP算法实现手写数字识别系统。
4.1 数据集加载
首先,加载MNIST手写数字数据集。该数据集共有60,000张训练图片,10,000张测试图片,每个图片大小为28x28。
import numpy as np
from keras.datasets import mnist
# Load data
(X_train, Y_train), (X_test, Y_test) = mnist.load_data()
代码解读
4.2 数据预处理
随后, 对数据进行预处理. 第一步是进行数据标准化(归一化): 将输入数值归一化至0至1范围.
# Data normalization
X_train = X_train / 255.0
X_test = X_test / 255.0
代码解读
然后,将输入数据reshape成一维向量:
# Reshape input to be [samples][pixels][width*height]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1]*X_train.shape[2]))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1]*X_test.shape[2]))
代码解读
最后,将目标变量转换为one-hot编码形式:
from keras.utils import to_categorical
# Convert target variable into one-hot encoded format
Y_train = to_categorical(Y_train, num_classes=10)
Y_test = to_categorical(Y_test, num_classes=10)
代码解读
4.3 模型建立
设计一个双层的全连接神经网络模型。在这里我们采用了ReLU作为隐藏层的激活函数。
from keras.models import Sequential
from keras.layers import Dense
# Create model
model = Sequential([
Dense(units=512, activation='relu', input_dim=(784,)),
Dense(units=10, activation='softmax')
])
model.summary()
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
代码解读
4.4 参数初始化
随机初始化模型的参数。
# Randomly initialize the weights and bias of the two layers with ReLU activation functions
weights1 = np.random.normal(loc=0., scale=0.1, size=[784,512])/np.sqrt(784)
bias1 = np.zeros(512,)
weights2 = np.random.normal(loc=0., scale=0.1, size=[512,10])/np.sqrt(512)
bias2 = np.zeros(10,)
# Concatenate all weights and biases
params = np.concatenate([weights1.flatten(), bias1, weights2.flatten(), bias2])
代码解读
4.5 BP算法迭代训练
启动BP算法迭代训练模型。
epochs = 10 # number of epochs
batch_size = 100 # batch size for each iteration
learning_rate = 0.01 # learning rate
for i in range(epochs):
# Shuffle dataset randomly
idx = np.arange(X_train.shape[0])
np.random.shuffle(idx)
X_train = X_train[idx,:]
Y_train = Y_train[idx,:]
for j in range(0, X_train.shape[0]-batch_size+1, batch_size):
# Get mini-batches
X_mini = X_train[j:j+batch_size,:]
Y_mini = Y_train[j:j+batch_size,:]
# Forward propagation
Z1 = X_mini.dot(weights1) + bias1
A1 = relu(Z1)
Z2 = A1.dot(weights2) + bias2
Y_pred = softmax(Z2)
# Compute cost function
J = cross_entropy_loss(Y_pred, Y_mini) + weight_decay * regularization(weights1, weights2)
# Backward propagation
dZ2 = Y_pred - Y_mini
dW2 = 1./batch_size * dZ2.dot(A1.T) + weight_decay * derivative_regularization(weights2, lmbda)
db2 = 1./batch_size * np.sum(dZ2, axis=0)
dA1 = dZ2.dot(weights2.T)
dZ1 = np.multiply(dA1, relu_backward(Z1))
dW1 = 1./batch_size * dZ1.dot(X_mini.T) + weight_decay * derivative_regularization(weights1, lmbda)
db1 = 1./batch_size * np.sum(dZ1, axis=0)
# Update parameters
weights1 -= learning_rate * dW1
bias1 -= learning_rate * db1
weights2 -= learning_rate * dW2
bias2 -= learning_rate * db2
# Evaluate performance on test set every epoch
scores = model.evaluate(X_test, Y_test, verbose=0)
print('Epoch %d/%d, Test accuracy: %.2f%%'%(i+1, epochs, scores[1]*100))
代码解读
4.6 超参数设置
超参数如学习率、正则化系数、动量等需要进行调整来获得更好的性能。
4.7 小结
本节我们完成了对BP算法的一个实际应用实例,在手写数字识别任务方面进行了重点研究。目的是识别手写数字,并使用MNIST作为数据集来源。其中包含6万张训练图片和1万张测试图片。通过BP算法对神经网络进行训练,并成功实现了模型的训练流程。
