跳至主要内容

神经网络中的前向传播:完整指南

了解神经网络中的前向传播如何工作,从数学基础到用 Python 的实践实现。借助代码示例与可视化,掌握这一关键深度学习概念。
· 12分钟 阅读

使用 AI 探索

神经网络已经重塑了人工智能,从图像识别到自然语言处理无所不包。而支撑这些强大模型的核心,正是一项基础过程——前向传播。本文将带您从基本原理到实际实现,系统讲解这一核心概念。

如果您希望对本文涉及的诸多概念进行动手实践,欢迎查看我们的Deep Learning in Python 技能路径。

什么是前向传播?

前向传播是神经网络将输入数据转换为预测或输出的过程。可以把它看作神经网络的“思考”阶段——当给定输入(如图像或文本)时,前向传播就是网络如何将信息在层间传递并处理,最终产生结果。

从技术上讲,它是按顺序进行的计算:数据从输入层出发,依次经过隐藏层,最终到达输出层。在此过程中,数据会通过带权连接和激活函数被不断变换,从而使网络能够捕捉复杂模式。

为什么要理解前向传播

理解前向传播至关重要,原因包括:

  1. 学习的基石:不先理解网络如何做出预测,就无法理解其如何学习。前向传播是理解 反向传播(实现学习的算法)的前提。
  2. 调试与优化:当神经网络表现不佳时,了解数据在网络中的流动有助于定位并修复问题。
  3. 模型设计:要设计有效的架构,就需要理解信息在不同层配置中的传播方式。
  4. 部署效率:优化前向传播对在资源受限环境中部署模型至关重要。

您将在本指南中学到什么

在读完本指南后,您将能够:

  • 理解神经网络计算背后的数学原理
  • 使用 NumPy 从零实现前向传播
  • 掌握使用 TensorFlow 和 PyTorch 等现代框架高效执行前向传播
  • 可视化信息在网络中流动时的变换过程
  • 将前向传播与神经网络的更广泛学习过程联系起来
  • 在真实数据上实现完整可运行的示例

先决条件

为充分受益于本指南,您应具备:

  • Python 编程的基础认识
  • 对基础数学概念(向量、矩阵、基础微积分)有一定熟悉度
  • 对机器学习概念有一定接触

如果您希望夯实基础,可以参考以下资源:

即便没有深厚背景,我们也循序渐进设计了本指南,帮助有决心的学习者迈入神经网络的世界。让我们开始吧!

前向传播的基础

要理解前向传播,我们先从其基本构件说起。从神经网络中最小的计算单元开始,逐步构建到更复杂的结构。

单个神经元:一切的起点

神经网络的故事从一个与生物学的有趣类比开始。正如人脑由数十亿个相互连接的神经元构成,人工神经网络由受这些生物细胞启发的数学模型搭建而成。

标注出若干主要结构的简化神经元示意图。

来源:Deep Learning - Visual Approach

在生物学中,神经元通过树突接收来自其他神经元的信号,在胞体中处理这些信号,然后通过轴突将结果传递给其他神经元。在计算模型中,我们用以下元素来对应这一过程:

  • 输入:进入神经元的信号(类似树突接收信号)。
  • 权重:对每个输入的重要性评估(类似突触连接的强度)。
  • 偏置:一个额外参数,帮助神经元在不依赖输入的情况下激活或保持沉默。
  • 激活函数:根据输入决定神经元是否以及多强地“触发”(类似胞体是否产生动作电位)。
  • 输出:发送到下一层的信号(类似轴突传递信号)。

我们用一个单个神经元来直观说明:

展示输入、权重、偏置、激活函数与求和运算的神经网络单个神经元示意图。

来源:Deep Learning - Visual Approach

这个简单的计算单元构成了即便是最复杂神经网络的基础。但神经元究竟如何把输入变成输出?这就要用到数学了。

单个神经元的数学基础

神经元的运作可以用一个简洁的方程来描述:

高层次表示前向传播过程的方程

逐项解释:

  • x 是输入向量 [x_1, x_2, x_3, …]
  • w 是权重向量 [w_1, w_2, …, w_n]
  • b 是偏置(单个标量)
  • z 是加权和加偏置(常称为“预激活”或“logit”)
  • f 是激活函数
  • a 是神经元的最终输出

