Pytorch | 从零构建ResNet对CIFAR10进行分类
Pytorch | 从零构建ResNet对CIFAR10进行分类
- CIFAR10数据集
- ResNet
- 核心思想
- 网络结构
- 创新点
- 优点
- 应用
- ResNet结构代码详解
- 结构代码
- 代码详解
- BasicBlock 类
- ResNet 类
- ResNet18、ResNet34、ResNet50、ResNet101、ResNet152函数
- 训练过程和测试结果
- 代码汇总
- resnet.py
- train.py
- test.py
前面文章我们构建了AlexNet、Vgg、GoogleNet对CIFAR10进行分类:
Pytorch | 从零构建AlexNet对CIFAR10进行分类
Pytorch | 从零构建Vgg对CIFAR10进行分类
Pytorch | 从零构建GoogleNet对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年提出的一种深度卷积神经网络架构,它在ILSVRC 2015图像识别挑战赛中取得了优异成绩,在图像分类、目标检测、语义分割等计算机视觉任务中具有广泛应用。以下是对ResNet的详细介绍:
核心思想
- 解决梯度消失和退化问题:随着神经网络层数的增加,会出现梯度消失或梯度爆炸问题,导致模型难以训练。同时,还会出现网络退化现象,即增加网络层数后,准确率反而下降。ResNet的核心思想是引入残差连接(Residual Connection),通过跨层的shortcut连接,将输入直接传递到后面的层,使得后面的层可以学习到输入的残差,从而缓解了梯度消失和网络退化问题。
网络结构
- 基本残差块:ResNet的基本组成单元是残差块(Residual Block)。一个典型的残差块包含两个3×3卷积层,中间有一个ReLU激活函数,并且在第二个卷积层之后也有一个ReLU激活函数。输入通过一个shortcut连接直接与残差块的输出相加,形成残差学习。
- 不同层数的架构:ResNet有多种不同层数的架构,如ResNet-18、ResNet-34、ResNet-50、ResNet-101和ResNet-152等。其中,数字表示网络中卷积层和全连接层的总层数。层数越深,模型的表示能力越强,但计算成本也越高。
创新点
- 瓶颈结构:在ResNet-50及更深的网络中,采用了瓶颈结构(Bottleneck)的残差块。这种结构先使用1×1卷积层进行降维,然后使用3×3卷积层进行特征提取,最后再使用1×1卷积层进行升维,这样可以在减少计算量的同时增加网络的深度和宽度,提高模型的性能。
- 全局平均池化:在网络的最后一层,ResNet采用了全局平均池化(Global Average Pooling)代替传统的全连接层进行分类。全局平均池化可以将每个特征图的空间维度压缩为一个值,得到一个固定长度的特征向量,然后直接输入到分类器中进行分类。
优点
- 训练深度网络更容易:残差连接使得梯度能够更有效地在网络中传播,大大降低了训练深度网络的难度,使得可以成功训练上百层甚至上千层的网络。
- 性能出色:在各种图像识别任务中,ResNet都取得了非常出色的性能,相比之前的网络结构,具有更高的准确率和更好的泛化能力。
- 模型可扩展性强:可以方便地通过增加残差块的数量来扩展网络的深度,以适应不同的任务和数据集需求。
应用
- 图像分类:ResNet在图像分类任务中取得了巨大成功,如在ImageNet数据集上达到了很高的准确率,成为了图像分类领域的主流模型之一。
- 目标检测:与其他目标检测算法结合,如Faster R-CNN、YOLO等,通过提取图像的特征,提高目标检测的准确率和召回率。
- 语义分割:用于对图像进行像素级的分类,将图像中的每个像素分配到不同的类别中,在城市景观分割、医学图像分割等领域有广泛应用。
ResNet结构代码详解
结构代码
import torch
import torch.nn as nnclass BasicBlock(nn.Module):expansion = 1def __init__(self, in_channels, out_channels, stride=1):super(BasicBlock, self).__init__()self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)self.bn1 = nn.BatchNorm2d(out_channels)self.relu = nn.ReLU(inplace=True)self.conv2 = nn.Conv2d(out_channels, out_channels * BasicBlock.expansion, kernel_size=3, padding=1, bias=False)self.bn2 = nn.BatchNorm2d(out_channels * BasicBlock.expansion)self.shortcut = nn.Sequential()if stride != 1 or in_channels != out_channels * BasicBlock.expansion:self.shortcut = nn.Sequential(nn.Conv2d(in_channels, out_channels * BasicBlock.expansion, kernel_size=1, stride=stride, bias=False),nn.BatchNorm2d(out_channels * BasicBlock.expansion))def forward(self, x):out = self.conv1(x)out = self.bn1(out)out = self.relu(out)out = self.conv2(out)out = self.bn2(out)out += self.shortcut(x)out = self.relu(out)return outclass ResNet(nn.Module):def __init__(self, block, num_blocks, num_classes):super(ResNet, self).__init__()self.in_channels = 64self.conv1 = nn.Conv2d(3, 64, kernel_size=3, padding=1, bias=False)self.bn1 = nn.BatchNorm2d(64)self.relu = nn.ReLU(inplace=True)self.maxpool = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)self.layer1 = self._make_layer(block, 64, num_blocks[0], 1)self.layer2 = self._make_layer(block, 128, num_blocks[1], 2)self.layer3 = self._make_layer(block, 256, num_blocks[2], 2)self.layer4 = self._make_layer(block, 512, num_blocks[3], 2)self.avgpool = nn.AdaptiveAvgPool2d((1, 1))self.fc = nn.Linear(512 * block.expansion, num_classes)def _make_layer(self, block, out_channels, num_blocks, stride=1):strides = [stride] + [1] * (num_blocks - 1)layers = []for stride in strides:layers.append(block(self.in_channels, out_channels, stride))self.in_channels = out_channels * block.expansionreturn nn.Sequential(*layers)def forward(self, x):out = self.conv1(x)out = self.bn1(out)out = self.relu(out)out = self.maxpool(out)out = self.layer1(out)out = self.layer2(out)out = self.layer3(out)out = self.layer4(out)out = self.avgpool(out)out = out.view(out.size(0), -1)out = self.fc(out)return out# ResNet18, ResNet34
def ResNet18(num_classes):return ResNet(BasicBlock, [2, 2, 2, 2], num_classes)def ResNet34(num_classes):return ResNet(BasicBlock, [3, 4, 6, 3], num_classes)# ResNet50, ResNet101, ResNet152 需要 BottleNeck
class Bottleneck(nn.Module):expansion = 4def __init__(self, in_channels, out_channels, stride=1):super(Bottleneck, self).__init__()self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False)self.bn1= nn.BatchNorm2d(out_channels)self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)self.bn2 = nn.BatchNorm2d(out_channels)self.conv3 = nn.Conv2d(out_channels, out_channels * self.expansion, kernel_size=1, bias=False)self.bn3 = nn.BatchNorm2d(out_channels * self.expansion)self.relu = nn.ReLU(inplace=True)self.shortcut = nn.Sequential()if stride != 1 or in_channels != out_channels * self.expansion:self.shortcut = nn.Sequential(nn.Conv2d(in_channels, out_channels * self.expansion, kernel_size=1, stride=stride, bias=False),nn.BatchNorm2d(out_channels * self.expansion))def forward(self, x):out = self.conv1(x)out = self.bn1(out)out = self.relu(out)out = self.conv2(out)out = self.bn2(out)out = self.relu(out)out = self.conv3(out)out = self.bn3(out)out += self.shortcut(x)out = self.relu(out)return outdef ResNet50(num_classes):return ResNet(Bottleneck, [3, 4, 6, 3], num_classes)def ResNet101(num_classes):return ResNet(Bottleneck, [3, 4, 23, 3], num_classes)def ResNet152(num_classes):return ResNet(Bottleneck, [3, 8, 36, 3], num_classes)
代码详解
以下是对上述提供的PyTorch代码的详细解释,这段代码实现了经典的ResNet(残差网络)系列模型,包括ResNet-18、ResNet-34、ResNet-50、ResNet-101和ResNet-152等不同深度的网络架构:
BasicBlock 类
class BasicBlock(nn.Module):expansion = 1def __init__(self, in_channels, out_channels, stride=1):super(BasicBlock, self).__init__()self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)self.bn1 = nn.BatchNorm2d(out_channels)self.relu = nn.ReLU(inplace=True)self.conv2 = nn.Conv2d(out_channels, out_channels * BasicBlock.expansion, kernel_size=3, padding=1, bias=False)self.bn2 = nn.BatchNorm2d(out_channels * BasicBlock.expansion)self.shortcut = nn.Sequential()if stride!= 1 or in_channels!= out_channels * BasicBlock.expansion:self.shortcut = nn.Sequential(nn.Conv2d(in_channels, out_channels * BasicBlock.expansion, kernel_size=1, stride=stride, bias=False),nn.BatchNorm2d(out_channels * BasicBlock.expansion))
- 类定义与属性:
- 定义了一个名为
BasicBlock
的类,继承自nn.Module
,这是PyTorch中定义神经网络模块的基类。 expansion
属性被设置为1
,用于表示该基本块在通道维度上的扩展倍数,在BasicBlock
中通道数不会进行额外的扩展(后续的Bottleneck
块会有不同的扩展倍数)。
- 定义了一个名为
- 初始化方法
__init__
:- 首先调用父类
nn.Module
的初始化方法super(BasicBlock, self).__init__()
,确保模块正确初始化。 - 定义了两个卷积层
conv1
和conv2
:conv1
:输入通道数为in_channels
,输出通道数为out_channels
,卷积核大小为3×3
,步长为stride
,填充为1
,并且不使用偏置(bias=False
),这是遵循ResNet论文中的实现方式,通常配合后续的BatchNorm
使用。conv2
:输入通道数为out_channels
,输出通道数为out_channels * BasicBlock.expansion
(实际就是out_channels
,因为expansion
为1
),卷积核大小同样是3×3
,填充为1
,无偏置。
- 定义了两个
BatchNorm2d
层bn1
和bn2
,分别对应两个卷积层之后,用于对卷积后的特征进行归一化处理,有助于加速训练和提高模型的稳定性。 - 定义了一个
ReLU
激活函数relu
,并且设置inplace=True
,表示直接在原张量上进行激活操作,节省内存空间(但要注意使用不当可能导致梯度计算问题,如前面提到的错误情况)。 - 定义了
shortcut
,初始化为一个空的nn.Sequential
序列。当输入和输出的通道数不一致或者步长不为1
时(意味着尺寸或通道数有变化),会重新构建shortcut
,使其包含一个1×1
卷积层(用于调整通道数)和一个BatchNorm2d
层,以保证shortcut
连接的特征维度能与主分支的输出特征维度相匹配,便于后续进行相加操作。
- 首先调用父类
def forward(self, x):out = self.conv1(x)out = self.bn1(out)out = self.relu(out)out = self.conv2(out)out = self.bn2(out)out += self.shortcut(x)out = self.relu(out)return out
- 前向传播方法
forward
:- 首先将输入
x
经过conv1
卷积、bn1
归一化后,再通过relu
激活函数得到中间特征。 - 接着将该中间特征再经过
conv2
卷积和bn2
归一化。 - 然后将主分支得到的特征
out
与shortcut
分支(直接连接输入x
经过调整后的特征)进行逐元素相加,实现残差连接的操作。 - 最后再经过一次
relu
激活函数后返回结果,作为该基本块的输出。
- 首先将输入
ResNet 类
class ResNet(nn.Module):def __init__(self, block, num_blocks, num_classes):super(ResNet, self).__init__()self.in_channels = 64self.conv1 = nn.Conv2d(3, 64, kernel_size=3, padding=1, bias=False)self.bn1 = nn.BatchNorm2d(64)self.relu = nn.ReLU(inplace=True)self.maxpool = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)self.layer1 = self._make_layer(block, 64, num_blocks[0], 1)self.layer2 = self._make_layer(block, 128, num_blocks[1], 2)self.layer3 = self._make_layer(block, 256, num_blocks[2], 2)self.layer4 = self._make_layer(block, 512, num_blocks[3], 2)self.avgpool = nn.AdaptiveAvgPool2d((1, 1))self.fc = nn.Linear(512 * block.expansion, num_classes)
- 类定义与属性:
- 定义了
ResNet
类,同样继承自nn.Module
,用于构建完整的ResNet网络架构。 - 初始化了一个属性
in_channels
为64
,用于记录当前层的输入通道数,后续会动态更新。 - 定义了网络的起始层,包括一个
3×3
卷积层conv1
(输入通道为3
,对应彩色图像的RGB三个通道,输出通道为64
),一个BatchNorm2d
层bn1
用于归一化,一个ReLU
激活函数relu
,以及一个最大池化层maxpool
(其参数设置按照常规的ResNet结构配置)。 - 分别定义了
layer1
、layer2
、layer3
、layer4
这四层网络结构,它们通过调用_make_layer
方法来构建,每层的输出通道数以及重复的块数量由传入的参数决定,并且随着层数加深,步长会相应改变(从第二层开始步长为2
,用于逐步减小特征图尺寸)。 - 定义了一个自适应平均池化层
avgpool
,它能将输入的特征图尺寸自适应地变为(1, 1)
大小,无论输入特征图的尺寸原本是多少,便于后续全连接层处理。最后定义了一个全连接层fc
,用于将池化后的特征映射到指定的类别数num_classes
上进行分类。
- 定义了
def _make_layer(self, block, out_channels, num_blocks, stride=1):strides = [stride] + [1] * (num_blocks - 1)layers = []for stride in strides:layers.append(block(self.in_channels, out_channels, stride))self.in_channels = out_channels * block.expansionreturn nn.Sequential(*layers)
_make_layer
方法:- 这个方法用于构建ResNet中的每一层网络结构(由多个基本块组成)。
- 首先根据传入的
stride
和num_blocks
生成一个步长列表strides
,例如,如果传入stride=2
和num_blocks=3
,那么strides
会是[2, 1, 1]
,意味着第一个基本块可能会改变特征图的尺寸和通道数,后面的基本块保持步长为1
。 - 然后循环遍历
strides
列表,每次创建一个指定的block
(可以是BasicBlock
或者后续定义的Bottleneck
块),并传入当前的输入通道数、输出通道数以及对应的步长,将创建好的块添加到layers
列表中。同时,更新self.in_channels
为当前块输出的通道数(考虑了块的扩展倍数)。 - 最后将
layers
列表中的所有块组合成一个nn.Sequential
序列并返回,形成一层完整的网络结构。
def forward(self, x):out = self.conv1(x)out = self.bn1(out)out = self.relu(out)out = self.maxpool(out)out = self.layer1(out)out = self.layer2(out)out = self.layer3(out)out = self.layer4(out)out = self.avgpool(out)out = out.view(out.size(0), -1)out = self.fc(out)return out
- 前向传播方法
forward
:- 首先将输入
x
依次经过网络起始层的卷积、归一化、激活和池化操作,得到初步的特征表示。 - 然后将该特征依次通过
layer1
、layer2
、layer3
、layer4
这四层网络结构,不断提取和融合特征,每一层都会进一步加深特征的抽象程度并且改变特征图的尺寸和通道数。 - 接着经过自适应平均池化层
avgpool
,将特征图变为(1, 1)
大小的特征向量。 - 通过
out.view(out.size(0), -1)
操作将特征向量展平为一维向量,使其能输入到全连接层fc
中。 - 最后将全连接层的输出作为整个网络的最终输出,返回分类结果。
- 首先将输入
ResNet18、ResNet34、ResNet50、ResNet101、ResNet152函数
# ResNet18, ResNet34
def ResNet18(num_classes):return ResNet(BasicBlock, [2, 2, 2, 2], num_classes)def ResNet34(num_classes):return ResNet(BasicBlock, [3, 4, 6, 3], num_classes)
- 这两个函数分别用于创建
ResNet-18
和ResNet-34
网络模型。它们通过调用ResNet
类的构造函数,传入BasicBlock
作为构建块类型,以及对应不同层数的重复块数量列表(如ResNet-18
中每层分别重复2
个基本块),还有指定的类别数num_classes
,最终返回构建好的相应深度的ResNet模型实例,用于图像分类等任务。
# ResNet50, ResNet101, ResNet152 需要 BottleNeck
class Bottleneck(nn.Module):expansion = 4def __init__(self, in_channels, out_channels, stride=1):super(Bottleneck, self).__init__()self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False)self.bn1= nn.BatchNorm2d(out_channels)self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)self.bn2 = nn.BatchNorm2d(out_channels)self.conv3 = nn.Conv2d(out_channels, out_channels * self.expansion, kernel_size=1, bias=False)self.bn3 = nn.BatchNorm2d(out_channels * self.expansion)self.relu = nn.ReLU(inplace=True)self.shortcut = nn.Sequential()if stride!= 1 or in_channels!= out_channels * self.expansion:self.shortcut = nn.Sequential(nn.Conv2d(in_channels, out_channels * self.expansion, kernel_size=1, stride=stride, bias=False),nn.BatchNorm2d(out_channels * self.expansion))
Bottleneck
类定义与初始化:- 定义了
Bottleneck
类,同样继承自nn.Module
,用于构建更深层的ResNet网络(如ResNet-50
及以上)中的基本块。 expansion
属性被设置为4
,意味着该块在经过一系列操作后,输出通道数会是输入通道数的4
倍,通过这种方式在增加网络深度的同时控制计算量。- 在初始化方法中,定义了三个卷积层
conv1
、conv2
、conv3
,分别是1×1
卷积用于降维、3×3
卷积进行主要的特征提取、1×1
卷积用于升维,并且每个卷积层后都有对应的BatchNorm2d
层进行归一化,还有ReLU
激活函数用于激活中间特征。 - 同样定义了
shortcut
,其构建逻辑和BasicBlock
中类似,根据输入输出通道数和步长情况来决定是否需要构建包含1×1
卷积和BatchNorm2d
层的调整结构,以保证残差连接的维度匹配。
- 定义了
def forward(self, x):out = self.conv1(x)out = self.bn1(out)out = self.relu(out)out = self.conv2(out)out = self.bn2(out)out = self.relu(out)out = self.conv3(out)out = self.bn3(out)out += self.shortcut(x)out = self.relu(out)return out
Bottleneck
块的前向传播方法:- 前向传播过程与
BasicBlock
类似,只是中间经过了三个卷积层及对应的归一化和激活操作,最后同样是将主分支特征与shortcut
分支特征相加后再经过ReLU
激活函数输出,实现残差学习。
- 前向传播过程与
def ResNet50(num_classes):return ResNet(Bottleneck, [3, 4, 6, 3], num_classes)def ResNet101(num_classes):return ResNet(Bottleneck, [3, 4, 23, 3], num_classes)def ResNet152(num_classes):return ResNet(Bottleneck, [3, 8, 36, 3], num_classes)
- 这几个函数分别用于创建
ResNet-50
、ResNet-101
和ResNet-152
网络模型,它们与创建ResNet-18
、ResNet-34
的函数类似,只是传入的构建块类型变为Bottleneck
,以及对应不同层数的重复Bottleneck
块数量列表,还有指定的类别数num_classes
,最终返回相应深度的ResNet模型实例,用于更复杂的图像分类等任务,这些更深层的网络结构在处理大规模图像数据集时往往能取得更好的性能表现。
训练过程和测试结果
训练过程损失函数变化曲线:
训练过程准确率变化曲线:
测试结果:
代码汇总
项目github地址
项目结构:
|--data
|--models|--__init__.py|-resnet.py|--...
|--results
|--weights
|--train.py
|--test.py
resnet.py
import torch
import torch.nn as nnclass BasicBlock(nn.Module):expansion = 1def __init__(self, in_channels, out_channels, stride=1):super(BasicBlock, self).__init__()self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)self.bn1 = nn.BatchNorm2d(out_channels)self.relu = nn.ReLU(inplace=True)self.conv2 = nn.Conv2d(out_channels, out_channels * BasicBlock.expansion, kernel_size=3, padding=1, bias=False)self.bn2 = nn.BatchNorm2d(out_channels * BasicBlock.expansion)self.shortcut = nn.Sequential()if stride != 1 or in_channels != out_channels * BasicBlock.expansion:self.shortcut = nn.Sequential(nn.Conv2d(in_channels, out_channels * BasicBlock.expansion, kernel_size=1, stride=stride, bias=False),nn.BatchNorm2d(out_channels * BasicBlock.expansion))def forward(self, x):out = self.conv1(x)out = self.bn1(out)out = self.relu(out)out = self.conv2(out)out = self.bn2(out)out += self.shortcut(x)out = self.relu(out)return outclass ResNet(nn.Module):def __init__(self, block, num_blocks, num_classes):super(ResNet, self).__init__()self.in_channels = 64self.conv1 = nn.Conv2d(3, 64, kernel_size=3, padding=1, bias=False)self.bn1 = nn.BatchNorm2d(64)self.relu = nn.ReLU(inplace=True)self.maxpool = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)self.layer1 = self._make_layer(block, 64, num_blocks[0], 1)self.layer2 = self._make_layer(block, 128, num_blocks[1], 2)self.layer3 = self._make_layer(block, 256, num_blocks[2], 2)self.layer4 = self._make_layer(block, 512, num_blocks[3], 2)self.avgpool = nn.AdaptiveAvgPool2d((1, 1))self.fc = nn.Linear(512 * block.expansion, num_classes)def _make_layer(self, block, out_channels, num_blocks, stride=1):strides = [stride] + [1] * (num_blocks - 1)layers = []for stride in strides:layers.append(block(self.in_channels, out_channels, stride))self.in_channels = out_channels * block.expansionreturn nn.Sequential(*layers)def forward(self, x):out = self.conv1(x)out = self.bn1(out)out = self.relu(out)out = self.maxpool(out)out = self.layer1(out)out = self.layer2(out)out = self.layer3(out)out = self.layer4(out)out = self.avgpool(out)out = out.view(out.size(0), -1)out = self.fc(out)return out# ResNet18, ResNet34
def ResNet18(num_classes):return ResNet(BasicBlock, [2, 2, 2, 2], num_classes)def ResNet34(num_classes):return ResNet(BasicBlock, [3, 4, 6, 3], num_classes)# ResNet50, ResNet101, ResNet152 需要 BottleNeck
class Bottleneck(nn.Module):expansion = 4def __init__(self, in_channels, out_channels, stride=1):super(Bottleneck, self).__init__()self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False)self.bn1= nn.BatchNorm2d(out_channels)self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)self.bn2 = nn.BatchNorm2d(out_channels)self.conv3 = nn.Conv2d(out_channels, out_channels * self.expansion, kernel_size=1, bias=False)self.bn3 = nn.BatchNorm2d(out_channels * self.expansion)self.relu = nn.ReLU(inplace=True)self.shortcut = nn.Sequential()if stride != 1 or in_channels != out_channels * self.expansion:self.shortcut = nn.Sequential(nn.Conv2d(in_channels, out_channels * self.expansion, kernel_size=1, stride=stride, bias=False),nn.BatchNorm2d(out_channels * self.expansion))def forward(self, x):out = self.conv1(x)out = self.bn1(out)out = self.relu(out)out = self.conv2(out)out = self.bn2(out)out = self.relu(out)out = self.conv3(out)out = self.bn3(out)out += self.shortcut(x)out = self.relu(out)return outdef ResNet50(num_classes):return ResNet(Bottleneck, [3, 4, 6, 3], num_classes)def ResNet101(num_classes):return ResNet(Bottleneck, [3, 4, 23, 3], num_classes)def ResNet152(num_classes):return ResNet(Bottleneck, [3, 8, 36, 3], num_classes)
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 = 'ResNet18'
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)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 = 'ResNet18'
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)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 | 从零构建ResNet对CIFAR10进行分类
Pytorch | 从零构建ResNet对CIFAR10进行分类 CIFAR10数据集ResNet核心思想网络结构创新点优点应用 ResNet结构代码详解结构代码代码详解BasicBlock 类ResNet 类ResNet18、ResNet34、ResNet50、ResNet101、ResNet152函数 训练过程和测试结果代码汇总resnet.pytrain.pytest.py 前…...
Spring整合Redis基本操作步骤
Spring 整合 Redis 操作步骤总结 1. 添加依赖 首先,在 pom.xml 文件中添加必要的 Maven 依赖。Redis 相关的依赖包括 Spring Boot 的 Redis 启动器和 fastjson(如果需要使用 Fastjson 作为序列化工具): <!-- Spring Boot Re…...
java中的方法的重载和重写、构造器
目录 方法的重载和重写、构造器1.java的修饰符:2.普通方法3.构造器(也叫构造方法/构造函数)4.方法的重载5.补充6.方法的重写7.类的执行顺序8.再看方法的重写 方法的重载和重写、构造器 1.java的修饰符: public修饰的代码…...
Vite 系列课程|1课程道路,2什么是构建工具
Vite 系列课程 1. 课程导论 1.1 为什么要学习 Vite? 1.1.1 Webpack vs. Vite:新旧霸主的交替? Webpack 长期以来一直是前端构建工具的事实标准,拥有庞大的用户群体、成熟的生态系统和丰富的学习资源。然而,随着前端技术…...
【蓝桥杯选拔赛真题96】Scratch风车旋转 第十五届蓝桥杯scratch图形化编程 少儿编程创意编程选拔赛真题解析
目录 scratch风车旋转 一、题目要求 编程实现 二、案例分析 1、角色分析 2、背景分析 3、前期准备 三、解题思路 1、思路分析 2、详细过程 四、程序编写 五、考点分析 六、推荐资料 1、入门基础 2、蓝桥杯比赛 3、考级资料 4、视频课程 5、python资料 scratc…...
SQL血缘解析
Druid 作为使用率特别高的的数据库连接池工具,在具备完善的连接池管理功能外,同时Druid 的 SQL解析功能可以用来防止 SQL注入等安全风险。通过对 SQL 语句进行解析和检查,Druid 可以识别并阻止潜在的恶意 SQL 语句执行,黑名单(阻止特定的 SQL 语句执行)、白名单(仅允许特…...
Docker 部署机器学习模型
1.编写机器学习代码 (1)新建一个 mlmodel.py import numpy as np import pandas as pd from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sk…...
leetcode 面试经典 150 题:无重复字符的最长子串
链接无重复字符的最长子串题序号3类型字符串解题方法滑动窗口难度中等 题目 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: s “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。 …...
LeetCode 283. 移动零 (C++实现)
1. 题目描述 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 请注意 ,必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0] 示例 …...
基于Spring Boot的个人财务系统
一、系统背景与目的 随着全球经济的发展和人们生活水平的提高,个人财务管理变得越来越重要。传统的个人财务软件存在操作复杂、用户体验差、数据不安全等问题,无法满足用户的个性化需求。因此,开发一种基于Spring Boot的个人财务系统&#x…...
【计算机网络2】计算机网络的性能能指标
目录 一 、计算机网络的性能指标 二、具体介绍 1、速 率 2、带 宽 3、吞 吐 量 4、时 延 5、时延带宽积 6、往 返 时 延 7、信道利用率 一 、计算机网络的性能指标 计算机网络的性能指标就是从不同方面度量计算机网络的性能,有如下7个指标: 速…...
Axure RP9 的详细安装及Axure入门应用
文章目录 一、Axure 是什么?二、Axure 的应用场景三、Axure 安装1. 下载安装2. 汉化授权 附:下载链接 一、Axure 是什么? 1、Axure 是一种强大的原型设计工具,它可以帮助设计师和产品经理快速创建交互式的、高保真度的原型,并进行用户体验…...
Scala学习记录 如何打印输出
在Scala中,打印输出可以通过多种方式实现,以下是一些常见的打印输出方法: 1.使用printf()方法: 这是一种传统的C语言风格的打印方式,通过格式化字符串来控制输出的格式。例如,printf("整数:…...
内网IP段介绍与汇总
IPV4内网段 IP地址段地址范围地址数量用途描述0.0.0.0/80.0.0.0–0.255.255.25516777216SoftwareCurrent network (only valid as source address).10.0.0.0/810.0.0.0–10.255.255.25516777216Private networkUsed for local communications within a private network.100.64…...
js常用方法之: 预览大图(uniapp原生方法封装)
方法: //预览图片 pic可传单个图片地址字符串 或 图片数组(带index) previewPic: function(pic, index) {if (!pic) return;if (index undefined) {let array [];array.push(pic);uni.previewImage({urls: array,current: array[0]});} else {uni.previewImage({urls: pic,…...
人脸生成3d模型 Era3D
从单视图图像进行3D重建是计算机视觉和图形学中的一项基本任务,因为它在游戏设计、虚拟现实和机器人技术中具有潜在的应用价值。早期的研究主要依赖于直接在体素上进行3D回归,这往往会导致过于平滑的结果,并且由于3D训练数据的限制࿰…...
「Mac畅玩鸿蒙与硬件46」UI互动应用篇23 - 自定义天气预报组件
本篇将带你实现一个自定义天气预报组件。用户可以通过选择不同城市来获取相应的天气信息,页面会显示当前城市的天气图标、温度及天气描述。这一功能适合用于动态展示天气信息的小型应用。 关键词 UI互动应用天气预报数据绑定动态展示状态管理 一、功能说明 自定义…...
深圳龙岗戴尔dell r730xd服务器故障维修
深圳龙岗一台DELL POWEREDGE R730XD服务器系统故障问题处理: 1:客户工厂年底产线整改,时不时的会意外断电,导致服务器也频繁停机, 2:多次异常停机后导致服务器开机后windows server系统无法正常启动了&…...
lxml提取某个外层标签里的所有文本
html如下 <div data-v-1cf6f280"" class"analysis-content">选项D错误:<strong>在衡量通货膨胀时,</strong><strong>消费者物价指数使用得最多、最普遍</strong>。 </div> 解析html文本 fro…...
【AI图像生成网站Golang】项目测试与优化
AI图像生成网站 目录 一、项目介绍 二、雪花算法 三、JWT认证与令牌桶算法 四、项目架构 五、图床上传与图像生成API搭建 六、项目测试与优化 六、项目测试与优化 在开发过程中,性能优化是保证项目可扩展性和用户体验的关键步骤。本文将详细介绍我如何使用一…...
使用Docker启用MySQL8.0.11
目录 一、Docker减小镜像大小的方式 1、基础镜像选择 2、减少镜像层数 3、清理无用文件和缓存 4、优化文件复制(COPY和ADD指令) 二、Docker镜像多阶段构建 1、什么是dockers镜像多阶段构建 1.1 概念介绍 1.2 构建过程和优势 2、怎样在Dockerfil…...
部署Mysql、镜像和容器、常见命令
目录 部署Mysql 镜像和容器 常见命令 部署Mysql 可以有多个容器 docker run -d \--name mysql \-p 3306:3306 \-e TZAsia/Shanghai \-e MYSQL_ROOT_PASSWORD123 \mysql docker run -d \--name mysql2 \-p 3307:3307 \-e TZAsia/Shanghai \-e MYSQL_ROOT_PASSWORD123 \mys…...
Windows部署Docker及PostgreSQL数据库相关操作
一、Windows安装Docker 1.wsl安装 以管理员身份启动命令行,运行:wsl --install; 安装结束后,重启电脑,以管理员身份启动命令行,运行:wsl --install -d Ubuntu; 中间需要输入用户名…...
Halcon例程代码解读:安全环检测(附源码|图像下载链接)
安全环检测核心思路与代码详解 项目目标 本项目的目标是检测图像中的安全环位置和方向。通过形状匹配技术,从一张模型图像中提取安全环的特征,并在后续图像中识别多个实例,完成检测和方向标定。 实现思路 安全环检测分为以下核心步骤&…...
Unity3D用正则判断身份证号或邮箱
系列文章目录 unity工具 文章目录 系列文章目录👉前言👉一、正则判断邮箱格式👉二、正则判断身份证号👉壁纸分享👉总结👉前言 C#正则表达式(Regex)是一种用来匹配字符串模式的强大工具。在C#中,可以使用System.Text.RegularExpressions命名空间下的Regex类来处…...
PostgreSQL表达式的类型
PostgreSQL表达式是数据库查询中非常重要的组成部分,它们由一个或多个值、运算符和PostgreSQL函数组合而成,用于计算出一个单一的结果。这些表达式类似于公式,可以用查询语言编写,并用于查询数据库中的特定数据集。 PostgreSQL表…...
C++简明教程(文章要求学过一点C语言)(10)
类的教程 C 类的完整教程 C 中,类(class)是面向对象编程的核心概念,用于定义对象的属性(数据成员)和行为(成员函数)。本教程将带你从零开始,循序渐进地学习如何定义和使…...
从腾讯云的恶意文件查杀学习下PHP的eval函数
问题来自于腾讯云的主机安全通知: 🚀一键接入,畅享GPT及AI大模型服务!【顶级API中转品牌】: https://api.ablai.top/ 病毒文件副本内容如下: <?php function x($x){eval($x);}x(str_rot13(riny($_CBF…...
OpenWRT——官方镜像安装Docker(网络环境需设置)并配置Sun-Panel
Pro更多功能预览地址https://pro.sun-panel.top/#/hpage/pro Github地址https://github.com/hslr-s/sun-panel?tabreadme-ov-file 首先确认宿主机网络环境符合要求 curl Google.com1.确认没问题后开始安装Docker opkg update opkg install dockerd docker luci-app-docker…...
MySQL 中的常见错误与排查
在 MySQL 数据库的日常运维中,管理员可能会遇到各种错误。无论是查询性能问题、连接异常、数据一致性问题,还是磁盘空间不足等,及时排查并解决这些问题是保证数据库稳定运行的关键。本文将列出 MySQL 中一些常见的错误及其排查方法。 一、连接…...
workman服务端开发模式-应用开发-gateway长链接端工作原理
一、长链接的工作原理 Register类其实也是基于基础的Worker开发的。Gateway进程和BusinessWorker进程启动后分别向Register进程注册自己的通讯地址,Gateway进程和BusinessWorker通过Register进程得到通讯地址后,就可以建立起连接并通讯了。而Gateway进程…...
8位移位寄存器的verilog语言
module shift_register (output reg [7:0] Q, // 8位移位寄存器输出input D, // 输入数据input rst, // 复位信号input clk // 时钟信号 );always (posedge clk) beginif (!rst)Q < 8b00000000; // 复位时将Q清零elseQ < {Q[6:0], D}; // 否则…...
Android学习(五)-Kotlin编程语言-面向对象中的 继承-构造函数-接口三模块学习
首先,我们需要定义一个 Person 类: open class Person {var name ""var age 0fun eat() {println("$name is eating.")} } 注意,Person 类前面加上了 open 关键字,表示这个类可以被继承。在 Kotlin 中&am…...
Java 集合框架中的 List、ArrayList 和 泛型 实例
— Java 集合框架中的 List、ArrayList 和 泛型 在 Java 中,集合框架提供了许多不同类型的集合类,用于存储和操作对象。List 和 ArrayList 是最常用的两种集合类型,而泛型(Generics)则是 Java 中的一项重要特性&…...
计算机网络-L2TP VPN基础概念与原理
一、概述 前面学习了GRE和IPSec VPN,今天继续学习另外一个也很常见的VPN类型-L2TP VPN。 L2TP(Layer 2 Tunneling Protocol) 协议结合了L2F协议和PPTP协议的优点,是IETF有关二层隧道协议的工业标准。L2TP是虚拟私有拨号网VPDN&…...
【Rust自学】4.4. 引用与借用
4.4.0 写在正文之前 这一节的内容其实就相当于C的智能指针移动语义在编译器层面做了一些约束。Rust中引用的写法通过编译器的约束写成了C中最理想、最规范的指针写法。所以学过C的人对这一章肯定会非常熟悉。 喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文…...
LLaMA-Factory 单卡3080*2 deepspeed zero3 微调Qwen2.5-7B-Instruct
环境安装 git clone https://gitcode.com/gh_mirrors/ll/LLaMA-Factory.gitcd LLaMA-Factorypip install -e ".[torch,metrics]"pip install deepspeed 下载模型 pip install modelscope modelscope download --model Qwen/Qwen2.5-7B-Instruct --local_dir /roo…...
[python SQLAlchemy数据库操作入门]-12.直接执行 SQL 语句处理股票数据
哈喽,大家好,我是木头左! 1. SQLAlchemy Core 简介 SQLAlchemy Core 是 SQLAlchemy 库的一个模块,它允许用户直接执行 SQL 语句而不必使用 ORM(对象关系映射)。对于需要精细控制 SQL 查询或处理复杂数据库操作的情况,SQLAlchemy Core 提供了一种灵活而强大的方式来与数…...
【Unity3D】实现可视化链式结构数据(节点数据)
关键词:UnityEditor、可视化节点编辑、Unity编辑器自定义窗口工具 使用Newtonsoft.Json、UnityEditor相关接口实现 主要代码: Handles.DrawBezier(起点,终点,起点切线向量,终点切线向量,颜色,n…...
C# WinForm移除非法字符的输入框
C# WinForm移除非法字符的输入框 文章目录 namespace System.Windows.Forms {using System.ComponentModel;/// <summary>/// 支持移除 非法字符 的输入框。/// </summary>public class RemoveInvalidCharTextBox : TextBox{/// <summary>/// 测试代码&#…...
linux安装宝塔面板及git
宝塔面板安装教程:https://www.bt.cn/new/download.html?bt_lybaidu&sdclkidALfs15q615oG15As&bd_vid9358688624393223862 Centos/OpenCloud/Alibaba稳定版9.0.0 urlhttps://download.bt.cn/install/install_lts.sh;if [ -f /usr/bin/curl ];then curl -s…...
GoTime#34期 Pachyderm, Provenance, Data Lakes
本篇内容是根据2017年2月份#34 Pachyderm, Provenance, Data Lakes音频录制内容的整理与翻译 Joe Doliner 加入了节目,谈论使用 Pachyderm 管理数据湖、数据容器、溯源(provenance) 以及其他有趣的 Go 项目和新闻。 Erik St. Martin: 大家好,欢迎收听新…...
数据库的三范式是什么?
第一范式(1NF) 每列的原子性,表中的每一个字段都是不可分割的,同一列中不能有多个值。第一范式是对关系模式的基本要求,不满足第一范式的数据库不是关系型数据库。 ・不满足第一范式的示例: 学生编号 学生…...
LOS/NLOS环境建模与三维TOA定位,MATLAB仿真程序,可自定义锚点数量和轨迹点长度
本代码的主要功能是建模 LOS(视距)和 NLOS(非视距)环境下的定位系统,估计目标的动态位置,三维空间 文章目录 运行结果源代码代码介绍 总结 运行结果 10个点的轨迹定位: 50个点的轨迹定位&#…...
css
已经学完html了,继续学习前端三剑客html、css、js之一的css。😀 1、什么是css css:用于网页结构的布局和修饰的一种样式脚本 层叠样式表:(英文全称:Cascading Style Sheets), 简称:样式表&…...
探索 Bokeh:轻松创建交互式数据可视化的强大工具
探索 Bokeh:轻松创建交互式数据可视化的强大工具 在数据科学和数据分析领域,交互式数据可视化是一项不可或缺的技能。Bokeh 是一个强大的 Python 库,它可以帮助我们快速构建高质量的交互式图表和仪表盘,同时兼具高性能和灵活性。…...
光谱相机在农业的应用
一、作物生长监测1、营养状况评估 原理:不同的营养元素在植物体内的含量变化会导致植物叶片或其他组织的光谱反射率特性发生改变。例如,氮元素是植物叶绿素的重要组成部分,植物缺氮时,叶绿素含量下降,其在可见光波段&a…...
SYD881X RTC定时器事件在调用timeAppClockSet后会出现比较大的延迟
RTC定时器事件在调用timeAppClockSet后会出现比较大的延迟 这里RTC做了两个定时器一个是12秒,一个是185秒: #define RTCEVT_NUM ((uint8_t) 0x02)//当前定时器事件数#define RTCEVT_12S ((uint32_t) 0x0000002)//定时器1s事件 /*整分钟定时器事件,因为其余的…...
【Java基础面试题026】Java中的String、StringBuffer和StringBuilder的区别是什么?
回答重点 他们都是Java中处理字符串的类,区别主要体现在可变性、线程安全和性能上 1)String 不可变:String是不可变类,字符串对象创建,存储在堆中,字符串内容存储在字符串常量池中,一旦创建内…...
Ajax中的axios
既然提到Ajax,那就先来说一说什么是Ajax吧 关于Ajax Ajax的定义 Asynchronous JavaScript And XML:异步的JavaScript和XML。 反正就是一句话总结: 使用XML HttpRequest 对象与服务器进行通讯。 AJAX 是一种在无需重新加载整个网页的情况下&…...