从零实现神经网络:前向传播与反向传播算法详解及代码实战

1次阅读
没有评论

共计 3609 个字符,预计需要花费 10 分钟才能阅读完成。

image.webp

为什么需要神经网络?

神经网络是模仿人脑神经元连接方式的数学模型,在图像识别、自然语言处理等领域表现出色。比如支付宝的人脸识别、微信的语音转文字,背后都是神经网络在发挥作用。它能够自动从数据中学习特征,避免了传统算法需要手工设计特征的麻烦。

从零实现神经网络:前向传播与反向传播算法详解及代码实战

人工神经元模型

神经网络的基本单元是神经元,它的数学模型可以表示为:

$$z = w_1x_1 + w_2x_2 + … + w_nx_n + b$$

其中:
– $w_i$ 是权重(weight)
– $x_i$ 是输入(input)
– $b$ 是偏置(bias)
– $z$ 是加权和

然后通过激活函数 $\sigma(z)$ 得到神经元的输出。这个简单的模型却能组合出强大的表达能力。

前向传播过程

前向传播就是数据从输入层流向输出层的过程:

  1. 输入数据传入输入层
  2. 每一层计算加权和 $z = WX + b$
  3. 通过激活函数得到输出 $a = \sigma(z)$
  4. 输出传递到下一层作为输入

用 Python 实现前向传播:

import numpy as np

class NeuralNetwork:
    def __init__(self, layers):
        self.weights = [np.random.randn(y, x) 
                        for x, y in zip(layers[:-1], layers[1:])]
        self.biases = [np.random.randn(y, 1) for y in layers[1:]]

    def forward(self, x):
        for w, b in zip(self.weights, self.biases):
            x = sigmoid(np.dot(w, x) + b)
        return x

反向传播算法

反向传播 (BP) 是神经网络训练的核心,通过计算损失函数对各个参数的梯度来更新权重。推导过程需要用到链式法则:

  1. 计算输出层误差:$\delta^L = \nabla_a C \odot \sigma'(z^L)$
  2. 反向传播误差:$\delta^l = ((w^{l+1})^T \delta^{l+1}) \odot \sigma'(z^l)$
  3. 计算梯度:$\frac{\partial C}{\partial w^l} = \delta^l (a^{l-1})^T$

Python 实现反向传播:

def backprop(self, x, y):
    # 前向传播
    activation = x
    activations = [x]
    zs = []
    for w, b in zip(self.weights, self.biases):
        z = np.dot(w, activation) + b
        zs.append(z)
        activation = sigmoid(z)
        activations.append(activation)

    # 反向传播
    delta = (activations[-1] - y) * sigmoid_prime(zs[-1])
    nabla_w = [np.zeros(w.shape) for w in self.weights]
    nabla_b = [np.zeros(b.shape) for b in self.biases]

    nabla_w[-1] = np.dot(delta, activations[-2].T)
    nabla_b[-1] = delta

    for l in range(2, self.num_layers):
        z = zs[-l]
        sp = sigmoid_prime(z)
        delta = np.dot(self.weights[-l+1].T, delta) * sp
        nabla_w[-l] = np.dot(delta, activations[-l-1].T)
        nabla_b[-l] = delta

    return (nabla_w, nabla_b)

常用激活函数实现

激活函数引入非线性,使神经网络可以拟合任意函数。常见的三种激活函数:

  1. Sigmoid:$\sigma(z) = \frac{1}{1+e^{-z}}$
  2. 输出在 (0,1) 之间,适合二分类
  3. 容易导致梯度消失

  4. Tanh:$tanh(z) = \frac{e^z – e^{-z}}{e^z + e^{-z}}$

  5. 输出在 (-1,1) 之间,中心对称
  6. 梯度消失问题比 Sigmoid 轻

  7. ReLU:$ReLU(z) = max(0,z)$

  8. 计算简单,缓解梯度消失
  9. 可能出现神经元死亡

代码实现:

def sigmoid(z):
    return 1.0/(1.0+np.exp(-z))

def sigmoid_prime(z):
    return sigmoid(z)*(1-sigmoid(z))

def relu(z):
    return np.maximum(0, z)

def relu_prime(z):
    return (z > 0).astype(float)

优化算法实现

最简单的优化算法是随机梯度下降(SGD):

$$w \leftarrow w – \eta \frac{\partial C}{\partial w}$$

其中 $\eta$ 是学习率,控制参数更新步长。学习率太大可能导致震荡,太小则收敛慢。

Python 实现:

def SGD(self, training_data, epochs, batch_size, eta):
    n = len(training_data)
    for j in range(epochs):
        np.random.shuffle(training_data)
        batches = [training_data[k:k+batch_size] 
                  for k in range(0, n, batch_size)]
        for batch in batches:
            self.update_batch(batch, eta)

def update_batch(self, batch, eta):
    nabla_w = [np.zeros(w.shape) for w in self.weights]
    nabla_b = [np.zeros(b.shape) for b in self.biases]
    for x, y in batch:
        delta_nabla_w, delta_nabla_b = self.backprop(x, y)
        nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
        nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
    self.weights = [w-(eta/len(batch))*nw 
                    for w, nw in zip(self.weights, nabla_w)]
    self.biases = [b-(eta/len(batch))*nb 
                   for b, nb in zip(self.biases, nabla_b)]

实战 MNIST 手写数字识别

现在用我们实现的神经网络来识别手写数字。MNIST 数据集包含 60000 张 28×28 的手写数字图片。

  1. 首先加载数据:
from tensorflow.keras.datasets import mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()

# 数据预处理
x_train = x_train.reshape(-1, 28*28).T / 255.0
x_test = x_test.reshape(-1, 28*28).T / 255.0

y_train = np.eye(10)[y_train].T
y_test = np.eye(10)[y_test].T
  1. 创建神经网络并训练:
# 创建网络:784 输入,30 隐藏层,10 输出
net = NeuralNetwork([784, 30, 10])

# 训练
net.SGD(list(zip(x_train.T, y_train.T)), 
       epochs=30, batch_size=10, eta=3.0)
  1. 评估准确率:
def evaluate(net, x, y):
    results = [(np.argmax(net.forward(x[:,i])), np.argmax(y[:,i])) 
              for i in range(x.shape[1])]
    return sum(int(x == y) for (x, y) in results) / len(results)

print("Test accuracy:", evaluate(net, x_test, y_test))

常见问题与解决方案

  1. 梯度消失:深层网络训练时梯度越来越小
  2. 解决方案:使用 ReLU 激活函数,Batch Normalization

  3. 过拟合:训练集表现好但测试集差

  4. 解决方案:增加数据量,L2 正则化,Dropout

  5. 学习率选择:太大震荡,太小收敛慢

  6. 解决方案:学习率衰减,Adam 等自适应优化算法

延伸学习

  1. 推荐书籍:《神经网络与深度学习》(Michael Nielsen)
  2. 进阶框架:TensorFlow/PyTorch
  3. 最新进展:注意力机制、Transformer

通过本文,我们从零实现了一个完整的神经网络,理解了前向传播和反向传播的原理,并在 MNIST 数据集上进行了实战。虽然我们的实现比较简单,但已经包含了神经网络最核心的思想。希望这篇文章能帮助你入门神经网络的世界!

正文完
 0
评论(没有评论)