Bex Tuychiev 2025-03-19
神经网络已经彻底改变了人工智能,从图像识别到自然语言处理,无处不在。这些强大模型的核心是一个称为前向传播(Forward Propagation)的基本过程。本指南将深入探讨这一核心概念,带你从基本原理走向实际实现。
什么是前向传播?
前向传播是神经网络将输入数据转换为预测或输出的过程。你可以把它看作是神经网络的“思考”阶段——当展示一个输入(比如一张图像或一段文本)时,前向传播就是网络如何通过其各层处理该信息以产生结果的方式。
从技术上讲,它是将数据从输入层依次计算,经过隐藏层,最终到达输出层的顺序过程。在这个过程中,数据通过加权连接和激活函数进行变换,使网络能够捕捉复杂的模式。
为什么理解前向传播很重要?
理解前向传播至关重要,原因如下:
- 学习的基础:如果不先理解神经网络如何做出预测,就无法掌握它们是如何学习的。前向传播是理解反向传播(backpropagation)的前提,而反向传播是实现学习的算法。
- 调试与优化:当神经网络表现不佳时,了解数据在网络中的流动方式有助于你识别并修复问题。
- 模型设计:有效的架构设计需要理解信息如何在不同层配置中传播。
- 部署效率:优化前向传播对于在资源受限环境中部署模型至关重要。
本指南你将学到什么
阅读完这份全面指南后,你将能够:
- 理解神经网络计算背后的数学原理
- 使用 NumPy 从零开始实现前向传播
- 了解如何使用 TensorFlow 和 PyTorch 等现代框架高效执行前向传播
- 可视化信息在网络中流动时的变换过程
- 将前向传播与神经网络的整体学习过程联系起来
- 在真实数据上实现一个完整的可运行示例
先决条件
为了从本指南中获得最大收益,你应该具备:
- 基本的 Python 编程知识
- 熟悉基础数学概念(向量、矩阵和基本微积分)
- 对机器学习概念有一定了解
即使没有深厚的知识背景,我们也设计了本指南,使其概念循序渐进,让有决心的学习者也能轻松掌握神经网络。现在,让我们开始吧!
前向传播的基础
要理解前向传播,我们需要从其最基本的构建模块开始。让我们从神经网络中最小的计算单元出发,逐步构建更复杂的结构。
单个神经元:一切的起点
神经网络的旅程始于一个与生物学惊人的类比。正如人脑由数十亿个相互连接的神经元组成一样,人工神经网络也是由受这些生物细胞启发的数学模型构建而成。

来源:Deep Learning - Visual Approach
一个生物神经元通过树突接收来自其他神经元的信号,在细胞体中处理这些信号,然后通过轴突将结果传递给其他神经元。在我们的计算模型中,我们通过以下方式模拟这一过程:
- 输入(Inputs):进入神经元的信号(类似于树突接收信号)。
- 权重(Weights):分配给每个输入的重要性(类似于突触连接的强度)。
- 偏置(Bias):一个额外的参数,无论输入如何,都能帮助神经元激活或保持休眠状态。
- 激活函数(Activation function):根据输入决定神经元是否以及以多强的强度“放电”(类似于细胞体决定是否产生动作电位)。
- 输出(Output):发送到下一层的信号(类似于轴突传递给其他神经元)。
让我们通过可视化一个单神经元来具体说明这一点:

来源:Deep Learning - Visual Approach
这个简单的计算单元构成了即使是最复杂神经网络的基础。但神经元究竟是如何将其输入转换为输出的呢?这就涉及到了数学。
单个神经元的数学基础
神经元的操作可以用一个简单的方程来描述:
分解来看:
- 是我们的输入向量
- 是我们的权重向量
- 是偏置(一个标量值)
- 是加权和加上偏置(通常称为“预激活”或“logit”)
- 是激活函数
- 是神经元的最终输出
让我们通过一个带有真实数字的具体例子来说明。假设我们有一个具有三个输入的神经元:
- 输入:
- 权重:
- 偏置:
首先,我们计算加权和加上偏置:
接下来,我们应用一个激活函数。我们使用流行的 ReLU(Rectified Linear Unit)函数,其定义为:
由于预激活值为负数,我们的 ReLU 神经元输出为 0,这意味着它对这个特定输入不会“放电”。
激活函数至关重要,因为它为网络引入了非线性。如果没有它,无论神经网络有多少层,都只能学习线性关系。常见的激活函数包括:
- Sigmoid: —— 输出介于 0 和 1 之间,适用于二分类
- ReLU: —— 简单、高效,并有助于缓解梯度消失问题
- Tanh: —— 输出介于 -1 和 1 之间,常用于隐藏层
每种激活函数都有其优势和适用场景,我们将在实现神经网络时进一步探讨。
从神经元到层
单个神经元很强大,但神经网络的真正力量在于将神经元组织成层。一层只是并行处理输入的一组神经元。神经网络中通常有三种类型的层:
- 输入层:接收原始数据并将其传递给下一层
- 隐藏层:处理来自前一层的信息
- 输出层:产生最终的预测或分类
当我们在一个层中有多个神经元时,每个神经元接收相同的输入,但具有不同的权重和偏置,我们可以用矩阵运算高效地表示这一点。让我们看看这是如何工作的。
假设我们有一个包含 3 个输入值和 4 个神经元的层。每个神经元都有自己的权重集和偏置:
- 输入:
- 神经元 1 的权重:
- 神经元 2 的权重:
- 神经元 3 的权重:
- 神经元 4 的权重:
- 偏置:
我们可以将这些权重组织成一个矩阵 :
现在,我们可以用一次矩阵乘法同时计算所有神经元的预激活值:
其中:
- 是我们的输入向量
- 是我们权重矩阵的转置
- 是我们的偏置向量
- 是预激活结果向量
然后我们逐元素应用激活函数以获得最终输出:
这种矩阵表示不仅仅是数学上的优雅;它还带来了计算效率。现代硬件(尤其是 GPU)针对矩阵运算进行了优化,使得这种方法比单独计算每个神经元的输出要快得多。
将这些层堆叠起来的能力——即一层的输出成为下一层的输入——赋予了神经网络学习复杂模式的非凡能力。当我们连接这些构建模块时,就可以探索前向传播在整个神经网络中的工作方式了。
通过完整网络的前向传播
现在我们已经理解了单个神经元和层,让我们退一步看看前向传播在整个神经网络中的工作方式。这正是深度学习真正强大的地方。
多层网络:完整图景
一个完整的神经网络由输入层、一个或多个隐藏层和输出层组成。“深度学习”中的“深度”指的是具有多个隐藏层的网络。每一层都以越来越抽象的方式变换数据,使网络能够学习复杂的表示。
让我们考虑一个简单的神经网络,它具有:
- 一个包含 4 个特征的输入层
- 两个分别包含 3 个和 2 个神经元的隐藏层
- 一个包含 3 个神经元的输出层(可能是用于多类分类)
从视觉上看,这个网络如下所示:

当数据流经这个网络时,我们在每一层执行一系列计算。如果我们表示:
- 输入向量为
- 权重矩阵为 (用于第一个隐藏层)、 (用于第二个隐藏层)
- 偏置向量为 、
- 激活函数为 (用于隐藏层)和 (用于输出层)
我们可以将整个网络的前向传播表示为:
计算第一个隐藏层的预激活值:
应用激活函数得到隐藏层输出:
计算第二个隐藏层的预激活值:
应用激活函数得到最终输出:
最终输出 代表网络的预测。对于分类问题,这可能是每个类别的概率;对于回归问题,这可能是预测值。
不同的层通常使用不同的激活函数。例如,隐藏层通常使用 ReLU,而输出层可能使用:
- Sigmoid 用于二分类
- Softmax 用于多类分类
- 线性(无激活)用于回归
这种多层结构的美妙之处在于,每一层都可以学习表示数据的不同方面。早期层通常检测简单特征,而更深的层将这些特征组合成更复杂的模式。这种层次化学习使神经网络在图像和语音识别等复杂任务中如此强大。
前向传播算法
让我们将前向传播过程形式化为一个算法。对于具有 L 层的神经网络,前向传播遵循以下步骤:
# 前向传播伪代码
def forward_propagation(X, parameters):
"""
X: 输入数据 (batch_size, n_features)
parameters: 包含每层权重和偏置的字典
返回: 最终输出和所有中间激活值
"""
# 存储所有激活值以供后续使用(例如在反向传播中)
activations = {'A0': X} # A0 是输入
# 遍历 L-1 层(不包括输出层)
for l in range(1, L):
# 获取前一层的激活值
A_prev = activations[f'A{l-1}']
# 获取当前层的权重和偏置
W = parameters[f'W{l}']
b = parameters[f'b{l}']
# 计算预激活值
Z = np.dot(A_prev, W.T) + b
# 应用激活函数(例如,隐藏层使用 ReLU)
A = relu(Z)
# 存储值以供后续使用
activations[f'Z{l}'] = Z
activations[f'A{l}'] = A
# 计算输出层(第 L 层)
A_prev = activations[f'A{L-1}']
W = parameters[f'W{L}']
b = parameters[f'b{L}']
# 计算输出层的预激活值
Z = np.dot(A_prev, W.T) + b
# 根据任务应用输出激活函数
if task == 'binary_classification':
A = sigmoid(Z)
elif task == 'multiclass_classification':
A = softmax(Z)
elif task == 'regression':
A = Z # 线性激活
# 存储输出层的值
activations[f'Z{L}'] = Z
activations[f'A{L}'] = A
return A, activations
该算法突出了前向传播的几个重要方面:
- 顺序处理:信息严格地逐层向前流动。
- 激活存储:我们不仅存储最终输出,还存储所有中间激活值,这对于使用反向传播训练网络至关重要。
- 任务特定输出:输出层的激活函数根据具体问题(分类 vs 回归)选择。
前向传播算法非常简单,但它允许神经网络逼近极其复杂的函数。当与通过反向传播的适当训练相结合时,这个简单的过程使网络能够从数据中学习并做出越来越准确的预测。
让我们通过一个具体例子来进一步巩固我们的理解。考虑输入 通过我们的示例网络,其中:
- 隐藏层权重 (4×3 矩阵)和偏置 (4 个值的向量)
- 输出层权重 (2×4 矩阵)和偏置 (2 个值的向量)
为简化起见,假设所有权重都是 0.1,所有偏置都是 0:
按照我们的算法:
第一步:计算隐藏层预激活值
第二步(使用 ReLU):应用激活函数得到隐藏层输出
第三步:计算输出层预激活值
第四步(使用 Sigmoid):应用激活函数得到最终输出
这就得到了我们的最终预测。在二分类上下文中,这些接近 0.5 的值表明两类之间的不确定性。
在下一节中,我们将在 Python 中实现前向传播,亲眼看到这些计算的实际效果。
在 Python 中实现前向传播
现在我们已经理解了前向传播的理论,让我们通过在 Python 中实现它来付诸实践。我们将首先使用仅 NumPy 的“从零开始”实现,然后看看现代深度学习框架如何简化这一过程。
使用 NumPy 从零开始构建
NumPy 提供了高效的数组操作,使我们能够实现所讨论的矩阵计算。让我们构建一个简单的神经网络类,该类通过多层执行前向传播。
首先,我们需要导入必要的库:
import numpy as np
import matplotlib.pyplot as plt
# 为了可重现性
np.random.seed(42)
现在,让我们定义将在网络中使用的激活函数:
def relu(Z):
"""ReLU 激活函数: max(0, Z)"""
return np.maximum(0, Z)
def sigmoid(Z):
"""Sigmoid 激活函数: 1/(1 + e^(-Z))"""
return 1 / (1 + np.exp(-Z))
def softmax(Z):
"""用于多类分类的 Softmax 激活函数"""
# 减去最大值以确保数值稳定性(防止溢出)
exp_Z = np.exp(Z - np.max(Z, axis=1, keepdims=True))
return exp_Z / np.sum(exp_Z, axis=1, keepdims=True)
接下来,让我们为神经网络创建一个类:
class NeuralNetwork:
def __init__(self, layer_dims, activations):
"""
使用指定的层维度和激活函数初始化神经网络
参数:
- layer_dims: 表示每层神经元数量的整数列表
(包括输入层和输出层)
- activations: 每层的激活函数列表(不包括输入层)
"""
self.L = len(layer_dims) - 1 # 层数(不包括输入层)
self.layer_dims = layer_dims
self.activations = activations
self.parameters = {}
# 初始化参数(权重和偏置)
self.initialize_parameters()
def initialize_parameters(self):
"""使用小的随机值初始化权重和偏置"""
for l in range(1, self.L + 1):
# He 初始化权重 - 有助于训练深层网络
self.parameters[f'W{l}'] = np.random.randn(self.layer_dims[l], self.layer_dims[l-1]) * np.sqrt(2 / self.layer_dims[l-1])
self.parameters[f'b{l}'] = np.zeros((self.layer_dims[l], 1))
def forward_propagation(self, X):
"""
执行网络中的前向传播
参数:
- X: 输入数据 (n_features, batch_size)
返回:
- AL: 网络的输出
- caches: 包含所有激活值和预激活值的字典
"""
caches = {}
A = X # 输入层激活值
caches['A0'] = X
# 处理 L-1 层(不包括输出层)
for l in range(1, self.L):
A_prev = A
# 获取当前层的权重和偏置
W = self.parameters[f'W{l}']
b = self.parameters[f'b{l}']
# 当前层的前向传播
Z = np.dot(W, A_prev) + b
# 应用激活函数
activation_function = self.activations[l-1]
if activation_function == "relu":
A = relu(Z)
elif activation_function == "sigmoid":
A = sigmoid(Z)
# 存储值以供反向传播使用
caches[f'Z{l}'] = Z
caches[f'A{l}'] = A
# 输出层
W = self.parameters[f'W{self.L}']
b = self.parameters[f'b{self.L}']
Z = np.dot(W, A) + b
# 应用输出激活函数
activation_function = self.activations[self.L-1]
if activation_function == "sigmoid":
AL = sigmoid(Z)
elif activation_function == "softmax":
AL = softmax(Z)
elif activation_function == "linear":
AL = Z # 回归问题无激活函数
# 存储输出层的值
caches[f'Z{self.L}'] = Z
caches[f'A{self.L}'] = AL
return AL, caches
我们上面实现的 NeuralNetwork 类提供了一个完整的框架,用于创建和使用具有可自定义架构的神经网络。让我们分解其关键组件:
初始化:网络使用指定的层维度和激活函数进行配置。它使用 He 初始化方法自动初始化权重和偏置,以获得更好的训练性能。
前向传播:我们刚刚实现的
forward_propagation方法是神经网络预测能力的核心。它:- 接收输入数据并将其传递 through each layer
- 在每一层应用适当的激活函数
- 将中间值存储在缓存中以供反向传播使用
- 返回最终输出和所有缓存值
激活函数:网络支持多种激活函数,包括 ReLU、sigmoid 和 softmax,使其能够处理不同类型的问题(回归或分类)。
灵活的架构:该实现允许任意深度和宽度的网络,使其适用于各种机器学习任务。
此实现遵循标准的神经网络设计模式,其中数据在网络中向前流动,每一层在将数据传递给下一层之前对其进行变换。
现在让我们用一个小的示例网络测试我们的实现:
# 创建一个示例网络
# 输入层: 3 个特征
# 隐藏层 1: 4 个神经元,使用 ReLU 激活
# 输出层: 2 个神经元,使用 sigmoid 激活(二分类)
layer_dims = [3, 4, 2]
activations = ["relu", "sigmoid"]
nn = NeuralNetwork(layer_dims, activations)
# 创建示例输入数据 - 5 个样本,每个有 3 个特征
X = np.random.randn(3, 5)
# 执行前向传播
output, caches = nn.forward_propagation(X)
print(f"输入形状: {X.shape}")
print(f"输出形状: {output.shape}")
print(f"输出值:\n{output}")
输出:
输入形状: (3, 5)
输出形状: (2, 5)
输出值:
[[0.00386784 0.54343014 0.39661893 0.5 0.51056934]
[0.11877049 0.32541093 0.44840699 0.5 0.45586633]]
让我们也可视化数据在网络中流动时的变换:
def visualize_activations(caches, example_idx=0):
"""可视化单个样本在网络中的激活值"""
plt.figure(figsize=(15, 6))
# 绘制输入
plt.subplot(1, 3, 1)
plt.bar(range(caches['A0'].shape[0]), caches['A0'][:, example_idx])
plt.title('输入层')
plt.xlabel('特征')
plt.ylabel('值')
# 绘制隐藏层激活值
plt.subplot(1, 3, 2)
plt.bar(range(caches['A1'].shape[0]), caches['A1'][:, example_idx])
plt.title('隐藏层 (ReLU 激活)')
plt.xlabel('神经元')
plt.ylabel('激活值')
# 绘制输出层
plt.subplot(1, 3, 3)
plt.bar(range(caches['A2'].shape[0]), caches['A2'][:, example_idx])
plt.title('输出层 (Sigmoid 激活)')
plt.xlabel('神经元')
plt.ylabel('激活值')
plt.tight_layout()
plt.show()
# 可视化第一个样本
visualize_activations(caches)