我们用一个具体数值例子来说明。假设一个神经元有三个输入:

Inputs: x = [2, 5, -1]
Weights: w = [0.5, -1, 2]
Bias: b = 0.5

首先,计算加权和加偏置:

依据前向传播公式的示例计算

接着,应用一个激活函数。我们使用流行的 ReLU(修正线性单元) 函数,定义如下:

将 ReLU 激活函数应用于示例值的公式

预激活值为负数时,ReLU 神经元输出 0,表示对该输入不“触发”。

激活函数至关重要,因为它为网络引入非线性。没有它,即使网络层数再多,也只能学习线性关系。常见的激活函数包括:

  • Sigmoid:Sigmoid 激活函数公式——输出介于 0 与 1 之间,适用于二分类
  • ReLU:ReLU 激活函数公式——简单高效,并有助于缓解梯度消失问题
  • Tanh:Tanh 激活函数公式——输出介于 -1 与 1 之间,常用于隐藏层

不同激活函数各有优势和适用场景,我们在实现网络时会进一步探讨。

从神经元到层

单个神经元功能强大,但神经网络真正的能力源自神经元被组织为层。层就是一组并行处理输入的神经元。通常神经网络包含三类层:

  1. 输入层:接收原始数据并传递给下一层
  2. 隐藏层:处理来自前一层的信息
  3. 输出层:生成最终预测或分类结果

当一层中有多个神经元时,每个神经元都接收相同输入,但具有不同权重和偏置。我们可以用矩阵运算高效表示这种情况。下面看看如何实现。

设想我们有一层,包含 3 个输入值和 4 个神经元。每个神经元都有自己的一组权重和偏置:

Inputs: x = [x₁, x₂, x₃]
Weights for neuron 1: w₁ = [w₁₁, w₁₂, w₁₃]
Weights for neuron 2: w₂ = [w₂₁, w₂₂, w₂₃]
Weights for neuron 3: w₃ = [w₃₁, w₃₂, w₃₃]
Weights for neuron 4: w₄ = [w₄₁, w₄₂, w₄₃]
Biases: b = [b₁, b₂, b₃, b₄]

我们可将这些权重组织为权重矩阵 W:

前向传播过程中权重矩阵 W 的示意

然后,我们可以用一次矩阵乘法同时计算所有神经元的预激活值:

用矩阵乘法计算神经元预激活值

其中:

  • X 为输入向量 [x₁, x₂, x₃]
  • W^T 为权重矩阵的转置
  • B 为偏置向量 [b₁, b₂, b₃, b₄]
  • Z 为得到的预激活向量 [z₁, z₂, z₃, z₄]

接着对每个元素应用激活函数,得到最终输出:

对预激活向量逐元素应用激活函数

这种矩阵表示不仅优雅,而且计算高效。现代硬件(尤其是 GPU)对矩阵运算高度优化,使这种方法远快于逐个神经元单独计算输出。

将这些层堆叠起来——上一层的输出作为下一层的输入——赋予了神经网络学习复杂模式的非凡能力。随着我们把这些积木连接起来,就可以研究前向传播在整个神经网络中如何运作。

完整网络中的前向传播

理解了单个神经元与层之后,我们再来看前向传播如何贯穿整个神经网络。这正是深度学习威力所在。

多层网络:全景图

完整的神经网络由输入层、一个或多个隐藏层,以及输出层组成。“深度”一词指拥有多个隐藏层的网络。每一层都会以更抽象的方式变换数据,从而让网络学到复杂表征。

考虑一个简单的神经网络,包含:

  • 一个具有 4 个特征的输入层
  • 两个隐藏层,分别含 3 个和 2 个神经元
  • 一个包含 3 个神经元的输出层(例如用于多分类)

其结构示意如下:

一个简单的神经网络架构

当数据在该网络中流动时,我们会在每一层执行一系列计算。若记为:

  • 输入向量 X = [x_1, x_2, x_3, x_4]
  • 权重矩阵为 (隐藏层)和 (输出层)
  • 偏置向量为 和
  • 激活函数为 f1(隐藏层)与 f2(输出层)

则整个网络的前向传播可表示为:

1. 计算隐藏层预激活: 

计算隐藏层预激活

