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

Pytorch | 从零构建EfficientNet对CIFAR10进行分类

Pytorch | 从零构建EfficientNet对CIFAR10进行分类

  • CIFAR10数据集
  • EfficientNet
    • 设计理念
    • 网络结构
    • 性能特点
    • 应用领域
    • 发展和改进
  • EfficientNet结构代码详解
    • 结构代码
    • 代码详解
      • MBConv 类
        • 初始化方法
        • 前向传播 forward 方法
      • EfficientNet 类
        • 初始化方法
        • 前向传播 forward 方法
  • 训练过程和测试结果
  • 代码汇总
    • efficientnet.py
    • train.py
    • test.py

前面文章我们构建了AlexNet、Vgg、GoogleNet、ResNet、MobileNet对CIFAR10进行分类:
Pytorch | 从零构建AlexNet对CIFAR10进行分类
Pytorch | 从零构建Vgg对CIFAR10进行分类
Pytorch | 从零构建GoogleNet对CIFAR10进行分类
Pytorch | 从零构建ResNet对CIFAR10进行分类
Pytorch | 从零构建MobileNet对CIFAR10进行分类
这篇文章我们来构建EfficientNet.

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个不同的类别,这些类别都是现实世界中常见的物体,具有一定的代表性。

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

EfficientNet

EfficientNet是由谷歌大脑团队在2019年提出的一种高效的卷积神经网络架构,在图像分类等任务上展现出了卓越的性能和效率,以下是对它的详细介绍:

设计理念

  • 平衡模型的深度、宽度和分辨率:传统的神经网络在提升性能时,往往只是单纯地增加网络的深度、宽度或输入图像的分辨率,而EfficientNet则通过一种系统的方法,同时对这三个维度进行优化调整,以达到在计算资源有限的情况下,模型性能的最大化。
    在这里插入图片描述

网络结构

  • MBConv模块:EfficientNet的核心模块是MBConv(Mobile Inverted Residual Bottleneck),它基于深度可分离卷积和倒置残差结构。这种结构在减少计算量的同时,能够有效提取图像特征,提高模型的表示能力。
  • Compound Scaling方法:使用该方法对网络的深度、宽度和分辨率进行统一缩放。通过一个固定的缩放系数,同时调整这三个维度,使得模型在不同的计算资源限制下,都能保持较好的性能和效率平衡。
    在这里插入图片描述
    上图中是EfficientNet-B0的结构.

性能特点

  • 高效性:在相同的计算资源下,EfficientNet能够取得比传统网络更好的性能。例如,与ResNet-50相比,EfficientNet-B0在ImageNet数据集上取得了相近的准确率,但参数量和计算量却大大减少。
  • 可扩展性:通过Compound Scaling方法,可以方便地调整模型的大小,以适应不同的应用场景和计算资源限制。从EfficientNet-B0到EfficientNet-B7,模型的复杂度逐渐增加,性能也相应提升,能够满足从移动端到服务器端的不同需求。

应用领域

  • 图像分类:在ImageNet等大规模图像分类数据集上,EfficientNet取得了领先的性能,成为图像分类任务的首选模型之一。
  • 目标检测:与Faster R-CNN等目标检测框架结合,EfficientNet作为骨干网络,能够提高目标检测的准确率和速度,在Pascal VOC、COCO等数据集上取得了不错的效果。
  • 语义分割:在语义分割任务中,EfficientNet也展现出了一定的优势,通过与U-Net等分割网络结合,能够对图像进行像素级的分类,在Cityscapes等数据集上有较好的表现。

发展和改进

  • EfficientNet v2:在EfficientNet基础上进行了进一步优化,主要改进包括改进了渐进式学习的方法,在训练过程中逐渐增加图像的分辨率,使得模型能够更好地学习到不同尺度的特征,同时优化了网络结构,提高了模型的训练速度和性能。
  • 其他改进:研究人员还在EfficientNet的基础上,结合其他技术如注意力机制、知识蒸馏等,进一步提高模型的性能和泛化能力。

EfficientNet结构代码详解

结构代码

import torch
import torch.nn as nn
import torch.nn.functional as Fclass MBConv(nn.Module):def __init__(self, in_channels, out_channels, expand_ratio, kernel_size, stride, padding, se_ratio=0.25):super(MBConv, self).__init__()self.stride = strideself.use_res_connect = (stride == 1 and in_channels == out_channels)# 扩展通道数(如果需要)expanded_channels = in_channels * expand_ratioself.expand_conv = nn.Conv2d(in_channels, expanded_channels, kernel_size=1, padding=0, bias=False)self.bn1 = nn.BatchNorm2d(expanded_channels)# 深度可分离卷积self.depthwise_conv = nn.Conv2d(expanded_channels, expanded_channels, kernel_size=kernel_size,stride=stride, padding=padding, groups=expanded_channels, bias=False)self.bn2 = nn.BatchNorm2d(expanded_channels)# 压缩和激励(SE)模块(可选,根据se_ratio判断是否添加)if se_ratio > 0:se_channels = int(in_channels * se_ratio)self.se = nn.Sequential(nn.AdaptiveAvgPool2d(1),nn.Conv2d(expanded_channels, se_channels, kernel_size=1),nn.ReLU(inplace=True),nn.Conv2d(se_channels, expanded_channels, kernel_size=1),nn.Sigmoid())else:self.se = None# 投影卷积,恢复到输出通道数self.project_conv = nn.Conv2d(expanded_channels, out_channels, kernel_size=1, padding=0, bias=False)self.bn3 = nn.BatchNorm2d(out_channels)def forward(self, x):identity = x# 扩展通道数out = F.relu(self.bn1(self.expand_conv(x)))# 深度可分离卷积out = F.relu(self.bn2(self.depthwise_conv(out)))# SE模块操作(如果存在)if self.se is not None:se_out = self.se(out)out = out * se_out# 投影卷积out = self.bn3(self.project_conv(out))# 残差连接(如果满足条件)if self.use_res_connect:out += identityreturn outclass EfficientNet(nn.Module):def __init__(self, num_classes, width_coefficient=1.0, depth_coefficient=1.0, dropout_rate=0.2):super(EfficientNet, self).__init__()self.stem_conv = nn.Conv2d(3, 32, kernel_size=3, stride=2, padding=1, bias=False)self.bn1 = nn.BatchNorm2d(32)mbconv_config = [# (in_channels, out_channels, expand_ratio, kernel_size, stride, padding)(32, 16, 1, 3, 1, 1),(16, 24, 6, 3, 2, 1),(24, 40, 6, 5, 2, 2),(40, 80, 6, 3, 2, 1),(80, 112, 6, 5, 1, 2),(112, 192, 6, 5, 2, 2),(192, 320, 6, 3, 1, 1)]# 根据深度系数调整每个MBConv模块的重复次数,这里简单地向下取整,你也可以根据实际情况采用更合理的方式repeat_counts = [max(1, int(depth_coefficient * 1)) for _ in mbconv_config]layers = []for i, config in enumerate(mbconv_config):in_channels, out_channels, expand_ratio, kernel_size, stride, padding = configfor _ in range(repeat_counts[i]):layers.append(MBConv(int(in_channels * width_coefficient),int(out_channels * width_coefficient),expand_ratio, kernel_size, stride, padding))self.mbconv_layers = nn.Sequential(*layers)self.last_conv = nn.Conv2d(int(320 * width_coefficient), 1280, kernel_size=1, bias=False)self.bn2 = nn.BatchNorm2d(1280)self.avgpool = nn.AdaptiveAvgPool2d(1)self.dropout = nn.Dropout(dropout_rate)self.fc = nn.Linear(1280, num_classes)def forward(self, x):out = F.relu(self.bn1(self.stem_conv(x)))out = self.mbconv_layers(out)out = F.relu(self.bn2(self.last_conv(out)))out = self.avgpool(out)out = out.view(out.size(0), -1)out = self.dropout(out)out = self.fc(out)return out

