6-8 残差网络(ResNet)

随着我们设计越来越深的网络,深刻理解“新添加的层如何提升神经网络的性能”变得至关重要。更重要的是设计网络的能力,在这种网络中,添加层会使网络更具表现力, 为了取得质的突破,我们需要一些数学基础知识。

残差网络源自于一个思想——我们要不断地去加深我的神经网络,但加深一定会给你带来好处吗?不一定
请添加图片描述
红色点为最优值,虽然 f 6 f_{6} f6更复杂了,但有可能它学偏了!

但是,如果我增加模型复杂度,但每一次,更复杂的模型是包含前面的小模型,所以我的新模型就能严格的比前面更大,所以模型效果至少不会变差。ResNet想的就是,我加更多的层,让你至少不至于变差,通常来说是变好的

函数类

请添加图片描述
请添加图片描述
残差网络的核心思想:每个附加层都应该更容易地包含原始函数作为其元素之一

残差块

请添加图片描述
假设我的 g ( x ) g(x) g(x)什么都不干,没有学到任何东西,那我的 f ( x ) f(x) f(x)至少还是可以学到原来的值 x x x的。 g ( x ) g(x) g(x)上任何能够发挥一点点作用的东西都能够把我们的整体模型变大一点

+ x +x +x意思就是我的复杂模型是包含了我前面的小模型的

请添加图片描述
ResNet沿用了VGG完整的 3 × 3 3\times 3 3×3卷积层设计。 残差块里首先有 2 2 2个有相同输出通道数的 3 × 3 3\times 3 3×3卷积层。 每个卷积层后接一个批量规范化层和ReLU激活函数。 然后我们通过跨层数据通路,跳过这 2 2 2个卷积运算,将输入直接加在最后的ReLU激活函数前。
这样的设计要求 2 2 2个卷积层的输出与输入形状一样,从而使它们可以相加。 如果想改变通道数,就需要引入一个额外的 1 × 1 1\times 1 1×1卷积层来将输入变换成需要的形状后再做相加运算。

残差块的实现如下:

import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2lclass Residual(nn.Module):  # 这是类的构造函数,负责初始化。它接收四个参数:# input_channels:输入的通道数。# num_channels:输出的通道数。# use_1x1conv:一个布尔值,决定是否使用一个1x1的卷积来改变维度或步长,默认为 False。# strides:卷积操作的步长,默认为1。def __init__(self, input_channels, num_channels,use_1x1conv=False, strides=1):super().__init__()self.conv1 = nn.Conv2d(input_channels, num_channels,kernel_size=3, padding=1, stride=strides)# 定义第一个卷积层 conv1,它是一个2D卷积层,接受 input_channels,输出 num_channels,# 卷积核大小为3x3,填充为1(为了保持数据的空间维度),步长为 strides。self.conv2 = nn.Conv2d(num_channels, num_channels,kernel_size=3, padding=1)# 定义第二个卷积层 conv2,配置类似于 conv1 但步长为1,用于进一步处理 conv1 的输出。if use_1x1conv:self.conv3 = nn.Conv2d(input_channels, num_channels,kernel_size=1, stride=strides)else:self.conv3 = None# 根据 use_1x1conv 的值决定是否创建第三个卷积层 conv3。# 这个层使用1x1的卷积核,可以改变输入的维度和/或调整步长,# 如果 use_1x1conv 为 False,则不创建这个层。self.bn1 = nn.BatchNorm2d(num_channels)self.bn2 = nn.BatchNorm2d(num_channels)# 定义两个批量归一化层,bn1 和 bn2,用于稳定和加速训练过程。def forward(self, X): # 定义网络的前向传播函数,X 是输入数据。Y = F.relu(self.bn1(self.conv1(X)))# 应用第一个卷积层 conv1,接着是批量归一化 bn1,最后是ReLU激活函数。结果存储在 Y 中。Y = self.bn2(self.conv2(Y))# Y 经过第二个卷积层 conv2 和第二个批量归一化层 bn2。if self.conv3:X = self.conv3(X)# 如果 conv3 存在,则对输入 X 应用这个层,可能用于维度匹配。Y += X # 将变换后的输入 X 加到 Y 上,实现残差连接。return F.relu(Y)# 最后应用ReLU激活函数并返回结果。# 这一步确保了网络的输出非负,同时加入了非线性,有助于处理更复杂的模式。