2. 应用激活函数得到隐藏层输出: 

应用激活函数得到隐藏层输出

3. 计算输出层预激活: 

计算输出层预激活

4. 应用激活函数得到最终输出:

应用激活函数得到最终输出

最终的 A^[2] 表示网络的预测结果。对于分类任务,它可能是各类别的概率;对于回归任务,则可能是预测值。

不同层常用不同激活函数。例如,隐藏层常用 ReLU,而输出层可能使用:

  • Sigmoid 用于二分类
  • Softmax 用于多分类
  • 线性(无激活)用于回归

这种多层结构的妙处在于每一层都可以学习数据的不同方面。早期层通常检测简单特征,更深层将其组合为更复杂的模式。这种分层学习能力使神经网络在图像与语音识别等复杂任务上大放异彩。

前向传播算法

我们将前向传播过程形式化为一个算法。对于拥有 L 层的神经网络,前向传播遵循以下步骤:

# Pseudocode for forward propagation
def forward_propagation(X, parameters):
   """
   X: Input data (batch_size, n_features)
   parameters: Dictionary containing weights and biases for each layer
   Returns: The final output and all intermediate activations
   """
   # Store all activations for later use (e.g., in backpropagation)
   activations = {'A0': X}  # A0 is the input
  
   # Loop through L-1 layers (excluding the output layer)
   for l in range(1, L):
       # Get previous activation
       A_prev = activations[f'A{l-1}']
      
       # Get weights and biases for current layer
       W = parameters[f'W{l}']
       b = parameters[f'b{l}']
      
       # Compute pre-activation
       Z = np.dot(A_prev, W.T) + b
      
       # Apply activation function (e.g., ReLU for hidden layers)
       A = relu(Z)
      
       # Store values for later use
       activations[f'Z{l}'] = Z
       activations[f'A{l}'] = A
  
   # Compute output layer (layer L)
   A_prev = activations[f'A{L-1}']
   W = parameters[f'W{L}']
   b = parameters[f'b{L}']
  
   # Compute pre-activation for output layer
   Z = np.dot(A_prev, W.T) + b
  
   # Apply output activation function (depends on the task)
   if task == 'binary_classification':
       A = sigmoid(Z)
   elif task == 'multiclass_classification':
       A = softmax(Z)
   elif task == 'regression':
       A = Z  # Linear activation
  
   # Store output layer values
   activations[f'Z{L}'] = Z
   activations[f'A{L}'] = A
  
   return A, activations

该算法突出了前向传播的若干关键点:

  1. 顺序处理:信息严格按层前行,逐层推进。
  2. 激活存储:不仅保存最终输出,也保存所有中间激活,这对使用反向传播训练网络至关重要。
  3. 任务特定的输出:输出层的激活函数依据具体问题(分类或回归)选择。

前向传播算法非常简单,却使神经网络能够逼近极其复杂的函数。与反向传播的恰当训练相结合,这一简单过程让网络能够从数据中学习并做出越来越准确的预测。

我们再通过一个具体示例加深理解。考虑输入 X = [0.5, -0.2, 0.1] 经过如下示例网络:

  • 隐藏层权重 W^[1](4×3 矩阵)与偏置 b^[1](含 4 个值的向量)
  • 输出层权重 W^[2](2×4 矩阵)与偏置 b^[2](含 2 个值的向量)

为简化,设所有权重均为 0.1,所有偏置为 0:

X = [0.5, -0.2, 0.1]
W[1] = [[0.1, 0.1, 0.1], [0.1, 0.1, 0.1], [0.1, 0.1, 0.1], [0.1, 0.1, 0.1]]
b[1] = [0, 0, 0, 0]
W[2] = [[0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1]]
b[2] = [0, 0]

按算法进行:

  • 第一步:

计算隐藏层预激活

  • 第二步(使用 ReLU): 

应用激活函数得到隐藏层输出

  • 第三步:

计算隐藏层预激活

  • 第四步(使用 sigmoid): 

计算输出层预激活

这就得到最终预测。在二分类背景下,接近 0.5 的值表明对两个类别的判断存在不确定性。

下一节,我们将用 Python 实现前向传播,直观展示这些计算过程。

在 Python 中实现前向传播