代码详解

以下是对上述EfficientNet代码的详细解释,代码整体定义了EfficientNet网络结构,主要由MBConv模块堆叠以及一些常规的卷积、池化和全连接层构成,下面按照类和方法分别进行剖析:

MBConv 类

这是EfficientNet中的核心模块,实现了MBConv(Mobile Inverted Residual Bottleneck Convolution)结构,其代码如下:

class MBConv(nn.Module):def __init__(self, in_channels, out_channels, expand_ratio, kernel_size, stride, padding, se_ratio=0.25):super(MBConv, self).__init__()self.stride = strideself.use_res_connect = (stride == 1 and in_channels == out_channels)# 扩展通道数(如果需要)expanded_channels = in_channels * expand_ratioself.expand_conv = nn.Conv2d(in_channels, expanded_channels, kernel_size=1, padding=0, bias=False)self.bn1 = nn.BatchNorm2d(expanded_channels)# 深度可分离卷积self.depthwise_conv = nn.Conv2d(expanded_channels, expanded_channels, kernel_size=kernel_size,stride=stride, padding=padding, groups=expanded_channels, bias=False)self.bn2 = nn.BatchNorm2d(expanded_channels)# 压缩和激励(SE)模块(可选,根据se_ratio判断是否添加)if se_ratio > 0:se_channels = int(in_channels * se_ratio)self.se = nn.Sequential(nn.AdaptiveAvgPool2d(1),nn.Conv2d(expanded_channels, se_channels, kernel_size=1),nn.ReLU(inplace=True),nn.Conv2d(se_channels, expanded_channels, kernel_size=1),nn.Sigmoid())else:self.se = None# 投影卷积,恢复到输出通道数self.project_conv = nn.Conv2d(expanded_channels, out_channels, kernel_size=1, padding=0, bias=False)self.bn3 = nn.BatchNorm2d(out_channels)def forward(self, x):identity = x# 扩展通道数out = F.relu(self.bn1(self.expand_conv(x)))# 深度可分离卷积out = F.relu(self.bn2(self.depthwise_conv(out)))# SE模块操作(如果存在)if self.se is not None:se_out = self.se(out)out = out * se_out# 投影卷积out = self.bn3(self.project_conv(out))# 残差连接(如果满足条件)if self.use_res_connect:out += identityreturn out
初始化方法
  • 参数说明
    • in_channels:输入张量的通道数。
    • out_channels:输出张量的通道数。
    • expand_ratio:用于确定扩展通道数时的比例系数,决定是否对输入通道数进行扩展以及扩展的倍数。
    • kernel_size:卷积核的大小,用于深度可分离卷积等操作。
    • stride:卷积的步长,控制特征图在卷积过程中的下采样程度等。
    • padding:卷积操作时的填充大小,保证输入输出特征图尺寸在特定要求下的一致性等。
    • se_ratio(可选,默认值为0.25):用于控制压缩和激励(SE)模块中通道压缩的比例,若为0则不添加SE模块。
  • 初始化操作
    • 首先保存传入的stride参数,并根据stride和输入输出通道数判断是否使用残差连接(use_res_connect),只有当步长为1且输入输出通道数相等时才使用残差连接,这符合残差网络的基本原理,有助于梯度传播和特征融合。
    • 根据expand_ratio计算扩展后的通道数expanded_channels,并创建expand_conv卷积层用于扩展通道数,同时搭配对应的bn1批归一化层,对扩展后的特征进行归一化处理,有助于加速网络收敛和提升模型稳定性。
    • 定义depthwise_conv深度可分离卷积层,其分组数设置为expanded_channels,意味着每个通道单独进行卷积操作,这种方式可以在减少计算量的同时保持较好的特征提取能力,同时搭配bn2批归一化层。
    • 根据se_ratio判断是否添加压缩和激励(SE)模块。如果se_ratio大于0,则创建一个包含自适应平均池化、卷积、激活函数(ReLU)、卷积和Sigmoid激活的序列模块se,用于对特征进行通道维度上的重加权,增强模型对不同通道特征的关注度;若se_ratio为0,则将se设为None
    • 最后创建project_conv投影卷积层用于将扩展和处理后的特征恢复到指定的输出通道数,并搭配bn3批归一化层。
