Pytorch | 对比Pytorch中的十种优化器:基于CIFAR10上的ResNet分类器

Pytorch | 对比Pytorch中的十种优化器:基于CIFAR10上的ResNet分类器

  • CIFAR10数据集
  • ResNet
    • 提出背景
    • 网络结构特点
    • 工作原理
    • 优势
  • 代码实现分析
    • utils.py
    • main.py
      • 导入必要的库
      • 设备选择与数据预处理定义
      • 加载训练集和测试集
      • 主函数部分
        • 训练部分
        • 测试部分
  • 结果
    • 10种优化器对应的训练损失下降曲线
      • Adadelta
      • Adagrad
      • Adam
      • Adamax
      • AdamW
      • NAdam
      • RMSprop
      • Rprop
      • SGD
      • SparseAdam
    • 测试结果
  • 代码汇总
    • utils.py
    • main.py

上篇文章中实现了十种优化算法:Python | 从零实现10种优化算法并比较
这篇文章我们用Pytorch上不同的优化器在CIFAR10数据集上训练ResNet模型,比较不同优化器的效果。

CIFAR10数据集

CIFAR-10数据集是由加拿大高级研究所(CIFAR)收集整理的用于图像识别研究的常用数据集,基本信息如下:

  • 数据规模:该数据集包含60,000张彩色图像,分为10个不同的类别,每个类别有6,000张图像。通常将其中50,000张作为训练集,用于模型的训练;10,000张作为测试集,用于评估模型的性能。
  • 图像尺寸:所有图像的尺寸均为32×32像素,这相对较小的尺寸使得模型在处理该数据集时能够相对快速地进行训练和推理,但也增加了图像分类的难度。
  • 类别内容:涵盖了飞机(plane)、汽车(car)、鸟(bird)、猫(cat)、鹿(deer)、狗(dog)、青蛙(frog)、马(horse)、船(ship)、卡车(truck)这10个不同的类别,这些类别都是现实世界中常见的物体,具有一定的代表性。

下面是一些示例样本:
在这里插入图片描述

ResNet

ResNet(Residual Network)即残差网络,是由微软研究院的何恺明等人在2015年提出的一种深度卷积神经网络架构,在图像识别等计算机视觉任务中取得了巨大成功。

提出背景

随着神经网络深度的增加,出现了梯度消失/爆炸以及网络退化等问题,导致训练难度增大,精度饱和甚至下降。ResNet通过引入残差连接(shortcut connection)有效地解决了这些问题,使得训练极深的网络成为可能。

网络结构特点

  • 残差块(Residual Block):这是ResNet的核心结构。它由多个卷积层组成,并且在卷积层之间引入了shortcut connection。一个基本的残差块包含两个3×3卷积层,中间有一个ReLU激活函数,其输入可以直接跳过这两个卷积层与输出相加,这种结构使得网络能够学习到残差函数,即输入与输出之间的差异,而不是直接学习输出本身。
    在这里插入图片描述

  • 多种层数的网络结构:ResNet有多种不同层数的架构,如ResNet-18、ResNet-34、ResNet-50、ResNet-101和ResNet-152等,其中数字表示网络的层数。层数越深,模型的表示能力越强,但计算成本也越高,训练难度也相应增大。
    在这里插入图片描述

  • 瓶颈结构(Bottleneck):在较深的ResNet架构如ResNet-50及以上中,使用了瓶颈结构来减少计算量。它由1×1、3×3和1×1三个卷积层组成,1×1卷积层用于降低输入特征图的通道数,3×3卷积层进行主要的特征提取,最后1×1卷积层用于恢复通道数。

工作原理

在正向传播时,输入特征图通过残差块中的卷积层进行特征提取,得到输出特征图。然后将输入特征图与输出特征图相加,得到最终的输出。如果残差块中的卷积层没有学到有用的特征,那么它们的输出接近于零,此时最终的输出就近似等于输入,即网络可以学习到恒等映射。在反向传播时,由于shortcut connection的存在,梯度可以直接通过捷径传播到较早的层,避免了梯度消失或爆炸的问题,使得网络能够更容易地训练深层网络。

