您好,登录后才能下订单哦!
手写数字识别是计算机视觉领域的一个经典问题,也是深度学习入门的一个重要案例。本文将介绍如何使用纯numpy库,通过数值微分法实现一个简单的神经网络,并在MNIST数据集上进行手写数字识别。我们将从基础的神经网络概念出发,逐步构建并训练一个能够识别手写数字的模型。
神经网络是一种模拟人脑神经元工作方式的数学模型。它由多个层(Layer)组成,每一层包含若干个神经元(Neuron)。每个神经元接收来自上一层神经元的输入,经过加权求和和激活函数处理后,输出到下一层神经元。
数值微分法是一种通过计算函数在某一点的微小变化来近似求导的方法。在神经网络中,我们通常使用反向传播算法来计算梯度,但数值微分法可以作为一种替代方案,尤其是在实现简单模型时。
手写数字识别是指通过计算机自动识别手写数字的任务。MNIST数据集是手写数字识别领域的一个经典数据集,包含60000个训练样本和10000个测试样本,每个样本是一个28x28的灰度图像。
MNIST数据集由Yann LeCun等人于1998年发布,是手写数字识别领域的一个基准数据集。数据集中的每个样本都是一个28x28的灰度图像,像素值在0到255之间。每个图像对应一个0到9的标签,表示图像中的数字。
在使用MNIST数据集之前,我们需要对其进行预处理。常见的预处理步骤包括:
import numpy as np
from keras.datasets import mnist
# 加载MNIST数据集
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# 归一化
x_train = x_train.astype('float32') / 255
x_test = x_test.astype('float32') / 255
# 扁平化
x_train = x_train.reshape(-1, 28*28)
x_test = x_test.reshape(-1, 28*28)
# 标签编码
def one_hot_encode(y, num_classes=10):
return np.eye(num_classes)[y]
y_train = one_hot_encode(y_train)
y_test = one_hot_encode(y_test)
我们将设计一个简单的全连接神经网络,包含一个输入层、一个隐藏层和一个输出层。输入层有784个神经元(对应28x28的图像),隐藏层有128个神经元,输出层有10个神经元(对应0-9的数字)。
class NeuralNetwork:
def __init__(self, input_size, hidden_size, output_size):
self.input_size = input_size
self.hidden_size = hidden_size
self.output_size = output_size
# 初始化权重和偏置
self.W1 = np.random.randn(input_size, hidden_size) * 0.01
self.b1 = np.zeros((1, hidden_size))
self.W2 = np.random.randn(hidden_size, output_size) * 0.01
self.b2 = np.zeros((1, output_size))
激活函数用于引入非线性,使得神经网络能够学习复杂的模式。常见的激活函数包括Sigmoid、ReLU和Softmax。在本文中,我们将在隐藏层使用ReLU激活函数,在输出层使用Softmax激活函数。
def relu(x):
return np.maximum(0, x)
def softmax(x):
exp_x = np.exp(x - np.max(x, axis=1, keepdims=True))
return exp_x / np.sum(exp_x, axis=1, keepdims=True)
损失函数用于衡量模型预测值与真实值之间的差距。在手写数字识别任务中,我们通常使用交叉熵损失函数。
def cross_entropy_loss(y_pred, y_true):
m = y_true.shape[0]
log_likelihood = -np.log(y_pred[range(m), y_true.argmax(axis=1)])
loss = np.sum(log_likelihood) / m
return loss
前向传播是指从输入层到输出层的计算过程。我们首先计算隐藏层的输出,然后计算输出层的输出。
def forward(self, X):
self.z1 = np.dot(X, self.W1) + self.b1
self.a1 = relu(self.z1)
self.z2 = np.dot(self.a1, self.W2) + self.b2
self.a2 = softmax(self.z2)
return self.a2
反向传播是指从输出层到输入层的梯度计算过程。我们首先计算输出层的误差,然后计算隐藏层的误差。
def backward(self, X, y_true, y_pred):
m = y_true.shape[0]
# 输出层误差
dz2 = y_pred - y_true
dW2 = np.dot(self.a1.T, dz2) / m
db2 = np.sum(dz2, axis=0, keepdims=True) / m
# 隐藏层误差
dz1 = np.dot(dz2, self.W2.T) * (self.a1 > 0)
dW1 = np.dot(X.T, dz1) / m
db1 = np.sum(dz1, axis=0, keepdims=True) / m
return dW1, db1, dW2, db2
数值微分法通过计算函数在某一点的微小变化来近似求导。我们可以通过以下公式计算梯度:
\[ \frac{\partial L}{\partial w} \approx \frac{L(w + h) - L(w - h)}{2h} \]
其中,\(L\)是损失函数,\(w\)是权重,\(h\)是一个很小的数。
def numerical_gradient(self, X, y_true, h=1e-5):
grad_W1 = np.zeros_like(self.W1)
grad_b1 = np.zeros_like(self.b1)
grad_W2 = np.zeros_like(self.W2)
grad_b2 = np.zeros_like(self.b2)
for i in range(self.W1.shape[0]):
for j in range(self.W1.shape[1]):
self.W1[i, j] += h
loss_plus = cross_entropy_loss(self.forward(X), y_true)
self.W1[i, j] -= 2 * h
loss_minus = cross_entropy_loss(self.forward(X), y_true)
self.W1[i, j] += h
grad_W1[i, j] = (loss_plus - loss_minus) / (2 * h)
for i in range(self.b1.shape[0]):
for j in range(self.b1.shape[1]):
self.b1[i, j] += h
loss_plus = cross_entropy_loss(self.forward(X), y_true)
self.b1[i, j] -= 2 * h
loss_minus = cross_entropy_loss(self.forward(X), y_true)
self.b1[i, j] += h
grad_b1[i, j] = (loss_plus - loss_minus) / (2 * h)
for i in range(self.W2.shape[0]):
for j in range(self.W2.shape[1]):
self.W2[i, j] += h
loss_plus = cross_entropy_loss(self.forward(X), y_true)
self.W2[i, j] -= 2 * h
loss_minus = cross_entropy_loss(self.forward(X), y_true)
self.W2[i, j] += h
grad_W2[i, j] = (loss_plus - loss_minus) / (2 * h)
for i in range(self.b2.shape[0]):
for j in range(self.b2.shape[1]):
self.b2[i, j] += h
loss_plus = cross_entropy_loss(self.forward(X), y_true)
self.b2[i, j] -= 2 * h
loss_minus = cross_entropy_loss(self.forward(X), y_true)
self.b2[i, j] += h
grad_b2[i, j] = (loss_plus - loss_minus) / (2 * h)
return grad_W1, grad_b1, grad_W2, grad_b2
在计算出梯度后,我们可以使用梯度下降法来更新模型的参数。
def update_parameters(self, dW1, db1, dW2, db2, learning_rate):
self.W1 -= learning_rate * dW1
self.b1 -= learning_rate * db1
self.W2 -= learning_rate * dW2
self.b2 -= learning_rate * db2
在训练开始之前,我们需要初始化模型的参数。通常,我们可以使用随机初始化或Xavier初始化。
def initialize_parameters(self):
self.W1 = np.random.randn(self.input_size, self.hidden_size) * 0.01
self.b1 = np.zeros((1, self.hidden_size))
self.W2 = np.random.randn(self.hidden_size, self.output_size) * 0.01
self.b2 = np.zeros((1, self.output_size))
训练循环包括前向传播、反向传播和参数更新三个步骤。我们重复这个过程多次,直到模型收敛。
def train(self, X, y, epochs=1000, learning_rate=0.01):
for epoch in range(epochs):
# 前向传播
y_pred = self.forward(X)
# 计算损失
loss = cross_entropy_loss(y_pred, y)
# 反向传播
dW1, db1, dW2, db2 = self.backward(X, y, y_pred)
# 参数更新
self.update_parameters(dW1, db1, dW2, db2, learning_rate)
if epoch % 100 == 0:
print(f'Epoch {epoch}, Loss: {loss}')
学习率是影响模型训练效果的一个重要超参数。我们可以使用学习率调度器来动态调整学习率。
def learning_rate_scheduler(epoch, initial_lr=0.01, decay_rate=0.1, decay_step=100):
return initial_lr * (decay_rate ** (epoch // decay_step))
在训练完成后,我们需要在测试集上评估模型的性能。我们可以计算模型的准确率、精确率、召回率等指标。
def evaluate(self, X, y):
y_pred = self.forward(X)
y_pred_labels = np.argmax(y_pred, axis=1)
y_true_labels = np.argmax(y, axis=1)
accuracy = np.mean(y_pred_labels == y_true_labels)
return accuracy
混淆矩阵是一种用于评估分类模型性能的工具。它可以显示模型在每个类别上的预测情况。
from sklearn.metrics import confusion_matrix
def plot_confusion_matrix(y_true, y_pred):
cm = confusion_matrix(y_true, y_pred)
plt.figure(figsize=(10, 8))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.xlabel('Predicted')
plt.ylabel('True')
plt.show()
我们可以通过可视化一些样本的预测结果来直观地了解模型的性能。
def visualize_predictions(X, y_true, y_pred, num_samples=10):
indices = np.random.choice(X.shape[0], num_samples, replace=False)
plt.figure(figsize=(15, 5))
for i, idx in enumerate(indices):
plt.subplot(1, num_samples, i+1)
plt.imshow(X[idx].reshape(28, 28), cmap='gray')
plt.title(f'True: {y_true[idx]}\nPred: {y_pred[idx]}')
plt.axis('off')
plt.show()
正则化是一种用于防止模型过拟合的技术。常见的正则化方法包括L2正则化和Dropout。
def l2_regularization(self, lambda_=0.01):
return 0.5 * lambda_ * (np.sum(self.W1**2) + np.sum(self.W2**2))
批量归一化是一种用于加速训练和提高模型性能的技术。它通过对每一层的输入进行归一化来减少内部协变量偏移。
def batch_normalization(self, X, gamma, beta, epsilon=1e-5):
mean = np.mean(X, axis=0)
var = np.var(X, axis=0)
X_norm = (X - mean) / np.sqrt(var + epsilon)
return gamma * X_norm + beta
学习率调度是一种用于动态调整学习率的技术。常见的学习率调度方法包括Step Decay、Exponential Decay和Cosine Annealing。
def step_decay_scheduler(epoch, initial_lr=0.01, decay_rate=0.1, decay_step=100):
return initial_lr * (decay_rate ** (epoch // decay_step))
本文介绍了如何使用纯numpy库,通过数值微分法实现一个简单的神经网络,并在MNIST数据集上进行手写数字识别。我们从基础的神经网络概念出发,逐步构建并训练了一个能够识别手写数字的模型。尽管数值微分法在计算效率上不如反向传播算法,但它为我们提供了一种直观的理解梯度计算的方式。
未来,我们可以进一步优化模型,例如引入更复杂的网络结构、使用更高效的优化算法、以及尝试更多的正则化技术。此外,我们还可以将模型应用于其他计算机视觉任务,如图像分类、目标检测等。
以上是《纯numpy数值微分法如何实现手写数字识别》的完整文章内容。希望这篇文章能够帮助你理解如何使用纯numpy库实现手写数字识别,并为你在深度学习领域的学习和实践提供参考。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。