前向传播 forward 方法
  • 首先将输入张量x保存为identity,用于后续可能的残差连接。
  • 通过F.relu(self.bn1(self.expand_conv(x)))对输入进行通道扩展,并使用ReLU激活函数和批归一化进行处理,得到扩展后的特征表示。
  • 接着对扩展后的特征执行深度可分离卷积操作F.relu(self.bn2(self.depthwise_conv(out))),同样使用ReLU激活和批归一化处理。
  • 如果存在SE模块(self.se不为None),则将经过深度可分离卷积后的特征传入SE模块进行通道重加权,即se_out = self.se(out),然后将特征与重加权后的结果相乘out = out * se_out
  • 通过self.bn3(self.project_conv(out))进行投影卷积操作,将特征恢复到输出通道数,并进行批归一化处理。
  • 最后,如果满足残差连接条件(self.use_res_connectTrue),则将投影卷积后的特征与最初保存的输入特征identity相加,实现残差连接,最终返回处理后的特征张量。

EfficientNet 类

这是整体的EfficientNet网络模型类,代码如下:

class EfficientNet(nn.Module):def __init__(self, num_classes, width_coefficient=1.0, depth_coefficient=1.0, dropout_rate=0.2):super(EfficientNet, self).__init__()self.stem_conv = nn.Conv2d(3, 32, kernel_size=3, stride=2, padding=1, bias=False)self.bn1 = nn.BatchNorm2d(32)mbconv_config = [# (in_channels, out_channels, expand_ratio, kernel_size, stride, padding)(32, 16, 1, 3, 1, 1),(16, 24, 6, 3, 2, 1),(24, 40, 6, 5, 2, 2),(40, 80, 6, 3, 2, 1),(80, 112, 6, 5, 1, 2),(112, 192, 6, 5, 2, 2),(192, 320, 6, 3, 1, 1)]# 根据深度系数调整每个MBConv模块的重复次数,这里简单地向下取整,你也可以根据实际情况采用更合理的方式repeat_counts = [max(1, int(depth_coefficient * 1)) for _ in mbconv_config]layers = []for i, config in enumerate(mbconv_config):in_channels, out_channels, expand_ratio, kernel_size, stride, padding = configfor _ in range(repeat_counts[i]):layers.append(MBConv(int(in_channels * width_coefficient),int(out_channels * width_coefficient),expand_ratio, kernel_size, stride, padding))self.mbconv_layers = nn.Sequential(*layers)self.last_conv = nn.Conv2d(int(320 * width_coefficient), 1280, kernel_size=1, bias=False)self.bn2 = nn.BatchNorm2d(1280)self.avgpool = nn.AdaptiveAvgPool2d(1)self.dropout = nn.Dropout(dropout_rate)self.fc = nn.Linear(1280, num_classes)def forward(self, x):out = F.relu(self.bn1(self.stem_conv(x)))out = self.mbconv_layers(out)out = F.relu(self.bn2(self.last_conv(out)))out = self.avgpool(out)out = out.view(out.size(0), -1)out = self.dropout(out)out = self.fc(out)return out
初始化方法
  • 参数说明
    • num_classes:最终分类任务的类别数量,用于确定全连接层的输出维度。
    • width_coefficient(默认值为1.0):用于控制模型各层的通道数,实现对模型宽度的缩放调整。
    • depth_coefficient(默认值为1.0):用于控制模型中MBConv模块的重复次数,实现对模型深度的缩放调整。
    • dropout_rate(默认值为0.2):在全连接层之前使用的Dropout概率,用于防止过拟合。
  • 初始化操作
    • 首先创建stem_conv卷积层,它将输入的图像数据(通常通道数为3,对应RGB图像)进行初始的卷积操作,步长为2,起到下采样的作用,同时不使用偏置(bias=False),并搭配bn1批归一化层对卷积后的特征进行归一化处理。
    • 定义mbconv_config列表,其中每个元素是一个元组,包含了MBConv模块的各项配置参数,如输入通道数、输出通道数、扩展比例、卷积核大小、步长和填充等,这是构建MBConv模块的基础配置信息。
    • 根据depth_coefficient计算每个MBConv模块的重复次数,通过列表推导式 repeat_counts = [max(1, int(depth_coefficient * 1)) for _ in mbconv_config] 实现,这里简单地将每个配置对应的重复次数设置为与depth_coefficient成比例(向下取整且保证至少重复1次),你可以根据更精细的设计规则来调整这个计算方式。
    • 构建self.mbconv_layers,通过两层嵌套循环实现。外层循环遍历mbconv_config配置列表,内层循环根据对应的重复次数来多次添加同一个MBConv模块实例到layers列表中,最后将layers列表转换为nn.Sequential类型的模块,这样就实现了根据depth_coefficient对模型深度进行调整以及MBConv模块的堆叠搭建。
    • 创建last_conv卷积层,它将经过MBConv模块处理后的特征进行进一步的卷积操作,将通道数转换为1280,同样不使用偏置,搭配bn2批归一化层。
    • 定义avgpool自适应平均池化层,将特征图转换为固定大小(这里为1x1),方便后续全连接层处理。
    • 创建dropout Dropout层,按照指定的dropout_rate在全连接层之前进行随机失活操作,防止过拟合。
    • 最后定义fc全连接层,其输入维度为1280(经过池化后的特征维度),输出维度为num_classes,用于最终的分类预测。
前向传播 forward 方法
  • 首先将输入x传入stem_conv卷积层进行初始卷积,然后通过F.relu(self.bn1(self.stem_conv(x)))进行激活和批归一化处理,得到初始的特征表示。
  • 将初始特征传入self.mbconv_layers,即经过一系列堆叠的MBConv模块进行特征提取和变换,充分挖掘图像中的特征信息。
  • 接着对经过MBConv模块处理后的特征执行F.relu(self.bn2(self.last_conv(out)))操作,进行最后的卷积以及激活、批归一化处理。
  • 使用self.avgpool(out)进行自适应平均池化,将特征图尺寸变为1x1,实现特征的压缩和固定维度表示。
  • 通过out = out.view(out.size(0), -1)将池化后的特征张量展平为一维向量,方便全连接层处理,这里-1表示自动根据张量元素总数和已知的批量大小维度(out.size(0))来推断展平后的维度大小。
  • 然后将展平后的特征传入self.dropout(out)进行Dropout操作,随机丢弃一部分神经元,防止过拟合。
  • 最后将特征传入self.fc(out)全连接层进行分类预测,得到最终的输出结果,输出的维度与设定的num_classes一致,表示每个样本属于各个类别的预测概率(或得分等,具体取决于任务和后续处理),并返回该输出结果。

训练过程和测试结果

训练过程损失函数变化曲线:

在这里插入图片描述

训练过程准确率变化曲线:
在这里插入图片描述

测试结果:
在这里插入图片描述

代码汇总

项目github地址
项目结构:

|--data
|--models|--__init__.py|-efficientnet.py|--...
|--results
|--weights
|--train.py
|--test.py

efficientnet.py

import torch
import torch.nn as nn
import torch.nn.functional as Fclass MBConv(nn.Module):def __init__(self, in_channels, out_channels, expand_ratio, kernel_size, stride, padding, se_ratio=0.25):super(MBConv, self).__init__()self.stride = strideself.use_res_connect = (stride == 1 and in_channels == out_channels)# 扩展通道数(如果需要)expanded_channels = in_channels * expand_ratioself.expand_conv = nn.Conv2d(in_channels, expanded_channels, kernel_size=1, padding=0, bias=False)self.bn1 = nn.BatchNorm2d(expanded_channels)# 深度可分离卷积self.depthwise_conv = nn.Conv2d(expanded_channels, expanded_channels, kernel_size=kernel_size,stride=stride, padding=padding, groups=expanded_channels, bias=False)self.bn2 = nn.BatchNorm2d(expanded_channels)# 压缩和激励(SE)模块(可选,根据se_ratio判断是否添加)if se_ratio > 0:se_channels = int(in_channels * se_ratio)self.se = nn.Sequential(nn.AdaptiveAvgPool2d(1),nn.Conv2d(expanded_channels, se_channels, kernel_size=1),nn.ReLU(inplace=True),nn.Conv2d(se_channels, expanded_channels, kernel_size=1),nn.Sigmoid())else:self.se = None# 投影卷积,恢复到输出通道数self.project_conv = nn.Conv2d(expanded_channels, out_channels, kernel_size=1, padding=0, bias=False)self.bn3 = nn.BatchNorm2d(out_channels)def forward(self, x):identity = x# 扩展通道数out = F.relu(self.bn1(self.expand_conv(x)))# 深度可分离卷积out = F.relu(self.bn2(self.depthwise_conv(out)))# SE模块操作(如果存在)if self.se is not None:se_out = self.se(out)out = out * se_out# 投影卷积out = self.bn3(self.project_conv(out))# 残差连接(如果满足条件)if self.use_res_connect:out += identityreturn outclass EfficientNet(nn.Module):def __init__(self, num_classes, width_coefficient=1.0, depth_coefficient=1.0, dropout_rate=0.2):super(EfficientNet, self).__init__()self.stem_conv = nn.Conv2d(3, 32, kernel_size=3, stride=2, padding=1, bias=False)self.bn1 = nn.BatchNorm2d(32)mbconv_config = [# (in_channels, out_channels, expand_ratio, kernel_size, stride, padding)(32, 16, 1, 3, 1, 1),(16, 24, 6, 3, 2, 1),(24, 40, 6, 5, 2, 2),(40, 80, 6, 3, 2, 1),(80, 112, 6, 5, 1, 2),(112, 192, 6, 5, 2, 2),(192, 320, 6, 3, 1, 1)]# 根据深度系数调整每个MBConv模块的重复次数,这里简单地向下取整,你也可以根据实际情况采用更合理的方式repeat_counts = [max(1, int(depth_coefficient * 1)) for _ in mbconv_config]layers = []for i, config in enumerate(mbconv_config):in_channels, out_channels, expand_ratio, kernel_size, stride, padding = configfor _ in range(repeat_counts[i]):layers.append(MBConv(int(in_channels * width_coefficient),int(out_channels * width_coefficient),expand_ratio, kernel_size, stride, padding))self.mbconv_layers = nn.Sequential(*layers)self.last_conv = nn.Conv2d(int(320 * width_coefficient), 1280, kernel_size=1, bias=False)self.bn2 = nn.BatchNorm2d(1280)self.avgpool = nn.AdaptiveAvgPool2d(1)self.dropout = nn.Dropout(dropout_rate)self.fc = nn.Linear(1280, num_classes)def forward(self, x):out = F.relu(self.bn1(self.stem_conv(x)))out = self.mbconv_layers(out)out = F.relu(self.bn2(self.last_conv(out)))out = self.avgpool(out)out = out.view(out.size(0), -1)out = self.dropout(out)out = self.fc(out)return out

train.py

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from models import *
import matplotlib.pyplot as pltimport ssl
ssl._create_default_https_context = ssl._create_unverified_context# 定义数据预处理操作
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.491, 0.482, 0.446), (0.247, 0.243, 0.261))])# 加载CIFAR10训练集
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=2)# 定义设备(GPU优先,若可用)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 实例化模型
model_name = 'EfficientNet'
if model_name == 'AlexNet':model = AlexNet(num_classes=10).to(device)
elif model_name == 'Vgg_A':model = Vgg(cfg_vgg='A', num_classes=10).to(device)
elif model_name == 'Vgg_A-LRN':model = Vgg(cfg_vgg='A-LRN', num_classes=10).to(device)
elif model_name == 'Vgg_B':model = Vgg(cfg_vgg='B', num_classes=10).to(device)
elif model_name == 'Vgg_C':model = Vgg(cfg_vgg='C', num_classes=10).to(device)
elif model_name == 'Vgg_D':model = Vgg(cfg_vgg='D', num_classes=10).to(device)
elif model_name == 'Vgg_E':model = Vgg(cfg_vgg='E', num_classes=10).to(device)
elif model_name == 'GoogleNet':model = GoogleNet(num_classes=10).to(device)
elif model_name == 'ResNet18':model = ResNet18(num_classes=10).to(device)
elif model_name == 'ResNet34':model = ResNet34(num_classes=10).to(device)
elif model_name == 'ResNet50':model = ResNet50(num_classes=10).to(device)
elif model_name == 'ResNet101':model = ResNet101(num_classes=10).to(device)
elif model_name == 'ResNet152':model = ResNet152(num_classes=10).to(device)
elif model_name == 'MobileNet':model = MobileNet(num_classes=10).to(device)
elif model_name == 'EfficientNet':model = EfficientNet(num_classes=10).to(device)criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)# 训练轮次
epochs = 15def train(model, trainloader, criterion, optimizer, device):model.train()running_loss = 0.0correct = 0total = 0for i, data in enumerate(trainloader, 0):inputs, labels = data[0].to(device), data[1].to(device)optimizer.zero_grad()outputs = model(inputs)loss = criterion(outputs, labels)loss.backward()optimizer.step()running_loss += loss.item()_, predicted = outputs.max(1)total += labels.size(0)correct += predicted.eq(labels).sum().item()epoch_loss = running_loss / len(trainloader)epoch_acc = 100. * correct / totalreturn epoch_loss, epoch_accif __name__ == "__main__":loss_history, acc_history = [], []for epoch in range(epochs):train_loss, train_acc = train(model, trainloader, criterion, optimizer, device)print(f'Epoch {epoch + 1}: Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.2f}%')loss_history.append(train_loss)acc_history.append(train_acc)# 保存模型权重,每5轮次保存到weights文件夹下if (epoch + 1) % 5 == 0:torch.save(model.state_dict(), f'weights/{model_name}_epoch_{epoch + 1}.pth')# 绘制损失曲线plt.plot(range(1, epochs+1), loss_history, label='Loss', marker='o')plt.xlabel('Epoch')plt.ylabel('Loss')plt.title('Training Loss Curve')plt.legend()plt.savefig(f'results\\{model_name}_train_loss_curve.png')plt.close()# 绘制准确率曲线plt.plot(range(1, epochs+1), acc_history, label='Accuracy', marker='o')plt.xlabel('Epoch')plt.ylabel('Accuracy (%)')plt.title('Training Accuracy Curve')plt.legend()plt.savefig(f'results\\{model_name}_train_acc_curve.png')plt.close()