这个可视化帮助我们理解输入数据在网络传播过程中是如何变换的。
我们可以看到:
- 输入特征可能有正负值
- 隐藏层经过 ReLU 激活后,所有激活值都是非负的
- 输出层的 sigmoid 激活将值限制在 0 到 1 之间
我们的“从零开始”实现展示了前向传播的核心原理,但现代深度学习框架提供了更高效和灵活的工具来构建神经网络。
使用深度学习框架
现在,让我们使用流行的深度学习框架 TensorFlow 和 PyTorch 实现相同的神经网络。这些框架优化了性能并提供了更高层次的抽象,使构建复杂模型变得更加容易。
首先,让我们看看 TensorFlow/Keras 实现:
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Input
# 为了可重现性
tf.random.set_seed(42)
# 创建相同的网络架构
tf_model = Sequential([
Input(shape=(3,)), # 输入层,3 个特征
Dense(4, activation='relu'), # 隐藏层,4 个神经元
Dense(2, activation='sigmoid') # 输出层,2 个神经元
])
tf_model.summary()
TensorFlow 模型摘要示例
# 具有 Keras 期望形状的示例输入数据
X_tf = np.random.randn(5, 3) # 5 个样本,3 个特征
# TensorFlow 中的前向传播
tf_output = tf_model.predict(X_tf)
print(f"TensorFlow 输出形状: {tf_output.shape}")
print(f"TensorFlow 输出值:\n{tf_output}")
输出:
TensorFlow 输出形状: (5, 2)
TensorFlow 输出值:
[[0.6855308 0.7139542 ]
[0.4952355 0.5231934 ]
[0.50174904 0.5198488 ]
[0.44331825 0.6860926 ]
[0.6624589 0.5385444 ]]
我们刚刚使用 TensorFlow/Keras 实现了我们的神经网络,并成功地对一些示例数据执行了前向传播。模型摘要显示了我们的架构:输入层接受 3 个特征,隐藏层有 4 个使用 ReLU 激活的神经元,输出层有 2 个使用 sigmoid 激活的神经元。总共,该模型有 26 个可训练参数。
前向传播结果显示输出形状为 (5, 2),对应我们的 5 个输入样本,每个样本产生 2 个输出值。由于输出层使用了 sigmoid 激活函数,这些输出被限制在 0 到 1 之间。
接下来,让我们看看如何使用另一个流行的深度学习框架 PyTorch 实现相同的神经网络架构,以比较这两种方法。
import torch
import torch.nn as nn
# 为了可重现性
torch.manual_seed(42)
# 在 PyTorch 中创建网络
class PyTorchNN(nn.Module):
def __init__(self):
super(PyTorchNN, self).__init__()
self.hidden = nn.Linear(3, 4) # 3 个输入,4 个隐藏神经元
self.relu = nn.ReLU()
self.output = nn.Linear(4, 2) # 来自隐藏层的 4 个输入,2 个输出
self.sigmoid = nn.Sigmoid()
def forward(self, x):
x = self.relu(self.hidden(x))
x = self.sigmoid(self.output(x))
return x
# 实例化模型
torch_model = PyTorchNN()
# 打印模型结构
print(torch_model)
输出:
PyTorchNN(
(hidden): Linear(in_features=3, out_features=4, bias=True)
(relu): ReLU()
(output): Linear(in_features=4, out_features=2, bias=True)
(sigmoid): Sigmoid()
)
# 示例输入数据
X_torch = torch.randn(5, 3) # 5 个样本,3 个特征
# PyTorch 中的前向传播
torch_output = torch_model(X_torch)
print(f"PyTorch 输出形状: {torch_output.shape}")
print(f"PyTorch 输出值:\n{torch_output}")
输出:
PyTorch 输出形状: torch.Size([5, 2])
PyTorch 输出值:
tensor([[0.4516, 0.4116],
[0.4289, 0.4267],
[0.4278, 0.4172],
[0.3771, 0.4321],
[0.5769, 0.3328]], grad_fn=<SigmoidBackward0>)
让我们比较我们的实现:
# 比较输出的维度和结构
print("\n实现比较:")
print(f"NumPy 输出形状: {output.shape}")
print(f"TensorFlow 输出形状: {tf_output.shape}")
print(f"PyTorch 输出形状: {torch_output.shape}")
输出:
实现比较:
NumPy 输出形状: (2, 5)
TensorFlow 输出形状: (5, 2)
PyTorch 输出形状: torch.Size([5, 2])
我们实现的关键差异:
结构和可读性:
- NumPy:显式实现每个步骤,提供完全控制和透明度
- TensorFlow/Keras:高级抽象隐藏了实现细节,但使代码更简单
- PyTorch:面向对象的方法,具有显式的 forward 方法
性能:
- TensorFlow 和 PyTorch 为 GPU 加速优化操作
- 两个框架都实现了高效的梯度计算以进行训练
- 框架为大型网络优化内存使用
可扩展性:
- 框架提供预构建组件(层、优化器等)
- 内置工具用于保存/加载模型、可视化和部署
- 广泛的社区支持和文档
对于简单网络,我们的 NumPy 实现工作良好,但随着复杂性的增加,深度学习框架变得必不可少。它们处理低级优化并为整个机器学习工作流程提供工具,从开发到部署。
在下一节中,我们将构建一个完整的可工作示例,将前向传播应用于现实世界的问题。
前向传播示例
现在我们已经从零开始以及使用流行框架实现了前向传播,让我们在一个现实世界的问题上看看它的实际应用。我们将构建一个完整的示例,并探索如何优化该过程以获得更好的性能。
完整的可工作示例
让我们将我们的理解应用于一个经典的机器学习问题:使用 MNIST 数据集进行手写数字识别。该数据集包含 70,000 张手写数字(0-9)的图像,每张图像大小为 28x28 像素。
首先,让我们加载和准备数据:
from tensorflow.keras.datasets import mnist
import numpy as np
import matplotlib.pyplot as plt
# 加载 MNIST 数据集
(X_train, y_train), (X_test, y_test) = mnist.load_data()
# 将像素值归一化到范围 [0, 1]
X_train = X_train / 255.0
X_test = X_test / 255.0
# 将图像重塑为向量(将 28x28 展平为 784)
X_train_flat = X_train.reshape(X_train.shape[0], -1).T # 形状: (784, 60000)
X_test_flat = X_test.reshape(X_test.shape[0], -1).T # 形状: (784, 10000)
# 将标签转换为 one-hot 编码
def one_hot_encode(y, num_classes=10):
one_hot = np.zeros((num_classes, y.size))
one_hot[y, np.arange(y.size)] = 1
return one_hot
y_train_one_hot = one_hot_encode(y_train) # 形状: (10, 60000)
y_test_one_hot = one_hot_encode(y_test) # 形状: (10, 10000)
# 显示样本图像
plt.figure(figsize=(10, 5))
for i in range(5):
plt.subplot(1, 5, i+1)
plt.imshow(X_train[i], cmap='gray')
plt.title(f"标签: {y_train[i]}")
plt.axis('off')
plt.tight_layout()
plt.show()
神经网络数字分类的输出
现在,让我们使用我们的 NumPy 实现为这个任务构建一个神经网络。我们将创建一个具有以下结构的网络:
- 输入层:784 个神经元(28x28 像素)
- 隐藏层:128 个神经元,使用 ReLU 激活
- 输出层:10 个神经元,使用 softmax 激活(用于 10 个数字)
# 定义我们的网络架构
layer_dims = [784, 128, 10]
activations = ["relu", "softmax"]
nn = NeuralNetwork(layer_dims, activations)
# 为演示取一个小批量
batch_size = 64
batch_indices = np.random.choice(X_train_flat.shape[1], batch_size, replace=False)
X_batch = X_train_flat[:, batch_indices]
y_batch = y_train_one_hot[:, batch_indices]
# 执行前向传播
output, caches = nn.forward_propagation(X_batch)
# 计算准确率
predictions = np.argmax(output, axis=0)
true_labels = np.argmax(y_batch, axis=0)
accuracy = np.mean(predictions == true_labels)
print(f"批量准确率: {accuracy:.4f}")
输出:批量准确率: 0.0781
我们得到了 7% 的准确率,这甚至比随机猜测模型还要差。但这在意料之中,因为我们只执行了前向传播——没有涉及任何学习过程。
现在,让我们可视化网络如何通过每一层处理单个图像:
def visualize_network_processing(nn, image, label, caches):
"""可视化单个图像的网络处理过程"""
plt.figure(figsize=(15, 10))
# 绘制原始图像
plt.subplot(2, 3, 1)
plt.imshow(image.reshape(28, 28), cmap='gray')
plt.title(f"输入图像 (数字: {label})")
plt.axis('off')
# 绘制展平的输入(前 100 个值)
plt.subplot(2, 3, 2)
plt.bar(range(100), image.flatten()[:100])
plt.title("输入层 (前 100 个像素)")
plt.xlabel("像素索引")
plt.ylabel("像素值")
# 绘制隐藏层激活值(前 50 个神经元)
plt.subplot(2, 3, 3)
hidden_activations = caches['A1'][:50, 0]
plt.bar(range(len(hidden_activations)), hidden_activations)
plt.title("隐藏层激活值 (前 50 个神经元)")
plt.xlabel("神经元索引")
plt.ylabel("激活值")
# 绘制输出层激活值
plt.subplot(2, 3, 4)
output_activations = caches['A2'][:, 0]
plt.bar(range(10), output_activations)
plt.xticks(range(10))
plt.title("输出层激活值 (Softmax)")
plt.xlabel("数字类别")
plt.ylabel("概率")
# 绘制预测 vs 实际
plt.subplot(2, 3, 5)
prediction = np.argmax(output_activations)
plt.bar(['实际', '预测'], [label, prediction], color=['blue', 'orange'])
plt.title(f"预测: {prediction}, 实际: {label}")
plt.ylabel("数字")
plt.tight_layout()
plt.show()
# 可视化我们批量中第一张图像的前向传播
image_idx = 0
image = X_batch[:, image_idx].reshape(784, 1)
label = true_labels[image_idx]
visualize_network_processing(nn, image, label, caches)