如 图7.6.3所示,此代码生成两种类型的网络:

  • 一种是当use_1x1conv=False时,应用ReLU非线性函数之前,将输入添加到输出。
  • 另一种是当use_1x1conv=True时,添加通过 1 × 1 1\times 1 1×1卷积调整通道和分辨率。
    请添加图片描述
    下面我们来查看输入和输出形状一致的情况。
blk = Residual(3,3)
# 创建了一个 Residual 块的实例,名为 blk。
# 这个残差块的输入和输出通道数都是3。
# 由于没有指定 use_1x1conv 和 strides 参数,它们默认为 False 和 1,
# 分别意味着不使用1x1卷积来改变维度或步长,且卷积操作的步长为1。
X = torch.rand(4, 3, 6, 6)
# 这行代码使用 torch.rand 函数生成一个随机张量 X,其形状为 (4, 3, 6, 6)。这个张量的维度意味着:
# 第一维度为4,表示批量大小(即有4个独立的数据样本)。
# 第二维度为3,表示每个样本有3个通道(例如RGB图像)。
# 第三和第四维度都是6,表示每个通道的空间维度(6x6像素)。
Y = blk(X)
# 通过在残差块 blk 中传递张量 X 来计算其输出 Y。
# 在残差块内部,数据 X 将通过两个卷积层、两个批量归一化层和ReLU激活函数进行处理,如果有需要,还可能通过一个1x1卷积来调整维度。最后,通过残差连接将输入加到输出上,然后再次应用ReLU激活函数。
Y.shape

请添加图片描述
我们也可以在增加输出通道数的同时,减半输出的高和宽。

blk = Residual(3,6, use_1x1conv=True, strides=2)
# 创建了一个 Residual 类的实例,名为 blk。与之前的示例相比,此处有以下不同的参数设置:
# input_channels=3:输入通道数为3。
# num_channels=6:输出通道数设置为6,即该残差块会将输出的通道数从3增加到6。
# use_1x1conv=True:这意味着将使用一个1x1的卷积层 conv3。这个卷积层不仅用于调整通道数量(从3到6),同时也会使用 strides 参数指定的步长。
# strides=2:在主卷积层 conv1 和1x1卷积层 conv3 中使用步长2,这将导致输出的空间尺寸(高度和宽度)减半。
blk(X).shape

请添加图片描述

ResNet模型

ResNet的前两层跟之前介绍的GoogLeNet中的一样: 在输出通道数为 64 64 64、步幅为 2 2 2 7 × 7 7\times 7 7×7卷积层后,接步幅为 2 2 2 3 × 3 3\times 3 3×3的最大池化层。 不同之处在于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 4 4个由Inception块组成的模块。 ResNet则使用 4 4 4个由残差块组成的模块每个模块使用若干个同样输出通道数的残差块。 第一个模块的通道数同输入通道数一致。 由于之前已经使用了步幅为 2 2 2的最大池化层,所以无须减小高和宽。 之后的每个模块在第一个残差块里将上一个模块的通道数翻倍,并将高和宽减半。

下面我们来实现这个模块。注意,我们对第一个模块做了特别处理。

# 这段代码定义了一个名为 resnet_block 的函数,用于创建一个残差块(或多个残差块)的列表,这是在构建深度残差网络(ResNet)时的典型用法
def resnet_block(input_channels, num_channels, num_residuals, first_block=False):
# 这是函数的定义行,其中 resnet_block 接受四个参数:
# input_channels:输入通道数。
# num_channels:每个残差块输出的通道数。
# num_residuals:在这个块中要创建的残差单元的数量。
# first_block:一个布尔值,默认为 False,用来指示这是否是网络的第一个块。这很重要,因为第一个块的处理通常与其他块有所不同,以适应网络输入特性。blk = []# 初始化一个空列表 blk,用于存储接下来创建的所有残差单元。for i in range(num_residuals):# 开始一个循环,迭代次数由 num_residuals 决定,即这个块中将创建的残差单元数量。if i == 0 and not first_block:# 在每次迭代中,首先检查当前是否是第一个残差单元 (i == 0) 并且这不是整个网络的第一个块 (not first_block)。
# 如果这两个条件同时满足,意味着需要对输入进行下采样,通常是通过改变通道数和减小空间维度来实现。blk.append(Residual(input_channels, num_channels,use_1x1conv=True, strides=2))# 如果满足上述条件,这行代码将创建一个新的 Residual 对象,这个对象使用1x1的卷积(use_1x1conv=True)和步长为2(strides=2),这样可以在第一个单元中改变通道数并减半空间维度,添加到列表 blk 中。else:blk.append(Residual(num_channels, num_channels))# 对于块中的其余残差单元,或者如果这是第一个块,这行代码创建一个标准的残差单元,其中输入和输出通道数相同,且不改变空间维度。这些单元也被添加到列表 blk 中。return blk# 函数返回列表 blk,其中包含了所有创建的残差单元。# 这个列表可以被整合到更大的网络结构中,作为构建复杂ResNet架构的一部分。# 总体上,resnet_block 函数允许灵活地创建具有或不具有下采样的残差块,这对于构建深度残差网络中不同的层级结构是非常有用的。

