MobileOne(CVPR 2023)原理与代码解析

news2024/11/29 13:31:47

paper:MobileOne: An Improved One millisecond Mobile Backbone

official implementation:https://github.com/apple/ml-mobileone 

third-party implementation:mmpretrain/mobileone.py at main · open-mmlab/mmpretrain · GitHub

前言 

针对移动设备的高效深度学习架构的设计和部署已经取得了很大进展,很多轻量模型在减少浮点操作(floating-point operations, FLOPS)和参数量(parameter count)的同时不断提高精度。但是就延迟latency而言,这些指标没有很好的与模型的效率关联起来,像FLOPs这样的指标没有考虑到内存访问成本memory access cost和模型并行的程度degree of parallelism,而后者在推理时可能会对延迟产生很大的影响。参数量也与延迟没有很好的相关性,比如共享参数可以减小模型大小但会使FLOPs增大。此外,像skip-connections和braching这种参数量小的操作会产生大量的内存访问成本。

本文的目标是找到影响延迟的关键瓶颈并进行优化,降低延迟成本同时提高模型精度。优化optimization是另一个瓶颈特别是在训练较小的网络时,这可以通过解耦训练和推理的网络结构来缓解,即结构重参数化技术。此外通过在训练过程中动态的减缓正则化进一步缓解了优化瓶颈,防止已经很小的模型被过度正则化。

基于找到的结构和优化瓶颈,本文设计了一个新的网络结构MobileOne,其变体在iPhone12上以小于1ms的延迟获得了SOTA的精度。MobileOne和之前的结构重参数化模型的关键区别在于引入了过参数化分支over-parameterization branch和模型缩放策略。

本文的贡献

  • 本文设计了MobileOne,这是一种在移动设备上延迟小于1 ms内的新架构,并在高效的模型架构中实现了最先进的图像分类精度。该模型的表现也可以推广到桌面CPU。
  • 本文分析了激活activations和分支结构branching中的性能瓶颈,这导致了移动设备上的高延迟成本。
  • 本文分析了结构重参数化和动态减缓正则化的影响,这两者结合有助于缓解训练小模型时遇到的优化瓶颈。
  • 本文设计的MobileOne,可以很好的推广到其它下游任务,比如目标检测和语义分割,性能优于此前的SOTA模型。

方法介绍

Metric Correlations

作者首先分析了常用的两个指标FLOPs和参数量与移动设备上延迟的相关性,一些常见的轻量模型的FLOPs和参数量与在iPhone12上的延迟的关系如下图所示,可以看出低FLOPs或低参数量并不代表实际的延迟就低。

Key Bottlenecks

作者从网络结构上分析了延迟的瓶颈,首先是激活函数。为了分析激活函数对延迟的影响,作者构建了一个30层的神经网络,并使用不同的激活函数在iPhone12上进行基准测试,结果如表3所示,可以看出ReLU的延迟最低,因此作者在MobileOne中只使用ReLU激活函数。 

作者又分析了Block结构对延迟的影响。影响延迟的两个关键因素是内存访问成本和模型并行程度,在多分支结构中,内存访问成本显著增加,因为每个分支的激活都需要存储起来计算图graph中的下一个张量。如果网络的分支较少就可以避免这种内存瓶颈。此外必须进行同步synchronization操作的block结构比如SE block中的全局平均池化操作,同步成本也会影响延迟。因此作者采用了一种推理时没有分支的结构,从而降低内存访问成本。此外只在MobileOne最大的variant中使用SE block来提高精度。

MobileOne Architecture

MobileOne Block

MobileOne block的结构被分解成depthwise层和pointwise层,此外还引入了over-parameterization分支。basic block的结构基于MobileNet-V1的设计,3x3 depthwise卷积后接1x1 pointwise卷积,然后作者又加入可重参数化的skip-connection分支,具体包括一个batchnorm分支和一个可复制多次的分支,如图3所示 

其中over-parameterization factor \(k\) 是超参,在推理阶段,通过结构重参数化将多分支合并只保留单分支结构。

Model Scaling

最近的一些工作通过缩放scale模型的width、depth、resolution来提高性能。MobileOne深度缩放的策略和MobileNet-V2类似,即浅层early stage采用较少的层,因为浅层的特征图分辨率更大,相比于分辨率较小的深层要慢得多。对于宽度,作者采用了5种不同的尺度,如表2所示。

对于分辨率,作者并没有进行缩放,因为不利于移动设备上的运行性能。 

Training

