一文读懂现代卷积神经网络—稠密连接网络(DenseNet)

目录

什么是 DenseNet?

 稠密块(Dense Block)详解

一、稠密块的核心思想

二、稠密块的结构组成

1. 卷积单元(的结构)

2. 密集连接的具体方式

3. 关键参数:增长率(Growth Rate, k)

4. 过渡层(Transition Layer)

三、稠密块的优势

四、与 ResNet 残差块的对比

过渡层(Transition Layer)详解

一、什么是过渡层?

二、过渡层的核心作用

三、过渡层的典型结构

四、设计细节与参数

五、与 ResNet 中对应组件的对比

DenseNet 的作用

DenseNet 与 ResNet 的核心区别

ResNet和DenseNet的结构示意图

为啥卷积块中采用 “批量归一化(BN)→ 激活函数 → 卷积层” 的顺序?

1. 批量归一化(BN)放在最前:从根源稳定输入分布

2. 激活函数放在 BN 之后:让激活更 “有效”

3. 卷积层放在最后:利用稳定输入提升参数学习效率

 完整代码

实验结果


什么是 DenseNet?

稠密连接网络(Dense Convolutional Network,简称 DenseNet)是 2017 年由 Huang 等人提出的一种深层卷积神经网络,其核心创新是“稠密连接(Dense Connection)”:网络中的每个层都会与前面所有层直接连接,即第l层的输入是前l-1层的输出的拼接(而非简单相加)。

核心结构

  • 稠密块(Dense Block):由多个卷积层组成,层间通过稠密连接融合特征。设第i层的输出为x_i,则第l层的输入为前所有层输出的拼接:x_l = H_l([x_0, x_1, ..., x_{l-1}])其中[x_0, ..., x_{l-1}]表示通道维度上的拼接,H_l是第l层的卷积操作(含 BN、ReLU、卷积核)。

  • 过渡层(Transition Layer):用于连接不同的稠密块,通过 1×1 卷积降维和 2×2 平均池化减小特征图尺寸,防止网络参数爆炸。

  • 稠密块(dense block)和过渡层(transition layer)。 前者定义如何连接输入和输出,而后者则控制通道数量,使其不会太复杂。

 稠密块(Dense Block)详解

稠密块(Dense Block)是深度学习网络 DenseNet(Densely Connected Convolutional Networks) 的核心组成单元,其设计核心是通过密集连接(Dense Connection) 实现极致的特征重用,是对传统卷积网络和 ResNet 中 “跳跃连接” 的进一步升级。

一、稠密块的核心思想

传统卷积网络中,每一层的输入仅来自上一层;ResNet 通过 “跳跃连接” 让层的输入包含上一层的输出(元素相加);而稠密块则更进一步 ——每一层的输入是前面所有层的输出的拼接(Concatenation)

具体来说,若稠密块包含 L 层,第 l 层(记为H_l)的输入是第 0,1,...,l-1 层的输出的拼接,输出为 x_l = H_l([x_0, x_1, ..., x_{l-1}]),其中:

  • x_0 是稠密块的初始输入特征图;
  • [x_0, x_1, ..., x_{l-1}] 表示将这些特征图在通道维度上拼接;
  • H_l是第 l 层的卷积操作(通常包含 BN、ReLU、卷积等子操作)。

这种 “密集连接” 使得特征在网络中能够被充分传递和复用,从根本上解决了深层网络的 “特征衰减” 问题。

二、稠密块的结构组成

一个典型的稠密块由多个 “卷积单元” 和 “密集连接” 组成,同时为了控制计算量,通常会包含瓶颈层(Bottleneck) 和过渡层(Transition Layer)(过渡层用于连接不同稠密块,非稠密块内部结构,但需结合理解)。

1. 卷积单元(H_l的结构)

稠密块中的每一层 \(H_l\) 通常由以下子操作组成(顺序固定):

  • BN(Batch Normalization):标准化特征,加速训练;
  • ReLU:非线性激活,增强表达能力;
  • 1x1 卷积(可选,瓶颈层):减少输入特征的通道数(如将通道数压缩至原来的 1/4),降低计算量;
  • 3x3 卷积:提取局部特征,输出固定数量的特征图(由 “增长率” 决定)。

其中,1x1 卷积作为 “瓶颈层” 是 DenseNet 的重要优化:若直接对拼接后的高维特征做 3x3 卷积,计算量会爆炸;而 1x1 卷积可先将通道数降至较低维度(如 4k,k 为增长率),再用 3x3 卷积输出 k 个特征图,既保证特征提取效率,又控制了参数规模。

