当前位置: 首页 > news >正文

吴恩达机器学习课程(PyTorch适配)学习笔记:2.2 前向传播与推理

前向传播(Forward Propagation)是神经网络进行预测的核心机制,而推理(Inference)则是利用训练好的模型进行实际预测的过程。理解这两个概念及其在PyTorch中的实现方式,对于掌握深度学习至关重要。

2.2.1 单层前向传播(流程 + 公式)

单层神经网络是深度学习中最基础的结构,理解其前向传播过程是掌握更复杂网络的基础。

单层神经网络结构

一个典型的单层神经网络包含:

  • 输入层:接收输入特征
  • 一个全连接层:包含权重和偏置参数
  • 激活函数:引入非线性变换
  • 输出层:产生预测结果

在这里插入图片描述

图:单层神经网络结构示意图

前向传播流程

单层神经网络的前向传播过程可以分为以下步骤:

  1. 接收输入:获取输入特征向量 xxx
  2. 加权求和:计算输入特征与权重的线性组合,并加上偏置
  3. 激活函数:将线性组合的结果通过激活函数进行非线性变换
  4. 输出结果:产生最终的预测值

数学公式表达

对于单个样本:

  1. 加权求和(线性变换):
    z=w1x1+w2x2+...+wnxn+b=wTx+bz = w_1x_1 + w_2x_2 + ... + w_nx_n + b = \mathbf{w}^T\mathbf{x} + bz=w1x1+w2x2+...+wnxn+b=wTx+b
    其中:

    • x=[x1,x2,...,xn]T\mathbf{x} = [x_1, x_2, ..., x_n]^Tx=[x1,x2,...,xn]T 是输入特征向量
    • w=[w1,w2,...,wn]T\mathbf{w} = [w_1, w_2, ..., w_n]^Tw=[w1,w2,...,wn]T 是权重向量
    • bbb 是偏置项
    • zzz 是线性组合结果
  2. 激活函数(非线性变换):
    y^=f(z)\hat{y} = f(z)y^=f(z)
    其中:

    • f(⋅)f(\cdot)f() 是激活函数
    • y^\hat{y}y^ 是网络的输出(预测值)

对于批量样本(mmm 个样本):

使用矩阵表示可以同时处理多个样本,提高计算效率:
Z=XWT+b\mathbf{Z} = \mathbf{X}\mathbf{W}^T + \mathbf{b}Z=XWT+b
Y^=f(Z)\hat{\mathbf{Y}} = f(\mathbf{Z})Y^=f(Z)

其中:

  • X∈Rm×n\mathbf{X} \in \mathbb{R}^{m \times n}XRm×n 是输入矩阵,每行代表一个样本
  • W∈Rk×n\mathbf{W} \in \mathbb{R}^{k \times n}WRk×n 是权重矩阵,kkk 是输出维度
  • b∈Rk\mathbf{b} \in \mathbb{R}^{k}bRk 是偏置向量
  • Z∈Rm×k\mathbf{Z} \in \mathbb{R}^{m \times k}ZRm×k 是线性组合结果矩阵
  • Y^∈Rm×n\hat{\mathbf{Y}} \in \mathbb{R}^{m \times n}Y^Rm×n 是输出矩阵

PyTorch实现单层前向传播