接着在ResNet加入所有残差块,这里每个模块使用2个残差块。

# 这几行代码演示了如何使用前面定义的 resnet_block 函数来构建深度残差网络(ResNet)的几个不同的块
# 这些块是按顺序连接起来的,每个块包含多个残差单元。让我们逐行解析:
b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
# resnet_block(64, 64, 2, first_block=True) 调用 resnet_block 函数,生成两个残差单元,用于处理64个输入通道和64个输出通道的数据。因为这是第一个块,first_block 设置为 True,这意味着即使这是第一个块,也不会进行下采样,步长和通道数都保持不变。
# * 操作符用于解包函数返回的列表,使其成为 nn.Sequential 构造函数的参数。
# b2 是一个 nn.Sequential 容器,按顺序包含了这些残差单元。
b3 = nn.Sequential(*resnet_block(64, 128, 2))
# resnet_block(64, 128, 2) 生成两个残差单元,其中第一个单元将使用1x1卷积和步长为2进行下采样,输入通道从64增加到128,输出也是128通道。
# 这里,第一个残差单元特别重要,因为它处理了通道数的增加和尺寸的减半,这对于逐渐增加网络的深度和学习更复杂的特征至关重要。
b4 = nn.Sequential(*resnet_block(128, 256, 2))
# resnet_block(128, 256, 2) 生成两个残差单元,首个单元将通道数从128增加到256,并且通过步长为2的1x1卷积下采样,减小空间尺寸。
b5 = nn.Sequential(*resnet_block(256, 512, 2))
# resnet_block(256, 512, 2) 在这个函数调用中,首个残差单元将输入通道从256增加到512,同时步长为2的1x1卷积再次下采样。
# b5 继续这一趋势,为最终层级的特征提供足够的抽象能力。# 这一系列的残差块构建是为了逐步深化网络,每个块通过增加通道数和减少尺寸来增加其能力处理更抽象的概念
# 这样的结构使得ResNet能够在处理深度神经网络时保持较好的训练效果,防止梯度消失问题。

最后,与GoogLeNet一样,在ResNet中加入全局平均池化层,以及全连接层输出。

net = nn.Sequential(b1, b2, b3, b4, b5,nn.AdaptiveAvgPool2d((1,1)),nn.Flatten(), nn.Linear(512, 10))

每个模块有4个卷积层(不包括恒等映射的 1 × 1 1\times 1 1×1卷积层)。 加上第一个 7 × 7 7\times 7 7×7卷积层和最后一个全连接层,共有18层。 因此,这种模型通常被称为ResNet-18。 通过配置不同的通道数和模块里的残差块数可以得到不同的ResNet模型,例如更深的含152层的ResNet-152。 虽然ResNet的主体架构跟GoogLeNet类似,但ResNet架构更简单,修改也更方便。这些因素都导致了ResNet迅速被广泛使用。 图7.6.4描述了完整的ResNet-18。
请添加图片描述
在训练ResNet之前,让我们观察一下ResNet中不同模块的输入形状是如何变化的。 在之前所有架构中,分辨率降低,通道数量增加,直到全局平均池化层聚集所有特征。

X = torch.rand(size=(1, 1, 224, 224))
for layer in net:X = layer(X)print(layer.__class__.__name__,'output shape:\t', X.shape)

请添加图片描述
通道数加倍,高宽减半

训练模型

同之前一样,我们在Fashion-MNIST数据集上训练ResNet。

lr, num_epochs, batch_size = 0.05, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())

请添加图片描述