2. 密集连接的具体方式

假设稠密块的初始输入特征图为x_0(通道数为 k_0),每一层的输出特征图通道数为 k(即 “增长率”),则:

  • 第 1 层输入:x_0,输出:x_1 = H_1(x_0)(通道数 k);
  • 第 2 层输入:[x_0, x_1](通道数k_0 + k),输出:x_2 = H_2([x_0, x_1])(通道数 k);
  • 第 3 层输入:[x_0, x_1, x_2](通道数 k_0 + 2k),输出:x_3 = H_3([x_0, x_1, x_2])(通道数 k);
  • ...
  • 第 L 层输入:[x_0, x_1, ..., x_{L-1}](通道数 k_0 + (L-1)k),输出:x_L = H_L([x_0, ..., x_{L-1}])(通道数 k)。

最终,整个稠密块的输出是所有层输出的拼接:[x_0, x_1, ..., x_L](通道数 k_0 + Lk)。

3. 关键参数:增长率(Growth Rate, k)

增长率 k 是稠密块的核心参数,定义为每一层输出的特征图通道数。它控制了特征图的增长速度:

  • 若 k 较小(如 12、24),即使层数较多,拼接后的总通道数也不会过大,保证计算效率;
  • 实验表明,较小的 k(如 k=12)即可让 DenseNet 达到优异性能,说明密集连接对特征的利用率极高。
4. 过渡层(Transition Layer)

稠密块之间通过 “过渡层” 连接,作用是压缩特征图通道数并降低尺寸,避免网络冗余

  • 过渡层通常由 “BN + 1x1 卷积 + 2x2 平均池化” 组成;
  • 1x1 卷积将前一个稠密块的输出通道数压缩(如压缩至原来的 θ 倍,θ∈(0,1],称为 “压缩因子”);
  • 平均池化将特征图尺寸减半(如从 32x32 变为 16x16),控制网络深度和计算量。

三、稠密块的优势

  1. 极致的特征重用 每一层都能直接访问前面所有层的特征,特征在网络中被反复利用,避免了传统网络中 “特征随层数增加而衰减” 的问题。

  2. 缓解梯度消失 反向传播时,梯度可通过密集连接直接从深层传递到浅层,大幅缓解深层网络的梯度消失问题,使训练更深的网络成为可能。

  3. 参数效率更高 由于特征重用充分,DenseNet 无需像其他网络(如 ResNet)那样通过增加通道数提升性能,小增长率 k 即可实现高准确率,参数规模通常小于 ResNet。

  4. 正则化效果 密集连接增加了层之间的相互依赖,一定程度上减少了过拟合,提升模型泛化能力。

四、与 ResNet 残差块的对比

对比维度稠密块(Dense Block)残差块(Residual Block)
连接方式特征图拼接(Concatenation)特征图元素相加(Element-wise Add)
输入来源前面所有层的输出仅上一层的输出(跨层相加)
特征利用效率极高(所有历史特征直接参与当前层)较高(仅上一层特征与当前层特征融合)
特征维度变化随层数线性增长(由增长率 k 控制)基本不变(相加不改变通道数)
计算量控制依赖瓶颈层(1x1 卷积)和过渡层依赖残差连接的 “恒等映射”

过渡层(Transition Layer)详解

过渡层(Transition Layer)是稠密连接网络(DenseNet) 中的关键组件,主要用于连接相邻的稠密块(Dense Block),并实现特征图的降维和压缩,在保证网络效率的同时控制模型复杂度。以下从定义、作用、结构细节及设计意义展开详解:

一、什么是过渡层?

过渡层是 DenseNet 中位于两个稠密块之间的连接模块,其核心功能是对前一个稠密块输出的特征图进行处理,为下一个稠密块提供合适维度的输入。由于稠密块会通过密集连接生成大量特征图(如一个包含 12 层的稠密块可能输出数百个特征图),过渡层的作用类似于 “桥梁”,通过降维减少特征数量,避免网络参数和计算量过度膨胀。