test.py

import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
from models import *import ssl
ssl._create_default_https_context = ssl._create_unverified_context
# 定义数据预处理操作
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.491, 0.482, 0.446), (0.247, 0.243, 0.261))])# 加载CIFAR10测试集
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=2)# 定义设备(GPU优先,若可用)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 实例化模型
model_name = 'EfficientNet'
if model_name == 'AlexNet':model = AlexNet(num_classes=10).to(device)
elif model_name == 'Vgg_A':model = Vgg(cfg_vgg='A', num_classes=10).to(device)
elif model_name == 'Vgg_A-LRN':model = Vgg(cfg_vgg='A-LRN', num_classes=10).to(device)
elif model_name == 'Vgg_B':model = Vgg(cfg_vgg='B', num_classes=10).to(device)
elif model_name == 'Vgg_C':model = Vgg(cfg_vgg='C', num_classes=10).to(device)
elif model_name == 'Vgg_D':model = Vgg(cfg_vgg='D', num_classes=10).to(device)
elif model_name == 'Vgg_E':model = Vgg(cfg_vgg='E', num_classes=10).to(device)
elif model_name == 'GoogleNet':model = GoogleNet(num_classes=10).to(device)
elif model_name == 'ResNet18':model = ResNet18(num_classes=10).to(device)
elif model_name == 'ResNet34':model = ResNet34(num_classes=10).to(device)
elif model_name == 'ResNet50':model = ResNet50(num_classes=10).to(device)
elif model_name == 'ResNet101':model = ResNet101(num_classes=10).to(device)
elif model_name == 'ResNet152':model = ResNet152(num_classes=10).to(device)
elif model_name == 'MobileNet':model = MobileNet(num_classes=10).to(device)
elif model_name == 'EfficientNet':model = EfficientNet(num_classes=10).to(device)criterion = nn.CrossEntropyLoss()# 加载模型权重
weights_path = f"weights/{model_name}_epoch_15.pth"  
model.load_state_dict(torch.load(weights_path, map_location=device))def test(model, testloader, criterion, device):model.eval()running_loss = 0.0correct = 0total = 0with torch.no_grad():for data in testloader:inputs, labels = data[0].to(device), data[1].to(device)outputs = model(inputs)loss = criterion(outputs, labels)running_loss += loss.item()_, predicted = outputs.max(1)total += labels.size(0)correct += predicted.eq(labels).sum().item()epoch_loss = running_loss / len(testloader)epoch_acc = 100. * correct / totalreturn epoch_loss, epoch_accif __name__ == "__main__":test_loss, test_acc = test(model, testloader, criterion, device)print(f"================{model_name} Test================")print(f"Load Model Weights From: {weights_path}")print(f'Test Loss: {test_loss:.4f}, Test Acc: {test_acc:.2f}%')