小结

  • 学习嵌套函数(nested function)是训练神经网络的理想情况。在深层神经网络中,学习另一层作为恒等映射(identity function)较容易(尽管这是一个极端情况)。

  • 残差映射可以更容易地学习同一函数,例如将权重层中的参数近似为零

  • 利用残差块(residual blocks)可以训练出一个有效的深层神经网络:输入可以通过层间的残余连接更快地向前传播

  • 残差网络(ResNet)对随后的深层神经网络设计产生了深远影响。残差连接,不仅在ResNet被使用,几乎现在所有新的网络都在使用残差连接
    请添加图片描述
    为什么可以训练很深的网络?
    因为我使用残差连接的思想,加深层数,它总是能包含我之前的网络

“残差”的概念体现在什么地方呢?
f ( x ) = x + g ( x ) f(x)=x+g(x) f(x)=x+g(x)
我在fit这个函数的时候,假设这个 x x x是一个小模型的输出,那么首先说我会fit x x x里面的那个小模型,如果这个小模型fit差不多之后(底层train的差不多了),然后把 g ( x ) g(x) g(x)能够给我improve的那些东西,那些残差。
请添加图片描述
假设我要训练这样的曲线
请添加图片描述
我的一个做法是我先去训练一个平滑的东西(蓝色线),剩下的那些误差,layer2在layer1的基础上叠加做出来的效果。
请添加图片描述ResNet先会训练一些基础的比较下层的

《在线胡讲深度残差网络ResNet》笔记

对神经网络来说,隐藏层越多,模型越深,它应该效果更好才对?
理论上来说,堆叠神经网络的层数应该可以提升模型的精度,但是现实中真的是这样吗?
请添加图片描述
实验数据证明,一开始随着模型层数的增加,模型的精度会达到饱和,如果再增加网络的层数的话,它就会开始退化
从实验数据可以看到,在训练轮次相同的情况下,56层的网络误差居然比20层的网络误差还要高。这个现象是由于深层网络训练难度太高导致的,我们给这个现象起名叫退化,这个现象经常被和过拟合搞混淆,但是过拟合其实是会让训练误差变得越来越小,而测试误差变高,退化则是让训练误差和测试误差都变高。
与此同时,深度神经网络还有一个难题,我们以一个最简单的神经网络为例:
请添加图片描述
在反向传播的过程中,我们可以推导出每一层的误差项,都依赖于它后面一层的误差项,在层数很多的情况下,我们很难保证每一层的权值和梯度的大小,举一个最经典的例子,如果我们使用sigmoid函数作为激活函数,它的导数的最大值只有0.25,梯度在传播的过程中越来越趋近于0,误差就没有办法传播到底层的参数了,这就是梯度消失。虽然批量规范化和层规范化可以缓解梯度消失的问题,但是我们有没有什么办法,既可以解决退化的问题,又能顺便给梯度开个后门呢?

我们先来想一想,为什么深层神经网络会出现退化的问题呢?
假设我们的神经网络在层数为 l l l的时候达到了最优的效果,这个时候我们把这个网络构建的更深,那么第 l l l层之后的每一层,理论上来说应该是个恒等映射,但是呢拟合一个恒等映射是很难的,所以我们可不可以换个思路?
请添加图片描述
请添加图片描述

如果我们用 H ( x ) H(x) H(x)来表示我们想让这个神经网络学到的映射,用 x x x来表示我们已经学到的内容,那么现在我们可不可以让我们的神经网络去拟合 H ( x ) H(x) H(x) x x x之间的残差呢?

也就是说,如果我们选择优化的不是 H ( x ) H(x) H(x),而是把 H ( x ) H(x) H(x)拆分为 x x x H ( x ) − x H(x)-x H(x)x两个部分,我们选择去优化 H ( x ) − x H(x)-x H(x)x,我们给这个残差取名叫 F ( x ) F(x) F(x) F ( x ) F(x) F(x)通常包含着卷积和激活之类的操作,我们把 F ( x ) F(x) F(x) x x x相加之后,仍然能得到我们想要的 H ( x ) H(x) H(x),我们把这样从输入额外连一条线到输出来表示,将输入输出相加的操作叫做skip connection,如果让 F ( x ) F(x) F(x)趋近于0,那么就相当于我们构造了一个恒等映射。

那为什么这种方法可以有效解决退化和梯度消失的问题呢?
请添加图片描述