二、过渡层的核心作用
  1. 特征降维 稠密块的输出特征图数量通常较多(例如,每个稠密块会累计生成 k \times L 个特征图,其中 k 是增长率,L 是块内层数)。过渡层通过卷积操作将特征图数量按比例压缩(通常压缩至原来的 \theta 倍,\theta称为压缩因子,一般取 0.5),减少后续计算量。

  2. 空间尺寸缩减 通过池化操作(通常是 2×2 的平均池化)将特征图的空间尺寸(高和宽)缩小一半,与传统卷积网络中 “下采样→增大感受野” 的设计思路一致,帮助网络捕捉更全局的特征。

  3. 特征融合与平滑 过渡层中的卷积操作(通常是 1×1 卷积)可以对稠密块输出的多通道特征进行融合,减少冗余信息,同时保持特征的连续性,为下一个稠密块的输入提供更精炼的特征。

三、过渡层的典型结构

过渡层的结构简洁,通常由两个操作组成,按顺序执行:

  1. 1×1 卷积

    • 作用:对输入特征图进行通道压缩(降维),并融合通道间的信息。
    • 细节:卷积核数量为前一个稠密块输出特征数的\theta倍(\theta \in (0,1]),当\theta=1时不压缩),步长为 1, padding 为 0。
    • 示例:若前一个稠密块输出 200 个特征图,\theta=0.5,则 1×1 卷积后输出 100 个特征图。
  2. 2×2 平均池化

    • 作用:将特征图的空间尺寸(如H \times W)缩减为 H/2 \times W/2,实现下采样。
    • 细节:池化核大小 2×2,步长 2,无 padding,确保尺寸减半。
四、设计细节与参数
  • 压缩因子\theta:是 DenseNet 的重要超参数,控制特征压缩比例。当 \theta < 1时,DenseNet 称为 “压缩 DenseNet”(如 DenseNet-C),若 \theta = 1则不压缩。实验中\theta =0.5 是常用设置,可在精度和效率间取得平衡。
  • 激活函数与归一化:过渡层的 1×1 卷积后通常会跟随批量归一化(BN)和 ReLU 激活函数,确保特征分布稳定并引入非线性。
五、与 ResNet 中对应组件的对比

在 ResNet 中,连接不同残差块的下采样通常通过 “stride=2 的 3×3 卷积” 或 “单独的池化层 + 卷积” 实现,目的是缩减尺寸但不刻意压缩通道数。而过渡层的核心差异在于:

  • 主动降维:通过 1×1 卷积和压缩因子主动减少通道数,更注重控制模型参数。
  • 与稠密块的配合:由于稠密块会累计大量特征,过渡层的降维是 DenseNet 控制复杂度的关键,而 ResNet 的残差块不会累计特征,因此无需专门的压缩机制。

DenseNet 的作用

  1. 缓解梯度消失问题 稠密连接让每个层都能直接接收前面所有层的梯度(反向传播时,梯度无需经过多层累积),确保深层网络的梯度能有效传递到浅层,解决了深层网络训练困难的问题。

  2. 促进特征复用 每个层的输入包含前面所有层的特征(而非仅前一层),特征在网络中被多次复用,减少了冗余特征的学习,提升了特征利用效率。例如,浅层的边缘特征和深层的语义特征可直接融合,增强模型表达能力。

  3. 减少参数数量 由于特征复用充分,DenseNet 无需像 ResNet 那样通过增加通道数来提升性能(通道数通常远小于 ResNet),因此参数总量更少(例如 DenseNet-121 的参数约 800 万,仅为 ResNet-50 的 1/3)。

  4. 抑制过拟合 特征的多次复用相当于给网络引入了 “正则化” 效果,尤其在小数据集上,过拟合风险更低,泛化能力更强。

DenseNet 与 ResNet 的核心区别

对比维度ResNet(残差网络)DenseNet(稠密连接网络)
连接方式每个层仅与前一层连接(“链式”):\(x_l = H_l(x_{l-1}) + x_{l-1}\)每个层与前面所有层连接(“稠密”):\(x_l = H_l([x_0, ..., x_{l-1}])\)
特征融合方式残差相加(元素级加法,要求通道数相同)特征拼接(通道级拼接,通道数随层数累积)
通道数变化随深度翻倍(如 64→128→256→512),通过升维提升能力单一层通道数固定(如 32),通过拼接累积总通道数(复用特征)
参数效率较低(通道数大,参数多)较高(通道数小,特征复用减少冗余参数)
计算复杂度中等(加法操作轻量,但通道数大)较高(拼接导致总通道数大,需通过过渡层控制)
梯度传播梯度通过残差连接间接传递(需经过前一层)梯度直接传递到所有浅层(无中间层阻碍)
适用场景超深网络(如 ResNet-152)、大数据集(需强表达能力)中小数据集(泛化能力强)、对参数敏感的场景