相关文章:

Pytorch | 从零构建EfficientNet对CIFAR10进行分类

Pytorch | 从零构建EfficientNet对CIFAR10进行分类 CIFAR10数据集EfficientNet设计理念网络结构性能特点应用领域发展和改进 EfficientNet结构代码详解结构代码代码详解MBConv 类初始化方法前向传播 forward 方法 EfficientNet 类初始化方法前向传播 forward 方法 训练过程和测…...

Saprk和Flink的区别

1 、设计理念方面 Spark 的技术理念是使用微批来模拟流的计算,基于 Micro-batch ,数据流以时间为单位被切分为一个个 批次,通过分布式数据集RDD 进行批量处理,是一种伪实时。 Flink 是基于事件驱动的,是面向流的处理…...

Spring Boot 动态定时任务管理系统(轻量级实现)

Spring Boot项目中,实现动态增删和启停定时任务的功能对于许多应用场景来说至关重要。虽然Quartz框架是一个广泛使用的解决方案,但其复杂性和重量级特性可能使得项目变得臃肿和难以维护。为了解决这个问题,本项目旨在实现一个轻量级的定时任务…...

开发微信小程序的过程与心得

起因 作为家长,我近期参与了学校的护学岗工作。在这个过程中,我发现需要使用水印相机来记录护学活动,但市面上大多数水印相机应用都要求开通会员才能使用完整功能。作为一名程序员,我决定利用自己的技术背景,开发一个…...