理解了前向传播的理论后,我们通过在 Python 中实现将其付诸实践。我们先用纯 NumPy “从零开始”实现,然后看看现代深度学习框架如何简化这一过程。

用 NumPy 从零构建

NumPy 提供高效的数组运算,使我们能够实现上述矩阵计算。下面构建一个执行多层前向传播的简单神经网络类。

首先导入必要的库:

import numpy as np
import matplotlib.pyplot as plt

# For reproducibility
np.random.seed(42)

接着定义我们将用到的激活函数:

def relu(Z):
   """ReLU activation function: max(0, Z)"""
   return np.maximum(0, Z)

def sigmoid(Z):
   """Sigmoid activation function: 1/(1 + e^(-Z))"""
   return 1 / (1 + np.exp(-Z))

def softmax(Z):
   """Softmax activation function for multi-class classification"""
   # Subtract max for numerical stability (prevents overflow)
   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):
       """
       Initialize a neural network with specified layer dimensions and activations
      
       Parameters:
       - layer_dims: List of integers representing the number of neurons in each layer
                    (including input and output layers)
       - activations: List of activation functions for each layer (excluding input layer)
       """
       self.L = len(layer_dims) - 1  # Number of layers (excluding input layer)
       self.layer_dims = layer_dims
       self.activations = activations
       self.parameters = {}
      
       # Initialize parameters (weights and biases)
       self.initialize_parameters()
  
   def initialize_parameters(self):
       """Initialize weights and biases with small random values"""
       for l in range(1, self.L + 1):
           # He initialization for weights - helps with training deep networks
           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):
       """
       Perform forward propagation through the network
      
       Parameters:
       - X: Input data (n_features, batch_size)
      
       Returns:
       - AL: Output of the network
       - caches: Dictionary containing all activations and pre-activations
       """
       caches = {}
       A = X  # Input layer activation
       caches['A0'] = X
      
       # Process through L-1 layers (excluding output layer)
       for l in range(1, self.L):
           A_prev = A
          
           # Get weights and biases for current layer
           W = self.parameters[f'W{l}']
           b = self.parameters[f'b{l}']
          
           # Forward propagation for current layer
           Z = np.dot(W, A_prev) + b
          
           # Apply activation function
           activation_function = self.activations[l-1]
           if activation_function == "relu":
               A = relu(Z)
           elif activation_function == "sigmoid":
               A = sigmoid(Z)
          
           # Store values for backpropagation
           caches[f'Z{l}'] = Z
           caches[f'A{l}'] = A
      
       # Output layer
       W = self.parameters[f'W{self.L}']
       b = self.parameters[f'b{self.L}']
       Z = np.dot(W, A) + b
      
       # Apply output activation function
       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  # No activation for regression
      
       # Store output layer values
       caches[f'Z{self.L}'] = Z
       caches[f'A{self.L}'] = AL
      
       return AL, caches

上面的 NeuralNetwork 类提供了一个可自定义架构的完整神经网络框架。其关键组件包括:

  1. 初始化:根据指定的层规模与激活函数配置网络。使用 He 初始化自动初始化权重与偏置,有助于提升训练表现。
  2. 前向传播:我们刚实现的 forward_propagation 方法是网络进行预测的核心。它:
  • 接收输入数据并逐层传递
  • 在每层应用相应激活函数
  • 将中间值存入缓存,以便后续反向传播使用
  • 返回最终输出及所有缓存值

3. 激活函数:网络支持多种激活函数,包括 ReLU、sigmoid 和 softmax,因而既可用于回归也可用于分类。

4. 灵活的架构:该实现允许任意深度与宽度的网络,适用于多种机器学习任务。

该实现遵循标准的神经网络设计范式:数据在网络中前向流动,每一层在传递给下一层之前对数据进行变换。

现在用一个小示例网络测试实现:

# Create a sample network
# Input layer: 3 features
# Hidden layer 1: 4 neurons with ReLU activation
# Output layer: 2 neurons with sigmoid activation (binary classification)
layer_dims = [3, 4, 2]
activations = ["relu", "sigmoid"]
nn = NeuralNetwork(layer_dims, activations)

# Create sample input data - 3 features for 5 examples
X = np.random.randn(3, 5)

# Perform forward propagation
output, caches = nn.forward_propagation(X)