我们假设第 l l l层的输入是 X l X_{l} Xl,那它这一层的输出就是 F X l + X l F_{X_{l}}+X_{l} FXl+Xl,同时,它也是第 l + 1 l+1 l+1层的输入 X l + 1 X_{l+1} Xl+1,那我们现在可以根据这个规律,去推导一下第 l + 2 l+2 l+2层的输入,到了这一步我们是不是就不难发现,我们可以得到任意一个更深的层数 L L L和一个更浅的层数 l l l之间的关系的表达式。首先,是任意一层的输入 x L x_{L} xL,可以写成比他更浅的任意一层的输入 x l x_{l} xl和两层之间的所有残差的和,我们这样是不是可以初步推测出,和普通的神经网络相比,残差网络在前向传播时候,可以让任意低层的信息更容易传播到高层,根据这个式子,我们也可以推导出损失函数关于 X L X_{L} XL的梯度,我们从这里可以发现,损失函数关于 X L X_{L} XL的梯度,可以直接传播到任意一个更浅的层,后面的这一坨不可能一直等于 − 1 -1 1,也就是说,残差网络中不会出现梯度消失的问题。作者何恺明的观点是这样的属性让残差网络无论是正向传播还是反向传播,都可以将信号直接传播到任意一层。
请添加图片描述
在没有skip connection的情况下,网络越深,损失函数的非凸性就越强,在求梯度的时候,我们还是更喜欢凸函数,函数的非凸性越强,更难找到全局最优解。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【虚拟化】KVM使用virt-manager部署及管理虚拟机

目录 一、KVM 概述 二、KVM工作原理 三、部署KVM 四、新建虚拟机步骤 4.1 创建存储池并创建存储卷 4.1.1 创建存储池 4.1.2 创建存储卷 4.3 创建ISO存储池 4.4 生成新的虚拟机 一、KVM 概述 KVM 是 Kernel-based Virtual Machine 的缩写,是一种用于虚拟化的…

大模型微调深入研究

在本博文系列的前一部分中,我们探讨了情境学习的概念,这是一种克服大型语言模型 (LLM) 的“舒适区”限制的强大方法。我们讨论了如何使用这些技术来转换任务并将其移回模型的专业领域,从而提高性能并与有用性、诚实性和无害性的关键设计原则保…

WebBench源码分析

WebBench 源码解析 一、前言 WebBench 作为一款网站性能测试工具,其源码蕴含着丰富的技术细节和逻辑流程。本文将深入剖析其安装编译过程以及关键函数的核心逻辑。 二、安装编译 1. 克隆代码到本地仓库 git clone https://github.com/EZLippi/WebBench.git2. 编…

使用 Squid 搭建 Http 代理服务器隐藏 IP

在一些情况下,需要变更自己的访问 IP,可以通过 Squid 搭建代理服务器实现。 本文使用的是 CentOS 7.6 系统。 一、部署 Squid 安装 Squid。 yum install squid -y启动服 systemctl start squid二、访问控制 总有刁民想害郑,疯狂访问朕的…

基于宝塔面板稳定快速安装 ssl 证书脚本

背景 我通过AI制作了不少关于签发ssl证书的脚本,目的是方便无脑安装,不需要懂代码。 但全都是基于acme.sh这个工具来设计的脚本,而且证书申请有点慢,有时还会申请失败。 然后我发现了certbot, 安装证书可谓神速! c…

ASP.NET Core基础 - 简介

目录 一. 简介 A、跨平台性 B、高性能 C、开源性 D、模块化与可扩展性 E、集成现代前端技术 二. ASP.NET 4.x 和 ASP.NET Core 比较 A、架构与平台支持 B、性能 C、开发体验 D、社区支持与生态系统 三. NET 与 .NET Framework 比较 A、概念范围 B、跨平台能力 C…

基于JAVA的高考智能排考场系统设计与实现,源码、部署+讲解

绪 论 随着教育规模的不断扩大和技术的进步,传统的考试管理方式面临着诸多挑战,如考试安排的复杂性、作弊现象的频发以及考试过程中的监督和管理等问题。因此,针对这些挑战,智能排考系统应运而生。 智能排考系统利用先进的技术…

数据结构(邓俊辉)学习笔记】词典 03—— 排解冲突(1)

文章目录 1. 一山二虎2. 泾渭分明3. 开放定址4. 线性试探5. 赖惰删除 1. 一山二虎 此前我们已经多次指出,对于需要动态维护的散列表冲突是不可避免的,无论你的散列函数设计的有多么精妙,因此我们不得不回答的第二个重要问题就是一旦发生冲突&…

零售EDI:OBI欧倍德EDI项目案例

OBI欧倍德公司是德国建材和家居装饰零售连锁店,在德国以及其他欧洲国家拥有众多分店,是欧洲领先的DIY(Do It Yourself)零售商之一。为了更好地处理与全球供应商之间的业务数据往来,OBI采用EDI提高其供应链的自动化水平…