项目代码第6讲:UpdownController.cs;理解 工艺/工序 流程、机台信息;前端的“历史 警报/工艺 记录”

一、UpdownController.cs 1、前端传入 当用户在下图的“记录查询”中的 两个界面选项 中,点击“导出”功能时,向后端发起请求,请求服务器下载文件的权限 【权限是在Program.cs中检测的,这个控制器里只需要进行“谁在哪个接口下载了文件”的日志记录】 【导出:是用户把…...

Apache RocketMQ 5.1.3安装部署文档

官方文档不好使,可以说是一坨… 关键词:Apache RocketMQ 5.0 JDK 17 废话少说,开整。 1.版本 官网地址,版本如下。 https://rocketmq.apache.org/download2.配置文件 2.1namesrv端口 在ROCKETMQ_HOME/conf下 新增namesrv.pro…...

uni-app开发收货地址管理

目录 一:功能描述 二:功能实现 一:功能描述 收货地址在个人中心的我的地址里面,点击我的地址可以查看我的收货地址列表,可以新增收货地址,点击特定收货地址可以编辑和删除该地址信息。 二:功能实现 1:收货地址列表 <view v-for="(item, index) in data_lis…...

BOE(京东方)“向新2025”年终媒体智享会落地成都 持续创新引领产业步入高价值增长新纪元

12月20日,BOE(京东方)“向新 2025”年终媒体智享会的脚步从上海延伸至成都。川渝之地,作为 BOE(京东方)产业生态战略布局中的关键一子,此刻再度成为行业瞩目的焦点。本次活动全面回溯了BOE(京东方)在2024年多个关键领域斩获的斐然佳绩,深入剖析了六大维度构建的“向新”发展格局…...

JavaWeb - ⭐ AOP 面相切面编程原理及用户校验功能实战

一、概述 定义&#xff1a; AOP (Aspect Oriented Programming 面向切面编程) &#xff0c;一种面向方法编程的思想 功能&#xff1a;管理 bean 对象的过程中&#xff0c;通过底层的动态代理机制对特定方法进行功能的增强或改变 实现方式&#xff1a;动态代理技术&#xff0c…...

鸿道Intewell工业操作系统,三大关键技术,领跑行业前沿

鸿道工业操作系统&#xff0c;是软件定义控制的工业实时操作系统&#xff0c;具备三大关键技术&#xff1a; 1、确定性计算与高实时响应(微秒级、关键指标) 提供确定性的计算与高实时的系统环境&#xff0c;保障工业生产运作的灵活高效。 2、微内核、并发处理(微内核的强大…...

java 对ElasticSearch数据库操作封装工具类(对你是否适用嘞)

在 Java 中封装 ElasticSearch&#xff08;ES&#xff09;数据库操作&#xff08;ES版本之间变动挺大的&#xff0c;别轻易换版本&#xff09;&#xff0c;可以使得与 ES 的交互更加简洁和易于维护。通过封装常见的操作&#xff0c;如插入文档、查询、更新和删除等&#xff0c;…...

HarmonyOS NEXT 技术实践-基于基础视觉服务实现骨骼点识别

本示例展示了如何在HarmonyOS Next中实现基于基础视觉服务的骨骼点识别功能。骨骼点识别是计算机视觉中的一项重要技术&#xff0c;广泛应用于运动分析、健身监控和增强现实等领域。通过使用HarmonyOS Next提供的视觉API&#xff0c;开发者能够轻松地对人物图像进行骨骼点检测&…...

广州大学计算机组成原理课程设计

一.课设性质&#xff0c;目的&#xff0c;任务 《计算机组成与系统结构课程设计》是计算机学院各专业集中实践性环节之一&#xff0c;是学习完《计算机组成与系统结构》课程后进行的一次全面的综合练习。其目的是综合运用所学计算机原理知识&#xff0c;设计并实现一台模型计算…...

【漏洞复现】CVE-2021-45788 SQL Injection

漏洞信息 NVD - cve-2021-45788 Time-based SQL Injection vulnerabilities were found in Metersphere v1.15.4 via the “orders” parameter. Authenticated users can control the parameters in the “order by” statement, which causing SQL injection. API: /test…...

【Java基础面试题035】什么是Java泛型的上下界限定符?

回答重点 Java泛型的上下界限定符用于对泛型类型参数进行范围限制&#xff0c;主要有上界限定符和下届限定符。 1&#xff09;上界限定符 (? extends T)&#xff1a; 定义&#xff1a;通配符?的类型必须是T或者T的子类&#xff0c;保证集合元素一定是T或者T的子类作用&…...

无需GPU也能跑的修图神器(祛痣、去水印、去路人)

背景 在这个视觉内容创作盛行的时代&#xff0c;大家对图片质量要求愈发严苛。无论是日常分享的自拍照想祛痣变得完美无瑕&#xff0c;还是摄影作品需去水印彰显原创&#xff0c;亦或是旅游打卡照要去路人还原美景。如今&#xff0c;一款能在 CPU 上流畅运行的修改神器应运而生…...

R语言数据分析案例47-上海译文出版社旗舰店图书分析和可视化

一、研究背景 随着数字化时代的发展&#xff0c;图书出版行业面临着日益激烈的市场竞争。上海译文出版社作为一家知名的出版机构&#xff0c;其旗舰店的图书销售数据蕴含着丰富的信息。对最新入库图书进行深入分析和可视化呈现&#xff0c;有助于出版社更好地了解市场动态、读…...

牵手红娘:牵手App红娘助力“牵手”,脱单精准更便捷

随着互联网的普及&#xff0c;现代青年的社交圈层加速扩大&#xff0c;他们的恋爱观也正经历着前所未有的转变。在繁忙的工作之余&#xff0c;人们希望能够找到一种既高效又真诚的交友方式。于是&#xff0c;线上交友平台成为了他们寻找爱情的新选择。让不同文化背景、不同工作…...

什么是根服务器?有什么作用?