优势

  • 有效解决梯度消失和退化问题:使得训练非常深的网络成为可能,能够提取更高级的图像特征,从而提高了模型的准确性和泛化能力。
  • 降低模型训练难度:残差连接使得网络在训练过程中更容易收敛,减少了对超参数调整的依赖,提高了训练效率。
  • 模型具有很强的可扩展性:可以通过增加残差块的数量来构建更深的网络,以适应不同的任务和数据集。

代码实现分析

utils.py

该文件中我们预先为调用不同的优化器做好准备,对于不同的优化器参数,为了方便,这里只设置学习率,其余参数可根据需要自己设置。

import torch
import torch.optim as optimdef get_optimizer(optimizer_name, model_parameters, lr=0.001, **kwargs):"""根据传入的优化器名称返回对应的优化器实例。参数:- optimizer_name: 优化器名称,如 "Adadelta", "Adagrad" 等。- model_parameters: 模型的可训练参数,通常通过 model.parameters() 获取。- lr: 学习率,默认值为 0.001。- **kwargs: 其他特定优化器需要的额外参数。返回:- optimizer: 对应的优化器实例。"""if optimizer_name == "Adadelta":return optim.Adadelta(model_parameters, lr=lr, **kwargs)elif optimizer_name == "Adagrad":return optim.Adagrad(model_parameters, lr=lr, **kwargs)elif optimizer_name == "Adam":return optim.Adam(model_parameters, lr=lr, **kwargs)elif optimizer_name == "Adamax":return optim.Adamax(model_parameters, lr=lr, **kwargs)elif optimizer_name == "AdamW":return optim.AdamW(model_parameters, lr=lr, **kwargs)elif optimizer_name == "NAdam":return optim.NAdam(model_parameters, lr=lr, **kwargs)elif optimizer_name == "RMSprop":return optim.RMSprop(model_parameters, lr=lr, **kwargs)elif optimizer_name == "Rprop":return optim.Rprop(model_parameters, lr=lr, **kwargs)elif optimizer_name == "SGD":return optim.SGD(model_parameters, lr=lr, **kwargs)elif optimizer_name == "SparseAdam":return optim.SparseAdam(model_parameters, lr=lr, **kwargs)else:raise ValueError(f"不支持的优化器名称: {optimizer_name}")

main.py

本文这里使用Pytorch自带的ResNet18模型。

以下是对上述代码的分块讲解:

导入必要的库

import torch
import torchvision
import torchvision.transforms as transforms
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
from utils import get_optimizerimport warnings
warnings.filterwarnings("ignore")import sslssl._create_default_https_context = ssl._create_unverified_context
  • from utils import get_optimizer 从前面的 utils 模块中导入 get_optimizer 函数,用于调用不同的优化器;
  • warnings.filterwarnings("ignore") 用于忽略一些可能出现的警告信息,让代码运行时输出更简洁;
  • ssl 相关的代码是为了解决在下载数据集时可能出现的SSL验证问题,通过创建一个默认的不验证SSL证书的上下文来允许数据正常下载。

设备选择与数据预处理定义

# 检查cuda是否可用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 定义数据预处理操作,将图像转换为张量并进行归一化
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.491, 0.482, 0.446), (0.247, 0.243, 0.261))])
  • 检查 gpu(cuda) 是否可用;
  • transforms.Normalize 对图像张量进行归一化操作,传入的两个元组分别表示每个通道的均值和标准差

加载训练集和测试集

# 加载训练集,设置batch_size等参数
# 这里batch_size设为128,可按需调整
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=128,shuffle=True, num_workers=2)# 加载测试集
testset = torchvision.datasets.CIFAR10(root='./data', train=False,download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=128,shuffle=False, num_workers=2)

这部分代码用于加载CIFAR-10数据集,它是一个常用的图像分类数据集,包含10个不同类别的图像。