import torch
import torch.nn as nn
import torch.nn.functional as F# 1. 手动实现单层前向传播
def manual_forward_pass(x, weights, bias, activation='sigmoid'):"""手动实现单层神经网络的前向传播参数:x: 输入张量,形状为(batch_size, input_features)weights: 权重张量,形状为(output_features, input_features)bias: 偏置张量,形状为(output_features,)activation: 激活函数类型"""# 步骤1: 加权求和 z = x * w^T + bz = torch.matmul(x, weights.t()) + bias# 步骤2: 应用激活函数if activation == 'sigmoid':output = torch.sigmoid(z)elif activation == 'relu':output = F.relu(z)elif activation == 'tanh':output = torch.tanh(z)elif activation == 'linear':output = zelse:raise ValueError(f"不支持的激活函数: {activation}")return output, z# 2. 使用PyTorch的nn.Linear实现
class SingleLayerNN(nn.Module):def __init__(self, input_size, output_size, activation='sigmoid'):super(SingleLayerNN, self).__init__()self.linear = nn.Linear(input_size, output_size)self.activation_type = activationdef forward(self, x):# 线性变换z = self.linear(x)# 激活函数if self.activation_type == 'sigmoid':output = torch.sigmoid(z)elif self.activation_type == 'relu':output = F.relu(z)elif self.activation_type == 'tanh':output = torch.tanh(z)elif self.activation_type == 'linear':output = zelse:raise ValueError(f"不支持的激活函数: {self.activation_type}")return output, z# 测试单层前向传播
if __name__ == "__main__":# 随机输入数据 (10个样本,每个样本5个特征)x = torch.randn(10, 5)# 1. 测试手动实现weights = torch.randn(3, 5)  # 3个输出特征,5个输入特征bias = torch.randn(3)output_manual, z_manual = manual_forward_pass(x, weights, bias, activation='relu')print("手动实现输出形状:", output_manual.shape)  # 应该是(10, 3)# 2. 测试PyTorch实现model = SingleLayerNN(input_size=5, output_size=3, activation='relu')output_pytorch, z_pytorch = model(x)print("PyTorch实现输出形状:", output_pytorch.shape)  # 应该是(10, 3)

注意事项与易错点

  1. 维度匹配

    • 确保权重矩阵的维度与输入特征维度匹配
    • 权重矩阵形状应为 (output_size, input_size)
    • 输入数据形状应为 (batch_size, input_size)
  2. 广播机制

    • PyTorch会自动处理偏置的广播(从(output_size,) 广播到 (batch_size, output_size))
    • 理解广播规则有助于避免维度错误
  3. 激活函数选择

    • 根据任务类型选择合适的激活函数
    • 回归问题通常使用线性激活(无激活函数)
    • 二分类问题输出层常用sigmoid
    • 多分类问题输出层常用softmax
  4. 数值范围

    • 注意输入数据的数值范围,极端值可能导致激活函数饱和
    • 例如,sigmoid在输入值很大或很小时会饱和,导致梯度消失

2.2.2 前向传播通用实现(框架思路)

对于深层神经网络,我们需要一种通用的前向传播实现框架,能够灵活处理任意层数和结构的网络。

深层神经网络的前向传播流程

深层神经网络的前向传播是单层网络的自然扩展,数据从输入层开始,依次通过每个隐藏层,最终到达输出层:

  1. 输入层接收原始数据 a[0]=xa^{[0]} = xa[0]=x
  2. 对于每一层 lll(从1到L):
    • 计算加权和:z[l]=W[l]a[l−1]+b[l]z^{[l]} = W^{[l]}a^{[l-1]} + b^{[l]}z[l]=W[l]a[l1]+b[l]
    • 应用激活函数:a[l]=f[l](z[l])a^{[l]} = f^{[l]}(z^{[l]})a[l]=f[l](z[l])
  3. 输出层产生最终预测:y^=a[L]\hat{y} = a^{[L]}y^=a[L]

其中,上标 [l][l][l] 表示第 lll 层的参数或输出。

在这里插入图片描述

图:深层神经网络的前向传播过程

通用实现框架思路

实现深层神经网络的前向传播可以采用以下思路:

  1. 模块化设计:将网络层、激活函数等封装为独立模块
  2. 层的有序组织:使用列表或字典有序地存储网络各层
  3. 循环迭代计算:通过循环依次计算每一层的输出
  4. 灵活配置:允许为不同层配置不同的激活函数和参数

PyTorch实现通用前向传播框架

PyTorch的nn.Modulenn.Sequential提供了构建深层网络的便捷方式:

import torch
import torch.nn as nn
import torch.nn.functional as F# 1. 使用nn.Sequential实现简单的前向传播
def create_sequential_model(input_size, hidden_sizes, output_size, activations=None):"""创建一个序列模型,实现通用前向传播参数:input_size: 输入特征维度hidden_sizes: 列表,每个元素表示隐藏层的大小output_size: 输出维度activations: 列表,每个元素表示对应层的激活函数"""# 默认激活函数为ReLUif activations is None:activations = ['relu'] * len(hidden_sizes)# 输出层默认使用线性激活if output_size > 1:activations.append('softmax')else:activations.append('sigmoid')layers = []sizes = [input_size] + hidden_sizes + [output_size]for i in range(len(sizes) - 1):# 添加线性层layers.append(nn.Linear(sizes[i], sizes[i+1]))# 添加激活函数if activations[i] == 'relu':layers.append(nn.ReLU())elif activations[i] == 'sigmoid':layers.append(nn.Sigmoid())elif activations[i] == 'tanh':layers.append(nn.Tanh())elif activations[i] == 'softmax':layers.append(nn.Softmax(dim=1))elif activations[i] == 'linear':pass  # 不添加激活函数else:raise ValueError(f"不支持的激活函数: {activations[i]}")return nn.Sequential(*layers)# 2. 自定义Module实现更灵活的前向传播
class FlexibleNN(nn.Module):def __init__(self, input_size, hidden_sizes, output_size, activations=None):super(FlexibleNN, self).__init__()self.sizes = [input_size] + hidden_sizes + [output_size]# 默认激活函数if activations is None:activations = ['relu'] * len(hidden_sizes)activations.append('sigmoid' if output_size == 1 else 'softmax')self.activations = activations# 创建线性层self.layers = nn.ModuleList()for i in range(len(self.sizes) - 1):self.layers.append(nn.Linear(self.sizes[i], self.sizes[i+1]))def forward(self, x, return_intermediates=False):"""前向传播参数:x: 输入张量return_intermediates: 是否返回中间层输出"""intermediates = []current = xfor i, layer in enumerate(self.layers):# 线性变换z = layer(current)# 应用激活函数if self.activations[i] == 'relu':current = F.relu(z)elif self.activations[i] == 'sigmoid':current = torch.sigmoid(z)elif self.activations[i] == 'tanh':current = torch.tanh(z)elif self.activations[i] == 'softmax':current = F.softmax(z, dim=1)elif self.activations[i] == 'linear':current = z# 保存中间结果if return_intermediates:intermediates.append((z, current))if return_intermediates:return current, intermediatesreturn current# 测试通用前向传播框架
if __name__ == "__main__":# 网络配置input_size = 10hidden_sizes = [20, 15]  # 两个隐藏层,分别有20和15个神经元output_size = 3           # 输出层3个神经元(多分类)# 1. 测试Sequential模型seq_model = create_sequential_model(input_size, hidden_sizes, output_size)x = torch.randn(5, input_size)  # 5个样本output_seq = seq_model(x)print("Sequential模型输出形状:", output_seq.shape)  # 应该是(5, 3)print("输出概率和:", output_seq.sum(dim=1))  # softmax输出和应为1# 2. 测试自定义灵活模型flex_model = FlexibleNN(input_size, hidden_sizes, output_size)output_flex, intermediates = flex_model(x, return_intermediates=True)print("灵活模型输出形状:", output_flex.shape)  # 应该是(5, 3)# 打印中间层信息print("\n中间层信息:")for i, (z, a) in enumerate(intermediates):print(f"第{i+1}层 - z形状: {z.shape}, 激活值形状: {a.shape}")

通用框架的优势与扩展性

  1. 灵活性:可以轻松调整网络层数和每层大小
  2. 可维护性:模块化设计使代码更易于理解和修改
  3. 可扩展性:可以方便地添加新的层类型或激活函数
  4. 一致性:确保网络各层的前向传播遵循相同的接口规范

注意事项与最佳实践

  1. 网络深度选择

    • 过深的网络可能导致梯度消失/爆炸和训练困难
    • 过浅的网络可能无法捕捉复杂模式
    • 建议从较简单的网络开始,逐步增加复杂度
  2. 激活函数组合

    • 隐藏层通常使用ReLU或其变体
    • 输出层根据任务选择合适的激活函数
    • 避免在深层网络中使用sigmoid和tanh作为隐藏层激活函数
  3. 参数初始化

    • 合理的参数初始化对深层网络至关重要
    • PyTorch的默认初始化通常效果不错
    • 对于特定激活函数(如ReLU),可以使用专门的初始化方法
  4. 中间结果跟踪

    • 在调试时跟踪中间层输出有助于理解网络行为
    • 注意中间层输出的分布,避免激活值过于集中或分散