ResNet和DenseNet的结构示意图

为啥卷积块中采用 “批量归一化(BN)→ 激活函数 → 卷积层” 的顺序?

1. 批量归一化(BN)放在最前:从根源稳定输入分布

BN 的核心功能是标准化输入数据的分布(将每个通道的输入调整为均值 0、方差 1 的标准分布),从而解决 “内部协变量偏移”。

如果将 BN 放在卷积层之后(即 “卷积→BN→激活”),则 BN 只能标准化卷积层的输出;而放在卷积层之前(即 “BN→激活→卷积”),BN 可以直接标准化进入卷积层的原始输入,从更早期稳定数据分布,效果更彻底:

  • 卷积层的输入分布更稳定,参数更新时输出变化更平缓,训练更稳定;
  • 避免卷积层因输入分布剧烈波动而陷入 “参数震荡”(例如卷积核反复调整以适应突变的输入)。
2. 激活函数放在 BN 之后:让激活更 “有效”

激活函数(如 ReLU)的作用是引入非线性,但其效果高度依赖输入分布:

  • ReLU 对负数输入会 “截断”(输出 0),若输入分布不稳定(例如均值偏移到负数区域),会导致大量神经元 “死亡”(输出恒为 0);
  • BN 将输入标准化为 “均值 0、方差 1” 的分布后,ReLU 的输入会均匀分布在正负区间,既能保留足够多的正输入(激活有效神经元),又能通过负输入的截断引入非线性,避免激活函数失效。
3. 卷积层放在最后:利用稳定输入提升参数学习效率

卷积层是特征提取的核心,其参数学习(通过梯度下降更新 W 和 b)需要稳定的输入分布:

  • 经过 BN 标准化和激活函数非线性变换后,输入到卷积层的数据分布已非常稳定,卷积核可以更高效地学习 “有意义的特征模式”(例如边缘、纹理);
  • 若卷积层放在最前,其输出分布会因参数更新而剧烈变化,后续的 BN 和激活函数需要不断 “适配” 这种变化,降低训练效率。

先稳定分布,再引入非线性,最后高效提取特征

 完整代码