对于训练集:

  • torchvision.datasets.CIFAR10 函数用于创建训练集对象 trainset,其中 root='./data' 指定了数据集下载和保存的根目录(如果不存在会自动下载到该目录下),train=True 表示加载的是训练集部分,download=True 表示如果数据集不存在则自动下载,transform=transform 表示应用前面定义好的数据预处理操作。
  • torch.utils.data.DataLoader 用于将数据集 trainset 包装成一个可迭代的数据加载器 trainloader,设置 batch_size=128 意味着每次迭代会返回一个包含128张图像及其对应标签的批次数据,shuffle=True 会在每个训练轮次开始时打乱数据顺序,num_workers=2 表示使用2个子进程来并行加载数据,加快数据读取速度。

对于测试集:

  • 同样使用 torchvision.datasets.CIFAR10 函数创建 testset,不过 train=False 表示加载的是测试集部分,其他参数作用和训练集加载时类似。
  • 再通过 torch.utils.data.DataLoader 创建 testloader,只是 shuffle=False 因为测试集一般不需要打乱顺序。

主函数部分

训练部分

训练部分实现了使用10种优化器训练,共训练10个epoch,并记录损失下降情况以可视化,具体可参考下面代码和其中注释:

# 定义类别标签,CIFAR-10有10个类别
classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
if __name__ == "__main__":# 使用不同的优化器optimizer_names = ["Adadelta", "Adagrad", "Adam", "Adamax", "AdamW", "NAdam", "RMSProp", "RProp", "SGD", "SparseAdam"]for optimizer_name in optimizer_names:print(f"========= Optimizer: {optimizer_name} ==========")# 使用PyTorch自带的ResNet18模型,修改全连接层输出维度为10(对应CIFAR-10的类别数)model = torchvision.models.resnet18(pretrained=False)num_ftrs = model.fc.in_featuresmodel.fc = nn.Linear(num_ftrs, 10)model = model.to(device)# 定义交叉熵损失函数,常用于分类任务criterion = nn.CrossEntropyLoss()# 定义优化器optimizer = get_optimizer(optimizer_name, model.parameters(), lr=0.001)# 训练轮数,设为10轮,可根据实际情况更改num_epochs = 10loss_history = []for epoch in range(num_epochs):running_loss = 0.0for i, data in enumerate(trainloader, 0):# 获取输入数据和标签inputs, labels = datainputs = inputs.to(device)labels = labels.to(device)# 梯度清零optimizer.zero_grad()# 前向传播 + 计算损失outputs = model(inputs)loss = criterion(outputs, labels)# 反向传播并更新权重loss.backward()optimizer.step()running_loss += loss.item()loss_history.append(loss.item())if i % 100 == 99:    # 每100个小批次打印一次平均损失print(f'Epoch: {epoch + 1}  Batch: {i + 1}  loss: {running_loss / 100}')running_loss = 0.0# 绘制并保存损失下降曲线plt.plot(loss_history)plt.xlabel('Iteration')plt.ylabel('Loss')plt.title(f'Loss Curve by {optimizer_name}')plt.savefig(f'results\\loss_curve_{optimizer_name}.png')  # 保存图像为 loss_curve.png 文件,可根据需求修改文件名和路径plt.close()
测试部分

测试部分使用已经训练好的模型对测试集进行预测,具体参考下面代码:

    correct = 0total = 0with torch.no_grad():for data in testloader:images, labels = dataimages = images.to(device)labels = labels.to(device)outputs = model(images)_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).sum().item()print(f'Optimizer: {optimizer_name} -- Accuracy of the network on the 10000 test images: {100 * correct / total}%')

结果

10种优化器对应的训练损失下降曲线

Adadelta

在这里插入图片描述

Adagrad

在这里插入图片描述

Adam

在这里插入图片描述

Adamax

在这里插入图片描述

AdamW

在这里插入图片描述

NAdam

在这里插入图片描述

RMSprop

在这里插入图片描述

Rprop

在这里插入图片描述

SGD

SparseAdam

本文训练到这里时,报错:RuntimeError: SparseAdam does not support dense gradients, please consider Adam instead .
原因: SparseAdam 优化器主要是设计用于处理稀疏梯度的场景,也就是梯度张量中大部分元素为零的情况(比如在处理稀疏的文本数据表示等情况时)。而在当前的代码应用场景中,很可能模型计算得到的梯度是密集的(即梯度张量中元素大多是非零值),这就导致 SparseAdam 优化器无法正常处理这样的梯度,进而抛出这个错误提示,建议你考虑使用 Adam 优化器来替代 SparseAdam 优化器。
----因此这里不再给出SparseAdam的优化结果----