2.2.3 推理过程(预测逻辑 + PyTorch 适配)

推理是使用训练好的模型对新数据进行预测的过程,它是将模型应用于实际问题的关键步骤。

推理过程概述

推理过程可以分为以下几个关键步骤:

  1. 准备输入数据:获取并预处理新的输入数据
  2. 加载模型与参数:加载训练好的模型结构和权重参数
  3. 执行前向传播:将输入数据通过模型进行前向计算
  4. 解析输出结果:将模型输出转换为有意义的预测结果
  5. 返回最终预测:以合适的形式返回推理结果

预测逻辑详解

根据不同的任务类型,预测逻辑有所不同:

  1. 分类任务

    • 输出层通常使用softmax(多分类)或sigmoid(二分类)激活函数
    • 预测结果为概率分布
    • 通常选择概率最高的类别作为最终预测:y^=arg⁡max⁡(y^prob)\hat{y} = \arg\max(\hat{y}_{prob})y^=argmax(y^prob)
  2. 回归任务

    • 输出层通常不使用激活函数(线性输出)
    • 预测结果为连续值
    • 直接使用输出值作为预测结果,或根据需要进行后处理
  3. 序列任务

    • 输出可能是序列或序列概率分布
    • 可能需要使用束搜索(beam search)等方法生成最终序列

PyTorch适配的推理实现

PyTorch提供了便捷的API来实现推理过程:

import torch
import torch.nn as nn
import torchvision.transforms as transforms
from PIL import Image
import numpy as np# 1. 定义模型(与训练时相同)
class Classifier(nn.Module):def __init__(self, input_size, num_classes):super(Classifier, self).__init__()self.fc1 = nn.Linear(input_size, 128)self.fc2 = nn.Linear(128, 64)self.fc3 = nn.Linear(64, num_classes)def forward(self, x):x = torch.relu(self.fc1(x))x = torch.relu(self.fc2(x))x = torch.softmax(self.fc3(x), dim=1)return x# 2. 通用推理函数
def predict(model, input_data, device='cpu', return_probs=False):"""通用预测函数参数:model: 训练好的模型input_data: 输入数据张量device: 运行设备 ('cpu' 或 'cuda')return_probs: 是否返回概率分布"""# 将模型和数据移动到指定设备model = model.to(device)input_data = input_data.to(device)# 设置为评估模式model.eval()# 禁用梯度计算with torch.no_grad():# 前向传播outputs = model(input_data)# 对于分类任务,获取预测类别if outputs.shape[1] > 1:  # 多分类_, predicted = torch.max(outputs, 1)else:  # 二分类predicted = (outputs > 0.5).float().squeeze()# 转换为numpy数组(如果需要)predicted = predicted.cpu().numpy() if device == 'cuda' else predicted.numpy()if return_probs:probs = outputs.cpu().numpy() if device == 'cuda' else outputs.numpy()return predicted, probsreturn predicted# 3. 针对不同数据类型的推理实现# 3.1 表格数据推理
def tabular_inference(model, input_data, scaler=None):"""表格数据推理,支持特征缩放"""# 数据预处理if scaler is not None:input_data = scaler.transform(input_data)# 转换为张量input_tensor = torch.FloatTensor(input_data)# 确保输入有批次维度if input_tensor.ndim == 1:input_tensor = input_tensor.unsqueeze(0)# 预测return predict(model, input_tensor)# 3.2 图像数据推理
def image_inference(model, image_path, image_size=(32, 32)):"""图像数据推理"""# 图像预处理transform = transforms.Compose([transforms.Resize(image_size),transforms.ToTensor(),transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])])# 加载并预处理图像image = Image.open(image_path).convert('RGB')image_tensor = transform(image).unsqueeze(0)  # 添加批次维度# 预测return predict(model, image_tensor)# 测试推理过程
if __name__ == "__main__":# 创建模型并加载预训练权重input_size = 30  # 示例输入特征数num_classes = 5  # 示例类别数model = Classifier(input_size, num_classes)# 模拟加载预训练权重(实际应用中使用真实权重)# model.load_state_dict(torch.load('model_weights.pth'))# 1. 测试表格数据推理tabular_data = np.random.randn(10, input_size)  # 10个样本predictions = tabular_inference(model, tabular_data)print("表格数据预测结果:", predictions)print("预测结果形状:", predictions.shape)# 2. 测试图像数据推理(需要实际图像文件)# 注意:这里使用占位路径,实际使用时替换为真实图像路径# image_path = "test_image.jpg"# try:#     image_pred = image_inference(model, image_path)#     print("图像预测结果:", image_pred)# except Exception as e:#     print("图像推理测试:", str(e))# 3. 测试返回概率sample_data = torch.randn(1, input_size)pred, probs = predict(model, sample_data, return_probs=True)print("\n样本预测类别:", pred)print("类别概率分布:", probs)print("概率和:", probs.sum())