基于微信小程序的宠物服务平台(系统源码+lw+部署文档+讲解等)

文章目录 目录 详细视频演示 系统详细设计截图 微信小程序系统的实现 1.1系统前台功能的实现 2.1微信小程序开发环境搭建 2.2微信开发者工具 2.3程序应用相关技术和知识 2.3.1小程序目录结构以及框架介绍 2.3.2 Java技术 2.3.3 MySQL数据库 2.3.4 SSM框架 源码获…

Pygame制作简单的跑酷游戏

今天我们来看看如何使用Pygame框架制作一个简单的跑酷游戏。这个游戏包含了基本的游戏元素,如玩家角色、障碍物、背景、音效等,可以作为入门Pygame游戏开发的一个不错的示例。 游戏概述 这是一个简单的横版跑酷游戏,玩家控制一个忍者角色,通过跳跃来躲避迎面而来的各种障碍物…

【研发日记】嵌入式处理器技能解锁(二)——TI C2000 DSP的SCI(串口)通信

文章目录 前言 背景介绍 SCI通信 Transmitter Receiver SCI中断 分析和应用 总结 参考资料 前言 见《【研发日记】嵌入式处理器技能解锁(一)——多任务异步执行调度的三种方法》 背景介绍 近期使用TI C2000 DSP做的一个嵌入式系统开发项目中,在使用它的SCI&…

Pytorch系列-张量的类型转换

🌈个人主页:羽晨同学 💫个人格言:“成为自己未来的主人~” 张量转换为NumPy数组 使用Tensor.numpy()函数可以将张量转换为ndarray数组 # 1.将张量转换为numpy数组 data_tensortorch.tensor([2,3,4]) # 使用张量对象中的numpy函数进行转…

c++STL中list介绍,模拟实现和list与vector对比

目录 前言 : 1. list的介绍及使用 1.1list的介绍 1.2 list的使用 1.2.1 list的构造 1.2.2 list iterator的使用 1.2.3 list capacity 1.2.4 list element access 1.2.5 list modifiers 1.2.6 list的迭代器失效 2. list的模拟实现 3. list与vector的对…

串行并行数据转换

前言 串行数据传输通常在数据传输距离较远时使用,而并行数据传输适用于短距离、高速数据交换。通过转换,可以根据实际需求选择合适的传输方式,以优化数据传输效率和速度。串行数据传输在长距离传输中可以减少信号的干扰和失真,因为…

springboot整合libreoffice(两种方式,使用本地和远程的libreoffice);docker中同时部署应用和libreoffice

一、 背景 因为项目中需要使用word转pdf功能,因为转换速度原因,最后选用了libreoffice,原因及部署请参考 linux ubuntu环境安装libreoffice,word转pdf 远程调用的话可选docker部署,请看2.3.1 二、springboot整合libr…

达梦数据库的系统视图v$mem_pool

达梦数据库的系统视图v$mem_pool 达梦数据库的V$MEM_POOL视图主要用于显示所有内存池的信息。通过查询这个视图,用户可以监控数据库中各个内存组件的使用状况,包括内存池的大小、使用情况等。这有助于用户判断内存池是否空闲或紧张,从而进行…

【机器人学】6-4.六自由度机器人运动学参数辨识-机器人精度验证【附MATLAB代码】

前言 前两个章节以及完成了机器人参数辨识。 【机器人学】6-1.六自由度机器人运动学参数辨识-辨识数学模型的建立 【机器人学】6-2.六自由度机器人运动学参数辨识-优化方法求解辨识参数 标定了工具端、基座以及机器人本身的DH参数。那么我们的机器人精度如何呢?机…

Unity射击游戏开发教程:(31)制造一定追踪行为的敌人

在本文中,我们将介绍如何在两种敌人行为之间切换。本文是前两篇文章的延续,分别介绍了敌人躲避玩家射击以及敌人不断旋转并向玩家射击的情况。我只是介绍如何在这两种行为之间进行转换。 这种新的敌人行为的目标: 当不开火时,敌人可以躲避玩家的射击。射击时,敌人无法躲避…

谷粒商城实战笔记-137-商城业务-首页-整合dev-tools渲染一级分类数据

文章目录 一,使用热加载工具spring-boot-devtools1,引入devtools依赖2,ctrlshiftf9 编译静态资源 二,thymeleaf原理三,渲染一级分类 一,使用热加载工具spring-boot-devtools 因为我们采用的前后端一体的开…