测试结果

在这里插入图片描述
从测试结果来看,刨除 SparseAdamAdam 优化器的训练效果最优,Adadelta 优化器的训练效果最差.

代码汇总

项目结构:

|--data
|--results
|--utils.py
|--main.py

utils.py

import torch
import torch.optim as optimdef get_optimizer(optimizer_name, model_parameters, lr=0.001, **kwargs):"""根据传入的优化器名称返回对应的优化器实例。参数:- optimizer_name: 优化器名称,如 "Adadelta", "Adagrad" 等。- model_parameters: 模型的可训练参数,通常通过 model.parameters() 获取。- lr: 学习率,默认值为 0.001。- **kwargs: 其他特定优化器需要的额外参数。返回:- optimizer: 对应的优化器实例。"""if optimizer_name == "Adadelta":return optim.Adadelta(model_parameters, lr=lr, **kwargs)elif optimizer_name == "Adagrad":return optim.Adagrad(model_parameters, lr=lr, **kwargs)elif optimizer_name == "Adam":return optim.Adam(model_parameters, lr=lr, **kwargs)elif optimizer_name == "Adamax":return optim.Adamax(model_parameters, lr=lr, **kwargs)elif optimizer_name == "AdamW":return optim.AdamW(model_parameters, lr=lr, **kwargs)elif optimizer_name == "NAdam":return optim.NAdam(model_parameters, lr=lr, **kwargs)elif optimizer_name == "RMSprop":return optim.RMSprop(model_parameters, lr=lr, **kwargs)elif optimizer_name == "Rprop":return optim.Rprop(model_parameters, lr=lr, **kwargs)elif optimizer_name == "SGD":return optim.SGD(model_parameters, lr=lr, **kwargs)elif optimizer_name == "SparseAdam":return optim.SparseAdam(model_parameters, lr=lr, **kwargs)else:raise ValueError(f"不支持的优化器名称: {optimizer_name}")

main.py

import torch
import torchvision
import torchvision.transforms as transforms
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
from utils import get_optimizerimport warnings
warnings.filterwarnings("ignore")import sslssl._create_default_https_context = ssl._create_unverified_context# 检查cuda是否可用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 定义数据预处理操作,将图像转换为张量并进行归一化
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.491, 0.482, 0.446), (0.247, 0.243, 0.261))])# 加载训练集,设置batch_size等参数
# 这里batch_size设为128,可按需调整
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,download=False, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=128,shuffle=True, num_workers=4)# 加载测试集
testset = torchvision.datasets.CIFAR10(root='./data', train=False,download=False, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=128,shuffle=False, num_workers=4)# 定义类别标签,CIFAR-10有10个类别
classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')if __name__ == "__main__":# 使用不同的优化器optimizer_names = ["Adadelta", "Adagrad", "Adam", "Adamax", "AdamW", "NAdam", "RMSprop", "Rprop", "SGD", "SparseAdam"]for optimizer_name in optimizer_names:print(f"========= Optimizer: {optimizer_name} ==========")# 使用PyTorch自带的ResNet18模型,修改全连接层输出维度为10(对应CIFAR-10的类别数)model = torchvision.models.resnet18(pretrained=False)num_ftrs = model.fc.in_featuresmodel.fc = nn.Linear(num_ftrs, 10)model = model.to(device)# 定义交叉熵损失函数,常用于分类任务criterion = nn.CrossEntropyLoss()# 定义优化器optimizer = get_optimizer(optimizer_name, model.parameters(), lr=0.001)# 训练轮数,设为10轮,可根据实际情况更改num_epochs = 10loss_history = []for epoch in range(num_epochs):running_loss = 0.0for i, data in enumerate(trainloader, 0):# 获取输入数据和标签inputs, labels = datainputs = inputs.to(device)labels = labels.to(device)# 梯度清零optimizer.zero_grad()# 前向传播 + 计算损失outputs = model(inputs)loss = criterion(outputs, labels)# 反向传播并更新权重loss.backward()optimizer.step()running_loss += loss.item()loss_history.append(loss.item())if i % 100 == 99:    # 每100个小批次打印一次平均损失# print(f'Epoch: {epoch + 1}  Batch: {i + 1}  loss: {running_loss / 100}')running_loss = 0.0# 绘制并保存损失下降曲线plt.plot(loss_history)plt.xlabel('Iteration')plt.ylabel('Loss')plt.title(f'Loss Curve by {optimizer_name}')plt.savefig(f'results\\loss_curve_{optimizer_name}.png')  # 保存图像为 loss_curve.png 文件,可根据需求修改文件名和路径plt.close()correct = 0total = 0with torch.no_grad():for data in testloader:images, labels = dataimages = images.to(device)labels = labels.to(device)outputs = model(images)_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).sum().item()print(f'Optimizer: {optimizer_name} -- Accuracy of the network on the 10000 test images: {100 * correct / total}%')

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

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