这个可视化显示了信息如何在我们的网络中流动:
- 输入层:展平的图像像素(值在 0 到 1 之间)
- 隐藏层:ReLU 激活的神经元捕捉数字的不同特征
- 输出层:softmax 激活给出 10 个可能数字的概率分布
最活跃的输出神经元对应于网络的预测。即使使用随机权重(因为我们还没有训练网络),我们也可以看到前向传播如何将输入数据转换为预测。
为了获得更完整的示例,我们会使用反向传播训练这个网络来调整权重和偏置,随着时间的推移改进预测。然而,即使没有训练,这个示例也展示了在真实数据上的前向传播过程。
优化前向传播
随着神经网络变得更大,数据集变得更加庞大,优化前向传播变得至关重要。以下是提高前向传播效率的关键策略:
1. 批处理(Batch processing)
不是一次处理一个样本,而是同时在一批样本上计算前向传播。这利用了现代硬件的并行处理能力。
2. 向量化(Vectorization)
使用矩阵运算而不是循环可以显著加快计算速度。这就是为什么我们的实现使用了 NumPy 的数组操作。
3. 内存管理
对于大型网络,我们需要小心内存使用。技术包括:
- 重用中间激活值的内存
- 逐层计算激活值而不存储所有中间值
- 使用较低精度的表示(例如,16 位浮点数而不是 32 位)
4. GPU 加速
图形处理单元在矩阵运算方面表现出色。现代框架可以无缝地将计算移动到 GPU 上以获得显著的速度提升:
# TensorFlow GPU 加速示例
import tensorflow as tf
# 检查可用的 GPU
print("可用的 GPUs:", tf.config.list_physical_devices('GPU'))
# TensorFlow 自动使用可用的 GPU
with tf.device('/GPU:0'): # 如果有多个 GPU,显式指定
# 如果可用,计算在 GPU 上运行
result = tf_model.predict(X_tf)
5. 专用硬件
除了 GPU,Google 的张量处理单元(TPU)等硬件专门为神经网络运算而设计。
6. 算法优化
某些激活函数和网络架构是为了计算效率而设计的:
- ReLU 比 sigmoid 或 tanh 计算更快
- 深度可分离卷积相比标准卷积减少了计算量
- 网络剪枝移除不必要的连接,减少计算
7. 框架优化
深度学习框架实现了众多低级优化:
- 融合操作将多个步骤合并为单个优化的内核
- 自动调优算法为特定硬件选择最快的实现
- JIT(即时)编译为特定网络配置创建优化代码
随着网络变得更深更宽,前向传播优化变得越来越重要。现代架构如 ResNets、Transformers 和 EfficientNets 包含了专门的设计选择,以使前向传播更高效,同时保持或提高模型准确性。
在下一节中,我们将探讨前向传播如何与反向传播(backpropagation)连接,完成神经网络如何学习的完整图景。
与反向传播的关系
我们现在已深入探讨了前向传播,但这只是神经网络故事的一半。让我们简要探讨前向传播如何与反向传播(backpropagation)连接,后者是使神经网络能够学习的算法。
连接前向和反向传播
前向传播和反向传播在神经网络中作为互补过程工作:
- 前向传播从左到右通过网络,将输入转换为预测。
- 反向传播从右到左通过网络,将误差转换为权重更新。
这两个过程在学习过程中是不可分割的。没有先执行前向传播,反向传播就无法发生,因为它需要:
- 每一层的激活值(我们在缓存中存储的)
- 用于与真实标签比较的最终预测
- 前向传播期间使用的网络架构和权重
可以把前向传播看作是神经网络根据其当前理解做出的最佳猜测,而反向传播则是它如何基于错误来完善这种理解。
学习循环
整个学习过程遵循一个循环模式:
- 前向传播:网络处理输入数据并做出预测。
- 损失计算:将预测与实际目标进行比较以计算误差。
- 反向传播:将误差向后传播 through the network 以确定每个权重对误差的贡献。
- 权重更新:调整权重以减少误差。
- 重复:该过程继续使用新数据,直到网络表现良好。
使这个过程强大的是,前向传播为反向传播提供了所需的上下文。在前向传播期间,网络不仅做出预测,还跟踪所有的中间值和决策。然后反向传播使用这些信息进行有针对性的调整。
这个系统的美妙之处在于,复杂的学习从这两个相对简单的过程协同工作中涌现出来。前向传播很简单——它只是按顺序应用的矩阵乘法和激活函数。反向传播更复杂,但直接遵循微积分原理。
它们共同使神经网络能够学习几乎任何模式,只要有足够的数据和计算资源。这种学习能力推动了计算机视觉、自然语言处理、游戏和其他无数领域的突破。
像我们在本文中所做的那样彻底理解前向传播,为掌握神经网络中的完整学习过程奠定了基础。
结论
前向传播是使神经网络能够将输入转换为预测的基础过程。正如我们所探讨的,它涉及顺序的矩阵乘法和激活函数,这些函数通过网络的层逐步变换数据。无论你是从零开始实现网络、使用现代框架,还是排查模型性能问题,理解这一过程都至关重要。
通过掌握前向传播,你已经迈出了构建和理解能够跨领域解决复杂问题的深度学习模型的重要一步。