与大模型相比,小模型只需要较少的正则化来对抗过拟合。对于学习率,作者采用cosine schedule策略,对于weight decay系数,也采用相同的策略。此外作者还采用了渐进式课程学习progressive learning curriculum的思想,表5是不同的训练设置对精度的提升

实验结果

作者在ImageNet-1K上评估MobileOne的分类效果,模型的具体细节如下:采用了label smooth正则化,smoothing factor设置为0.1;初始学习率0.1,采用余弦退火schedule调整学习率。weight decay coeffificient设置为\(10^{-4}\),同样采用cosine schedule减小到\(10^{-5}\);只在训练MobileOne较大的变体即S2、S3、S4时采用AutoAugment数据增强;autoaugment的强度和输入分辨率大小采用EfficientNetv2的方法,在训练过程中逐渐增加;对于较小的变体即S0、S1,采用基本的数据增强方法 - 随机缩放裁剪和水平翻转;对于所有变体,都采用EMA(Exponential Moving Average),decay constant设置为0.9995。实验结果如下 

结果按照延迟进行了分组,可以看到MobileOne的不同版本都以最低的延迟得到了最高的精度。

作者还用MobileOne作为backbone,比较了在目标检测和语义分割任务上的性能,结果如下所示

 

可以看出,MobileOne作为backbone在目标检测和语义分割任务上的性能都优于其它轻量模型,表现除了优异的泛化性能。 

代码解析

这里以mmdetection中的实现为例,下面分别是S0~S4的规格,可结合表2一起看。其中num_blocks是不同stage的block个数,注意其中不包含表2中的stage1和stage7、8,同时将stage4、5合并到了一起,因此列表只有4个元素。width_factor表示宽度即通道的缩放因子即表2中的 \(\alpha\)。num_conv_branches表示block中over-parameterization分支重复的个数,即表2中的 \(k\)。num_se_blocks表示每个stage的block中采用SE layer的个数。这4个列表的元素个数都是4,分别对应表2中的stage2,stage3,stage45,stage6。

arch_zoo = {
    's0':
    dict(
        num_blocks=[2, 8, 10, 1],
        width_factor=[0.75, 1.0, 1.0, 2.0],
        num_conv_branches=[4, 4, 4, 4],
        num_se_blocks=[0, 0, 0, 0]),
    's1':
    dict(
        num_blocks=[2, 8, 10, 1],
        width_factor=[1.5, 1.5, 2.0, 2.5],
        num_conv_branches=[1, 1, 1, 1],
        num_se_blocks=[0, 0, 0, 0]),
    's2':
    dict(
        num_blocks=[2, 8, 10, 1],
        width_factor=[1.5, 2.0, 2.5, 4.0],
        num_conv_branches=[1, 1, 1, 1],
        num_se_blocks=[0, 0, 0, 0]),
    's3':
    dict(
        num_blocks=[2, 8, 10, 1],
        width_factor=[2.0, 2.5, 3.0, 4.0],
        num_conv_branches=[1, 1, 1, 1],
        num_se_blocks=[0, 0, 0, 0]),
    's4':
    dict(
        num_blocks=[2, 8, 10, 1],
        width_factor=[3.0, 3.5, 3.5, 4.0],
        num_conv_branches=[1, 1, 1, 1],
        num_se_blocks=[0, 0, 5, 1])
    }

MobileOneBlock的代码如下,注意如图3所示,一个完整的block包含一个3x3 depthwise的block和一个1x1 pointwise的block,而下面的实现只是一个单独block,因此要调用2次。 

看其中的forward函数,self.branch_norm是BN分支,只有当stride=1且输入输出通道数相同时才有该分支。self.branch_scale是3x3 depthwise block中的1x1分支,1x1 pointwise block中没有该分支。self.branch_conv_list就是重复 \(k\) 次的over-parameterization分支,即3x3 depthwise conv或1x1 pointwise conv。