相关文章

Linux系统操作03|chmod、vim

上文: Linux系统操作02|基本命令-CSDN博客 目录 六、chmod:给文件设置权限 1、字母法 2、数字法(用的最多) 七、vim:代码编写和文本编辑 1、启动和退出 1️⃣启动 2️⃣退出 2、vim基本操作 六、chmod&#x…

徐州数字孪生工业互联网可视化技术,赋能新型工业化智能制造工厂

#徐州数字孪生工业互联网#在当下智能制造的热潮之下,徐州作为中国制造业的重要基地,正积极拥抱数字化转型,通过数字孪生工业互联网可视化技术,赋能新型工业化智能制造工厂,引领制造业向更高效、更智能、更绿色的方向发…

C# 探险之旅:第二十四节 - 类型class基础,一场“类”似的奇妙冒险

嘿,勇敢的探险家们!欢迎来到C#王国的“类”似奇妙冒险!今天,我们要深入探索一个神秘而强大的领域——class(类)。想象一下,class就像C#世界里的一块魔法土地,每块土地上都能孕育出独…

(五)机器学习 - 数据分布

数据分布(Data Distribution)是指数据在不同值或值区间内的分布情况,它描述了数据点在整个数据集中是如何分散或集中的。数据分布可以通过多种方式来分析和表示,包括图形和数值方法。 常见的数据分布特征和描述数据分布的方法&…

基于stm32的红外测温系统设计(论文+源码)

1总体方案设计 本课题为基于STM32的红外测温系统设计,在此将系统架构设计如图3.1所示, 整个系统包括STM32F103单片机,红外测温模块MLX90614,显示模块OLED12864,蜂鸣器以及按键等构成,在功能上,…

排序算法(5):归并排序

问题 排序 [30, 24, 5, 58, 18, 36, 12, 42, 39] 归并排序 归并排序采用分治法,将序列分成若干子序列,每个子序列有序后再合并成有序的完整序列。 在数组排序中,如果只有一个数,那么它本身就是有序的。如果有两个数&#xff0…

JSSIP的使用及问题(webRTC,WebSockets)