"""
文件名: 7.7  稠密连接网络(DenseNet)
作者: 墨尘
日期: 2025/7/14
项目名: dl_env
备注: 实现完整的DenseNet网络,包含稠密块、过渡层及端到端训练流程,用于Fashion-MNIST分类
"""import torch
from torch import nn
from d2l import torch as d2l
# 手动显示图像相关库
import matplotlib.pyplot as plt  # 绘图库
import matplotlib.text as text  # 用于修改文本绘制(解决符号显示问题)# -------------------------- 核心解决方案:解决文本显示问题 --------------------------
# 定义替换函数:将Unicode减号(U+2212,可能导致显示异常)替换为普通减号(-)
def replace_minus(s):"""解决Matplotlib中Unicode减号显示异常的问题参数:s: 待处理的字符串或其他类型对象返回:处理后的字符串或原始对象(非字符串类型)"""if isinstance(s, str):  # 判断输入是否为字符串return s.replace('\u2212', '-')  # 替换特殊减号为普通减号return s  # 非字符串直接返回# 重写matplotlib的Text类的set_text方法,解决减号显示异常
original_set_text = text.Text.set_text  # 保存原始的set_text方法
def new_set_text(self, s):"""重写后的文本设置方法,在设置文本前先处理减号显示问题"""s = replace_minus(s)  # 调用替换函数处理文本中的减号return original_set_text(self, s)  # 调用原始方法设置文本
text.Text.set_text = new_set_text  # 应用重写后的方法# -------------------------- 字体配置(确保中文和数学符号正常显示)--------------------------
plt.rcParams["font.family"] = ["SimHei"]  # 设置中文字体(支持中文显示)
plt.rcParams["text.usetex"] = True  # 使用LaTeX渲染文本(提升数学符号显示效果)
plt.rcParams["axes.unicode_minus"] = True  # 确保负号正确显示(避免显示为方块)
plt.rcParams["mathtext.fontset"] = "cm"  # 设置数学符号字体为Computer Modern(更美观)
d2l.plt.rcParams.update(plt.rcParams)  # 让d2l库的绘图工具继承上述字体配置# 定义卷积块:BN + ReLU + 3x3卷积(DenseNet的基础单元)
def conv_block(input_channels, num_channels):"""构建DenseNet中的基础卷积单元,实现特征提取参数:input_channels: 输入特征图的通道数num_channels: 输出特征图的通道数(即增长率的一部分)返回:nn.Sequential: 包含批量归一化、激活函数和卷积层的序列模块"""return nn.Sequential(nn.BatchNorm2d(input_channels),  # 批量归一化:稳定输入分布,加速训练nn.ReLU(),  # ReLU激活:引入非线性,增强特征表达能力# 3x3卷积:提取局部空间特征,padding=1确保输出尺寸与输入相同nn.Conv2d(input_channels, num_channels, kernel_size=3, padding=1))class DenseBlock(nn.Module):"""稠密块(DenseBlock):DenseNet的核心组件,通过密集连接实现特征复用参数:num_convs: 块内包含的卷积块数量input_channels: 初始输入通道数num_channels: 每个卷积块的输出通道数(即"增长率",控制特征增长速度)"""def __init__(self, num_convs, input_channels, num_channels):super(DenseBlock, self).__init__()layer = []# 逐个添加卷积块,每个卷积块的输入通道数随层数递增for i in range(num_convs):# 第i个卷积块的输入通道数 = 初始通道数 + i×增长率# 例如:第0个卷积块输入=input_channels,第1个=input_channels+num_channels,以此类推layer.append(conv_block(input_channels + i * num_channels,  # 动态计算输入通道数num_channels  # 固定输出通道数(增长率)))self.net = nn.Sequential(*layer)  # 将所有卷积块组合成序列def forward(self, X):"""前向传播:通过密集连接拼接所有卷积块的输出参数:X: 输入特征图,形状为(batch_size, input_channels, height, width)返回:拼接后的特征图,形状为(batch_size, final_channels, height, width)其中final_channels = input_channels + num_convs×num_channels"""for blk in self.net:Y = blk(X)  # 当前卷积块的输出# 在通道维度(dim=1)上拼接原始输入X和当前输出Y(密集连接的核心)X = torch.cat((X, Y), dim=1)return X# 过渡层:连接两个稠密块,实现特征降维和尺寸缩减
def transition_block(input_channels, num_channels):"""过渡层:压缩特征通道数并减小空间尺寸,避免网络参数爆炸参数:input_channels: 输入特征图的通道数(前一个稠密块的输出)num_channels: 输出特征图的通道数(通常为输入的1/2,即压缩因子0.5)返回:nn.Sequential: 包含批量归一化、激活、卷积和池化的序列模块"""return nn.Sequential(nn.BatchNorm2d(input_channels),  # 批量归一化:稳定过渡层输入分布nn.ReLU(),  # 激活函数:引入非线性# 1x1卷积:减少通道数(核心降维操作,参数少且计算高效)nn.Conv2d(input_channels, num_channels, kernel_size=1),# 2x2平均池化:将空间尺寸减半(height和width各除以2)nn.AvgPool2d(kernel_size=2, stride=2))if __name__ == '__main__':# -------------------------- 测试核心组件功能 --------------------------# 测试1:稠密块(2个卷积块,输入3通道,增长率10)dense_blk = DenseBlock(num_convs=2, input_channels=3, num_channels=10)X = torch.randn(4, 3, 8, 8)  # 随机输入:4个样本,3通道,8x8尺寸Y = dense_blk(X)print(f"稠密块输出形状: {Y.shape}")  # 预期:(4, 3+2×10=23, 8, 8)# 测试2:过渡层(输入23通道,输出10通道)trans_blk = transition_block(input_channels=23, num_channels=10)Z = trans_blk(Y)print(f"过渡层输出形状: {Z.shape}")  # 预期:(4, 10, 4, 4)(尺寸减半)# -------------------------- 构建完整DenseNet网络 --------------------------# 第一个模块:初始卷积+池化(预处理输入图像)b1 = nn.Sequential(# 7x7大卷积:初步提取全局特征,步长2压缩尺寸nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),nn.BatchNorm2d(64),  # 批量归一化nn.ReLU(),  # 激活# 3x3最大池化:进一步将尺寸减半(为后续稠密块做准备)nn.MaxPool2d(kernel_size=3, stride=2, padding=1))# 配置稠密块参数num_channels = 64  # 初始通道数(与b1输出通道一致)growth_rate = 32  # 增长率:每个卷积块的输出通道数(控制特征增长速度)num_convs_in_dense_blocks = [4, 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  # 更新通道数为过渡层输出# 组装完整网络net = nn.Sequential(b1,  # 初始模块*blks,  # 所有稠密块和过渡层nn.BatchNorm2d(num_channels),  # 最终批量归一化nn.ReLU(),  # 激活nn.AdaptiveAvgPool2d((1, 1)),  # 全局平均池化:将特征图压缩为1x1nn.Flatten(),  # 展平为一维向量nn.Linear(num_channels, 10)  # 全连接层:输出10类(Fashion-MNIST))# -------------------------- 训练DenseNet模型 --------------------------# 训练参数lr = 0.1  # 学习率(DenseNet对学习率较鲁棒)num_epochs = 10  # 训练轮数batch_size = 256  # 批量大小# 加载Fashion-MNIST数据集(调整图像大小为96x96适配网络)train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)# 启动训练(使用d2l库的训练函数,自动支持GPU)print("\n开始训练DenseNet模型...")d2l.train_ch6(net, train_iter, test_iter,num_epochs, lr,device=d2l.try_gpu()  # 自动选择GPU(如有))# 显示训练曲线(损失+准确率)plt.show(block=True)

实验结果

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

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

相关文章

关于僵尸进程

深入理解僵尸进程&#xff1a;成因、危害与解决方案 进程终止的条件 我们先了解一下进程销毁的条件&#xff1a; 调用了exit函数在main函数中执行了return语句 无论采用哪种方式&#xff0c;都会有一个返回值&#xff0c;这个返回值由操作系统传递给该进程的父进程。操作系统不…

深入解析进程、线程与协程:现代并发编程的三大支柱

深入解析进程、线程与协程&#xff1a;现代并发编程的三大支柱在计算资源日益丰富的时代&#xff0c;理解并发执行机制已成为每位开发者的必修课。本文将带你深入探索操作系统中的三大并发模型&#xff1a;进程、线程与协程&#xff0c;揭开它们的神秘面纱。引言&#xff1a;并…

奇安信下一代防火墙SecGate3600

一、实验拓扑&#xff1a;二、实验目的&#xff08;1&#xff09;让内网可以访问外网。&#xff08;2&#xff09;让外网能够访问dmz区域的web服务器。&#xff08;3&#xff09;测试防火墙的防毒功能&#xff0c;并进行检测。三、实验步骤&#xff08;1&#xff09;防火墙配置…

基于STM32的智能抽水灌溉系统设计(蓝牙版)

✌️✌️大家好&#xff0c;这里是5132单片机毕设设计项目分享&#xff0c;今天给大家分享的是基于《基于STM32的智能抽水灌溉系统设计》。 目录 1、系统功能 2.1、硬件清单 2.2、功能介绍 2.3、控制模式 2、演示视频和实物 3、系统设计框图 4、软件设计流程图 5、原理…

CISSP知识点汇总- 通信与网络安全

CISSP知识点汇总 域1---安全与风险管理域2---资产安全域3---安全工程域4---通信与网络安全域5---访问控制域6---安全评估与测试域7---安全运营域8---应用安全开发一、安全网络架构和保护网络组件 1、OSI 7层协议模型 应用层:SMTP、HTTP、SNMP 、TELNET、 FTP、SFTP、POP3、IM…

C++怎么将可变参数传递给第三方可变参数接口

文章目录&#x1f527; 1. 使用 va_list 转发&#xff08;兼容C/C的传统方案&#xff09;⚙️ 2. 模板参数包转发&#xff08;C11 类型安全方案&#xff09;&#x1f9e9; 3. 替代方案&#xff1a;参数封装与适配**方案A&#xff1a;使用 std::initializer_list (同类型参数)**…

服务端实现阿里云OSS直传

介绍 阿里云上传 OSS 有两种方式&#xff0c;一种是普通上传&#xff0c;一种是客户端直传。 普通上传&#xff0c;就是需要先将文件上传到服务端&#xff0c;然后调用接口将文件上传到阿里云。 当然这种方案经常出现不合理的使用方式&#xff0c;即客户端充当服务端的角色&…

on-policy和offpolicy算法

一句话总结On-policy&#xff08;同策略&#xff09;&#xff1a;边学边用&#xff0c;用当前策略生成的数据更新当前策略。例子&#xff1a;演员自己演完一场戏后&#xff0c;根据观众反馈改进演技。Off-policy&#xff08;异策略&#xff09;&#xff1a;学用分离&#xff0c…

CA-IS3082W 隔离485 收发器芯片可能存在硬件BUG

RT&#xff0c;这个RS485 隔离收发器芯片基本上不可用。本来要买CA-IS3082WX&#xff0c;不小心在某宝买到了没有X 的CA-IS3082W。立创上说没有X 的版本已经停产&#xff0c;连对应的数据手册都找不到&#xff0c;全换成WX 了。 这类半双工485 收发器芯片电路一般都直接把DE 和…

dockerfile 笔记

# 设置JAVA版本 FROM openjdk:20-ea-17-jdk MAINTAINER aaa # 指定存储卷, 任何向/tmp写入的信息都不会记录到容器存储层 VOLUME /tmp # 拷贝运行JAR包 ARG JAR_FILE COPY app.jar /app.jar RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo "Asia/…

高德开放平台携手阿里云,面向开发者推出地图服务产品MCP Server

高德开放平台携手阿里云&#xff0c;面向开发者推出地图服务产品MCP Server&#xff0c;通过技术能力与生态资源的深度协同&#xff0c;助力开发者高效构建标准化地图服务&#xff0c;加速智能化场景落地。 高德开放平台携手阿里云&#xff0c;面向开发者推出MCP Server技术融合…

【论文阅读】AdaptThink: Reasoning Models Can Learn When to Think

AdaptThink: Reasoning Models Can Learn When to Think3 Motivation3.1 理论基础3.2 NoThinking在简单问题中的优势3.3 动机总结4. AdaptThink4.1 约束优化目标数学建模基本定义原始优化问题惩罚项转换归一化处理策略梯度实现优势函数定义PPO风格损失函数4.2 重要性采样策略问…

Redis高可用集群一主从复制概述

一、环境概述在分布式集群系统中为了解决服务单点故障问题&#xff0c;通常会把数据复制出多个副本部署到不同的机器中&#xff0c;满足故障恢复和负载均衡等需求。Redis也是如此&#xff0c;它为我们提供了复制功能&#xff0c;实现了相同数据的多个Redis副本。复制功能是高可…

Java 树形结构、层级结构数据构建

目录前言一、树状结构数据库存储二、工具类三、测试四、自定义树节点返回类型&#xff08;只保留部分字段&#xff09;1. 新增 TreeNodeDTO 类2.修改TreeUtil 类3.测试4.输出前言 有时候&#xff0c;开发过程中我们会遇到一些树状层级结构。 比如&#xff0c;公司部门组织架构…

求解线性规划模型最优解

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 既然选择了远方&#xff0c;当不负青春…

达梦国产数据库安装

打开ISO 、文件点击运行接受选择安装路径数据初始化 新数据库要创建数据库实例 选择一般用途数据库位置 选择所以系统用户&#xff0c;设置初始密码创建示例库可以选可以不选查找最近添加文件登录

互斥锁与同步锁

1. 锁的本质&#xff1a;解决并发问题的基石在多线程/多进程环境中&#xff0c;临界区&#xff08;Critical Section&#xff09; 是访问共享资源的代码段。锁的核心目标是确保互斥访问——任意时刻仅有一个执行单元能进入临界区。// 典型临界区示例 pthread_mutex_lock(&m…

高密度PCB板生产厂商深度解析

在电子制造领域&#xff0c;高密度PCB&#xff08;印制电路板&#xff09;作为核心基础元件&#xff0c;其技术精度与生产稳定性直接影响终端产品性能。本文精选五家具备核心技术优势的国内厂商&#xff0c;通过实地调研与行业数据验证&#xff0c;为读者呈现真实可信的供应商选…

力扣 hot100 Day44

98. 验证二叉搜索树 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左子树只包含 小于 当前节点的数。 节点的右子树只包含 大于 当前节点的数。 所有左子树和右子树自身必须也是二叉搜索树 //自…

【基础架构】——软件系统复杂度的来源(低成本、安全、规模)

目录 一、软件系统复杂度的来源之低成本二、软件系统复杂度的来源之安全2.1、功能安全2.2、架构安全2.3、规模2.3.1、功能越来越多,导致系统复杂度指数级上升2.3.2、数据越来越多,系统复杂度发生质变本文来源:极客时间vip课程笔记 一、软件系统复杂度的来源之低成本 当我们设…