深度学习-多分类

在这里插入图片描述
​开头摘要​​:
本文将深入探讨如何使用PyTorch实现基于Softmax回归的MNIST手写数字识别系统。从多分类问题的核心概念出发,详细解析​​One-Hot编码​​技术如何将类别标签向量化,剖析​​交叉熵损失函数​​的数学原理及其在训练中的优化机制。通过完整代码实战,展示数据加载、网络构建、模型训练与评估的全流程,并重点介绍​​TensorBoard可视化​​工具的应用技巧。本教程涵盖模型保存加载、预测结果可视化等实用功能,为初学者提供从理论到实践的全面指导,帮助快速掌握分类任务的核心技术栈。

文章目录

    • 多分类
    • one_hot编码
      • 编码原理
    • 交叉熵损失函数
      • 示例
    • 实战案例基于mnnist的手写识别
      • TensorBoard的使用
      • 部分代码解释
    • 总体实现
    • 核心收获
      • 代码

多分类

  • 输出是每个类别的概率,要求有多少个分类最终的输出层就有多少个神经元
  • 各分类输出概率的总和是1(使用softmax归一化)
    在这里插入图片描述

one_hot编码

One-Hot 编码是机器学习中处理​​分类数据​​的核心技术,它将离散类别转换为向量表示,使其能被神经网络处理。

一句话:在一个样本中:n个分类,结果是第k个,则yone-hot=[0,⋯,1⏟第k位,⋯,0]y_{\text{one-hot}} = [0, \cdots, \underbrace{1}_{\text{第k位}}, \cdots, 0]yone-hot=[0,,k1,,0]

编码原理

对于一个包含 C 个类别的特征:

  • 创建长度为 C 的零向量
  • 对第 k 类数据,将其向量中第 k 个位置设为 1
  • 其余位置保持为 0

yone-hot=[0,⋯,1⏟第k位,⋯,0]y_{\text{one-hot}} = [0, \cdots, \underbrace{1}_{\text{第k位}}, \cdots, 0]yone-hot=[0,,k1,,0]

假设动物分类的类别为:[“狗”, “猫”, “鸟”]
类别 One-Hot 编码
狗 [1, 0, 0]
猫 [0, 1, 0]
鸟 [0, 0, 1]

交叉熵损失函数

一句话:在H(y,pi)=−∑xylog⁡piH(y,p_i)=-\sum_{x}y\log p_iH(y,pi)=xylogpi中,只有一条1*log(pi)为最终交叉熵损失函数值,其它都是0*log(pi)=0

交叉熵损失函数与逻辑回归中的损失函数效果相同,都是为如何调整参数指明方向,即通过求取梯度,调整参数使损失函数的值逼近0,只是交叉熵损失函数用在多分类中
L(y^,y)=−ylog⁡(y^)−(1−y)log⁡(1−y^)L(\hat{y}, y) = - y \log(\hat{y}) - (1 - y) \log(1 - \hat{y}) L(y^,y)=ylog(y^)(1y)log(1y^)

交叉熵损失函数
H(y,pi)=−∑xylog⁡piH(y,p_i)=-\sum_{x}y\log p_iH(y,pi)=xylogpi