简介 项目中有一个需要拨打电话的功能,要求实时的进行音频接听,并且可以在电话接听或者挂断等情况下做出相应的操作。jssip作为一个强大的实现实时通信的javascript库,这不门当户对了嘛。 jssip(官网: JsSIP - the J…

DP3复现代码运行逻辑全流程(六)—— gen_demonstration_adroit.sh 演示生成与可视化

用于生成演示、培训和评估的脚本都在 Scripts/ 文件夹中 DP3 通过 gen_demonstration 生成演示,即训练数据,例如: bash scripts/gen_demonstration_adroit.sh hammer 这将在 Adroit 环境中生成锤子任务的演示。数据将自动保存在 3D-Diffusion-Policy/…

Python常用字符串排序●sorted()函数--一行语句简洁实现

在Python等编程中,时常会用到字符串排序。 今天在这里只讲讲最常用的Python字符串排序。 同时,只讲sorted()函数方法。 给定一个字符串列表: sl [共和国, 中国, 中华人民共和国, 大中华, 人民共和国]。 第一种排序方法是不使用任何参数…

知从科技总经理受邀参加上海临港新片区商会“湖畔TECS”技术分享沙龙(第五期)

11月26日,上海知从科技有限公司创始人陈荣波先生受邀出席临港新片区商会 “湖畔TECS”技术分享沙龙(第五期)活动,并在活动上为参会嘉宾们做了主题分享。本次活动由临港新片区商会主办,智能网联汽车创新联盟协办&#x…

【MySQL数据库】Ubuntu下的mysql

目录 1,安装mysql数据库 2,mysql默认安装路径 3,my.cnf配置文件 4,mysql运用的相关指令及说明 5,数据库、表的备份和恢复 mysql是一套给我们提供数据存取的,更加有利于管理数据的服务的网络程序。下面…

Docker与虚拟机:虚拟化技术的差异解析

在信息技术飞速发展的今天,虚拟化技术已成为现代IT架构不可或缺的一部分。而虚拟化从技术层面划分则分为以下几种: 完全虚拟化:虚拟机能够完全模拟底层硬件的特权指令的执行过程,客户操作系统无须进行修改。 硬件辅助虚拟化&#…

2024.12.14 TCP/IP 网络模型有哪几层?

2024.12.14 TCP/IP 网络模型有哪几层? 2024.12.14 今天周六 看到大伙都在考六级,我来复盘小林coding的计算机网络的知识点: TCP/IP 网络模型有哪几层? 问大家,为什么要有 TCP/IP 网络模型? 对于同一台设备上的进程间通信,有…

一次Mysql查询踩坑经历(查询索引失效问题)

1、之前的sql建表脚本 CREATE TABLE crm_driver (id bigint(22) NOT NULL AUTO_INCREMENT COMMENT 主键,clue_id bigint(20) NOT NULL COMMENT 线索表id,driver_name varchar(128) NOT NULL COMMENT 试驾人姓名,driver_phone varchar(32) NOT NULL COMMENT 试驾人手机号,drive…

从 SSM 视角剖析校园一卡通密钥管理系统的技术架构演进

第2章 开发环境与技术 开发校园一卡通密钥管理系统需要搭建编程的环境,也需要通过调查,对各个相关技术进行分析,选取适合本系统开发的技术与工具。 2.1 MYSQL数据库 题目确定了是一个应用程序之后,就开始按部就班的进行设计与分析…

RabbitMQ中的Publish-Subscribe模式

在现代分布式系统中,消息队列(Message Queue)是实现异步通信和解耦系统的关键组件。RabbitMQ 是一个功能强大且广泛使用的开源消息代理,支持多种消息传递模式。其中,Publish/Subscribe(发布/订阅&#xff0…

【iOS】OC高级编程 iOS多线程与内存管理阅读笔记——自动引用计数(四)

目录 ARC规则 规则 对象型变量不能作为C语言结构体的成员 显式转换id和void* 属性 数组 ARC规则 规则 在ARC有效的情况下编译源代码必须遵守一定的规则: 主要解释一下最后两条 对象型变量不能作为C语言结构体的成员 要把对象型变量加入到结构体成员中时&a…

路由引入问题(双点双向路由回馈问题)

简介 总所周知,路由引入import又称路由重分发redistribute,为了解决不同路由协议进程间路由信息不互通而使用的技术,由于不同路由协议的算法、机制、开销等因素的差异,它们之间无法直接交换路由信息。因此,路由引入技…

26. Three.js案例-自定义多面体

26. Three.js案例-自定义多面体 实现效果 知识点 WebGLRenderer WebGLRenderer 是 Three.js 中用于渲染场景的主要类。它支持 WebGL 渲染,并提供了多种配置选项。 构造器 new THREE.WebGLRenderer(parameters) 参数类型描述parametersObject可选参数对象&…

【在Linux世界中追寻伟大的One Piece】HTTP Session

目录 1 -> 引入HTTP Session 1.1 -> 定义 1.2 -> 工作原理 1.3 -> 安全性 1.4 -> 超时和失效 1.5 -> 用途 2 -> 模拟session行为 3 -> 实验测试session 1 -> 引入HTTP Session 1.1 -> 定义 HTTP Session是服务器用来跟踪用户与服务器交…