不同任务类型的推理适配

  1. 二分类任务

    def binary_classification_inference(model, input_data, threshold=0.5):"""二分类任务推理,支持自定义阈值"""input_tensor = torch.FloatTensor(input_data)if input_tensor.ndim == 1:input_tensor = input_tensor.unsqueeze(0)# 获取概率_, probs = predict(model, input_tensor, return_probs=True)probs = probs.squeeze()# 根据阈值判断类别predictions = (probs >= threshold).astype(int)return predictions, probs
    
  2. 多分类任务

    def multiclass_inference(model, input_data, top_k=1):"""多分类任务推理,支持返回Top-K预测结果"""input_tensor = torch.FloatTensor(input_data)if input_tensor.ndim == 1:input_tensor = input_tensor.unsqueeze(0)# 获取概率_, probs = predict(model, input_tensor, return_probs=True)# 获取Top-K预测top_probs, top_indices = torch.topk(torch.tensor(probs), k=top_k, dim=1)return top_indices.numpy(), top_probs.numpy()
    
  3. 回归任务

    def regression_inference(model, input_data, scale_factor=1.0):"""回归任务推理,支持结果缩放"""input_tensor = torch.FloatTensor(input_data)if input_tensor.ndim == 1:input_tensor = input_tensor.unsqueeze(0)# 设置模型为评估模式并预测model.eval()with torch.no_grad():predictions = model(input_tensor).numpy()# 结果缩放(如果需要)predictions = predictions * scale_factorreturn predictions.squeeze()
    

注意事项与优化建议

  1. 输入数据一致性

    • 推理时的输入预处理必须与训练时完全一致
    • 确保特征缩放、标准化等操作使用训练数据的统计量
  2. 设备兼容性

    • 确保模型和输入数据在同一设备上(CPU/GPU)
    • 对于GPU推理,注意批量大小不要超过显存限制
  3. 数值稳定性

    • 注意输入数据范围,避免极端值导致的数值问题
    • 对于概率计算,考虑使用log_softmax避免数值下溢
  4. 推理效率

    • 对于大规模部署,考虑批量处理以提高效率
    • 对于实时应用,可使用模型量化、剪枝等技术加速推理

2.2.4 推理代码框架(eval 模式 + 无梯度计算)

在PyTorch中进行推理时,正确设置模型状态和计算环境至关重要。本部分将介绍完整的推理代码框架,包括eval模式的使用和无梯度计算的实现。

推理代码框架的核心组件

一个完整的推理代码框架应包含以下核心组件:

  1. 模型定义:与训练时一致的网络结构
  2. 模型加载:加载训练好的权重参数
  3. 设备配置:选择合适的计算设备(CPU/GPU)
  4. 数据预处理:对输入数据进行必要的转换
  5. 推理模式设置:将模型设置为eval模式
  6. 无梯度计算上下文:使用torch.no_grad()禁用梯度计算
  7. 前向传播:执行推理计算
  8. 结果后处理:解析和格式化模型输出

完整推理代码框架实现