class MobileOneBlock(BaseModule):
    """MobileOne block for MobileOne backbone.

    Args:
        in_channels (int): The input channels of the block.
        out_channels (int): The output channels of the block.
        kernel_size (int): The kernel size of the convs in the block. If the
            kernel size is large than 1, there will be a ``branch_scale`` in
             the block.
        num_convs (int): Number of the convolution branches in the block.
        stride (int): Stride of convolution layers. Defaults to 1.
        padding (int): Padding of the convolution layers. Defaults to 1.
        dilation (int): Dilation of the convolution layers. Defaults to 1.
        groups (int): Groups of the convolution layers. Defaults to 1.
        se_cfg (None or dict): The configuration of the se module.
            Defaults to None.
        norm_cfg (dict): Configuration to construct and config norm layer.
            Defaults to ``dict(type='BN')``.
        act_cfg (dict): Config dict for activation layer.
            Defaults to ``dict(type='ReLU')``.
        deploy (bool): Whether the model structure is in the deployment mode.
            Defaults to False.
        init_cfg (dict or list[dict], optional): Initialization config dict.
            Defaults to None.
    """

    def __init__(self,
                 in_channels: int,
                 out_channels: int,
                 kernel_size: int,
                 num_convs: int,
                 stride: int = 1,
                 padding: int = 1,
                 dilation: int = 1,
                 groups: int = 1,
                 se_cfg: Optional[dict] = None,
                 conv_cfg: Optional[dict] = None,
                 norm_cfg: Optional[dict] = dict(type='BN'),
                 act_cfg: Optional[dict] = dict(type='ReLU'),
                 deploy: bool = False,
                 init_cfg: Optional[dict] = None):
        super(MobileOneBlock, self).__init__(init_cfg)

        assert se_cfg is None or isinstance(se_cfg, dict)
        if se_cfg is not None:
            self.se = SELayer(channels=out_channels, **se_cfg)
        else:
            self.se = nn.Identity()

        self.in_channels = in_channels
        self.out_channels = out_channels
        self.kernel_size = kernel_size
        self.num_conv_branches = num_convs
        self.stride = stride
        self.padding = padding
        self.se_cfg = se_cfg
        self.conv_cfg = conv_cfg
        self.norm_cfg = norm_cfg
        self.act_cfg = act_cfg
        self.deploy = deploy
        self.groups = groups
        self.dilation = dilation

        if deploy:
            self.branch_reparam = build_conv_layer(
                conv_cfg,
                in_channels=in_channels,
                out_channels=out_channels,
                kernel_size=kernel_size,
                groups=self.groups,
                stride=stride,
                padding=padding,
                dilation=dilation,
                bias=True)
        else:
            # judge if input shape and output shape are the same.
            # If true, add a normalized identity shortcut.
            if out_channels == in_channels and stride == 1:
                self.branch_norm = build_norm_layer(norm_cfg, in_channels)[1]
            else:
                self.branch_norm = None

            self.branch_scale = None
            if kernel_size > 1:
                self.branch_scale = self.create_conv_bn(kernel_size=1)

            self.branch_conv_list = ModuleList()
            for _ in range(num_convs):
                self.branch_conv_list.append(
                    self.create_conv_bn(
                        kernel_size=kernel_size,
                        padding=padding,
                        dilation=dilation))

        self.act = build_activation_layer(act_cfg)

    def create_conv_bn(self, kernel_size, dilation=1, padding=0):
        """cearte a (conv + bn) Sequential layer."""
        conv_bn = Sequential()
        conv_bn.add_module(
            'conv',
            build_conv_layer(
                self.conv_cfg,
                in_channels=self.in_channels,
                out_channels=self.out_channels,
                kernel_size=kernel_size,
                groups=self.groups,
                stride=self.stride,
                dilation=dilation,
                padding=padding,
                bias=False))
        conv_bn.add_module(
            'norm',
            build_norm_layer(self.norm_cfg, num_features=self.out_channels)[1])

        return conv_bn

    def forward(self, x):

        def _inner_forward(inputs):
            if self.deploy:
                return self.branch_reparam(inputs)

            inner_out = 0
            if self.branch_norm is not None:
                inner_out = self.branch_norm(inputs)

            if self.branch_scale is not None:
                inner_out += self.branch_scale(inputs)

            for branch_conv in self.branch_conv_list:
                inner_out += branch_conv(inputs)

            return inner_out

        return self.act(self.se(_inner_forward(x)))

    def switch_to_deploy(self):
        """Switch the model structure from training mode to deployment mode."""
        if self.deploy:
            return
        assert self.norm_cfg['type'] == 'BN', \
            "Switch is not allowed when norm_cfg['type'] != 'BN'."

        reparam_weight, reparam_bias = self.reparameterize()
        self.branch_reparam = build_conv_layer(
            self.conv_cfg,
            self.in_channels,
            self.out_channels,
            kernel_size=self.kernel_size,
            stride=self.stride,
            padding=self.padding,
            dilation=self.dilation,
            groups=self.groups,
            bias=True)
        self.branch_reparam.weight.data = reparam_weight
        self.branch_reparam.bias.data = reparam_bias

        for param in self.parameters():
            param.detach_()
        delattr(self, 'branch_conv_list')
        if hasattr(self, 'branch_scale'):
            delattr(self, 'branch_scale')
        delattr(self, 'branch_norm')

        self.deploy = True

    def reparameterize(self):
        """Fuse all the parameters of all branches.

        Returns:
            tuple[torch.Tensor, torch.Tensor]: Parameters after fusion of all
                branches. the first element is the weights and the second is
                the bias.
        """
        weight_conv, bias_conv = 0, 0
        for branch_conv in self.branch_conv_list:
            weight, bias = self._fuse_conv_bn(branch_conv)
            weight_conv += weight
            bias_conv += bias

        weight_scale, bias_scale = 0, 0
        if self.branch_scale is not None:
            weight_scale, bias_scale = self._fuse_conv_bn(self.branch_scale)
            # Pad scale branch kernel to match conv branch kernel size.
            pad = self.kernel_size // 2
            weight_scale = F.pad(weight_scale, [pad, pad, pad, pad])

        weight_norm, bias_norm = 0, 0
        if self.branch_norm:
            tmp_conv_bn = self._norm_to_conv(self.branch_norm)
            weight_norm, bias_norm = self._fuse_conv_bn(tmp_conv_bn)

        return (weight_conv + weight_scale + weight_norm,
                bias_conv + bias_scale + bias_norm)

    def _fuse_conv_bn(self, branch):
        """Fuse the parameters in a branch with a conv and bn.

        Args:
            branch (mmcv.runner.Sequential): A branch with conv and bn.

        Returns:
            tuple[torch.Tensor, torch.Tensor]: The parameters obtained after
                fusing the parameters of conv and bn in one branch.
                The first element is the weight and the second is the bias.
        """
        if branch is None:
            return 0, 0
        kernel = branch.conv.weight
        running_mean = branch.norm.running_mean
        running_var = branch.norm.running_var
        gamma = branch.norm.weight
        beta = branch.norm.bias
        eps = branch.norm.eps

        std = (running_var + eps).sqrt()
        fused_weight = (gamma / std).reshape(-1, 1, 1, 1) * kernel
        fused_bias = beta - running_mean * gamma / std

        return fused_weight, fused_bias

    def _norm_to_conv(self, branch_nrom):
        """Convert a norm layer to a conv-bn sequence towards
        ``self.kernel_size``.

        Args:
            branch (nn.BatchNorm2d): A branch only with bn in the block.

        Returns:
            (mmcv.runner.Sequential): a sequential with conv and bn.
        """
        input_dim = self.in_channels // self.groups
        conv_weight = torch.zeros(
            (self.in_channels, input_dim, self.kernel_size, self.kernel_size),
            dtype=branch_nrom.weight.dtype)

        for i in range(self.in_channels):
            conv_weight[i, i % input_dim, self.kernel_size // 2,
                        self.kernel_size // 2] = 1
        conv_weight = conv_weight.to(branch_nrom.weight.device)

        tmp_conv = self.create_conv_bn(kernel_size=self.kernel_size)
        tmp_conv.conv.weight.data = conv_weight
        tmp_conv.norm = branch_nrom
        return tmp_conv

表2中的stage1的代码如下,注意stage1只有1个block,且只有3x3 depthwise block没有后面的1x1 pointwise block。

self.stage0 = MobileOneBlock(
    self.in_channels,
    channels,
    stride=2,
    kernel_size=3,
    num_convs=1,
    conv_cfg=conv_cfg,
    norm_cfg=norm_cfg,
    act_cfg=act_cfg,
    deploy=deploy)

接下里就是按照arch_zoo中指定的版本参数,遍历stage2~stage6。

self.stages = []
for i, num_blocks in enumerate(self.arch['num_blocks']):
    planes = int(base_channels[i] * self.arch['width_factor'][i])

    stage = self._make_stage(planes, num_blocks,
                             arch['num_se_blocks'][i],
                             arch['num_conv_branches'][i])

    stage_name = f'stage{i + 1}'
    self.add_module(stage_name, stage)
    self.stages.append(stage_name)

其中self._make_stage就是构建每个stage,代码如下。可以看到和stage1不一样,这里每个block包含一个depthwise block和一个pointwise block。当stride=2时,在每个stage的第一个block中的depthwise block中进行下采样。

def _make_stage(self, planes, num_blocks, num_se, num_conv_branches):
    strides = [2] + [1] * (num_blocks - 1)
    if num_se > num_blocks:
        raise ValueError('Number of SE blocks cannot '
                         'exceed number of layers.')
    blocks = []
    for i in range(num_blocks):
        use_se = False
        if i >= (num_blocks - num_se):
            use_se = True

        blocks.append(
            # Depthwise conv
            MobileOneBlock(
                in_channels=self.in_planes,
                out_channels=self.in_planes,
                kernel_size=3,
                num_convs=num_conv_branches,
                stride=strides[i],
                padding=1,
                groups=self.in_planes,
                se_cfg=self.se_cfg if use_se else None,
                conv_cfg=self.conv_cfg,
                norm_cfg=self.norm_cfg,
                act_cfg=self.act_cfg,
                deploy=self.deploy))

        blocks.append(
            # Pointwise conv
            MobileOneBlock(
                in_channels=self.in_planes,
                out_channels=planes,
                kernel_size=1,
                num_convs=num_conv_branches,
                stride=1,
                padding=0,
                se_cfg=self.se_cfg if use_se else None,
                conv_cfg=self.conv_cfg,
                norm_cfg=self.norm_cfg,
                act_cfg=self.act_cfg,
                deploy=self.deploy))

        self.in_planes = planes

    return Sequential(*blocks)

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

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

相关文章

在Ubuntu Kylin系统中安装并使用minicom

1、minicom的安装 首先在命令窗口中输入:minicom -s 如果显示的是:程序“minicom”尚未安装,您可以使用一下命令安装:sudo apt install minicom 这时需要minicom安装包 sudo apt-get install minicom 2、minicom的配置 sudo minicom -s # 打开minicom配置界面 3、配置…

(第44册)Java程序设计应用开发

书名:Java程序设计应用开发 书号:978-7-113-29847-0 作者:张西广,夏敏捷,罗菁 编著 出版日期:2023年1月 目前学习和关注 Java 语言的人越来越多,Java 语言已是目前世界上最为流行的程序开发语言之一。由于具有功能…

用于半监督体积医学图像分割的动量对比体素表示学习

文章目录 Momentum Contrastive Voxel-Wise Representation Learning for Semi-supervised Volumetric Medical Image Segmentation摘要本文方法Voxel-Wise Contrastive ObjectiveDimensional Contrastive ObjectiveConsistency Loss总损失 实验结果 Momentum Contrastive Voxe…

可以白嫖的语音识别开源项目whisper的搭建详细过程 | 如何在Linux中搭建OpenAI开源的语音识别项目Whisper

原文来自我个人的博客。 1、前提条件 服务器为GPU服务器。点击这里跳转到我使用的GPU服务器。我搭建 whisper 选用的是 NVIDIA A 100显卡,4GB显存。 Python版本要在3.8~3.11之间。 输入下面命令查看使用的Python版本。 python3 -V2、安装Anaconda 为啥要安装A…

ORACLE数据库长连接客户端持久的CLOSE_WAIT

前言 根据以往的项目构造,业务层数据库基本使用长连接形式进行批量操作。大部分周期有执行的链接基本正常。再长期的内测中也没有发生CLOSE_WAIT的现象。 上线后采用的数据库使用了新的版本,发现产生CLOSE_WAIY。根据开发经验和网上搜索,发…

『手撕 Mybatis 源码』01 - 基本原理与搭建

MyBatis的架构设计 Api接口层:提供API 增加、删除、修改、查询等接口,通过API接口对数据库进行操作 例如下面这些操作 sqlSession.selectOne(statementId, param); mapperProxy.findByCondition(param);数据处理层:解析sql根据调用请求完成…

机器学习模型的评估

(1)数据划分 将可用数据划分为三部分:训练集、验证集和测试集。在训练数据上训练模型。在验证数据上评估模型。模型准备上线之前,在测试数据上最后测试一次 不将数据划分为两部分,即训练集和测试集?在训练…

Java基础(二十三):反射机制

Java基础系列文章 Java基础(一):语言概述 Java基础(二):原码、反码、补码及进制之间的运算 Java基础(三):数据类型与进制 Java基础(四):逻辑运算符和位运算符 Java基础(五):流程控制语句 Java基础(六)&#xff1…

Linux内存管理 (1):内核镜像线性映射的建立

文章目录 1. 前言2. 分析背景3. 内核镜像线性映射的建立过程3.1 预备工作:内核解压缩3.2 建立内核镜像区域的线性映射3.2.1 定位内核入口3.2.2 建立内核线性映射前的其它启动工作3.2.2.1 将 CPU 设为 SVC 模式,且禁用 IRQ FIQ 中断3.2.2.2 获取处理器类…

【C++】实现 priority_queue --- 反函数

priority_queue 实际上是以 堆 的规则组织起来的数组&#xff0c;是一颗完全二叉树 **反函数 !!! 堆的向上向下调整 #pragma oncenamespace xiong {//反函数template<class T>struct less{bool operator()(const T& x, const T& y){return x < y;}};templat…

python列表逆序排列的方法

python中的列表是可以直接进行逆序排列的&#xff0c;但是在 python中&#xff0c;逆序排列也是有一定规则的&#xff0c;一般是按升序排序&#xff0c;也就是从左到右。比如 list[1,2,3,4]&#xff1b; 注意&#xff1a;顺序相同的元素可以放在同一行&#xff1b; 在 python中…

嵌入式电路基础

电路基础 器件基础基本电路术语与符号 信号浮动三态门&#xff08;三态缓冲器&#xff09;上下拉电阻基本元件与逻辑OC/OD门&#xff08;掌握原理&#xff0c;用途&#xff09;开放收集器&#xff08;OC门&#xff0c;NPN型三极管&#xff09;掌握原理、用途漏极开路&#xff0…

C++ STL—vector,map,set,list,deque等

STL是什么 STL是标准模板库&#xff0c;包括算法、容器和迭代器。 算法&#xff1a;包括排序、复制等常用算法容器&#xff1a;数据存放形式&#xff0c;包括序列式容器和关联式容器&#xff0c;序列式容器就是list,vector&#xff0c;关联式容器就是set,map等迭代器是在不暴…

考研复试刷题第八天:日期累加 【日期问题】

本来以为和上次那个简单题一样的&#xff0c;没啥难度&#xff0c;就是循环就完事了&#xff0c;结果超时了 超时代码: #include <iostream> using namespace std;//平年各个月份都多少天&#xff1f; int mouths [13] {0,31,28,31,30,31,30,31,31,30,31,30,31 };//判…

Spring事务深度学习

jdbcTemp Spring 框架对 JDBC 进行封装&#xff0c;使用 JdbcTemplate 方便实现对数据库操作。 JdbcTemp的使用 对应依赖 <!-- MySQL驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><ve…

已知相机内外参通过COLMAP进行稀疏/稠密模型重建操作过程

在https://colmap.github.io/faq.html#reconstruct-sparse-dense-model-from-known-camera-poses 中介绍了已知相机内外参如何通过COLMAP进行稀疏和稠密模型重建的过程&#xff0c;这里按照说明操作一遍&#xff1a; 在instant-ngp中&#xff0c;执行scripts/colmap2nerf.py时…

request页面代码逻辑

一. 封装请求基地址 在src目录下面建一个api文件夹 然后在文件夹里面新建一个专门放用户请求的use.js 用axios发送请求 在use.js文件夹里导入request 在src目录新建发送请求的页面并导入封装好的请求 然后把这个请求封装成一个函数&#xff0c;这个函数里需要传入两个参数。 …

Xavier或TX2配置ipv4地址

输入ifconfig查看本地ipv4地址&#xff0c;发现并没有设置&#xff0c;无法通过以太网与其他主机通信。下面来配置系统的以太网地址。 1、编辑文件/etc/network/interfaces: sudo gedit /etc/network/interfaces2、用下面的内容来替换有关eth0的行&#xff0c;并且将ip地址等信…

Java中抽象类和接口的区别,一文弄懂,图文并茂

目录 前言 1. 抽象类 1.1 定义 1.2 示例 1.3 使用 1.3.1代码-抽象类 1.3.2代码-抽象类继承类使用 1.3.3输出结果为&#xff1a; 1.4UML类图展示类间的关系 2. 接口 2.1 定义 2.2 示例 2.2.1代码-接口 2.3 使用 2.3.1代码-接口实现 2.3.2代码-接口实现类使用 2…

【Linux】驱动内核调试,没有几板斧,怎么能行?

目录 前言&#xff1a; 一、基础打印工具 &#xff08;1&#xff09;printk---最常用 ①Log Buffer: ②Console&#xff1a; ③RAM Console&#xff1a; &#xff08;2&#xff09;动态打印 ①动态打印与printk之间的区别联系 ②动态打印常用的例子 ③动态打印转为pri…