MobileNet
MobileNet是一种为移动和嵌入式视觉应用设计的轻量级深度神经网络架构。它的核心特点是使用深度可分离卷积(depthwise separable convolutions),这种卷积将标准卷积分解为两个部分:深度卷积(depthwise convolution)和逐点卷积(pointwise convolution)。这样做可以显著减少模型的参数数量和计算复杂度,从而使得模型更适合在资源受限的设备上运行。
MobileNet的结构通常包括以下几个关键部分:
- 初始卷积层:一个全卷积层,用于提取初步特征。
- 深度可分离卷积块:多个深度可分离卷积块,每个块包括一个深度卷积和一个逐点卷积。
- 瓶颈层:在MobileNetV2中引入了倒置残差结构,其中残差连接位于瓶颈层之间。
- 分类层:包括全局平均池化层和全连接层,用于输出最终的分类结果。
MobileNet的算法原理主要基于以下几点:
- 深度卷积:对输入的每个通道分别应用滤波器,从而提取空间特征。
- 逐点卷积:使用1x1的卷积核对深度卷积的输出进行组合和变换,以获得跨通道的特征。
- 宽度乘数:允许模型根据需要调整通道数,以在模型大小和性能之间进行权衡。
- 分辨率乘数:调整输入图像的分辨率,进一步控制模型的复杂度。
MobileNet系列有多个版本,包括MobileNetV1、MobileNetV2等,每个版本都在前一个版本的基础上进行了改进和优化。
跨层均衡化
跨层均衡化(Cross-Layer Equalization)在神经网络量化中的作用是优化网络参数,以减少量化误差并提高量化后模型的性能。具体来说,它通过以下方式发挥作用:
-
权重重参数化:利用ReLU激活函数的特性,跨层均衡化通过调整连续两层网络间的权重,使得不同通道的权重数值范围更加接近。这样做可以使得量化过程中每个通道的量化范围和步长更加一致,从而减少量化误差¹。
-
减少通道间差异:在对权重进行均衡缩放处理后,相应的激活值的范围也会变化。跨层均衡化有助于避免不同通道的激活值差异过大,通过吸收高偏差到下一层,保持激活值的均衡,从而有利于量化⁴。
-
误差校正:量化过程中可能会引入有偏的误差,即某些层经过量化后,其激活值的均值可能会发生偏移。跨层均衡化可以通过调整参数来校正这种偏差,保持数据分布的一致性,从而提高量化模型的准确性。
总的来说,跨层均衡化是量化前的一种优化策略,它通过调整模型参数来减少量化带来的性能损失,使得量化模型能够在保持较低位宽的同时,尽可能地保持原模型的性能。这对于在资源受限的设备上部署深度学习模型尤为重要。
pytorch permute函数
import torch
# 创建一个3维张量
x = torch.randn(2, 3, 5)
# 输出原始张量的大小
print(x.size()) # 输出: torch.Size([2, 3, 5])
# 使用permute改变维度顺序
y = x.permute(2, 0, 1)
# 输出新的张量大小
print(y.size()) # 输出: torch.Size([5, 2, 3])
在Python中,permute
函数通常与PyTorch库中的张量操作相关联。这个函数用于改变张量的维度顺序。例如,如果你有一个三维张量,你可以使用permute
来重新排列这些维度的顺序。
下面是一个使用permute
函数的示例:
import torch
# 创建一个3维张量
x = torch.randn(2, 3, 5)
# 输出原始张量的大小
print(x.size()) # 输出: torch.Size([2, 3, 5])
# 使用permute改变维度顺序
y = x.permute(2, 0, 1)
# 输出新的张量大小
print(y.size()) # 输出: torch.Size([5, 2, 3])
在这个例子中,我们将原始张量的维度从(2, 3, 5)
改变为(5, 2, 3)
。这意味着原来的第三维度变成了第一维度,第一维度变成了第二维度,第二维度变成了第三维度。
需要注意的是,permute
函数仅适用于PyTorch张量,不是Python标准库的一部分。
在PyTorch中,量化observer的类型主要包括以下几种:
MinMaxObserver: 这种观察者记录输入张量中的最大值和最小值,用来计算量化参数,如scale和zero-point。
MovingAverageMinMaxObserver: 它使用移动平均来记录张量的最大值和最小值,这有助于平滑短期的极端变化。
HistogramObserver: 使用直方图方法来更精确地估计张量的分布,从而计算量化参数。
PerChannelMinMaxObserver: 对于每个通道分别记录最大值和最小值,允许每个通道有不同的量化参数。
MovingAveragePerChannelMinMaxObserver: 结合了移动平均和每个通道分别量化的特点。
这些observer用于在量化过程中收集必要的统计信息,以便计算量化时使用的参数。
import torch
from torch.quantization import prepare_qat, get_default_qat_qconfig, convert
from torchvision.models import quantization
# Step1:修改模型
# 这里直接使用官方修改好的MobileNet V2,下文会对修改点进行介绍
model = quantization.mobilenet_v2()
print("original model:")
print(model)
# Step2:折叠算子
# fuse_model()在training或evaluate模式下算子折叠结果不同,
# 对于QAT,需确保在training状态下进行算子折叠
assert model.training
model.fuse_model()
print("fused model:")
print(model)
# Step3:指定量化方案
# 通过给模型实例增加一个名为"qconfig"的成员变量实现量化方案的指定
# backend目前支持fbgemm和qnnpack
BACKEND = "fbgemm"
model.qconfig = get_default_qat_qconfig(BACKEND)
# Step4:插入伪量化模块
prepare_qat(model, inplace=True)
print("model with observers:")
print(model)
# 正常的模型训练,无需修改代码
# Step5:实施量化
model.eval()
# 执行convert函数前,需确保模型在evaluate模式
model_int8 = convert(model)
print("quantized model:")
print(model_int8)
# Step6:int8模型推理
# 指定与qconfig相同的backend,在推理时使用正确的算子
torch.backends.quantized.engine = BACKEND
# 目前Pytorch的int8算子只支持CPU推理,需确保输入和模型都在CPU侧
# 输入输出仍为浮点数
fp32_input = torch.randn(1, 3, 224, 224)
y = model_int8(fp32_input)
print("output:")
print(y)
step1:修改模型 QuantStab() DeQuantStab()
step1:算子融合 fuse_model()
step2:指定量化方案 qconfig
step3:插入伪量化节点prepare_qat()
step4:实施量化 convert()
这段代码是PyTorch中的forward
方法的实现,它是在一个量化观察者(quantization observer)类中定义的。这个方法的目的是更新观察者中的最小值(min_val
)和最大值(max_val
),这些值用于后续的量化参数计算。下面是代码的逐行解释:
if x_orig.numel() == 0:
检查输入张量x_orig
是否为空,如果是,则直接返回。x = x_orig.detach()
创建x_orig
的一个副本,这样做是为了避免保留自动微分(autograd)的历史。x = x.to(self.min_val.dtype)
将x
转换为与min_val
相同的数据类型。min_val = self.min_val
和max_val = self.max_val
获取当前的最小值和最大值。x_dim = x.size()
获取x
的尺寸。new_axis_list = [i for i in range(len(x_dim))]
创建一个新的轴列表,用于后续的维度排列。new_axis_list[self.ch_axis] = 0
和new_axis_list[0] = self.ch_axis
交换ch_axis
和第一个轴的位置。y = x.permute(new_axis_list)
根据新的轴列表重新排列x
的维度。y = torch.flatten(y, start_dim=1)
将y
从第二个维度开始展平。if min_val.numel() == 0 or max_val.numel() == 0:
如果min_val
或max_val
为空,则计算y
的最小值和最大值。else:
如果min_val
和max_val
不为空,则计算当前的最小值和最大值,并使用移动平均更新它们。self.min_val.resize_(min_val.shape)
和self.max_val.resize_(max_val.shape)
调整min_val
和max_val
的尺寸以匹配新的形状。self.min_val.copy_(min_val)
和self.max_val.copy_(max_val)
将计算出的最小值和最大值复制到观察者的属性中。return x_orig
返回原始输入张量。
这个forward
方法是量化过程中的一个关键步骤,它确保了观察者能够跟踪输入数据的范围,这对于后续的量化操作至关重要。
def init(self, *args: Any, **kwargs: Any) -> None: 代码中->None的作用
__init__方法的类型注解-> None表明这个方法不应该返回任何值,或者更准确地说,它的返回值是NoneType。
class QuantizedResNet18(nn.Module): 在python中定义一个类括号中的传参作用
在Python中定义一个类时,括号中的传参指定了这个类的基类或父类。这意味着新定义的类将继承基类的所有方法和属性。
if name == ‘main’
在Python中,if name == ‘main’: 这行代码的含义是检查模块是被直接运行还是被导入。当Python文件被直接运行时,name 变量被设置为 ‘main’。如果文件被作为模块导入到其他文件中,name 将被设置为模块的名字。
这个语句通常用于以下情况:
当你想要在模块被直接运行时执行某些代码,比如测试代码或主程序。
当模块被导入时,你不希望这些代码被执行。
这样,你可以在一个文件中既定义函数和类,又包含可以直接执行的代码,而不会影响到导入此文件的其他模块。这是一种常见的Python编程习惯
model = ResNet(block, layers, **kwargs) 中kwargs的作用
在Python中,**kwargs是一个常用于函数定义中的参数,它允许函数接收任意数量的关键字参数。这意味着当你在调用函数或初始化类的实例时,可以传递任意多的命名参数,而不需要在函数或类的定义中显式声明它们。
在 ResNet(block, layers, **kwargs) 这个例子中,ResNet 是一个类,block 和 layers 是它的位置参数,而 **kwargs 允许你传递额外的关键字参数。这些参数可能用于设置模型的其他配置,如初始化权重、应用正则化等。这样做的好处是提供了更大的灵活性,因为你可以根据需要向模型添加额外的设置,而不需要修改类的定义
例如,如果ResNet类有一个可选参数num_classes来指定模型的输出类别数,你可以这样初始化一个ResNet实例:
model = ResNet(block, layers, num_classes=100)
all = [ ‘ResNet’, ‘resnet18’, ‘resnet34’, ‘resnet50’, ‘resnet101’, ‘resnet152’, ‘resnext50_32x4d’, ‘resnext101_32x8d’, ‘wide_resnet50_2’, ‘wide_resnet101_2’]什么作用
在Python模块中,all 是一个特殊的变量,它定义了当从该模块中使用 from module import * 语句导入时,哪些名称会被导出。如果定义了 all,只有在这个列表中的名称会被导出。如果没有定义 all,默认会导出所有不以下划线开头的全局名称。
这意味着如果其他模块执行 from module import *,它将只导入列表 all 中定义的那些类和函数,即 ResNet, resnet18, resnet34, 等等。这是一种控制模块公共接口的方法,并且有助于避免导入不需要的额外名称,可能会引起命名冲突或者不必要的内存占用。
Resnet18的主要模块组成
ResNet18是一个深度卷积神经网络,由以下主要组件构成:
初始卷积层:这是网络的第一层,通常包括一个较大的卷积核(如7x7),用于提取图像的初步特征。
残差块(Residual Blocks):这是ResNet的核心,每个残差块包含两个3x3的卷积层,每个卷积层后面跟着批量归一化(Batch Normalization)和ReLU激活函数。残差块利用跳跃连接(skip connections)来解决深度网络中的梯度消失问题。
下采样(Downsampling):在某些残差块之间,网络会通过步长为2的卷积来减少特征图的尺寸,同时增加特征图的深度。
全局平均池化(Global Average Pooling):在网络的末端,使用全局平均池化来减少每个特征图的空间维度,只保留深度维度。
全连接层(Fully Connected Layer):最后,一个全连接层将网络的输出映射到目标类别的数量,用于分类任务。
ResNet18的设计允许网络学习到更深层次的特征表示,同时避免了训练中的梯度消失问题。这使得ResNet18在图像识别任务中表现出色,尤其适合计算资源有限的场景
ResNet18的bottleneck是指
在ResNet架构中,"bottleneck"通常指的是一种特殊的残差块,它使用1x1卷积层来减少和恢复维度,从而在3x3卷积层之前和之后创建一个“瓶颈”。这种设计可以减少参数数量和计算量,同时保持网络性能。
然而,值得注意的是,ResNet18模型本身不使用bottleneck结构。ResNet18使用的是基本残差块,每个块包含两个3x3卷积层。bottleneck结构主要用于更深的ResNet版本,如ResNet50、ResNet101和ResNet152,其中的残差块包含三个卷积层:一个1x1卷积层用于降维,一个3x3卷积层用于处理特征,然后又一个1x1卷积层用于恢复维度
def _resnet(arch: str, block: Type[Union[BasicBlock, Bottleneck]], layers: List[int], pretrained: bool, progress: bool, **kwargs: Any) -> ResNet: 其中block: Type[Union[BasicBlock, Bottleneck]]的作用是什么?
在_resnet函数定义中,block: Type[Union[BasicBlock, Bottleneck]]是一个类型注解,它指定block参数可以是BasicBlock或Bottleneck中的任何一个。这两种类型都是PyTorch中定义的类,它们代表不同类型的残差块结构。
BasicBlock是较简单的残差块结构,通常用于较浅的网络,如ResNet18和ResNet34。
Bottleneck是更复杂的残差块结构,它使用1x1卷积层来减少和增加维度,从而在3x3卷积层之前和之后创建一个“瓶颈”。这种结构通常用于较深的网络,如ResNet50、ResNet101和ResNet152。
Type[Union[BasicBlock, Bottleneck]] 这是什么语法
Type[Union[BasicBlock, Bottleneck]] 是使用Python类型提示(Type Hints)的语法,它结合了几个不同的概念:
Type:这是一个类型提示,用于指示变量将是一个类类型,而不是该类的实例。
Union:这是一个类型提示,用于指示变量可以是多个类型中的任何一个。
BasicBlock 和 Bottleneck:这些是类名,它们在这里作为Union的参数,表示变量可以是BasicBlock类或Bottleneck类的任何一个。
Union 是python中的关键字吗,它的作用
在Python中,Union 不是一个关键字,而是 typing 模块中的一个类,用于类型注解。它允许你指定一个变量可以是多个类型中的任何一个。例如,如果一个函数参数可以是整数或字符串,你可以使用 Union[int, str] 来注解这个参数。
从Python 3.10开始,Union 类型可以用更简洁的方式表示,即使用管道符号 |。所以,Union[int, str] 可以被写为 int | str。
在python中 *args 和 **kwargs 传参有什么区别
在Python中,*args和**kwargs都用于在函数定义中传递可变数量的参数。它们的区别主要在于参数类型和如何使用它们:
*args用于传递任意数量的非关键字参数(也称为位置参数)。这些参数被打包成一个元组(tuple)。例如:
for arg in args:
print(arg)
function_with_args('hello', 'world', 123)
**kwargs用于传递任意数量的关键字参数。这些参数被打包成一个字典(dict)。例如:
def function_with_kwargs(**kwargs):
for key, value in kwargs.items():
print(f"{key} = {value}")
function_with_kwargs(name='Alice', age=25)
python 中传参分为关键字参数和非关键字参数,有什么区别
在Python中,关键字参数和非关键字参数的区别主要体现在参数的传递方式和函数内部如何处理这些参数:
非关键字参数(也称为位置参数)是按照参数在函数定义中的位置顺序传递的。调用函数时,你需要按照函数定义的顺序提供相应的参数值。例如:
def func(a, b, c):
print(a, b, c)
func(1, 2, 3) # 输出: 1 2 3
关键字参数允许你在调用函数时指定参数的名称。这意味着你可以不按照参数在函数中定义的顺序来传递参数,只要提供参数的名称即可。例如:
def func(a, b, c):
print(a, b, c)
func(c=3, a=1, b=2) # 输出: 1 2 3
关键字参数的一个优点是提高了代码的可读性,因为你可以明确每个参数的意图。此外,如果函数有许多参数或包含默认值,使用关键字参数可以使函数调用更加清晰和灵活。
if np.allclose(a=y1, b=y2, rtol=rtol, atol=atol, equal_nan=False) == False:
这段代码使用了NumPy库中的np.allclose函数来检查两个数组y1和y2是否在给定的容差范围内近似相等。这里的参数rtol代表相对容差,atol代表绝对容差,equal_nan=False意味着在比较时不将NaN视为相等。
如果y1和y2在指定的容差内不近似相等(即np.allclose返回False)
如果y1和y2在指定的容差内不近似相等(即np.allclose返回False)
在训练机器学习模型时,样本数、epoch和batch是三个重要的概念,它们之间的关系如下:
样本数:这是指用于训练模型的数据集中的总样本数量。
Epoch:一个epoch完成时,意味着整个训练数据集已经被模型完整地学习了一次。通常,模型需要多个epoch来学习数据,以便更好地泛化未见过的数据。每个epoch结束后,模型会对整个数据集进行一次完整的预测,并根据预测结果更新自身的参数。epoch的数量越多,模型的学习效果可能越好,但同时也会增加训练时间123。
Batch:在每个epoch中,数据集被分成多个batch,每个batch包含一定数量的样本。模型在每个batch上进行一次参数更新,这称为一次迭代。batch size(每个batch的样本数量)的选择对模型的训练速度和性能有重要影响。如果batch size过小,模型训练过程可能不稳定,容易受到噪声数据的干扰;而batch size过大则可能导致模型训练时间过长,同时可能会限制模型的学习能力。
torch.jit.save(torch.jit.script(model), model_filepath)
model = torch.jit.load(model_filepath, map_location=device)
构建一个单层的卷积网络,用来测试卷积计算
# 构建一个简单的卷积网络,用来测试卷积计算
import torch
import torch.nn as nn
class SimpleConvModel(nn.Module):
def __init__(self):
super(SimpleConvModel, self).__init__()
# 创建一个卷积层
self.conv_layer = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=(3,3),stride=1)
def forward(self, x):
x = self.conv_layer(x)
return x
if __name__ == '__main__':
# 准备数据
input = torch.randn(8,3,640,640)
# 加载模型
model = SimpleConvModel()
# 模型推理
input = model(input)
print(input.shape)
打印输出:
为什么我用nn.Conv2d(in_channels=32, out_channels=64, kernel_size=(3,3),stride=(2, 2)),输入是[8,32,640,640]的时候 输出值得shape是[8,64,319,319].而在yolov中经过该卷积层时得到得特征图大小为[8,64,320,320]
在PyTorch中,卷积层的输出大小是根据输入大小、卷积核大小、步幅和填充等参数计算得出的。通常,输出大小可以使用以下公式计算:
在你提供的例子中,使用nn.Conv2d(in_channels=32, out_channels=64, kernel_size=(3,3), stride=(2, 2))
,并且没有指定填充(默认为0),所以输出大小的计算如下:
这就解释了为什么输出的形状是[8, 64, 319, 319]。
然而,在YOLOv模型中,可能使用了不同的填充策略,比如"same"填充,它会自动添加足够的填充以保持输出大小不变。如果使用"same"填充,即使步幅为2,输入和输出的空间维度也会保持一致,这就是为什么在YOLOv中得到的特征图大小为[8, 64, 320, 320]。这种填充方式确保了卷积操作后的输出尺寸与输入尺寸相同或非常接近,即使步幅大于。
在实际应用中,这种填充策略对于保持特征图的空间分辨率非常有用,特别是在构建深度神经网络时,它可以帮助我们保持足够的信息,以便进行准确的目标检测。