你知道什么是根服务器吗?在互联网的庞大架构中&#xff0c;根服务器很多人对它的了解并不深入。那么&#xff0c;根服务器到底是什么&#xff0c;它有什么作用呢? 什么是根服务器? 根服务器是互联网域名系统(DNS)的一部分&#xff0c;负责管理和维护最顶层的域名信息。简单…...

Tavily人工智能代理和检索增强生成应用设计的搜索引擎

Tavily人工智能代理和检索增强生成应用设计的搜索引擎 Tavily是一款专为人工智能代理和检索增强生成应用设计的搜索引擎.以下是其使用示例: 安装与配置 需先安装相应的包,并获取API密钥来设置环境变量. %pip install -qU "langchain-community>=0.2.11" tavi…...

web的五个Observer API

IntersectionObserver&#xff1a; 一个元素从不可见到可见&#xff0c;从可见到不可见 ??IntersectionObserver是一种浏览器提供的 JavaScript API&#xff0c;用于监测元素与视窗的交叉状态。它可以告诉开发者一个元素是否进入或离开视窗&#xff0c;以及两者的交叉区域的…...

CSS学习记录19

CSS文本效果 CSS text-overflow 属性规定应如何向用户呈现未显示的溢出的内容。 //裁剪 text-overflow: clip; //隐藏 text-overflow: ellipsis; CSS字换行&#xff08;Word Wrapping&#xff09; CSS word-wrap 属性使长文字能够被折断并换到下一行。 如果一个单词太长而…...

使用Amazon Bedrock的无服务器的智能工作流

使用Amazon Bedrock的无服务器的智能工作流 智能工作流基于用户输入处理不可预见的任务&#xff0c;比如发起API调用。无服务器架构可以高效地管理这些任务和不同的工作负载&#xff0c;而无需维护服务器&#xff0c;从而加快部署速度。 你将学习如何通过使用带有保护措施的智能…...

SDMTSP:粒子群优化算法PSO求解单仓库多旅行商问题,可以更改数据集和起点(MATLAB代码)

一、单仓库多旅行商问题 单仓库多旅行商问题&#xff08;Single-Depot Multiple Travelling Salesman Problem, SD-MTSP&#xff09;&#xff1a;&#x1d45a;个推销员从同一座中心城市出发&#xff0c;访问其中一定数量的城市并且每座城市只能被某一个推销员访问一次&#x…...

Java中的访问修饰符:分类、作用及应用场景

在Java编程中&#xff0c;访问修饰符&#xff08;Access Modifiers&#xff09;是控制类、方法、变量和构造函数访问权限的关键工具。通过合理使用访问修饰符&#xff0c;可以有效地封装代码&#xff0c;保护数据&#xff0c;并确保代码的安全性和可维护性。本文将详细介绍Java…...

【day16】Java开发常用API

模块15回顾 在深入探讨模块16之前&#xff0c;让我们回顾一下【day15】中的重点内容&#xff1a; String类&#xff1a; 构造方法&#xff1a;String(), String(String s), String(char[] chars), String(byte[] bytes), String(char[] chars, int offset, int count), String…...

LLaMA-Factory(二)界面解析

这里写目录标题 1. 基础选项语言模型选择模型路径微调方法检查点路径量化等级量化方法提示模板RoPE加速方式 2.模型训练界面训练方式数据集超参数设置其他参数部分参数设置LoRA参数设置RLHF参数设置GaLore参数设置BAdam参数设置训练 3.评估预测界面4.Chat界面5.导出Export界面 …...

(14)CT137A- 动态数码管设计

&#xff08;1&#xff09;了解板卡原理图中数码管的特性&#xff1a;共阳极数码管&#xff0c;公共端连接了电源&#xff0c;FPGA IO口低电平有效&#xff0c;另外&#xff0c;可以看到位选端FPGA位选低电平时选通。 &#xff08;2&#xff09;刷新时间的设定&#xff1a;众所…...

RT-DETR学习笔记(2)

七、IOU-aware query selection 下图是原始DETR。content query 是初始化为0的label embedding, position query 是通过nn.Embedding初始化的一个嵌入矩阵&#xff0c;这两部分没有任何的先验信息&#xff0c;导致DETR的收敛慢。 RT-DETR则提出要给这两部分&#xff08;conten…...

JS、JSX 和 TSX 的对比指南

JS、JSX 和 TSX 的对比指南 1. JavaScript (.js) 简介 JavaScript 是一种轻量级的、解释型的编程语言&#xff0c;是 Web 开发中最基础的语言之一。 优点 学习曲线较低&#xff0c;容易入门使用范围广泛&#xff0c;生态系统成熟灵活性高&#xff0c;支持多种编程范式不需…...

【STM32】RTT-Studio中HAL库开发教程十:EC800M-4G模块使用

文章目录 一、简介二、模块测试三、OneNet物联网配置四、完整代码五、测试验证 一、简介 EC800M4G是一款4G模块&#xff0c;本次实验主要是进行互联网的测试&#xff0c;模块测试&#xff0c;以及如何配置ONENET设备的相关参数&#xff0c;以及使用STM32F4来测试模块的数据上报…...

增加nginx配置文件(conf.d), 管理多个项目