import torch
import torch.nn as nn
import numpy as np
from typing import Tuple, Union, List# 1. 模型定义(必须与训练时一致)
class BaseModel(nn.Module):"""基础模型类,可根据具体任务继承扩展"""def __init__(self, input_dim: int, output_dim: int):super(BaseModel, self).__init__()self.input_dim = input_dimself.output_dim = output_dimdef forward(self, x: torch.Tensor) -> torch.Tensor:"""前向传播方法,需要在子类中实现"""raise NotImplementedError("子类必须实现forward方法")# 示例模型:用于分类任务
class ClassificationModel(BaseModel):def __init__(self, input_dim: int, num_classes: int, hidden_dims: List[int] = None):super(ClassificationModel, self).__init__(input_dim, num_classes)if hidden_dims is None:hidden_dims = [128, 64]# 构建网络层layers = []prev_dim = input_dimfor dim in hidden_dims:layers.extend([nn.Linear(prev_dim, dim),nn.ReLU(),nn.BatchNorm1d(dim)])prev_dim = dim# 输出层layers.append(nn.Linear(prev_dim, num_classes))self.model = nn.Sequential(*layers)def forward(self, x: torch.Tensor) -> torch.Tensor:logits = self.model(x)return torch.softmax(logits, dim=1)# 2. 推理框架类
class InferenceEngine:def __init__(self, model: nn.Module, weights_path: str, device: str = None):"""初始化推理引擎参数:model: 模型实例weights_path: 模型权重文件路径device: 计算设备,None则自动选择"""# 自动选择设备self.device = self._get_device(device)# 加载模型self.model = model.to(self.device)self._load_weights(weights_path)# 设置为评估模式self.model.eval()# 记录输入输出信息self.input_shape = Noneself.output_shape = Nonedef _get_device(self, device: Union[str, None]) -> str:"""自动选择计算设备"""if device is not None:return devicereturn 'cuda' if torch.cuda.is_available() else 'cpu'def _load_weights(self, weights_path: str) -> None:"""加载模型权重"""try:# 加载权重,忽略可能的设备不匹配问题state_dict = torch.load(weights_path, map_location=self.device)self.model.load_state_dict(state_dict)print(f"成功加载权重: {weights_path}")except Exception as e:raise RuntimeError(f"加载权重失败: {str(e)}")def preprocess(self, input_data: Union[np.ndarray, List[float]]) -> torch.Tensor:"""数据预处理参数:input_data: 原始输入数据返回:处理后的张量"""# 转换为numpy数组if isinstance(input_data, list):input_data = np.array(input_data)# 确保至少有2维(批次维度 + 特征维度)if input_data.ndim == 1:input_data = input_data.reshape(1, -1)# 记录输入形状self.input_shape = input_data.shape# 转换为张量并移动到指定设备input_tensor = torch.FloatTensor(input_data).to(self.device)return input_tensordef postprocess(self, output_tensor: torch.Tensor, return_probs: bool = False) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]:"""结果后处理参数:output_tensor: 模型输出张量return_probs: 是否返回概率分布返回:处理后的预测结果"""# 移动到CPU并转换为numpy数组output_np = output_tensor.cpu().numpy()self.output_shape = output_np.shape# 对于分类任务,获取预测类别if self.model.output_dim > 1:  # 多分类predictions = np.argmax(output_np, axis=1)else:  # 二分类predictions = (output_np > 0.5).astype(int).squeeze()if return_probs:return predictions, output_npreturn predictionsdef predict(self, input_data: Union[np.ndarray, List[float]], return_probs: bool = False) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]:"""执行推理参数:input_data: 输入数据return_probs: 是否返回概率分布返回:预测结果"""# 数据预处理input_tensor = self.preprocess(input_data)# 无梯度计算的前向传播with torch.no_grad():output_tensor = self.model(input_tensor)# 结果后处理return self.postprocess(output_tensor, return_probs)def batch_predict(self, input_data: Union[np.ndarray, List[float]], batch_size: int = 32, return_probs: bool = False) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]:"""批量推理,适用于大量数据参数:input_data: 输入数据batch_size: 批次大小return_probs: 是否返回概率分布返回:预测结果"""# 数据预处理input_tensor = self.preprocess(input_data)num_samples = input_tensor.shape[0]# 初始化结果存储all_predictions = []all_probs = []# 分批处理for i in range(0, num_samples, batch_size):batch = input_tensor[i:i+batch_size]with torch.no_grad():output_tensor = self.model(batch)# 后处理批次结果if return_probs:preds, probs = self.postprocess(output_tensor, return_probs=True)all_predictions.append(preds)all_probs.append(probs)else:preds = self.postprocess(output_tensor)all_predictions.append(preds)# 合并结果if return_probs:return np.concatenate(all_predictions), np.concatenate(all_probs)return np.concatenate(all_predictions)# 3. 使用示例
if __name__ == "__main__":# 配置INPUT_DIM = 30NUM_CLASSES = 5WEIGHTS_PATH = "model_weights.pth"  # 替换为实际权重路径# 创建模型实例model = ClassificationModel(input_dim=INPUT_DIM, num_classes=NUM_CLASSES)# 创建推理引擎try:# 注意:这里使用了模拟权重,实际应用中请使用真实训练好的权重# engine = InferenceEngine(model, WEIGHTS_PATH)# 为了演示,我们创建一个不加载权重的引擎class DummyInferenceEngine(InferenceEngine):def _load_weights(self, weights_path):print("演示模式:未加载实际权重")engine = DummyInferenceEngine(model, WEIGHTS_PATH)print(f"使用设备: {engine.device}")# 生成测试数据test_data = np.random.randn(100, INPUT_DIM)  # 100个测试样本# 单批推理predictions, probs = engine.predict(test_data[:5], return_probs=True)print("\n前5个样本的预测结果:", predictions)print("预测概率形状:", probs.shape)# 批量推理batch_preds = engine.batch_predict(test_data, batch_size=16)print("\n批量预测结果形状:", batch_preds.shape)except Exception as e:print(f"推理过程出错: {str(e)}")

