7. 现代卷积神经网络
文章目录
- 7.1. 深度卷积神经网络(AlexNet)
- 7.2. 使用块的网络(VGG)
- 7.3. 网络中的网络(NiN)
- 7.4. 含并行连结的网络(GoogLeNet)
- 7.5. 批量规范化
- 7.5.1. 训练深层网络
- 7.5.2. 批量规范化层
- 7.5.2.1. 全连接层
- 7.5.2.2. 卷积层
- 7.5.2.3. 预测过程中的批量规范化
- 7.5.3. 从零实现
- 7.5.4. 使用批量规范化层的 LeNe
- 7.5.5. 简明实现
- 7.5.7. 小结
- 7.6. 残差网络(ResNet)
- 7.6.1. 函数类
- 7.6.2. 残差块
- 7.6.3. ResNet模型
- 7.6.5. 小结
- 7.7. 稠密连接网络(DenseNet)
- 7.7.1. 从ResNet到DenseNet
- 7.7.2. 稠密块体
- 7.7.3. 过渡层
- 7.7.4. DenseNet模型
- 7.7.6 小结
7.1. 深度卷积神经网络(AlexNet)
-
AlexNet的网络结构:AlexNet和LeNet的设计理念非常相似,但也存在显著差异。
- 模型设计:在AlexNet的第一层,卷积窗口的形状是 11x11,由于ImageNet中大多数图像的宽和高比MNIST图像的多10倍以上,因此,需要一个更大的卷积窗口来捕获目标。而且,AlexNet的卷积通道数目是LeNet的10倍。
- 激活函数: AlexNet使用ReLU而不是sigmoid作为其激活函数。一方面,ReLU激活函数的计算更简单,它不需要如sigmoid激活函数那般复杂的求幂运算。 另一方面,当使用不同的参数初始化方法时,ReLU激活函数使训练模型更加容易,因为当sigmoid激活函数的输出非常接近于0或1时,这些区域的梯度几乎为0,因此反向传播无法继续更新一些模型参数。相反,ReLU激活函数在正区间的梯度总是1。
- 容量控制和预处理:AlexNet通过暂退法控制全连接层的模型复杂度,而LeNet只使用了权重衰减。 为了进一步扩充数据,AlexNet在训练时增加了大量的图像增强数据,如翻转、裁切和变色。
-
AlexNet 代码实现:
import torch from torch import nn from d2l import torch as d2lnet = nn.Sequential(# 这里使用一个11*11的更大窗口来捕捉对象。# 同时,步幅为4,以减少输出的高度和宽度。# 另外,输出通道的数目远大于LeNet# 注意这里输入通道为1是因为后面训练在Fashion-MNIST数据集上训练,而不是ImageNet上nn.Conv2d(1, 96, kernel_size=11, stride=4, padding=1), nn.ReLU(),nn.MaxPool2d(kernel_size=3, stride=2),# 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数nn.Conv2d(96, 256, kernel_size=5, padding=2), nn.ReLU(),nn.MaxPool2d(kernel_size=3, stride=2),# 使用三个连续的卷积层和较小的卷积窗口。# 除了最后的卷积层,输出通道的数量进一步增加。# 在前两个卷积层之后,汇聚层不用于减少输入的高度和宽度nn.Conv2d(256, 384, kernel_size=3, padding=1), nn.ReLU(),nn.Conv2d(384, 384, kernel_size=3, padding=1), nn.ReLU(),nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(),nn.MaxPool2d(kernel_size=3, stride=2),nn.Flatten(),# 这里,全连接层的输出数量是LeNet中的好几倍。使用dropout层来减轻过拟合nn.Linear(6400, 4096), nn.ReLU(),nn.Dropout(p=0.5),nn.Linear(4096, 4096), nn.ReLU(),nn.Dropout(p=0.5),# 最后是输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000nn.Linear(4096, 10))# 训练模型: Fashion-MNIST图像的分辨率28低于ImageNet图像,这里手动resize成224 batch_size = 128 train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)
查看网络每一层的输出(本章后面查看网络输出都是这个代码):
X = torch.randn(1, 1, 224, 224) for layer in net:X=layer(X)print(layer.__class__.__name__,'output shape:\t',X.shape)
结果:
Conv2d output shape: torch.Size([1, 96, 54, 54]) ReLU output shape: torch.Size([1, 96, 54, 54]) MaxPool2d output shape: torch.Size([1, 96, 26, 26]) Conv2d output shape: torch.Size([1, 256, 26, 26]) ReLU output shape: torch.Size([1, 256, 26, 26]) MaxPool2d output shape: torch.Size([1, 256, 12, 12]) Conv2d output shape: torch.Size([1, 384, 12, 12]) ReLU output shape: torch.Size([1, 384, 12, 12]) Conv2d output shape: torch.Size([1, 384, 12, 12]) ReLU output shape: torch.Size([1, 384, 12, 12]) Conv2d output shape: torch.Size([1, 256, 12, 12]) ReLU output shape: torch.Size([1, 256, 12, 12]) MaxPool2d output shape: torch.Size([1, 256, 5, 5]) Flatten output shape: torch.Size([1, 6400]) Linear output shape: torch.Size([1, 4096]) ReLU output shape: torch.Size([1, 4096]) Dropout output shape: torch.Size([1, 4096]) Linear output shape: torch.Size([1, 4096]) ReLU output shape: torch.Size([1, 4096]) Dropout output shape: torch.Size([1, 4096]) Linear output shape: torch.Size([1, 10])
-
AlexNet 模型训练:与LeNet相比,这里的主要变化是使用更小的学习速率训练,这是因为网络更深更广、图像分辨率更高,训练卷积神经网络就更昂贵。
7.2. 使用块的网络(VGG)
-
经典卷积神经网络的基本组成部分是下面的这个序列:
- 带填充以保持分辨率的卷积层;
- 非线性激活函数,如ReLU;
- 汇聚层,如最大汇聚层。
一个VGG块与之类似,由一系列卷积层组成,后面再加上用于空间下采样的最大汇聚层。在最初的VGG论文中 ,作者使用了带有 3x3卷积核、填充为1(保持高度和宽度)的卷积层,和带有2x2汇聚窗口、步幅为2(每个块后的分辨率减半)的最大汇聚层。
下面定义一个名为vgg_block的函数来实现一个VGG块,该函数有三个参数,分别对应于卷积层的数量num_convs、输入通道的数量in_channels 和输出通道的数量out_channels.
import torch from torch import nn from d2l import torch as d2ldef vgg_block(num_convs, in_channels, out_channels):layers = []for _ in range(num_convs):layers.append(nn.Conv2d(in_channels, out_channels,kernel_size=3, padding=1))layers.append(nn.ReLU())in_channels = out_channels# 注意只有在整个vgg块的最后才加入一个MaxPool2d层layers.append(nn.MaxPool2d(kernel_size=2,stride=2))# *layers表示对list进行解包,直接得到里面的所有元素return nn.Sequential(*layers)
-
VGG网络:与AlexNet、LeNet一样,VGG网络可以分为两部分:第一部分主要由卷积层和汇聚层组成,第二部分由全连接层组成。此外,在VGG论文中,作者尝试了各种架构。特别是他们发现深层且窄的卷积(即 3x3)比较浅层且宽的卷积更有效。
VGG神经网络连接图中的几个VGG块(在vgg_block函数中定义)。其中有超参数变量conv_arch。该变量指定了每个VGG块里卷积层个数和输出通道数。全连接模块则与AlexNet中的相同。
原始VGG网络有5个卷积块,其中前两个块各有一个卷积层,后三个块各包含两个卷积层。 第一个模块有64个输出通道,每个后续模块将输出通道数量翻倍,直到该数字达到512。由于该网络使用8个卷积层和3个全连接层,因此它通常被称为VGG-11。
# 制定VGG网络中,每个vgg块中卷积层的个数以及输出通道维度
conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))def vgg(conv_arch):conv_blks = []# 最开始进入卷积层的图像通道数为1,这里使用Fashion-MNIST数据集in_channels = 1# 卷积层部分for (num_convs, out_channels) in conv_arch:conv_blks.append(vgg_block(num_convs, in_channels, out_channels))in_channels = out_channelsreturn nn.Sequential(*conv_blks, nn.Flatten(),# 全连接层部分nn.Linear(out_channels * 7 * 7, 4096), nn.ReLU(), nn.Dropout(0.5),nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(0.5),nn.Linear(4096, 10))net = vgg(conv_arch)
网络每一层的输出:
Sequential output shape: torch.Size([1, 64, 112, 112])
Sequential output shape: torch.Size([1, 128, 56, 56])
Sequential output shape: torch.Size([1, 256, 28, 28])
Sequential output shape: torch.Size([1, 512, 14, 14])
Sequential output shape: torch.Size([1, 512, 7, 7])
Flatten output shape: torch.Size([1, 25088])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 10])
7.3. 网络中的网络(NiN)
-
NiN块:AlexNet和VGG对LeNet的改进主要在于如何扩大和加深这两个模块。 或者,可以想象在这个过程的早期使用全连接层。然而,如果使用了全连接层,可能会完全放弃表征的空间结构。 网络中的网络(NiN)提供了一个非常简单的解决方案:在每个像素的通道上分别使用多层感知机。
卷积层的输入和输出由四维张量组成,张量的每个轴分别对应样本、通道、高度和宽度。 另外,全连接层的输入和输出通常是分别对应于样本和特征的二维张量。 NiN的想法是在每个像素位置(针对每个高度和宽度)应用一个全连接层。 如果我们将权重连接到每个空间位置,我们可以将其视为 1x1 卷积层,或作为在每个像素位置上独立作用的全连接层。 从另一个角度看,即将空间维度中的每个像素视为单个样本,将通道维度视为不同特征(feature)。
下图说明了VGG和NiN及它们的块之间主要架构差异。 NiN块以一个普通卷积层开始,后面是两个 1x1 的卷积层。这两个 1x1 卷积层充当带有ReLU激活函数的逐像素全连接层(AlexNet和VGG最后都是对卷积的输出加了大的全连接层)。 第一层的卷积窗口形状通常由用户设置。 随后的卷积窗口形状固定为 1x1。
import torch from torch import nn from d2l import torch as d2ldef nin_block(in_channels, out_channels, kernel_size, strides, padding):return nn.Sequential(# 第一层的卷积窗口形状通常由用户设置nn.Conv2d(in_channels, out_channels, kernel_size, strides, padding),nn.ReLU(),# 后面两个固定的1x1卷积nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU(),nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU())
-
NiN模型:NiN和AlexNet之间的一个显著区别是NiN完全取消了全连接层。 相反,NiN使用一个NiN块,其输出通道数等于标签类别的数量。最后放一个全局平均汇聚层(global average pooling layer),生成一个对数几率 (logits)。NiN设计的一个优点是,它显著减少了模型所需参数的数量。然而,在实践中,这种设计有时会增加训练模型的时间。
net = nn.Sequential(# 输入图像是1通道的nin_block(1, 96, kernel_size=11, strides=4, padding=0),nn.MaxPool2d(3, stride=2),nin_block(96, 256, kernel_size=5, strides=1, padding=2),nn.MaxPool2d(3, stride=2),nin_block(256, 384, kernel_size=3, strides=1, padding=1),nn.MaxPool2d(3, stride=2),nn.Dropout(0.5),# 标签类别数是10,所以最后把卷积层的输出通道数设置为10nin_block(384, 10, kernel_size=3, strides=1, padding=1),# 全局平均池化,就是把每一个通道的(H,W)特征变成(1, 1)大小,最后Flatten成1*1nn.AdaptiveAvgPool2d((1, 1)),# 将四维的输出转成二维的输出,其形状为(批量大小,10)nn.Flatten())
网络输出形状:
Sequential output shape: torch.Size([1, 96, 54, 54]) MaxPool2d output shape: torch.Size([1, 96, 26, 26]) Sequential output shape: torch.Size([1, 256, 26, 26]) MaxPool2d output shape: torch.Size([1, 256, 12, 12]) Sequential output shape: torch.Size([1, 384, 12, 12]) MaxPool2d output shape: torch.Size([1, 384, 5, 5]) Dropout output shape: torch.Size([1, 384, 5, 5]) Sequential output shape: torch.Size([1, 10, 5, 5]) AdaptiveAvgPool2d output shape: torch.Size([1, 10, 1, 1]) Flatten output shape: torch.Size([1, 10])
-
nn.AdaptiveAvgPool2d:对二维输入数据进行自适应平均池化(Adaptive Average Pooling)。这个操作将输入数据的空间维度(高度和宽度)调整为指定的输出大小,比如上面代码的 (1, 1),而不管输入数据的空间维度是多少。
原理:根据输入数据的空间维度和目标输出维度(在这个例子中是 (1, 1)),计算每个输出像素应该覆盖的输入区域。然后,它会对这个区域中的所有输入像素值进行平均,以产生输出像素值。由于目标输出维度是 (1, 1),这意味着整个输入数据的空间信息被压缩成了一个单一的数值。
用途:这种操作在卷积神经网络(CNNs)中有多种用途,尤其是在需要将不同尺寸的输入数据转换为固定尺寸的特征向量以供全连接层(Fully Connected Layers)处理时。例如,在分类任务中,最后一个卷积层之后可能会应用 nn.AdaptiveAvgPool2d((1, 1)),以确保无论输入图像的尺寸如何,都可以生成固定大小的输出,然后这个输出可以被平铺(flattened)并传递给全连接层进行分类。
-
总结:
- NiN使用由一个卷积层和多个 1x1 卷积层组成的块。该块可以在卷积神经网络中使用,以允许更多的每像素非线性。
- NiN去除了容易造成过拟合的全连接层,将它们替换为全局平均汇聚层(即在所有位置上进行求和)。该汇聚层通道数量为所需的输出数量(例如,Fashion-MNIST的输出为10)。
- 移除全连接层可减少过拟合,同时显著减少NiN的参数。
- NiN的设计影响了许多后续卷积神经网络的设计。
7.4. 含并行连结的网络(GoogLeNet)
GoogLeNet吸收了NiN中串联网络的思想,并在此基础上做了改进。 这篇论文的一个重点是解决了什么样大小的卷积核最合适的问题,本文的一个观点是,有时使用不同大小的卷积核组合是有利的。
-
Inception块:Inception块由四条并行路径组成。 前三条路径使用窗口大小为 1x1、3x3
和 5x5 的卷积层,从不同空间大小中提取信息。 中间的两条路径在输入上执行 1x1 卷积,以减少通道数,从而降低模型的复杂性。 第四条路径使用 3x3 最大汇聚层,然后使用 1x1 卷积层来改变通道数。 这四条路径都使用合适的填充来使输入与输出的高和宽一致,最后我们将每条线路的输出在通道维度上连结,并构成Inception块的输出。在Inception块中,通常调整的超参数是每层输出通道数。import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2lclass Inception(nn.Module):# c1--c4是每条路径的输出通道数def __init__(self, in_channels, c1, c2, c3, c4, **kwargs):super(Inception, self).__init__(**kwargs)# 线路1,单1x1卷积层self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1)# 线路2,1x1卷积层后接3x3卷积层, 注意c2是一个列表或元组self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1)self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)# 线路3,1x1卷积层后接5x5卷积层self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1)self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2)# 线路4,3x3最大汇聚层后接1x1卷积层self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1)def forward(self, x):p1 = F.relu(self.p1_1(x))p2 = F.relu(self.p2_2(F.relu(self.p2_1(x))))p3 = F.relu(self.p3_2(F.relu(self.p3_1(x))))p4 = F.relu(self.p4_2(self.p4_1(x)))# 在通道维度上连结输出return torch.cat((p1, p2, p3, p4), dim=1)
为什么GoogLeNet这个网络如此有效呢? 首先我们考虑一下滤波器(filter)的组合,它们可以用各种滤波器尺寸探索图像,这意味着不同大小的滤波器可以有效地识别不同范围的图像细节。 同时,我们可以为不同的滤波器分配不同数量的参数。
-
GoogLeNet模型:GoogLeNet一共使用9个Inception块和全局平均汇聚层的堆叠来生成其估计值。Inception块之间的最大汇聚层可降低维度。 第一个模块类似于AlexNet和LeNet,Inception块的组合从VGG继承,全局平均汇聚层避免了在最后使用全连接层。
中间这些 Inception 块通道数的配置各不相同,具体可以去看 D2L 中原文的讲解。这里只给出最后一层,因为后面接了输出:第五模块包含输出通道数为 256 +320 + 128 + 128 = 832 和 384 + 384 + 128 + 128 = 1024 的两个Inception块。 其中每条路径通道数的分配思路和第三、第四模块中的一致,只是在具体数值上有所不同。 需要注意的是,第五模块的后面紧跟输出层,该模块同NiN一样使用全局平均汇聚层,将每个通道的高和宽变成1。 最后我们将输出变成二维数组,再接上一个输出个数为标签类别数的全连接层。
b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128),Inception(832, 384, (192, 384), (48, 128), 128),nn.AdaptiveAvgPool2d((1,1)),nn.Flatten())net = nn.Sequential(b1, b2, b3, b4, b5, nn.Linear(1024, 10))
-
小结:
- Inception块相当于一个有4条路径的子网络。它通过不同窗口形状的卷积层和最大汇聚层来并行抽取信息,并使用 1x1 卷积层减少每像素级别上的通道维数从而降低模型复杂度。
- GoogLeNet将多个设计精细的Inception块与其他层(卷积层、全连接层)串联起来。其中Inception块的通道数分配之比是在ImageNet数据集上通过大量的实验得来的。
- GoogLeNet和它的后继者们一度是ImageNet上最有效的模型之一:它以较低的计算复杂度提供了类似的测试精度。
7.5. 批量规范化
7.5.1. 训练深层网络
为什么需要批量规范化层呢?
- 数据预处理的方式通常会对最终结果产生巨大影响。 回想一下我们应用多层感知机来预测房价的例子( 4.10节)。 使用真实数据时,我们的第一步是标准化输入特征,使其平均值为0,方差为1。 直观地说,这种标准化可以很好地与我们的优化器配合使用,因为它可以将参数的量级进行统一。
- 第二,对于典型的多层感知机或卷积神经网络。当我们训练时,中间层中的变量(例如,多层感知机中的仿射变换输出)可能具有更广的变化范围:不论是沿着从输入到输出的层,跨同一层中的单元,或是随着时间的推移,模型参数的随着训练更新变幻莫测。 批量规范化的发明者非正式地假设,这些变量分布中的这种偏移可能会阻碍网络的收敛。 直观地说,我们可能会猜想,如果一个层的可变值是另一层的100倍,这可能需要对学习率进行补偿调整。
- 第三,更深层的网络很复杂,容易过拟合。 这意味着正则化变得更加重要。
批量规范化应用于单个可选层(也可以应用到所有层),其原理如下:在每次训练迭代中,我们首先规范化输入,即通过减去其均值并除以其标准差,其中两者均基于当前小批量处理。 接下来,我们应用比例系数和比例偏移。 正是由于这个基于批量统计的标准化,才有了批量规范化的名称。
请注意,如果我们尝试使用大小为1的小批量应用批量规范化,我们将无法学到任何东西。 这是因为在减去均值之后,每个隐藏单元将为0。 所以,只有使用足够大的小批量,批量规范化这种方法才是有效且稳定的。 请注意,在应用批量规范化时,批量大小的选择可能比没有批量规范化时更重要。
另外,批量规范化层在”训练模式“(通过小批量统计数据规范化)和“预测模式”(通过数据集统计规范化)中的功能不同。 在训练过程中,我们无法得知使用整个数据集来估计平均值和方差,所以只能根据每个小批次的平均值和方差不断训练模型。 而在预测模式下,可以根据整个数据集精确计算批量规范化所需的平均值和方差。
7.5.2. 批量规范化层
批量规范化和其他层之间的一个关键区别是,由于批量规范化在完整的小批量上运行,因此我们不能像以前在引入其他层时那样忽略批量大小。 我们在下面讨论这两种情况:全连接层和卷积层,他们的批量规范化实现略有不同。
7.5.2.1. 全连接层
7.5.2.2. 卷积层
7.5.2.3. 预测过程中的批量规范化
正如我们前面提到的,批量规范化在训练模式和预测模式下的行为通常不同。 首先,将训练好的模型用于预测时,我们不再需要样本均值中的噪声以及在微批次上估计每个小批次产生的样本方差了。 其次,例如,我们可能需要使用我们的模型对逐个样本进行预测。 一种常用的方法是通过移动平均估算整个训练数据集的样本均值和方差,并在预测时使用它们得到确定的输出。 可见,和暂退法一样,批量规范化层在训练模式和预测模式下的计算结果也是不一样的。
7.5.3. 从零实现
首先实现上述 batch_norm 的数学运算过程:
import torch
from torch import nn
from d2l import torch as d2ldef batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum):# 通过is_grad_enabled来判断当前模式是训练模式还是预测模式if not torch.is_grad_enabled():# 如果是在预测模式下,直接使用传入的移动平均所得的均值和方差X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)else:assert len(X.shape) in (2, 4)if len(X.shape) == 2: # (B, D)# 使用全连接层的情况,计算特征维上的均值和方差mean = X.mean(dim=0) # (D,), 即D个特征维度上每个特征的均值var = ((X - mean) ** 2).mean(dim=0) # (D,)else: # (B, C, H, W)# 使用二维卷积层的情况,计算通道维上(axis=1)的均值和方差。# 这里我们需要保持X的形状以便后面可以做广播运算mean = X.mean(dim=(0, 2, 3), keepdim=True) # (1, C, 1, 1), C个通道上每个通道的均值var = ((X - mean) ** 2).mean(dim=(0, 2, 3), keepdim=True) # (1, C, 1, 1)# 训练模式下,用当前的均值和方差做标准化# 主要上面计算的 var是方差,而 BN 公式中最后除以的是标准差,所以开根号X_hat = (X - mean) / torch.sqrt(var + eps) # torch.sqrt是开根号,逐元素运算# 更新移动平均的均值和方差moving_mean = momentum * moving_mean + (1.0 - momentum) * meanmoving_var = momentum * moving_var + (1.0 - momentum) * var# (1, D) * (B, D) + (1, D), 即D个特征维度的每一个维度都有一个scale和shift# 卷积层类似,就是C个通道维度的每一个通道都有一个scale和shiftY = gamma * X_hat + beta # 缩放和移位return Y, moving_mean.data, moving_var.data
现在可以创建一个正确的BatchNorm层。 这个层将保持适当的参数:拉伸gamma和偏移beta,这两个参数将在训练过程中更新。 此外,我们的层将保存均值和方差的移动平均值,以便在模型预测期间随后使用。
撇开算法细节,注意我们实现层的基础设计模式。 通常情况下,我们用一个单独的函数定义其数学原理,比如说batch_norm。 然后,我们将此功能集成到一个自定义层中,其代码主要处理数据移动到训练设备(如GPU)、分配和初始化任何必需的变量、跟踪移动平均线(此处为均值和方差)等问题。 为了方便起见,我们并不担心在这里自动推断输入形状,因此我们需要指定整个特征的数量。 不用担心,深度学习框架中的批量规范化API将为我们解决上述问题,我们稍后将展示这一点。
class BatchNorm(nn.Module):# num_features:完全连接层的输出数量或卷积层的输出通道数。# num_dims:2表示完全连接层,4表示卷积层def __init__(self, num_features, num_dims):super().__init__()if num_dims == 2: # (1, D), 即D个特征维度的每一个维度都有一个scale和shiftshape = (1, num_features)else:# (1, C, 1, 1), 就是C个通道维度的每一个通道都有一个scale和shiftshape = (1, num_features, 1, 1)# 参与求梯度和迭代的拉伸和偏移参数,分别初始化成1和0self.gamma = nn.Parameter(torch.ones(shape))self.beta = nn.Parameter(torch.zeros(shape))# 非模型参数的变量初始化为0和1self.moving_mean = torch.zeros(shape)self.moving_var = torch.ones(shape)def forward(self, X):# 如果X不在内存上,将moving_mean和moving_var# 复制到X所在显存上if self.moving_mean.device != X.device:self.moving_mean = self.moving_mean.to(X.device)self.moving_var = self.moving_var.to(X.device)# 保存更新过的moving_mean和moving_varY, self.moving_mean, self.moving_var = batch_norm(X, self.gamma, self.beta, self.moving_mean,self.moving_var, eps=1e-5, momentum=0.9)return Y
7.5.4. 使用批量规范化层的 LeNe
将 BatchNorm 应用于LeNet模型,批量规范化是在卷积层或全连接层之后、相应的激活函数之前应用的。
net = nn.Sequential(nn.Conv2d(1, 6, kernel_size=5), BatchNorm(6, num_dims=4), nn.Sigmoid(),nn.AvgPool2d(kernel_size=2, stride=2),nn.Conv2d(6, 16, kernel_size=5), BatchNorm(16, num_dims=4), nn.Sigmoid(),nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),nn.Linear(16*4*4, 120), BatchNorm(120, num_dims=2), nn.Sigmoid(),nn.Linear(120, 84), BatchNorm(84, num_dims=2), nn.Sigmoid(),nn.Linear(84, 10))
和以前一样,我们将在Fashion-MNIST数据集上训练网络。 这个代码与我们第一次训练LeNet( 6.6节)时几乎完全相同,主要区别在于学习率大得多。
lr, num_epochs, batch_size = 1.0, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
让我们来看看从第一个批量规范化层中学到的拉伸参数gamma和偏移参数beta。
net[1].gamma.reshape((-1,)), net[1].beta.reshape((-1,))
(tensor([0.4863, 2.8573, 2.3190, 4.3188, 3.8588, 1.7942], device='cuda:0',grad_fn=<ReshapeAliasBackward0>),tensor([-0.0124, 1.4839, -1.7753, 2.3564, -3.8801, -2.1589], device='cuda:0',grad_fn=<ReshapeAliasBackward0>))
7.5.5. 简明实现
直接使用深度学习框架中定义的BatchNorm。 该代码看起来几乎与我们上面的代码相同,不过不需要手动指定输入 feature 的维度是 2(全连接)还是 4(卷积)了。
net = nn.Sequential(nn.Conv2d(1, 6, kernel_size=5), nn.BatchNorm2d(6), nn.Sigmoid(),nn.AvgPool2d(kernel_size=2, stride=2),nn.Conv2d(6, 16, kernel_size=5), nn.BatchNorm2d(16), nn.Sigmoid(),nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),nn.Linear(256, 120), nn.BatchNorm1d(120), nn.Sigmoid(),nn.Linear(120, 84), nn.BatchNorm1d(84), nn.Sigmoid(),nn.Linear(84, 10))
7.5.7. 小结
-
在模型训练过程中,批量规范化利用小批量的均值和标准差,不断调整神经网络的中间输出,使整个神经网络各层的中间输出值更加稳定。
-
批量规范化在全连接层和卷积层的使用略有不同。
-
批量规范化层和暂退层一样,在训练模式和预测模式下计算不同。
-
批量规范化有许多有益的副作用,主要是正则化。另一方面,”减少内部协变量偏移“的原始动机似乎不是一个有效的解释。
7.6. 残差网络(ResNet)
7.6.1. 函数类
注意下面的解释对 ResNet 底层的数学思想有很好的解释:就是本质上想增大网络容量来更靠近真实的函数表示,但是为什么残差网络是有效的,而不能只是单纯的增加网络容量(比如增加网络层数等)?因为残差网络可以保证容量增加的同时,一定可以包含之间的网络。
7.6.2. 残差块
ResNet沿用了VGG完整的 3x3 卷积层设计。 残差块里首先有2个有相同输出通道数的
3x3 卷积层。 每个卷积层后接一个批量规范化层和ReLU激活函数。 然后我们通过跨层数据通路,跳过这2个卷积运算,将输入直接加在最后的ReLU激活函数前。 这样的设计要求2个卷积层的输出与输入形状一样,从而使它们可以相加。 如果想改变通道数,就需要引入一个额外的 1x1 卷积层来将输入变换成需要的形状后再做相加运算。 残差块的实现如下:
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2lclass Residual(nn.Module): #@save# input_channels: 输入feat的通道数# num_channels: 残差块最终输出的通道数# use_1x1conv:如果输入通道数和输出通道数不一致,则需要使用1x1卷积修改输入通道数和输出通道数一致def __init__(self, input_channels, num_channels,use_1x1conv=False, strides=1):super().__init__()# 这里可以通过 stride,将最终输出的feat宽高进行缩减self.conv1 = nn.Conv2d(input_channels, num_channels,kernel_size=3, padding=1, stride=strides)# 这里stride=1, 保证输出feat的宽高和conv1出来的宽高一致self.conv2 = nn.Conv2d(num_channels, num_channels,kernel_size=3, padding=1)if use_1x1conv:self.conv3 = nn.Conv2d(input_channels, num_channels,kernel_size=1, stride=strides)else:self.conv3 = Noneself.bn1 = nn.BatchNorm2d(num_channels)self.bn2 = nn.BatchNorm2d(num_channels)def forward(self, X):Y = F.relu(self.bn1(self.conv1(X)))Y = self.bn2(self.conv2(Y))if self.conv3:X = self.conv3(X)Y += Xreturn F.relu(Y)
此代码生成两种类型的网络: 一种是当use_1x1conv=False时,应用ReLU非线性函数之前,将输入添加到输出。 另一种是当use_1x1conv=True时,添加通过 1x1 卷积调整通道和分辨率。
下面我们来查看输入和输出形状一致的情况。
blk = Residual(3,3)
X = torch.rand(4, 3, 6, 6)
Y = blk(X)
Y.shape# 输出:
torch.Size([4, 3, 6, 6])
我们也可以在增加输出通道数的同时,通过给定 stride=2 减半输出的高和宽。
blk = Residual(3,6, use_1x1conv=True, strides=2)
blk(X).shape# 输出:
torch.Size([4, 6, 3, 3])
7.6.3. ResNet模型
ResNet的前两层跟之前介绍的GoogLeNet中的一样: 在输出通道数为64、步幅为2的
7x7 卷积层后,接步幅为2的 3x3 最大汇聚层。 不同之处在于ResNet每个卷积层后增加了批量规范化层。
b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),nn.BatchNorm2d(64), nn.ReLU(),nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
GoogLeNet在后面接了4个由Inception块组成的模块。 ResNet则使用4个由残差块组成的模块,每个模块使用若干个(上图的Reset-18中是2个)同样输出通道数的残差块。 第一个模块的通道数同输入通道数一致。 由于之前已经使用了步幅为2的最大汇聚层,所以无须减小高和宽。 之后的每个模块在第一个残差块里将上一个模块的通道数翻倍,并将高和宽减半。
下面我们来实现这个模块。注意,我们对第一个模块做了特别处理:对于非第一个模块中的第一个残差块,需要对输出宽高减半(stride=2),且输出通道对输入通道翻倍(use_1x1conv=True)
def resnet_block(input_channels, num_channels, num_residuals,first_block=False):blk = []for i in range(num_residuals):if i == 0 and not first_block:blk.append(Residual(input_channels, num_channels,use_1x1conv=True, strides=2))else:blk.append(Residual(num_channels, num_channels))return blk
接着在ResNet加入所有残差块,这里每个模块使用2个残差块。
b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
b3 = nn.Sequential(*resnet_block(64, 128, 2))
b4 = nn.Sequential(*resnet_block(128, 256, 2))
b5 = nn.Sequential(*resnet_block(256, 512, 2))
最后,与GoogLeNet一样,在ResNet中加入全局平均汇聚层,以及全连接层输出。
net = nn.Sequential(b1, b2, b3, b4, b5,nn.AdaptiveAvgPool2d((1,1)),nn.Flatten(), nn.Linear(512, 10))
上图中,每个模块有4个卷积层(每个模块有 2 个残差块,每个残差块中有 2 个 3x3 卷积层,不包括恒等映射的 1x1 卷积层)。 加上第一个 7x7 卷积层和最后一个全连接层,共有18层。 因此,这种模型通常被称为ResNet-18。
7.6.5. 小结
- 学习嵌套函数(nested function)是训练神经网络的理想情况。在深层神经网络中,学习另一层作为恒等映射(identity function)较容易(尽管这是一个极端情况)。
- 残差映射可以更容易地学习同一函数,例如将权重层中的参数近似为零。
- 利用残差块(residual blocks)可以训练出一个有效的深层神经网络:输入可以通过层间的残余连接更快地向前传播。
- 残差网络(ResNet)对随后的深层神经网络设计产生了深远影响。
7.7. 稠密连接网络(DenseNet)
7.7.1. 从ResNet到DenseNet
说白了,DenseNet 和 ResNet 最主要的区别就是对输入进行跳层连接的时候,使用的是 cat 而不是 add。
7.7.2. 稠密块体
在ResNet的后续版本中,作者将“卷积层、批量规范化层和激活层”架构更改为“批量规范化层、激活层和卷积层”架构,Densenet 也使用这种卷积块架构。
import torch
from torch import nn
from d2l import torch as d2ldef conv_block(input_channels, num_channels):return nn.Sequential(nn.BatchNorm2d(input_channels), nn.ReLU(),nn.Conv2d(input_channels, num_channels, kernel_size=3, padding=1))
一个稠密块由多个卷积块组成,每个卷积块使用相同数量的输出通道。 在前向传播中,我们将每个卷积块的输入和输出在通道维上连结。
class DenseBlock(nn.Module):def __init__(self, num_convs, input_channels, num_channels):""""num_convs: 使用的卷积块的个数input_channels: 输入稠密块的feat通道输num_channels: 每个卷积块的输出通道数"""super(DenseBlock, self).__init__()layer = []for i in range(num_convs):layer.append(conv_block(# 注意每个卷积块的输出和输入进行catnum_channels * i + input_channels, num_channels))self.net = nn.Sequential(*layer)def forward(self, X):for blk in self.net:Y = blk(X)# 连接通道维度上每个块的输入和输出X = torch.cat((X, Y), dim=1)return X
注意在上面的代码中,每个卷积块的输出和输入进行 cat,再送入到下一个卷积块。因此定义每个卷积块输入通道的大小时:
- 第一个卷积块的输入为 DenseBlock 的输入feat通道数,即 input_channels, 输出通道数 num_channels,连接后为 input_channels + num_channels,将其输入给下一个通道
- 第二个卷积块的输入为 input_channels + num_channels,输出通道数 num_channels,连接后为 input_channels + 2 * num_channels
我们定义一个有 2 个输出通道数为 10 的 DenseBlock。 使用通道数为 3的输入时,我们会得到通道数为 3 +2 * 10 = 23的输出。 卷积块的通道数控制了输出通道数相对于输入通道数的增长,因此也被称为增长率(growth rate)。
blk = DenseBlock(2, 3, 10)
X = torch.randn(4, 3, 8, 8)
Y = blk(X)
Y.shape# 输出:
torch.Size([4, 23, 8, 8])
7.7.3. 过渡层
由于每个稠密块都会带来通道数的增加,使用过多则会过于复杂化模型。 而过渡层可以用来控制模型复杂度。 它通过 1x1 卷积层来减小通道数,并使用步幅为 2 的平均汇聚层减半高和宽,从而进一步降低模型复杂度。
def transition_block(input_channels, num_channels):return nn.Sequential(nn.BatchNorm2d(input_channels), nn.ReLU(),nn.Conv2d(input_channels, num_channels, kernel_size=1),nn.AvgPool2d(kernel_size=2, stride=2))
7.7.4. DenseNet模型
DenseNet首先使用同ResNet一样的单卷积层和最大汇聚层。
b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),nn.BatchNorm2d(64), nn.ReLU(),nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
接下来,类似于ResNet使用的4个残差块,DenseNet使用的是4个稠密块。 与ResNet类似,我们可以设置每个稠密块使用多少个卷积层。 这里我们设成4,从而与 7.6节的ResNet-18保持一致。 稠密块里的卷积层通道数(即增长率)设为32,所以每个稠密块将增加128个通道。
在每个模块之间,ResNet通过步幅为2的残差块减小高和宽,DenseNet则使用过渡层来减半高和宽,并减半通道数。
# num_channels为当前的通道数
num_channels, growth_rate = 64, 32
num_convs_in_dense_blocks = [4, 4, 4, 4]
blks = []
for i, num_convs in enumerate(num_convs_in_dense_blocks):blks.append(DenseBlock(num_convs, num_channels, growth_rate))# 上一个稠密块的输出通道数num_channels += num_convs * growth_rate# 在稠密块之间添加一个转换层,使通道数量减半if i != len(num_convs_in_dense_blocks) - 1:blks.append(transition_block(num_channels, num_channels // 2))num_channels = num_channels // 2
与ResNet类似,最后接上全局汇聚层和全连接层来输出结果。
net = nn.Sequential(b1, *blks,nn.BatchNorm2d(num_channels), nn.ReLU(),nn.AdaptiveAvgPool2d((1, 1)),nn.Flatten(),nn.Linear(num_channels, 10))
7.7.6 小结
- 在跨层连接上,不同于ResNet中将输入与输出相加,稠密连接网络(DenseNet)在通道维上连结输入与输出。
- DenseNet的主要构建模块是稠密块和过渡层。
- 在构建DenseNet时,我们需要通过添加过渡层来控制网络的维数,从而再次减少通道的数量。
相关文章:
7. 现代卷积神经网络
文章目录 7.1. 深度卷积神经网络(AlexNet)7.2. 使用块的网络(VGG)7.3. 网络中的网络(NiN)7.4. 含并行连结的网络(GoogLeNet)7.5. 批量规范化7.5.1. 训练深层网络7.5.2. 批量规范化层…...
软件测试丨Pytest生命周期与数据驱动
Pytest的生命周期概述 Pytest 是一个强大的测试框架,提供了丰富的特性来简化测试执行。它的生命周期包括多个阶段,涉及从准备测试、执行测试到报告结果的完整流程。因此,理解Pytest的生命周期将帮助我们更好地设计和管理测试用例。 开始阶段…...
Python 网页控制自动化 getEdgeDriver
透过python 使用 edge 执行自动化时,原来的代码 出现报错了 执行报错啦:message info 如下显示 HTTPSConnectionPool(hostmsedgedriver.azureedge.net, port443): Max retries exceeded with url: /130.0.2849/edgedriver_win64.zip (Caused by NewConn…...
白鹿 Hands-on:消除冷启动——基于 Amazon Lambda SnapStart 轻松打造 Serverless Web 应用(二)
文章目录 前言一、前文回顾二、在 Lambda 上运行2.1、查看 Amazon SAM template2.2、编译和部署到 Amazon Lambda2.3、功能测试与验证 三、对比 Snapstart 效果四、资源清理五、实验总结总结 前言 在这个环节中,我们将延续《白鹿 Hands-on:消除冷启动——…...
pandas read_csv读取中文内容文件报错UnicodeDecodeError: ‘utf-8‘ codec can‘t decode byte
先用如下代码检查编码格式 import chardet# 检测文件编码 with open("data.csv", "rb") as f:result chardet.detect(f.read())print(result["encoding"]) # 打印检测到的编码我检查后我的文件编码格式是ISO-8859-1,因此读取文件时…...
LoRA微调原理 代码实践
LoRA(Low-Rank Adaptation)作为一种用于微调 LLM(大语言模型)的流行技术,最初由来自微软的研究人员在论文《 LORA: LOW-RANK ADAPTATION OF LARGE LANGUAGE MODELS 》中提出。不同于其他技术,LoRA 不是调整…...
数据结构--树二叉树顺序结构存储的二叉树(堆)
前言 前面我们学习了顺序表、链表、栈和队列,这些都是线性的数据结构。今天我们要来学习一种非线性的数据结构——树。 树的概念及结构 树的概念 树是一种非线性的数据结构,是由n(n≥0)个有效结点组成的一个具有层次关系的集合…...
mongodb shard 分片集群基础概念
目录 一、shard 集群 二、Config Server 1、config.shards 2、config.database 3、config.collection 4、config.chunks 5、config.settings 6、其他 三、shard机制 1、Primary Shard 2、Shard Key 2.1 范围分片 2.2 哈希分片 2.3 Shard Key重定义 2.4 版本约束…...
Streamlit 应用从本地部署到服务器并进行访问
目录 1 部署 Streamlit 应用到服务器2 配置服务器允许远程访问3 使用反向代理4 使用 HTTPS5 总结 1 部署 Streamlit 应用到服务器 1 选择一个服务器平台 首先,你需要选择一个服务器平台来部署你的 Streamlit 应用。常见的选择包括: 云服务器:…...
大数据新视界 -- 大数据大厂之 Hive 数据压缩:优化存储与传输的关键(上)(19/ 30)
💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...
Java开发中对List<Map<String, Object>>集合去重并按大小拆分子列表
Java开发中对List< Map< String, Object > >集合去重并按大小拆分子列表 一、使用场景二、实现步骤三、相关知识四、代码示例 一、使用场景 在处理大量List<Map<String, Object>>集合的数据时,为确保数据的唯一性,需要先根据Ma…...
vue3项目搭建-6-axios 基础配置
axios 基础配置 安装 axios npm install axios 创建 axios 实例,配置基地址,配置拦截器,目录:utils/http.js 基地址:在每次访问时,自动作为相对路径的根 // axios 基础封装 import axios from "axios";…...
git 学习笔记
目录 一、git 前期准备 1、托管平台的账号注册(以gitee码云为demo) 2、本地个人电脑配置 (1)配置用户属性 (2)配置SSH密钥 二、git 工作流程图 三、git 提交命令 (1)git ini…...
Y20030019 基于java+jsp+mysql的微信小程序校园二手交易平台的设计与实现 源代码 文档
旅游度假区微信小程序 1.摘要2. 系统开发的目的和意义3.系统功能4.界面展示5.源码获取 1.摘要 随着移动互联网的发展,微信小程序已经成为人们生活中不可或缺的一部分。微信小程序的优点在于其快速、轻量、易用,用户无需下载即可使用,节省了用…...
Cookie跨域
跨域:跨域名(IP) 跨域的目的是共享Cookie。 session操作http协议,每次既要request,也要response,cookie在创建的时候会产生一个字符串然后随着response返回。 全网站的各个页面都会带着登陆的时候的cookie …...
Mybatis:CRUD数据操作之删除一行数据
Mybatis基础环境准备请看:Mybatis基础环境准备 本篇讲解Mybati数据CRUD数据操作之单条删除数据 当用户点击了该按钮,就会将改行数据删除掉。那我们就需要思考,这种删除是根据什么进行删除呢?是通过主键id删除,因为id是…...
【机器学习】CatBoost 模型实践:回归与分类的全流程解析
一. 引言 本篇博客首发于掘金 https://juejin.cn/post/7441027173430018067。 PS:转载自己的文章也算原创吧。 在机器学习领域,CatBoost 是一款强大的梯度提升框架,特别适合处理带有类别特征的数据。本篇博客以脱敏后的保险数据集为例&#x…...
MySQL中如何减少回表
在MySQL中,回表是指在使用非聚集索引进行查询时,如果需要获取的数据不在索引页中,就需要根据索引页中的指针返回到数据表中查找实际数据行的过程。这个过程会增加额外的磁盘I/O操作,降低查询性能,特别是在查询大量数据…...
10. 函数
一、什么是函数 函数也是对象,对象是内存中专门用来存储数据的一块区域。函数可以用来保存一些可执行代码的,并且可以在需要时,对这些语句进行多次调用。 二、创建函数 创建函数也称为定义函数。我们可以使用 def 关键字来定义函数ÿ…...
计算机网络:数据链路层(二)
网课资源: 湖科大教书匠 1、网络适配器和MAC地址 习题1 1 以下哪个地址是广播MAC地址 A. 00-00-00-00-00-00 B. AB-CD-EF-11-22-33 C. FF-FF-FF-FF-FF-FF D. 29-29-29-29-29-29 2 以下哪个地址是多播MAC地址 A. 00-00-00-00-00-00 B. A9-8B-7C-6D-5E-4F C. FF-FF-…...
一万台服务器用saltstack还是ansible?
一万台服务器用saltstack还是ansible? 选择使用 SaltStack 还是 Ansible 来管理一万台服务器,取决于几个关键因素,如性能、扩展性、易用性、配置管理需求和团队的熟悉度。以下是两者的对比分析,帮助你做出决策: SaltStack&…...
设计模式学习之——观察者模式
观察者模式是一种行为型设计模式,它用于在对象之间建立一对多的依赖关系。 一、定义与角色 定义: 观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察…...
服务器实现ssh证书登录
1.生成公钥和私钥 ssh-keygen -t rsa 提示默认生成位置为/root/.ssh/id_rsa ,直接回车。(也可以自己修改) 提示输入证书的密码,可以留空,建议输入,如果输入了,则需要再次确认,记住这个证书密码(证书再加…...
python基础知识精讲
Python基础知识精讲 Python是一种广泛使用的高级编程语言,以其清晰的语法和代码可读性而闻名。它支持多种编程范式,包括面向对象、命令式、函数式和过程式编程。以下是Python基础知识的详细讲解。 1. Python简介 Python由Guido van Rossum创建&#x…...
分页查询日期格式不对
方式一:在属性上加入注解,对日期进行格式化 方式二:在 WebMvcConfiguration 中扩展Spring MVC的消息转换器,统一对日期类型进行格式化处理 /*** 统一转换处理扩展spring mvc* 后端返回前端的进行统一转化处理* param converters*/Overrideprotected voi…...
Windsurf可以上传图片开发UI了
背景 曾经羡慕Cursor的“画图”开发功能,这不Windsurf安排上了。 Upload Images to Cascade Cascade now supports uploading images on premium models Ask Cascade to build or tweak UI from on image upload New keybindings Keybindings to navigate betwe…...
工作坊报名|使用 TEN 与 Azure,探索你的多模态交互新场景
GPT-4o Realtime API 发布,语音 AI 技术正在进入一场新的爆发。语音AI技术的实时语音和视觉互动能力将为我们带来更多全新创意和应用场景。 实时音频交互: 允许应用程序实时接收并响应语音和文本输入。自然语音生成: 减少 AI 技术生成的语音…...
Java 虚拟机:承载 Java 生态的神奇魔盒
在软件开发的世界里,Java 虚拟机(JVM)就像一位智慧的管家,默默守护着 Java 生态系统的运行。它不仅让 Java 实现了"一次编写,到处运行"的梦想,更是成为了多种编程语言的运行平台。让我们一起走进…...
Linux VLAN 实现原理技术笔记
一、引言 VLAN(虚拟局域网)在整车网络架构中起着至关重要的作用,它能够在物理网络基础设施上创建逻辑隔离的网络区域,提高车内网络的安全性、灵活性和性能。Linux 内核通过一系列复杂的机制实现了 VLAN 功能,本技术笔记…...
【Git】:分支管理
目录 理解分支 创建分支 切换分支 合并分支 删除分支 合并冲突 分支管理策略 快进合并 正常合并 bug 分支 总结 理解分支 在版本控制系统中,分支是一条独立的开发线路。它允许开发者从一个主要的代码基线(例如master分支)分离出来…...
Java中的运算符“instanceof“详解
在Java中,instanceof运算符用于检查一个对象是否是某个特定类的实例,或者是否实现了某个特定接口。它返回一个布尔值(true或false),用于在运行时进行类型检查。这在处理多态性时尤其有用,可以帮助我们确定对…...
Profinet转Modbus TCP西门子SINAMICS G120变频器与施耐德M580通讯案例
一. 案例背景 在复杂的工业自动化场景中,企业常常会采用不同品牌的设备来构建生产系统。西门子SINAMICS G120变频器以其高性能、高精度的速度和转矩控制功能,在电机驱动领域应用广泛。施耐德M580可编程逻辑控制器则以强大的逻辑控制和数据处理能力著称&a…...
机器学习实战笔记39-43树模型基础
目前最常用的是CART树: 评价标准:每次划分后子节点的纯度(即是否标签都为0/1),分为信息熵、基尼系数(越小则纯度越高)和分类误差 找划分节点的方法:CART树无需区分连续和离散变量&am…...
`uni.setClipboardData` 是 uni-app 提供的一个 API 设置系统剪贴板的内容
uni.setClipboardData是uni-app提供的一个API,用于设置系统剪贴板的内容。 使用说明: 使用此API可以将指定的文本内容复制到系统剪贴板,使用户能够在其他应用或页面中粘贴这些内容。 uni.setClipboardData({data: , // 需要复制的内容 suc…...
Android opengl 绘制矩形,宽高相同,不能显示为正方形,是怎么回事
在Android上使用OpenGL绘制矩形(或尝试显示为正方形)时,如果结果显示为不是正方形,可能有几个原因。以下是一些常见的因素及解决方法: 视口(Viewport)设置不当: OpenGL的视口定义了…...
网络安全开源组件
本文只是针对开源项目进行收集,如果后期在工作中碰到其他开源项目将进行更新。欢迎大家在评论区留言,您在工作碰到的开源项目。 祝您工作顺利,鹏程万里! 一、FW(防火墙) 1.1 pfSense pfSense项目是一个免费…...
【C++初阶】第5课—模版初阶
文章目录 1. 函数模版1.1 函数模版格式1.2 函数模版原理1.3 函数模版的实例化1.4 模版参数的匹配原则 2. 类模版 1. 函数模版 在讲函数模版之前先看一个例子 为了实现泛型编程,C提出了函数模版的概念函数模版与类型无关,在使用时被参数化,根据…...
【力扣热题100】[Java版] 刷题笔记-3. 无重复字符的最长子串
题目:3. 无重复字符的最长子串 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。 解题思路 根据题目,只需要返回无重复字符串的最长子串的长度,所以我们不需要知道知道字符串内容是什么,在整个字符串 s 中&…...
无人机数据处理系统:原理与核心系统
一、数据处理系统的运行原理 数据获取:无人机在飞行过程中,通过搭载的传感器(如相机、激光雷达等)采集到各种类型的数据,例如图像、点云等。这些数据是后续处理和分析的基础。 数据传输:采集到的数据会通…...
Hadoop分布式文件系统(二)
目录 1. 引言1. Hadoop文件操作命令2. 部分常用的Hadoop FS Shell命令2.1 ls列出文件2.2 mkdir创建目录2.3 put上传文件2.4 cat查看文件2.5 get复制文件2.6 rm删除文件 3. Hadoop系统管理命令4. HDFS Java API 示例参考 1. 引言 大多数HDFS Shell命令的行为和对应的Unix Shell命…...
PortSwigger 原型污染
一、什么是原型污染 原型污染是一种 JavaScript 漏洞,它使攻击者能够向全局对象原型添加任意属性,然后这些属性可能被用户定义的对象继承。 二、JavaScript 原型和继承基础 1、原型 JavaScript 中的每个对象都链接到某种类型的另一个对象,称…...
雪花算法详解:分布式系统中高效唯一的ID生成方案
文章目录 原理与结构工作流程优势局限性应对高并发的方法适用场景 雪花算法(Snowflake Algorithm)是由Twitter开发的一种分布式全局唯一ID生成方案,旨在解决在分布式系统中快速、无冲突地生成唯一标识符的问题。它通过巧妙的设计,…...
[Redis#7] set | 命令 | 集合 | 用户画像 | UV
目录 1. 特点 2. 常用命令 2.1 普通命令 2.2 集合间操作 2.3. 命令小结 3.内部编码 4. 应用场景 1. 构造用户画像 2. 计算用户之间的共同好友 3. 统计 UV 1. 特点 集合类型也是保存多个字符串类型的元素的,和 list 类型不同的是: 无序性&…...
中介者模式 (Mediator Pattern)
文章目录 中介者模式 (Mediator Pattern)原理优点缺点示例代码场景描述1. 定义中介者接口2. 实现具体中介者3. 定义同事类接口4. 实现具体同事类5. 客户端代码输出结果 UML 类图使用场景小结 中介者模式 (Mediator Pattern) 中介者模式是一种 行为型设计模式,用来降…...
PVE 软路由单网口——VLAN 实践
从VLAN交换机出发,到PVE的Linux Bridge 、Linux VLAN,再到iKuai等软路由软件的设置,尽可能的了解VLAN设置细节,避免踩坑。 本文使用的快速切换CIDR的脚本 PVE 调试之“一键设置网络连接的以太网的CIDR“——“.PS1 脚本” 默认…...
搭建一个基于Web的文档管理系统,用于存储、共享和协作编辑文档
搭建一个基于Web的文档管理系统,用于存储、共享和协作编辑文档 本项目采用以下架构: NFS服务器: 负责存储文档资料。Web服务器: 负责提供文档访问和编辑功能。SELinux: 负责权限控制,确保文档安全。Git服务器: 负责存储文档版本历史&#x…...
【QT】控件8
1.QDial 通过调节旋钮位置来控制窗口的不透明度: void Widget::on_dial_valueChanged(int value) {qDebug()<<value;this->setWindowOpacity((double)value/100); }效果演示: 2.Date/Time Edit 计算两个日期的差值 ui界面设计 计算按钮按下…...
asyncio.to_thread 详解及示例代码
asyncio.to_thread 详解及示例代码 1. asyncio.to_thread() 简介函数签名返回值 2. 示例代码示例 1: 执行阻塞的 I/O 操作示例 2: 执行阻塞的 CPU 密集型操作 3. 注意事项4. 总结 在异步编程中,asyncio 是 Python 中用于编写异步代码的标准库。然而,有时…...
MYSQL字段变更
修改字段长度 ALTER TABLE tqt_sp_prod.t_receipt_order_head MODIFY COLUMN CUS_CONTRACT_CD VARCHAR(50) COMMENT 客户合同编号;添加varchar类型字段 AFTER 此处指在loc_cd字段后面 ALTER TABLE m_product ADD COLUMN FIXED_ASSET_NUM VARCHAR(100) DEFAULT NULL COMME…...
【通俗理解】步长和学习率在神经网络中是一回事吗?
【通俗理解】步长和学习率在神经网络中是一回事吗? 【核心结论】 步长(Step Size)和学习率(Learning Rate, LR)在神经网络中并不是同一个概念,但它们都关乎模型训练过程中的参数更新。 【通俗解释&#x…...