1.切换到nginx目录下, 新建conf.d文件夹 mkdir conf.d 2.赋予conf.d权限 chmod 777 conf.d 3.进入conf.d, 编辑conf文件 vim zc_travel.conf server { listen 13101; server_name localhost;location / {root /home/baoxin/app/web/insight-radar-rcfx-pre/html_dev;index …...

服务器中了挖矿病毒-应急响应

事件&#xff1a;客户反映服务器响应很慢&#xff0c;出行卡顿&#xff0c;服务器操作系统为windows server 2008。 现场解决步骤&#xff1a; 1、打开任务管理器&#xff0c;看到一个javs.exe的程序&#xff0c;占用CPU使用率比较高&#xff0c;怀疑可能是木马。 2、右键打开…...

pid学习感悟

飞行控制PID算法——无人机飞控_飞控算法-CSDN博客 首先pid是对现测量值和目标值的差值&#xff0c;即偏差值的操作 p是线性操作&#xff0c;偏差越大&#xff0c;则对于控制单元操作的值越大 d是微分操作&#xff0c;我们知道微分其实就是数值变化的斜率&#xff0c;所以当偏…...

Centos7.9安装openldap+phpldapadmin+grafana配置LDAP登录最详细步骤 亲测100%能行

一、部署LDAP 1、安装LDAP yum install -y openldap-servers openldap-clients openldap openldap-devel compat-openldap openldap-servers-sql systemctl start slapd systemctl enable slapd2、创建第一个管理账号密码&#xff08;设置为ldapadmin&#xff09; slappass…...

唯品会C++面试题及参考答案

内存池怎么可以保证不会产生碎片? 内存池通过预先分配一块较大的内存区域,然后对这块内存进行管理,来减少碎片产生。 首先,内存池采用固定大小的内存块分配策略。比如,将内存池划分成大小相同的小块,当有内存分配请求时,就从这些固定大小的块中选取合适的进行分配。这种…...

Flask中@app.route()的methods参数详解

诸神缄默不语-个人CSDN博文目录 在 Flask 中&#xff0c;app.route 是用于定义路由的核心装饰器&#xff0c;开发者可以通过它为应用指定 URL 映射及相应的处理函数。在处理 HTTP 请求时&#xff0c;不同的业务场景需要支持不同的 HTTP 方法&#xff0c;而 app.route 的 metho…...

使用Grafana中按钮插件实现收发HTTP请求

最近项目中需要监控分布式集群的各项指标信息&#xff0c;需要用到PrometheusGrafana的技术栈实现对分布式集群的各个节点状态进行可视化显示&#xff0c;但是要求前端需要提供一个易用的接口让用户可以触发一些操作&#xff0c;例如负载高时进行负载均衡或弹性伸缩。网上找到的…...

workman服务端开发模式-应用开发-vue-element-admin挂载websocket

一、项目根目录main.js添加全局引入 import /utils/websocket 二、在根目录app.vue 中初始化WebSocket连接 <template><div id"app"><router-view /></div> </template><script>import store from ./store export default {n…...

堆排序【东北大学oj数据结构9-4】C++

堆排序是一种基于堆的数据结构的排序&#xff0c;是一种快速排序算法&#xff0c;可以在输入数组中实现排序处理&#xff08;内存高效&#xff09;。 堆排序可以实现如下&#xff1a; maxHeapify(A, i) l left(i) r right(i) // select the node which has the m…...

Linux RTC 驱动框架

目录 一、实时时钟&#xff08;RTC&#xff09;介绍1.1 概述1.2 功能1.3 应用场景1.4 工作原理1.5 对外接口1.6 常见 RTC 芯片1.7 在 Linux 系统中的应用1.8 注意事项 二、Linux 内核 RTC 驱动框架2.1 相关源码文件介绍2.2 核心数据结构2.2.1 struct rtc_device2.2.2 rtc_class…...

智能订餐:基于JSP的管理系统设计与优化

相关技术介绍 2.1 B/S结构 目前使用较多的开发结构模式大致可以包括C/S模式和B/S模式[5]。其中&#xff0c;C/S模式全称为客户端/服务器模式&#xff08;Client/Server模式&#xff09;&#xff0c;B/S模式全称为浏览器/服务器模式&#xff08;Browser/Server模式&#xff09;。…...

Java基本查询(四)

JPA 查询All示例 JPA教程 - JPA查询All示例 我们可以在JPQL中使用带有子查询的ALL运算符。 List l em.createQuery("SELECT e FROM Professor e WHERE e.salary < " "ALL (SELECT d.salary FROM e.directs d)").getResultList();例子 下面的代码来自…...

【双指针算法】--复写零

文章目录 1. 题目2. 题目解析3. 代码 1. 题目 在线oj 给你一个长度固定的整数数组 arr &#xff0c;请你将该数组中出现的每个零都复写一遍&#xff0c;并将其余的元素向右平移。 注意&#xff1a;请不要在超过该数组长度的位置写入元素。请对输入的数组 就地 进行上述修改&a…...

leetcode hot100 两两交换链表之中的节点

24. 两两交换链表中的节点 已解答 中等 相关标签 相关企业 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09; # Definition fo…...

Yolo11改进策略:主干网络改进|FastVit与Yolo11完美融合,重参数重构Yolo11网络(全网首发)

文章目录 摘要FastViT:一种使用结构重新参数化的快速混合视觉变换器1、简介2、相关工作3、体系结构3.1、概述3.2、FastViT3.2.1、重新参数化跳过连接3.2.2、线性训练时间过参数化3.2.3、大核卷积4、实验4.1、图像分类4.2、鲁棒性评价4.3、3D Hand网格估计4.4、语义分割和目标检…...

vscode打开下一个文件的时候上一个文件会关闭

解决方法&#xff1a; 你可以通过设置 workbench.editor.enablePreview 来控制在 VS Code 中打开文件时是否会关闭上一个文件。将其设置为 false 可以防止这种行为。 {"workbench.editor.enablePreview": false } 在设置编辑器中显示 "workbench.editor.enab…...

windows使用zip包安装MySQL

windows通过zip包安装MySQL windows通过zip包安装MySQL下载MySQL的zip安装包创建安装目录和数据目录解压zip安装包创建配置目录 etc 和 配置文件 my.ini安装MySQL进入解压后的bin目录执行命令初始化执行命令安装 验证安装查看服务已安装 启动MySQL查看服务运行情况修改密码创建…...

【LeetCode每日一题】——415.字符串相加

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时空频度】九【代码实现】十【提交结果】 一【题目类别】 字符串 二【题目难度】 简单 三【题目编号】 415.字符串相加 四【题目描述】 给定两个字符…...

基于角色的访问控制(RBAC)在异构系统中的应用

在数字化蓬勃发展的当下&#xff0c;企业运营高度依赖各类异构系统。基于角色的访问控制&#xff08;RBAC&#xff09;成为管理这些系统中用户权限的核心策略。RBAC 依据组织内的岗位、职责定义角色&#xff0c;按角色分配权限&#xff0c;用户凭借所属角色获取相应访问权&…...