eval模式的重要性

在PyTorch中,model.eval()方法用于将模型设置为评估模式,这对推理至关重要,因为:

  1. Batch Normalization行为改变

    • 训练时:使用批次的均值和方差
    • 评估时:使用训练过程中累积的移动均值和方差
  2. Dropout层行为改变

    • 训练时:随机丢弃部分神经元
    • 评估时:不丢弃任何神经元,使用所有神经元
  3. 其他训练特定层

    • 如InstanceNorm、LayerNorm等归一化层的行为调整
    • 某些自定义层可能有训练/评估模式的区别
# 演示eval模式的影响
def demonstrate_eval_mode():# 创建一个包含BatchNorm和Dropout的模型class TestModel(nn.Module):def __init__(self):super(TestModel, self).__init__()self.fc = nn.Linear(10, 10)self.bn = nn.BatchNorm1d(10)self.dropout = nn.Dropout(0.5)def forward(self, x):x = self.fc(x)x = self.bn(x)x = self.dropout(x)return xmodel = TestModel()x = torch.randn(5, 10)  # 5个样本,10个特征# 训练模式model.train()output_train1 = model(x)output_train2 = model(x)  # Dropout会导致不同结果print("训练模式两次输出是否相同:", torch.allclose(output_train1, output_train2))  # 应该是False# 评估模式model.eval()output_eval1 = model(x)output_eval2 = model(x)  # 无Dropout,结果应相同print("评估模式两次输出是否相同:", torch.allclose(output_eval1, output_eval2))  # 应该是Truedemonstrate_eval_mode()

无梯度计算的优势

使用torch.no_grad()上下文管理器禁用梯度计算有以下优势:

  1. 节省内存:不需要存储计算图和梯度信息,减少内存占用
  2. 提高速度:跳过梯度计算步骤,加快推理速度
  3. 避免意外修改:防止在推理过程中意外修改模型参数
# 演示无梯度计算的性能优势
def demonstrate_no_grad_performance():model = ClassificationModel(input_dim=100, num_classes=10, hidden_dims=[200, 100])x = torch.randn(1000, 100)  # 1000个样本# 有梯度计算model.train()start_time = torch.utils.benchmark.default_timer()with torch.enable_grad():  # 显式启用梯度output = model(x)time_with_grad = torch.utils.benchmark.default_timer() - start_time# 无梯度计算model.eval()start_time = torch.utils.benchmark.default_timer()with torch.no_grad():  # 禁用梯度output = model(x)time_without_grad = torch.utils.benchmark.default_timer() - start_timeprint(f"有梯度计算时间: {time_with_grad:.6f}秒")print(f"无梯度计算时间: {time_without_grad:.6f}秒")print(f"速度提升: {time_with_grad / time_without_grad:.2f}倍")demonstrate_no_grad_performance()