yi:是真实标签(真实分布),通常采用one−hot编码(真实类别为1,其余为0)y_i:是真实标签(真实分布),通常采用one-hot编码(真实类别为1,其余为0)yi:是真实标签(真实分布),通常采用onehot编码(真实类别为1,其余为0
(乘的时候按类,分开乘了,0*log or 1*log ,单个样本,最终结果取决于那个唯一的1*log的值)

pi:是预测概率(模型输出的概率分布)p_i:是预测概率(模型输出的概率分布)pi:是预测概率(模型输出的概率分布)

log⁡(pi):是预测概率的对数值\log(p_i):是预测概率的对数值log(pi):是预测概率的对数值

整体计算:是真实标签yi与预测概率对数log⁡(pi)的乘积再求和取负整体计算:是真实标签y_i与预测概率对数\log(p_i)的乘积再求和取负整体计算:是真实标签yi与预测概率对数log(pi)的乘积再求和取负

示例

在这里插入图片描述

  • L=0.357 表示当前预测不够准确(理想值应接近0)
  • Log的底数无所谓,经过训练,任何底数的结果都是相同的

log(x)
在这里插入图片描述

实战案例基于mnnist的手写识别

TensorBoard的使用

from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter(comment='test_tensorboard')  # 用于记录要可视化的数据
#writer = SummaryWriter(存放的地址)

如果不指定绝对路径,PyTorch 默认创建runs在当前文件夹下
在这里插入图片描述

在你安装TensorBoard的虚拟py环境中运行以下代码即可

tensorboard --logdir="这个event文件所在目录的绝对地址"

在这里插入图片描述
在这里插入图片描述

部分代码解释

import torch
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
from torchvision.datasets import MNIST
import matplotlib.pyplot as plt
import os
import numpy as np
from datetime import datetime
import os
log_dir = "runs"
os.makedirs(log_dir, exist_ok=True)

创建一个名为runs的文件夹

import matplotlib.pyplot as plt
writer = SummaryWriter(log_dir=log_subdir)

记录数据便于后续画图

3.定义网络基本框架

class SoftmaxRegression(torch.nn.Module):#括号中的 torch.nn.Module 表示你的 SoftmaxRegression 类 继承自 PyTorch 的 Module 基类def __init__(self):  #self 指代 当前类的实例对象(即正在创建的具体模型)super().__init__()# 单层网络结构:784输入 -> 10输出self.linear = torch.nn.Linear(28 * 28, 10)#定义了这个网络的基本结构,有784个输入特征,10个输出def forward(self, x):# 应用log_softmax到线性层输出return torch.nn.functional.log_softmax(self.linear(x), dim=1)

log_softmax(self.linear(x), dim=1)相当于两步
softmax_output = exp(z_i) / sum(exp(z_j)) # 转换为概率分布
log_softmax = log(softmax_output) # 取自然对数

torch.nn.functional.log_softmax(self.linear(x), dim=1),dim=1
im=0:跨样本操作(通常不需要)(每个样本的第n类概率加起来为1)
dim=1:跨类别操作(分类任务的标准做法)(每个样本的n个类的各个概率加起来为1)

在这个模型中没有隐藏层,这是一个单层神经网络(也称为 Softmax 回归或多元逻辑回归),是直接从784个输出特征到,输出层的十个输出神经元

线性变换-神经元a0
线性变换-神经元a1
1......1
线性变换-神经元a9
输入的784个特征x
激活函数log_softmax(z)

4.加载训练数据

def get_data_loader(is_train, batch_size=128):transform = transforms.Compose([transforms.ToTensor(),#张量化图像transforms.Normalize((0.1307,), (0.3081,))  # MNIST的均值和标准差])dataset = MNIST(root='./data', train=is_train, download=True, transform=transform)return DataLoader(dataset, batch_size=batch_size, shuffle=is_train, pin_memory=True)

dataset = MNIST( # 创建MNIST数据集对象
root=‘./data’, # 指定数据存储路径为当前目录下的data文件夹
train=is_train, # 确定加载训练集(True)还是测试集(False)
download=True, # 如果本地没有数据集则自动下载
transform=transform # 应用指定的数据预处理转换
)
return DataLoader( # 返回数据加载器
dataset, # 使用的数据集对象
batch_size=batch_size, # 指定每次加载的数据批大小
shuffle=is_train, # 决定是否打乱数据顺序
pin_memory=True # 内存优化选项(GPU训练时使用)
)

5.返回正确率

#test_data是测试数据
#net 是一个 ​​已经实例化并训练好的神经网络对象​​"""评估模型准确率"""
def evaluate(test_data, net): net.eval()correct = 0total = 0with torch.no_grad():for images, labels in test_data:images, labels = images.to(device), labels.to(device)outputs = net(images.view(-1, 28 * 28))_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).sum().item()return correct / total

6.保存训练结果

def save_model(net, filename='softmax_model.pth'):#当前目录下"""保存模型"""torch.save(net.state_dict(), filename)#提取模型的所有可学习参数print(f"模型已保存至 {filename}")n

#net 是一个 ​​已经实例化并训练好的神经网络对象​​

7.加载之前的训练结果

def load_model(net, filename='softmax_model.pth'):"""加载模型"""if os.path.exists(filename):net.load_state_dict(torch.load(filename, map_location=device))#map_location=device是为了指定模型加载到哪个设备上(CPU或GPU)print(f"模型已从 {filename} 加载")else:print(f"警告: 未找到模型文件 {filename}")return net

device = torch.device(“cuda” if torch.cuda.is_available() else “cpu”)

8.可视化

def visualize_predictions(model, test_loader, num_images=12):"""可视化模型预测结果"""model.eval()images, labels = next(iter(test_loader))images, labels = images.to(device), labels.to(device)with torch.no_grad():outputs = model(images.view(-1, 28 * 28))_, predictions = torch.max(outputs, 1)plt.figure(figsize=(12, 8))for i in range(num_images):plt.subplot(3, 4, i + 1)img = images[i].cpu().numpy().squeeze()plt.imshow(img, cmap='gray')plt.title(f"预测: {predictions[i].item()} (真实: {labels[i].item()})")plt.axis('off')plt.tight_layout()plt.savefig('softmax_predictions.png', dpi=150)plt.show()# 将图像添加到TensorBoard(使用PIL图像替代)from PIL import Imagefrom torchvision.utils import save_image# 创建临时图像文件temp_img_path = "temp_grid.png"save_image(images[:num_images], temp_img_path, nrow=4)# 读取并添加到TensorBoardimg = Image.open(temp_img_path)img_array = np.array(img)writer.add_image('predictions', img_array, dataformats='HWC')

总体实现

import torch
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
from torchvision.datasets import MNIST
import matplotlib.pyplot as plt
import os
import numpy as np
from datetime import datetime# 解决Matplotlib中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']  # 使用黑体显示中文
plt.rcParams['axes.unicode_minus'] = False  # 正常显示负号# 检查CUDA设备是否可用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"使用设备: {device}")# 确保日志目录存在
log_dir = "runs"
os.makedirs(log_dir, exist_ok=True)# 创建TensorBoard记录器
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
log_subdir = os.path.join(log_dir, f"softmax_mnist_{timestamp}")
writer = SummaryWriter(log_dir=log_subdir) #用来画图的数据就放在log_dir=log_subdir里class SoftmaxRegression(torch.nn.Module):#括号中的 torch.nn.Module 表示你的 SoftmaxRegression 类 继承自 PyTorch 的 Module 基类"""简单的Softmax回归模型"""def __init__(self):  #self 指代 当前类的实例对象(即正在创建的具体模型)super().__init__()# 单层网络结构:784输入 -> 10输出self.linear = torch.nn.Linear(28 * 28, 10)def forward(self, x):# 应用log_softmax到线性层输出return torch.nn.functional.log_softmax(self.linear(x), dim=1)def get_data_loader(is_train, batch_size=128):"""获取数据加载器"""transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.1307,), (0.3081,))  # MNIST的均值和标准差])dataset = MNIST(root='./data', train=is_train, download=True, transform=transform)return DataLoader(dataset, batch_size=batch_size, shuffle=is_train, pin_memory=True)def evaluate(test_data, net):"""评估模型准确率"""net.eval()correct = 0total = 0with torch.no_grad():for images, labels in test_data:images, labels = images.to(device), labels.to(device)outputs = net(images.view(-1, 28 * 28))_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).sum().item()return correct / totaldef save_model(net, filename='softmax_model.pth'):"""保存模型"""torch.save(net.state_dict(), filename)print(f"模型已保存至 {filename}")def load_model(net, filename='softmax_model.pth'):"""加载模型"""if os.path.exists(filename):net.load_state_dict(torch.load(filename, map_location=device))print(f"模型已从 {filename} 加载")else:print(f"警告: 未找到模型文件 {filename}")return netdef visualize_predictions(model, test_loader, num_images=12):"""可视化模型预测结果"""model.eval()images, labels = next(iter(test_loader))images, labels = images.to(device), labels.to(device)with torch.no_grad():outputs = model(images.view(-1, 28 * 28))_, predictions = torch.max(outputs, 1)plt.figure(figsize=(12, 8))for i in range(num_images):plt.subplot(3, 4, i + 1)img = images[i].cpu().numpy().squeeze()plt.imshow(img, cmap='gray')plt.title(f"预测: {predictions[i].item()} (真实: {labels[i].item()})")plt.axis('off')plt.tight_layout()plt.savefig('softmax_predictions.png', dpi=150)plt.show()# 将图像添加到TensorBoard(使用PIL图像替代)from PIL import Imagefrom torchvision.utils import save_image# 创建临时图像文件temp_img_path = "temp_grid.png"save_image(images[:num_images], temp_img_path, nrow=4)# 读取并添加到TensorBoardimg = Image.open(temp_img_path)img_array = np.array(img)writer.add_image('predictions', img_array, dataformats='HWC')def main():# 获取数据加载器train_loader = get_data_loader(is_train=True, batch_size=128)test_loader = get_data_loader(is_train=False, batch_size=512)# 创建模型model = SoftmaxRegression().to(device)print(f"模型参数量: {sum(p.numel() for p in model.parameters()):,}")# 尝试加载现有模型model = load_model(model)# 评估初始准确率init_acc = evaluate(test_loader, model)print(f"初始准确率: {init_acc:.4f}")writer.add_scalar('Accuracy/test', init_acc, 0)# 使用Adam优化器optimizer = torch.optim.Adam(model.parameters(), lr=0.01)# 训练循环total_step = 0for epoch in range(10):model.train()total_loss = 0correct = 0total = 0for i, (images, labels) in enumerate(train_loader):images, labels = images.to(device), labels.to(device)# 前向传播outputs = model(images.view(-1, 28 * 28))loss = torch.nn.functional.nll_loss(outputs, labels)# 反向传播和优化optimizer.zero_grad()loss.backward()optimizer.step()# 统计信息total_loss += loss.item()_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).sum().item()# 每100个batch记录一次if (i + 1) % 100 == 0:train_acc = correct / totalavg_loss = total_loss / (i + 1)print(f"Epoch [{epoch + 1}/10], Step [{i + 1}/{len(train_loader)}], "f"Loss: {avg_loss:.4f}, Accuracy: {train_acc:.4f}")# 记录到TensorBoardwriter.add_scalar('Loss/train', avg_loss, total_step)writer.add_scalar('Accuracy/train', train_acc, total_step)total_step += 1# 每个epoch结束后评估测试集test_acc = evaluate(test_loader, model)print(f"Epoch [{epoch + 1}/10], 测试准确率: {test_acc:.4f}")writer.add_scalar('Accuracy/test', test_acc, epoch)# 训练后保存模型save_model(model)# 最终评估final_acc = evaluate(test_loader, model)print(f"最终测试准确率: {final_acc:.4f}")# 可视化预测结果visualize_predictions(model, test_loader)# 在TensorBoard中添加模型图dummy_input = torch.randn(1, 784).to(device)writer.add_graph(model, dummy_input)# 关闭TensorBoard写入器writer.close()print(f"TensorBoard日志保存在: {log_subdir}")print("使用命令查看TensorBoard: tensorboard --logdir=runs")if __name__ == '__main__':main()

核心收获

1. 多分类问题本质
○ 输出层神经元数=分类数
○ 使用Softmax确保概率归一化:σ(z)j=ezj∑k=1Kezk\sigma(z)_j = \frac{e^{z_j}}{\sum_{k=1}^K e^{z_k}}σ(z)j=k=1Kezkezj
2. 数据编码技术
○ One-Hot编码:y猫=[0,1,0]y_{\text{猫}} = [0,1,0]y=[0,1,0]
○ 数据标准化:Normalize((0.1307,),(0.3081,))Normalize((0.1307,), (0.3081,))Normalize((0.1307,),(0.3081,))
3. 损失函数优化
○ 交叉熵损失:L=−∑yilog⁡(pi)L = -\sum y_i \log(p_i)L=yilog(pi)
○ Adam优化器自适应调整学习率

代码

数据加载
网络构建
训练循环
评估验证
结果可视化
模型部署

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/88621.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/88621.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JVM 类加载过程

一、加载(Loading)目标:把字节码文件(.class)“读入 JVM”,生成类的 “半成品”(Class 对象)。Bootstrap ClassLoader(启动类加载器):负责加载 JV…

通俗范畴论13 鸡与蛋的故事番外篇

通俗范畴论13 鸡与蛋的故事番外篇 在上一篇中,我们得到了鸡与蛋的Set局部小范畴如下: 鸡与蛋 SetSetSet 局部小范畴 如上图所示,每个鸡来自于一个蛋,每个蛋来自于一只鸡,如此循环,以至于无穷… 是的,假设鸡与蛋两个对象代表的集合,都是无穷集合,这个系统就没有问题…

记录跟随recyclerview滑动的指示器

老早之前做的一个功能&#xff0c;横向recyclerview滑动时&#xff0c;底部做跟随滑动指示器。今天代码不用了&#xff0c;记录下代码。<LinearLayoutandroid:layout_width"match_parent"android:layout_height"wrap_content"android:layout_marginTop&…

快速过一遍Python基础语法

前言 本文章是深度学习的前导课&#xff0c;对有编程基础的小伙伴更加的友好&#xff08;C、C&#xff09;&#xff0c;如果完全没有学过任何一门编程语言也没有关系&#xff0c;本文章不会涉及到晦涩难懂的原理&#xff0c;只是简单的带大家过一遍Python的基础语法。 下面的操…

[爬虫实战] 多进程/多线程/协程-异步爬取豆瓣Top250

相关爬虫知识点&#xff1a;[爬虫知识] 深入理解多进程/多线程/协程的异步逻辑 相关爬虫专栏&#xff1a;JS逆向爬虫实战 爬虫知识点合集 爬虫实战案例 逆向知识点合集 前言&#xff1a; 在之前文章中&#xff0c;我们深入探讨了多进程、多线程和协程这三大异步技术的工作…

Git系列--1.初始Git

一、背景 目录 一、背景 二、认识 三、如何在Linux上安装Git 3.1检测git是否存在和版本 3.2安装和卸载git 3.2.1Centos 3.2.2Ubuntu 四、基本操作 4.1创建本地仓库 4.2必须的配置项 4.3宏观认识基本分区 我们会根据需求不断更改我们的文件内容&#xff0c;但有时我们会…

QWidget的属性

QWidget的属性 windowOpacityAPI说明windowOpacity()获取不透明数值&#xff0c;返回float&#xff0c;取值为0.0到1.0&#xff0c;其中0.0为全透明&#xff0c;1.0为完全不透明setWindowOpacity()设置控件的不透明数值注意点&#xff1a;窗口不透明度的变化并非精确的&#xf…

【PTA数据结构 | C语言版】后缀表达式求值

本专栏持续输出数据结构题目集&#xff0c;欢迎订阅。 文章目录题目代码题目 请编写程序&#xff0c;求给定的后缀表达式的值。 输入格式&#xff1a; 输入在一行中给出一个非空后缀表达式&#xff0c;其中操作数为 int 型整数&#xff0c;操作符包括加、减、乘、除、取模。各…

装配式建筑4.0:当房子像汽车一样被“智造”

传统建筑方式&#xff0c;如同手工打造艺术品一般&#xff0c;大部分工作依赖现场施工&#xff0c;工人在建筑工地进行混凝土浇筑、砖块堆砌、钢筋绑扎等繁杂工作。这种方式受天气、工人技术水平等因素影响极大&#xff0c;不仅施工周期漫长&#xff0c;质量也参差不齐。据统计…

Go语言生态成熟度分析:为何Go还无法像Java那样实现注解式框架?

近年来&#xff0c;Go语言因其性能高效、部署简单、并发模型优秀等特性&#xff0c;成为云原生与微服务架构中的热门语言。然而&#xff0c;在实际的企业级项目开发中&#xff0c;开发者普遍会发现一个现象&#xff1a;Go的开发效率&#xff0c;尤其在快速构建中大型业务系统时…

oc分类和swift扩展有哪些区别

目录1. 语言环境2. 主要目的3. 核心能力对比4. 关键差异详解4.1. 属性支持4.2. Swift 扩展4.3. 初始化器4.4. 方法冲突与覆盖4.5. 关联类型与泛型5. 设计哲学6. 总结表在 Objective-C 和 Swift 中&#xff0c;分类&#xff08;Category&#xff09;和扩展&#xff08;Extension…

go.work

一般学习一个小东西时&#xff0c;无非两点&#xff0c;1、怎么用&#xff1f; 2、为啥用&#xff1f;在写一个小的项目demo时&#xff0c;忽然看到一个奇怪的东西“go.work”&#xff1f;这是啥&#xff1f;好奇&#x1f62f;&#xff0c;想知道。我是这么问AI的&#xff1a;g…

Kimi K2万亿参数开源模型原理介绍

Kimi K2 技术全解&#xff1a;1T MoE 大模型如何炼成开放智能体 文章目录Kimi K2 技术全解&#xff1a;1T MoE 大模型如何炼成开放智能体1. 模型架构与特点&#xff1a;1 T MoE 的「大」与「省」2. 主要创新点&#xff1a;MuonClip、Agentic RL 与工具调用2.1 MuonClip 优化器&…

【CMake】CMake构建项目入门

一、CMake介绍 CMake 是一个跨平台的自动化构建工具&#xff0c;用于管理软件项目的编译过程。它通过简单的配置文件&#xff08;CMakeLists.txt&#xff09;生成特定平台的构建文件&#xff08;如 Makefile、Visual Studio 项目&#xff09;&#xff0c;让开发者可以专注于代…

贪心算法题解——划分字母区间【LeetCode】

763. 划分字母区间 本题目&#xff0c;“同一字母最多出现在一个片段中”&#xff0c;因为这句话&#xff0c;所以本质上 这道题目属于合并区间 一、算法逻辑&#xff08;逐步思路&#xff09; ✅ 目标&#xff1a; 将字符串 s 划分成尽可能多的片段&#xff0c;要求&#xf…

Python----目标检测(使用YOLOV8网络训练人脸)

一、Ultralytics安装 网址&#xff1a;主页 -Ultralytics YOLO 文档 Ultralytics提供了各种安装方法&#xff0c;包括pip、conda和Docker。通过 ultralytics pip包安装最新稳定版本的YOLOv8&#xff0c;或克隆Ultralytics GitHub 存储库以获取最新版本。可以使用Docker在隔离的…

Filament引擎(三) ——引擎渲染流程

通过Filament引擎(二) ——引擎的调用及接口层核心对象的介绍我们知道&#xff0c;要在项目中使用filament&#xff0c;首先我们需要构建出filament的Engine的对象&#xff0c;然后通过filament::Engine对象实例&#xff0c;来构建其他对象&#xff0c;组装渲染场景&#xff0c…

Oracle存储过程导出数据到Excel:全面实现方案详解

技术背景与需求分析 数据导出是企业级应用的核心功能,Oracle存储过程因其高性能执行(减少网络传输)、代码复用性(封装业务逻辑)和事务安全性(ACID保障)成为理想载体。Excel作为使用率$ \geq 95% $的办公工具,其兼容性需求尤为突出。典型场景包括: 财务报表自动生成物…

解决el-table右下角被挡住部分

一部分展示不全&#xff0c;被遮挡&#xff0c;因为 最右边加了fixed"right"<el-table-column fixed"right" label"操作" width"120">解决&#xff1a;1、去除fixed"right"或2、设置样式单页面<style lang"sc…

Waiting for server response 和 Content Download

在浏览器网络调试&#xff08;如 Chrome DevTools 的 Network 面板&#xff09;中&#xff0c;Timing 选项卡下的 Waiting for server response 和 Content Download 是两个关键性能指标&#xff0c;它们分别代表了 HTTP 请求生命周期的不同阶段。以下是详细解释和优化方案&…