print(f"Input shape: {X.shape}")
print(f"Output shape: {output.shape}")
print(f"Output values:\n{output}")

输出:

Input shape: (3, 5)
Output shape: (2, 5)
Output values:
[[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):
   """Visualize the activations for a single example through the network"""
   plt.figure(figsize=(15, 6))
  
   # Plot input
   plt.subplot(1, 3, 1)
   plt.bar(range(caches['A0'].shape[0]), caches['A0'][:, example_idx])
   plt.title('Input Layer')
   plt.xlabel('Feature')
   plt.ylabel('Value')
  
   # Plot hidden layer activation
   plt.subplot(1, 3, 2)
   plt.bar(range(caches['A1'].shape[0]), caches['A1'][:, example_idx])
   plt.title('Hidden Layer (ReLU Activation)')
   plt.xlabel('Neuron')
   plt.ylabel('Activation')
  
   # Plot output layer
   plt.subplot(1, 3, 3)
   plt.bar(range(caches['A2'].shape[0]), caches['A2'][:, example_idx])
   plt.title('Output Layer (Sigmoid Activation)')
   plt.xlabel('Neuron')
   plt.ylabel('Activation')
  
   plt.tight_layout()
   plt.show()

# Visualize the first example
visualize_activations(caches)

该可视化帮助我们理解输入数据如何在神经网络中传播并被逐步变换。

该可视化显示数据在网络中传播时的变化:

  1. 输入特征可能为正也可能为负
  2. 经过隐藏层的 ReLU 激活后,所有激活值均为非负
  3. 输出层的 sigmoid 激活将数值约束在 0 到 1 之间

“从零开始”的实现展示了前向传播的核心原理,但现代深度学习框架为构建神经网络提供了更高效、更灵活的工具。

使用深度学习框架

接下来,我们用流行的深度学习框架 TensorFlow 和 PyTorch 实现同样的神经网络。这些框架优化了性能,并提供更高层抽象,使构建复杂模型更容易。

首先看看 TensorFlow/Keras 的实现:

import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Input

# For reproducibility
tf.random.set_seed(42)

# Create the same network architecture
tf_model = Sequential([
   Input(shape=(3,)),  # Input layer with 3 features
   Dense(4, activation='relu'),  # Hidden layer with 4 neurons
   Dense(2, activation='sigmoid')  # Output layer with 2 neurons
])

tf_model.summary()

一个示例的 TensorFlow 模型概要

# Sample input data with the shape expected by Keras
X_tf = np.random.randn(5, 3)  # 5 examples, 3 features

# Forward propagation in TensorFlow
tf_output = tf_model.predict(X_tf)

print(f"TensorFlow output shape: {tf_output.shape}")
print(f"TensorFlow output values:\n{tf_output}")
TensorFlow output shape: (5, 2)
TensorFlow output values:
[[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

# For reproducibility
torch.manual_seed(42)

# Create a network in PyTorch
class PyTorchNN(nn.Module):
   def __init__(self):
       super(PyTorchNN, self).__init__()
       self.hidden = nn.Linear(3, 4)  # 3 inputs, 4 hidden neurons
       self.relu = nn.ReLU()
       self.output = nn.Linear(4, 2)  # 4 inputs from hidden, 2 outputs
       self.sigmoid = nn.Sigmoid()
  
   def forward(self, x):
       x = self.relu(self.hidden(x))
       x = self.sigmoid(self.output(x))
       return x

# Instantiate the model
torch_model = PyTorchNN()

# Print model structure
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()
)

```python
# Sample input data
X_torch = torch.randn(5, 3)  # 5 examples, 3 features

# Forward propagation in PyTorch
torch_output = torch_model(X_torch)

print(f"PyTorch output shape: {torch_output.shape}")
print(f"PyTorch output values:\n{torch_output}")

输出:

PyTorch output shape: torch.Size([5, 2])
PyTorch output values:
tensor([[0.4516, 0.4116],
       [0.4289, 0.4267],
       [0.4278, 0.4172],
       [0.3771, 0.4321],
       [0.5769, 0.3328]], grad_fn=<SigmoidBackward0>)

我们来比较这些实现:

# Compare dimensions and structure of outputs
print("\nComparison of implementations:")
print(f"NumPy output shape: {output.shape}")
print(f"TensorFlow output shape: {tf_output.shape}")
print(f"PyTorch output shape: {torch_output.shape}")

输出:

Comparison of implementations:
NumPy output shape: (2, 5)
TensorFlow output shape: (5, 2)
PyTorch output shape: torch.Size([5, 2])

这些实现的主要差异:

  1. 结构与可读性:
  • NumPy:逐步显式实现,完全掌控且透明
  • TensorFlow/Keras:高层抽象屏蔽细节,使代码更简洁
  • PyTorch:面向对象,显式定义 forward 方法
  1. 性能:
  • TensorFlow 与 PyTorch 对 GPU 加速进行了优化
  • 两大框架均高效实现了用于训练的梯度计算
  • 框架在大网络中优化内存使用
  1. 可扩展性:
  • 框架提供大量预构建组件(层、优化器等)
  • 内置模型保存/加载、可视化与部署工具
  • 广泛的社区支持与文档

对于简单网络,NumPy 实现足够使用;但随着复杂度提升,深度学习框架变得不可或缺。它们处理底层优化,并为从开发到部署的完整工作流提供工具。想进一步了解这些框架,请参阅我们的独立指南:

下一节,我们将构建一个完整的工作示例,将前向传播应用于真实问题。

前向传播示例

在我们用从零与框架两种方式实现前向传播之后,来看一个真实问题中的实践。我们将构建一个完整示例,并探讨如何优化流程以提升性能。

完整可运行示例

让我们把所学应用到经典任务:用 MNIST 数据集识别手写数字。该数据集包含 70,000 张 28×28 像素的手写数字(0–9)图像。

首先加载并准备数据:

from tensorflow.keras.datasets import mnist
import numpy as np
import matplotlib.pyplot as plt

# Load MNIST dataset
(X_train, y_train), (X_test, y_test) = mnist.load_data()

# Normalize pixel values to range [0, 1]
X_train = X_train / 255.0
X_test = X_test / 255.0

# Reshape images to vectors (flatten 28x28 to 784)
X_train_flat = X_train.reshape(X_train.shape[0], -1).T  # Shape: (784, 60000)
X_test_flat = X_test.reshape(X_test.shape[0], -1).T     # Shape: (784, 10000)

# Convert labels to one-hot encoding
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)  # Shape: (10, 60000)
y_test_one_hot = one_hot_encode(y_test)    # Shape: (10, 10000)

# Display sample images
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"Label: {y_train[i]}")
   plt.axis('off')
plt.tight_layout()
plt.show()

用于数字分类的神经网络输出示意

现在用我们的 NumPy 实现为此任务构建网络。我们将使用:

  • 输入层:784 个神经元(28×28 像素)
  • 隐藏层:128 个神经元,使用 ReLU 激活
  • 输出层:10 个神经元,使用 softmax 激活(对应 10 个数字)
# Define our network architecture
layer_dims = [784, 128, 10]
activations = ["relu", "softmax"]
nn = NeuralNetwork(layer_dims, activations)

# Take a small batch for demonstration
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]

# Perform forward propagation
output, caches = nn.forward_propagation(X_batch)

# Compute accuracy
predictions = np.argmax(output, axis=0)
true_labels = np.argmax(y_batch, axis=0)
accuracy = np.mean(predictions == true_labels)

print(f"Batch accuracy: {accuracy:.4f}")

Out: Batch accuracy: 0.0781

我们得到 7% 的准确率,甚至低于随机猜测。但这是意料之中的,因为我们只执行了前向传播——并未进行任何学习。

现在可视化网络如何在各层处理一张图像:

def visualize_network_processing(nn, image, label, caches):
   """Visualize network processing for a single image"""
   plt.figure(figsize=(15, 10))
  
   # Plot original image
   plt.subplot(2, 3, 1)
   plt.imshow(image.reshape(28, 28), cmap='gray')
   plt.title(f"Input Image (Digit: {label})")
   plt.axis('off')
  
   # Plot flattened input (first 100 values)
   plt.subplot(2, 3, 2)
   plt.bar(range(100), image.flatten()[:100])
   plt.title("Input Layer (first 100 pixels)")
   plt.xlabel("Pixel Index")
   plt.ylabel("Pixel Value")
  
   # Plot hidden layer activations (first 50 neurons)
   plt.subplot(2, 3, 3)
   hidden_activations = caches['A1'][:50, 0]
   plt.bar(range(len(hidden_activations)), hidden_activations)
   plt.title("Hidden Layer Activations (first 50 neurons)")
   plt.xlabel("Neuron Index")
   plt.ylabel("Activation")
  
   # Plot output layer activations
   plt.subplot(2, 3, 4)
   output_activations = caches['A2'][:, 0]
   plt.bar(range(10), output_activations)
   plt.xticks(range(10))
   plt.title("Output Layer Activations (Softmax)")
   plt.xlabel("Digit Class")
   plt.ylabel("Probability")
  
   # Plot prediction vs actual
   plt.subplot(2, 3, 5)
   prediction = np.argmax(output_activations)
   plt.bar(['Actual', 'Predicted'], [label, prediction], color=['blue', 'orange'])
   plt.title(f"Prediction: {prediction}, Actual: {label}")
   plt.ylabel("Digit")
  
   plt.tight_layout()
   plt.show()

# Visualize forward propagation for the first image in our batch
image_idx = 0
image = X_batch[:, image_idx].reshape(784, 1)
label = true_labels[image_idx]

visualize_network_processing(nn, image, label, caches)

展示数字 4 的图像如何在示例神经网络中流动的示意图

该可视化展示了信息如何在网络中流动:

  1. 输入层:展开后的图像像素(取值在 0 到 1 之间)
  2. 隐藏层:经 ReLU 激活的神经元捕捉到数字的不同特征
  3. 输出层:softmax 激活给出 10 个数字类别上的概率分布

最活跃的输出神经元对应网络的预测。即便权重随机(因为尚未训练),我们也能看到前向传播如何将输入数据转化为预测。

若要更完整的示例,我们会使用反向传播训练网络,调整权重与偏置,使预测不断改进。不过,即使不训练,该示例也展示了前向传播在真实数据上的工作方式。

优化前向传播

随着神经网络规模与数据集体量增大,优化前向传播愈发重要。以下是提升前向传播效率的关键策略:

1. 批处理

与逐样本处理相比,同时对批量样本进行前向传播可以利用现代硬件的并行能力。

2. 向量化

使用矩阵运算代替循环可显著加速计算。这就是我们使用 NumPy 数组运算的原因。

3. 内存管理

在大网络中需要注意内存使用。技巧包括:

  • 复用中间激活的内存。
  • 逐层计算激活,而非保存所有中间结果。
  • 使用更低精度表示(如 16 位浮点替代 32 位)。

4. GPU 加速

图形处理器擅长矩阵运算。现代框架可将计算无缝迁移至 GPU,从而显著提速:

# TensorFlow example of GPU acceleration
import tensorflow as tf

# Check for available GPUs
print("GPUs Available:", tf.config.list_physical_devices('GPU'))

# TensorFlow automatically uses available GPUs
with tf.device('/GPU:0'):  # Explicitly specify GPU if multiple are available
   # Computation runs on GPU if available
   result = tf_model.predict(X_tf)

5. 专用硬件

除了 GPU,还有诸如 Google 的 TPU(张量处理单元)等专为神经网络运算设计的硬件。

6. 算法级优化

某些激活函数与网络架构在计算上更高效:

  • ReLU 的计算速度快于 sigmoid 或 tanh。
  • 深度可分离卷积相较标准卷积能减少计算量。
  • 网络剪枝可去除不必要的连接,降低计算负担。

7. 框架级优化

深度学习框架实现了大量底层优化:

  • 融合算子将多步操作合并为单个优化内核。
  • 自动调优算法为特定硬件选择最快实现。
  • JIT(即时)编译为特定网络配置生成优化代码。

随着网络更深更宽,前向传播优化愈发重要。诸如 ResNet、Transformer、EfficientNet 等现代架构在设计上就兼顾了前向传播效率与模型精度。

下一节,我们将探讨前向传播如何与反向传播(Backpropagation)相互关联,从而完整呈现神经网络如何学习。

与反向传播的关系

我们已经深入探讨了前向传播,但这只是神经网络的“半边天”。下面简要说明前向传播如何与实现学习的反向传播相衔接。

连接前向与后向传播

在神经网络中,前向传播与反向传播彼此配合:

  • 前向传播从左到右处理输入并生成预测。
  • 反向传播从右到左传播误差,并基于预测误差计算梯度以更新权重。

在学习过程中两者缺一不可。反向传播离不开前向传播,因为它需要:

  1. 每一层的激活(我们已存入缓存)。
  2. 最终预测,以便与真实标签比较。
  3. 前向传播中使用的网络架构与权重。

可以将前向传播看作网络基于当前理解给出的“最佳猜测”,而反向传播则是根据错误来修正这种理解的过程。

学习循环

完整的学习过程遵循一个循环模式:

  1. 前向传播:网络处理输入并作出预测。
  2. 损失计算:将预测与真实目标比较,以计算误差。
  3. 反向传播:将误差反向传播,确定每个权重对误差的贡献。
  4. 权重更新:调整权重以减少误差。
  5. 重复:不断用新数据进行以上过程,直至网络表现良好。

这一过程之所以强大,是因为前向传播为反向传播提供了上下文。在前向过程中,网络不仅生成预测,还记录所有中间值与决策;反向传播据此进行针对性调整。

其妙处在于,复杂的学习能力源于这两种相对简单的过程协同工作。前向传播很直观——依次进行矩阵乘法和激活函数;反向传播更复杂,但直接基于微积分原理。

两者结合,使神经网络在有足够数据与算力时几乎能学习任何模式。这一学习能力推动了计算机视觉、自然语言处理、博弈等领域的突破。

如本文所示,深入理解前向传播可为把握神经网络完整学习过程奠定基础。若您想深入学习过程,请参阅我们的 反向传播详解,了解网络如何从错误中学习。

总结与后续

前向传播是神经网络将输入转化为预测的基础过程。正如我们所见,它由一系列顺序的矩阵乘法与激活函数组成,使数据在网络层间逐步变换。无论您是从零实现、使用现代框架,还是排查模型问题,理解这一过程都至关重要。

掌握前向传播,意味着您向构建与理解能够解决复杂问题的深度学习模型迈出了重要一步。

继续您的深度学习之旅,探索 DataCamp 的系统资源:

若您偏好结构化学习路径, Deep Learning in Python 路径将帮助您掌握构建与部署神经网络以应对真实应用所需的一切。

Forward Propagation 常见问答

前向传播与反向传播有什么区别?

前向传播是将数据从输入传到输出以进行预测的过程。反向传播的方向相反,它根据预测误差计算梯度以更新权重。两者在训练过程中协同工作——前向传播负责预测,反向传播帮助网络从错误中学习。

为什么前向传播需要激活函数?

激活函数为神经网络引入非线性,使其能学习超越简单线性关系的复杂模式。没有激活函数,无论有多少层,神经网络都会等同于单层线性模型,学习能力将被严重限制。

现代框架如何高效实现前向传播?

现代框架通过批处理、GPU 加速、向量化运算、内存管理技巧和专用算法来优化前向传播。TensorFlow 和 PyTorch 会自动处理这些优化,让您无需管理底层细节即可构建复杂网络。

不训练网络也能使用前向传播吗?

可以。网络训练完成后,在推理(对新数据进行预测)阶段只进行前向传播。这也是为什么理解前向传播对模型部署至关重要——带反向传播的训练仅在开发阶段需要。

激活函数的选择如何影响前向传播?

不同激活函数会影响网络的学习能力与计算效率。ReLU 计算更快,并有助于防止梯度消失;sigmoid 适合二分类输出;softmax 用于多分类的概率归一化。选择哪种激活函数既影响网络能学到的模式,也影响处理速度。

Bexruz (Bex) Tuychiev

作者

Bexruz (Bex) Tuychiev

我是一名数据科学内容创作者,拥有超过 2 年的经验,并在 Medium 上拥有较大的读者群。我喜欢用略带讽刺的笔调撰写有关 AI 和机器学习的深度文章——毕竟总得想点办法让这些内容不那么枯燥。我已发表 130 多篇文章,并制作了一门 DataCamp 课程,另有一门正在筹备中。我的内容已被超过 500 万人次阅读,其中有 2 万人在 Medium 和 LinkedIn 上成为关注者。 

主题