推理框架的扩展与定制

根据具体需求,可以扩展推理框架:

  1. 添加模型验证:验证输入输出形状是否符合预期
  2. 实现缓存机制:缓存频繁使用的输入的推理结果
  3. 添加性能计时:记录预处理、推理和后处理各阶段的时间
  4. 支持模型 ensemble:集成多个模型的预测结果
  5. 添加结果解释:集成SHAP、LIME等模型解释工具

注意事项与最佳实践

  1. 模型状态保存

    • 保存模型时建议只保存state_dict而非整个模型
    • 确保保存和加载的模型结构完全一致
  2. 设备管理

    • 推理时可以使用与训练不同的设备
    • 对于GPU推理,注意释放不再使用的张量以节省显存
  3. 异常处理

    • 对输入数据进行有效性检查
    • 处理可能的文件加载错误和设备错误
  4. 可重复性

    • 对于需要可重复结果的场景,设置随机种子
    • 注意某些操作在GPU上可能具有非确定性
  5. 日志记录

    • 记录推理过程中的关键信息(输入形状、输出形状、耗时等)
    • 对错误和异常情况进行详细日志记录

小结

前向传播是神经网络将输入映射到输出的核心过程,而推理则是利用训练好的模型进行实际预测的关键环节。本章详细介绍了单层和深层网络的前向传播流程与实现方式,解释了推理过程的完整逻辑,并提供了基于PyTorch的推理代码框架。

重点需要掌握:

  • 前向传播的数学原理和实现方式
  • PyTorch中eval模式的重要性及其对网络层的影响
  • torch.no_grad()在推理中的应用及其性能优势
  • 完整推理框架的构建,包括数据预处理、模型加载、前向传播和结果后处理
http://www.dtcms.com/a/457323.html

相关文章:

  • 530.二叉搜索树的最小绝对差(二叉树算法题)
  • 如何解决 pip install -r requirements.txt extras 语法 ‘package[extra’ 缺少 ‘]’ 解析失败问题
  • 青岛网站建设方案辽阳网站建设
  • 磁悬浮轴承中基于位移信号的转子位置与转速估计深度解析摘要
  • 个人和做网站方签合同模板一流的聊城网站建设
  • 什么是UIOTOS?
  • 网站建设书籍论文龙网网络推广软件
  • 蚌埠网站制作公司排名wordpress lazyload
  • Windows中通过wsl运行Ubuntu
  • TensorFlow2 Python深度学习 - TensorFlow2框架入门 - 计算图和 tf.function 简介
  • 怎样理解网站建设与开发这门课郑州妇科医院正规有哪些
  • 使用 C 语言连接 MySQL 客户端(重点)
  • 西安网站建设价格明细网站建设项目合同
  • 中国建设银行密码重置网站邯郸模板建站教程
  • HFish架构深度解析:从蜜罐诱捕到威胁狩猎的完整技术链路
  • 最小栈GO实现
  • 福田欧辉是国企吗做百度手机网站优化点
  • npm 扩展vite
  • 和15岁女儿做很舒服网站最新网页版传奇
  • 01.MMDetection3D训练
  • 手机 网站制作什么网站资源多
  • C++之日期类的实现
  • 构建AI智能体:五十七、LangGraph + Gradio:构建可视化AI工作流的趣味指南
  • Create/Assemble/Link x64 Windows
  • 网站建设与管理案例教程第三版答案中国货源大全网
  • 织梦建网站建设收费网站
  • Delphi字段值含有空格
  • 【第五章:计算机视觉-项目实战之生成式算法实战:扩散模型】2.CV黑科技:生成式算法理论-(3)经典扩散模型DDPM算法流程讲解
  • 牛客算法_哈希
  • Product Hunt 每日热榜 | 2025-10-08