论文阅读笔记:DepGraph: Towards Any Structural Pruning

论文阅读笔记:DepGraph: Towards Any Structural Pruning

  • 1 背景
  • 2 创新点
  • 3 方法
  • 4 模块
    • 4.1 分组
    • 4.2 依赖图
    • 4.3 网络分解
    • 4.4 依赖建模
    • 4.4 组级剪枝
  • 5 效果

论文:https://arxiv.org/pdf/2301.12900

代码:https://github.com/VainF/Torch-Pruning

1 背景

主流的剪枝方法可以被分为结构化剪枝和非结构化剪枝。两者的核心区别在于,结构化剪枝通过物理地移除分组参数来改变神经网络的结构,而非结构化剪枝在不修改网络结构的情况下,对部分权重进行归零。相比于非结构化剪枝,结构化剪枝不太以来特定的AI加速器或者软件来减少内存消耗和计算成本,从而在实际应用中找到更广泛的应用领域。

在这里插入图片描述

模型剪枝根据粒度的不同,至少可以粗分为4个粒度:

1.细粒度剪枝(fine-grained):即对连接或者神经元进行剪枝,它是粒度最小的剪枝。
2.向量剪枝(vector-level):它相对于细粒度剪枝粒度更大,属于对卷积核内部(intra-kernel)的剪枝。
3.核剪枝(kernel-level):即去除某个卷积核,它将丢弃对输入通道中对应计算通道的响应。
4.滤波器剪枝(Filter-level):对整个卷积核组进行剪枝,会造成推理过程中输出特征通道数的改变。

细粒度剪枝(fine-grained),向量剪枝(vector-level),核剪枝(kernel-level)方法在参数量与模型性能之间取得了一定的平衡,但是网络的拓扑结构本身发生了变化,需要专门的算法设计来支持这种稀疏的运算,被称之为非结构化剪枝。
而滤波器剪枝(Filter-level)只改变了网络中的滤波器组和特征通道数目,所获得的模型不需要专门的算法设计就能够运行,被称为结构化剪枝。除此之外还有对整个网络层的剪枝,它可以被看作是滤波器剪枝(Filter-level)的变种,即所有的滤波器都丢弃。

参考:

https://www.jianshu.com/p/8e1209d3127a

https://zhuanlan.zhihu.com/p/613899881

https://blog.csdn.net/baidu_38262850/article/details/125101878

然而,结构化剪枝本身的性质使其本身成为一项具有挑战性的任务,特别是对于具有耦合和复杂内部结构的现代深度神经网络。其原理在于,深度神经网络建立在卷积、归一化或激活等大量基本模块的基础上,而这些模块,无论参数化与否,都通过错综复杂的连接内在耦合。因此,即使试图从CNN中只删除一个通道,都必须同时照顾到它对所有层的相互依赖关系,否则最终会得到一个破碎的网络。准确地说,残差链接要求两个卷积层的输出共享相同的通道,从而迫使他们一起被剪枝。如图1所示,在其他架构,如transformers,RNNs和GNNs上也是如此。
在这里插入图片描述

现有的结构化剪枝方法在很大程度上依赖于逐个案例分析来处理网络中的依赖关系,尽管取得了较好的结构,但这种方式是费力且不具有推广性的。

在本文中,作者为任意结构化剪枝寻求了一个通用方案,其中任意网络架构上的剪枝都是自动执行的。方法的核心是估计依赖图(Dependency Graph, DepGraph),它显式地建模了神经网络中成对层之间的相互依赖关系。引入DepGraph进行结构化剪枝的动机源于观察到,一层的结构化剪枝有效地"触发"了相邻层的剪枝,这进一步导致了类似{ B N 2 ← C o n v 2 → B N 1 → C o n v 1 BN_2←Conv_2→BN_1→Conv_1 BN2Conv2BN1Conv1 }的链式效应,如图1 ( a )所示。

另外,值得注意的是,在结构化剪枝中,同时对分组层进行剪枝,期望同一组中所有被移除的参数始终保持不重要,但由于其他层参数的纠缠,单层中参数的重要性不会被正确的显示出来。为了解决这个问题,作者充分利用DepGraph提供的依赖建模的综合能力,设计了一个“组级别”的重要性准则,该准则学习组内一致的稀疏性,从而可以安全地删除那些零化的组,而不会带来太大的性能损失。

有很多剪枝方法的思路都是在指定范围内用某种方式表示权重,并将其进行从小到大的排序,将小于某一滤值的元素置为0,然后将是0的剪枝掉。

如用于结构化剪枝算法的L2Filter Pruner,使用L2范数来对权重矩阵进行排序,并删除排列最末的 k%。权重越稀疏,排名越靠后。

例如本文代码:

https://github.com/VainF/Torch-Pruning/blob/master/torch_pruning/pruner/importance.py#L194

local_imp = w.abs().pow(self.p).sum(1) # self.p = 2

https://github.com/VainF/Torch-Pruning/blob/master/torch_pruning/pruner/algorithms/metapruner.py#L532

_pruning_indices = (imp <= thres).nonzero().view(-1)
imp_argsort = torch.argsort(imp)
if len(_pruning_indices)>0 and self.round_to: 
    n_pruned = len(_pruning_indices)
    current_channels = get_channel_fn(module)
    n_pruned = self._round_to(n_pruned, current_channels, self.round_to)
    pruning_indices = imp_argsort[:n_pruned]
pruning_indices.append(_pruning_indices)

2 创新点

针对任何结构化剪枝的通用剪枝方案,称为依赖图( Dependency Graph,DepGraph ),它允许自动参数分组,并有效地提高了结构化剪枝在各种网络架构上的泛化性,包括CNN、RNN、GNN和Vision Transformers。

3 方法

在这里插入图片描述
本文工作专注于在参数依赖的限制下对任何神经网络进行结构化剪枝。

如图2(a)所示,从3个全连接层组成的神经网络开始,分别用二维权重矩阵 w l w_l wl w l + 1 w_{l+1} wl+1 w l + 2 w_{l+2} wl+2 进行参数化。这种简单的神经网络可以通过去除神经元进行结构修剪。在这种情况下,很容易发现参数之间存在某种依赖关系,记为 w l ⇔ w l + 1 w_l⇔w_{l+1} wlwl+1,这迫使 w l w_l wl w l + 1 w_{l+1} wl+1 同时被剪枝。具体来说,为了修剪连接 w l w_l wl w l + 1 w_{l+1} wl+1 的第 k k k 个神经元, w l [ k , : ] w_l[k,:] wl[k,:] w l + 1 [ : , k ] w_{l+1}[:,k] wl+1[:,k] 都将被移除。

如图2(b-d)所示,这种依赖关系有很多种,以逐个案例的方式人工分析所有依赖关系是很难的,更不用说简单的依赖关系可以嵌套或组合形成更复杂的模式。为了解决结构化剪枝中的依赖问题,本文引入了依赖图( Dependency Graph ),为依赖建模提供了一种通用的、全自动的机制。

4 模块

4.1 分组

为了实现结构化剪枝,首先需要根据层的相关性对层进行分组。目标是找到一个分组矩阵 G ∈ R L × L G∈R^{L×L} GRL×L,其中L表示待剪枝网络的层数, G i j = 1 G_{ij}=1 Gij=1 表示第 i i i 层和第 j j j 层之间存在依赖关系。为了方便起见,令 D i a g ( G ) = 1 1 × L Diag(G) = 1^{1×L} Diag(G)=11×L以实现自依赖。利用分组矩阵,可以很直观地找到相关性到第 i i i 层的所有耦合层,记为 g ( i ) g(i) g(i)
在这里插入图片描述
然而,由于现代深度网络可能由数以千计的具有复杂连接的层组成,从而产生了一个庞大而复杂的分组矩阵G。这个矩阵中, G i j G_{ij} Gij 不仅由第 i i i 层和第 j j j 层决定,还受到他们之间的中间层的影响。因此这种非局部和隐式的关系在大多数情况下不能用简单地规则来处理。为了克服这一挑战,作者不直接估计分组矩阵G,而是提出了一种等价但易于估计的依赖建模方法,即依赖图(Dependency graph)。从依赖图中高效的导出G。

4.2 依赖图

首先考虑一个群 g = { w 1 , w 2 , w 3 } g=\{w_1, w_2, w_3\} g={w1,w2,w3},它具有依赖关系 w 1 ⇔ w 2 , w 2 ⇔ w 3 , w 1 ⇔ w 3 w_1⇔w_2, w_2⇔w_3,w_1⇔w_3 w1w2,w2w3,w1w3。在仔细观察这种依赖关系建模后,可以发现存在一些冗余。哟如,从 w 1 ⇔ w 2 , w 2 ⇔ w 3 w_1⇔w_2, w_2⇔w_3 w1w2,w2w3可以通过递归过程导出依赖关系 w 1 ⇔ w 3 w_1⇔w_3 w1w3。首先以 w 1 w_1 w1 为起点,考察它对其它层的依赖性,例如 w 1 ⇔ w 2 w_1⇔w_2 w1w2。然后, w 2 w_2 w2 为递归扩展依赖关系提供了新的起点,依赖关系触发 w 2 ⇔ w 3 w_2⇔w_3 w2w3。这个递归过程最终以一个传递关系 w 1 ⇔ w 2 ⇔ w 3 w_1⇔w_2⇔w_3 w1w2w3结束。在这种情况下,只需要两个依赖关系来描述组g中的关系。同样地,分组矩阵G对于依赖建模也是冗余的,因此可以压缩为具有更少边数的更紧凑的形式,同时保留相同的信息。作者证明了一个新的度量相邻层之间局部相关性的图 D,称为依赖图,可以对分组矩阵G进行有效的约减。依赖图与G的不同之处在于,它只记录了具有直接连接的相邻层之间依赖关系。图D可以看成是图G的可迁约化,它包含了图G相同的顶点但具有尽可能少的边。形式上,构造D使得对所有 G i j = 1 G_{ij}=1 Gij=1 在D中存在一条顶点 i i i j j j 之间的路径。因此 G i j G_{ij} Gij 可以通过检查D中顶点 i i i j j j 之间是否存在一条路径得到。

4.3 网络分解

然而,作者在实际构建依赖图时发现可能存在问题,因为一些基本层(如全连接层)可能由两种不同的剪枝方案,如之前讨论的 w [ k , : ] w[k,:] w[k,:] w [ : , k ] w[:,k] w[:,k] ,他们分别压缩了输入和输出的维度。此外,网络还包括跳跃连接等非参数化操作,这也会影响层与层之间的依赖关系。为了解决这些问题,作者将网络 F ( x ; w ) F(x;w) F(x;w) 分解成更精细和更基本的组件,记为 F = { f 1 , f 2 , … , f L } F=\{f_1, f_2, …, f_L\} F={f1,f2,,fL} ,其中每个组件 f f f 指的是参数化的层(如卷积)或非参数化的操作(如残差相加)。取代在层级别上建模关系,作者专注于层的输入和输出之间的依赖关系。具体的,将分量 f i f_i fi 的输入和输出分别记为 f i − f_i^- fi f i + f_i^+ fi+。对于任意网络,最终的分解可以形式化为 F = f 1 − , f 1 + , … , f L − , f L + F={f_1^-,f_1^+,…,f_L^-,f_L^+} F=f1,f1+,,fL,fL+。这种表示方法有利于更容易的依赖建模,并且允许对同一层采用不同的剪枝方案。

4.4 依赖建模

将神经网络重新绘制为式(2),其中可以识别出两种主要类型的依赖关系,即层间依赖和层内依赖,如下图所示:
在这里插入图片描述#pic_center

其中,↔ 表示相邻两层之间的连通性,对这两种依赖关系的检验可以得到简单但通用的依赖关系建模规则:

(1)层间依赖:在连接层 f i − ↔ f j + f_i^-↔f_j^+ fifj+中一致产生一个依赖 f i − ⇔ f j + f_i^-⇔f_j^+ fifj+

(2)层内依赖:如果 f i − f_i^- fi f i + f_i^+ fi+具有相同的剪枝方案,则存在依赖 f i − ⇔ f i + f_i^-⇔f_i^+ fifi+ ,记为 s c h ( f i − ) = s c h ( f i + ) sch(f_i^-)=sch(f_i^+) sch(fi)=sch(fi+)

首先,如果已知网络的拓扑结构,可以很容易地估计层间依赖。对于 f i − ↔ f j + f_i^-↔f_j^+ fifj+ 的连接层,由于 f i − f_i^- fi f j + f_j+ fj+ 对应网络相同的中间特征,因此依赖关系是始终存在。

其次,层内依赖要求对单层的输入和和输出同时进行剪枝,许多网络层满足这个条件,例如BN层,其输入和输出共享相同的剪枝方案,记作 s c h ( f i − ) = s c h ( f i + ) sch(f_i^-)=sch(f_i^+) sch(fi)=sch(fi+) ,将同时进行剪枝,如图3所示。相反,卷积等层对其输入和输出具有不同的剪枝方案,如图3所示, w [ : , k , : , : ] ≠ w [ k , : , : , : ] w[:,k,:,:]≠w[k,:,:,:] w[:,k,:,:]=w[k,:,:,:] ,导致 s c h ( f i − ) ≠ s c h ( f i + ) sch(f_i^-)≠sch(f_i^+) sch(fi)=sch(fi+),在这种情况下,卷积层的输入和输出之间不存在依赖。
在这里插入图片描述

给定上述规则,可以形式的建立如下依赖建模:
在这里插入图片描述

其中, ∧ ∧ ∨ ∨ 表示逻辑与和或, 1 1 1 是一个条件成立则返回True的指示器函数。第一项考察由网络连接引起的层间依赖,而第二项考察由层输入和输出之间的共享剪枝方案引入的层内依赖。值得注意的是,DepGraph 是一个对称矩阵,即 D ( f i − , f j + ) = D ( f j + , f i − ) D(f_i^-,f_j^+)=D(f_j^+,f_i^-) D(fi,fj+)=D(fj+,fi)。因此可以检查所有的输入输出对来估计依赖图。图3中可视化了具有残差连接的CNN块的依赖图。

算法1和算法2总结了依赖建模和分组的算法。
在这里插入图片描述
在这里插入图片描述

4.4 组级剪枝

评估分组参数的重要性对剪枝提出了重大挑战,因为它涉及了多个耦合层。这里,作者利用一个简单的基于范数的准则来建立一个实用的组级剪枝方法。给定一个参数组 g = { w 1 , w 2 , … w ∣ g ∣ } g=\{w_1,w_2,…w_{|g|}\} g={w1,w2,wg},现有的L2范数重要性准则 I ( w ) = ∣ ∣ w ∣ ∣ 2 I(w)=||w||_2 I(w)=∣∣w2可以对每个 w ∈ g w∈g wg 产生独立的评分。估计群体重要性的一个自然方法是计算一个综合得分 I ( g ) = ∑ w ∈ g I ( w ) I(g)=\sum_{w∈g}I(w) I(g)=wgI(w) ,不幸的是,不同层独立评估的重要性得分很可能是不可加的,因为分布和量级的差异而因此没有意义。为了使这种简单的聚合方式适用于重要性估计,作者提出了一种稀疏训练方法来稀疏化组级别的参数,如图4(c)所示,以便那些被零化的组可以安全的从网络中移除。
在这里插入图片描述
具体来所,对于每一个具有 K K K 个可剪枝维度的参数 w w w,作者引入了一个简单的用于稀疏训练的正则化项,定义为:
在这里插入图片描述
其中, I g , k = ∑ w ∈ g ∣ ∣ w [ k ] ∣ ∣ 2 2 I_{g,k}=\sum_{w∈g}||w[k]||_2^2 Ig,k=wg∣∣w[k]22 表示 第 k k k 个可剪枝维度的重要性, γ k \gamma_k γk 指的是应用于这些参数的缩放强度。作者使用一个可控的指数策略来确定 γ k \gamma_k γk 如下:
在这里插入图片描述
其中,收缩强度 α k \alpha_k αk 采用归一化分数来控制,其变化范围为 [ 2 0 , 2 α ] [2^0,2^\alpha] [20,2α]。在实验中 α = 4 \alpha=4 α=4。在稀疏训练之后,作者进一步使用了简单的相对得分 I ^ g , k = N ⋅ I g , k / ∑ { T o p N ( I g ) } \hat{I}_{g,k}=N·I_{g,k}/\sum\{TopN(I_g)\} I^g,k=NIg,k/{TopN(Ig)},用于识别和去除不重要的参数。

这里正则化的目的就是为了让成对的参数重要性都趋向于0。

稀疏训练过程如下:

for epoch in range(epochs):
    model.train()
    pruner.update_regularizer() # <== initialize regularizer
    for i, (data, target) in enumerate(train_loader):
        data, target = data.to(device), target.to(device)
        optimizer.zero_grad()
        out = model(data)
        loss = F.cross_entropy(out, target)
        loss.backward() # after loss.backward()
        pruner.regularize(model) # <== for sparse training
        optimizer.step() # before optimizer.step()

以out_channel为例,正则化过程如下(代码见https://github.com/VainF/Torch-Pruning/blob/master/torch_pruning/pruner/algorithms/group_norm_pruner.py#L126):

alpha=2**4
imp = self.estimate_importance(group).sqrt()
gamma = alpha**((imp.max() - imp) / (imp.max() - imp.min()))
for i, (dep, idxs) in enumerate(group):
    layer = dep.target.module
    prune_fn = dep.handler
    if prune_fn in [
        function.prune_conv_out_channels,
        function.prune_linear_out_channels,
    ]:
        root_idxs = group[i].root_idxs
        _gamma = torch.index_select(gamma, 0, torch.tensor(root_idxs, device=gamma.device))

        w = layer.weight.data[idxs]
        g = w * _gamma.view( -1, *([1]*(len(w.shape)-1)) )
        layer.weight.grad.data[idxs]+=self.reg * g  # self.reg = 1e-4

个人理解是通过以权重的反方向和损失一起更新梯度,不重要的权重因为没有loss的梯度,所以会朝权重的反方向更新,直到趋向于0,重要的权重因为会有loss的梯度,所以不会趋向于0。

5 效果

在CIFAR10上剪枝了一个ResNet56,在CIFAR100上剪枝了VGG19,效果如表1。
在这里插入图片描述
一致稀疏性对于结构剪枝非常重要,因为它迫使所有被剪枝的参数一致不重要。如图5中,将图4(c)和(b)中一致和不一致策略学习到分组参数的范数可视化,对应 w/和 w/o。容易发现,本文提出的方法在组水平上产生了强稀疏性,这有利于识别不重要的参数。(被剪枝后值变小)
在这里插入图片描述
为了进一步验证分组的有效性,分别测试:

(1)不分组:在单个卷积层上独立进行稀疏学习和重要性评估

(2)仅卷积分组:组内卷积层以一致的方式进行稀疏化。

(3)全分组:组内的所有参数层,包括卷积,BN,全连接等都一致稀疏化

结果如表2。
在这里插入图片描述

层稀疏度也是剪枝的一个重要因素,它决定了剪枝后神经网络的最终结构。表2提供了一些关于层稀疏性的结果。这项工作主要关注两种类型的稀疏性,即均匀稀疏性和学习稀疏性。在具有均匀稀疏性的情况下,将对不同层施加相同的剪枝比例,假设冗余通过网络均匀分布。然而,图5中先前的实验表明,不同的层并不等同于可剪枝的。在大多数情况下,学习到的稀疏性优于均匀稀疏性,尽管有时它可能会过度剪枝某些层,从而导致精度下降。

同时表2也证明了本文框架的可推广性。

由于分组参数过程十分复杂,对大型神经网络进行剪枝是一个相当大的挑战。然而,通过使用DepGraph,可以毫不费力地获得所有耦合组。在图6中提供了DenseNet121 ,ResNet18和Vision Transformers 的DepGraph D和衍生分组矩阵G的可视化。分组矩阵由算法2中的DepGraph导出,其中G[ i , j] = 1表示第i层与第j层属于同一组。DenseNet121在同一稠密块内的层与层之间表现出很强的相关性,导致结构修剪时产生较大的组。在处理复杂网络时,所提出的依赖图被证明在处理复杂网络时很有帮助,因为手动分析此类网络中的所有依赖关系确实是一项困难的任务。
在这里插入图片描述

表3为剪枝结果在ImageNet上的效果,包括ResNet, DenseNet, MobileNet, ResNeXt, and Vision Transformers。
在这里插入图片描述

表4在其他网络结构上的泛化性如表4。
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Anaconda环境安装失败的解决方案

链接步骤的补充。 为了运行marlib&#xff0c;需要一个全新的Anaconda环境。但是&#xff0c;不想把文件安装在C盘&#xff0c;会造成空间不足。于是试着在.condarc文件里面改动了路径&#xff0c;具体如图。 上图中&#xff0c;在defaults前面添加了D盘的路径作为安装路径。 …

docker环境中配置phpstorm php xdebug调试工具

本文介绍通过docker compose的使用方式 第一步&#xff1a;在php镜像中安装phpxdebug扩展&#xff0c;比如php7.4对应的是xdebug3.1.6 第二步&#xff1a;设置项目中的docker-compose.yml docker-compose 增加开启xdebug的环境变量,host.docker.internal是宿主机的地址&#…

错题记录(小测)

单选 错题1 错题2 错题3 代码题 反转链表 链表的回文结构

java第二十三课 —— 继承

面向对象的三大特征 继承 继承可以解决代码复用&#xff0c;让我们的编程更加靠近人类思维&#xff0c;当多个类存在相同的属性&#xff08;变量&#xff09;和方法时&#xff0c;可以从这些类中抽象出父类&#xff0c;在父类中定义这些相同的属性和方法&#xff0c;所有的子…

利用flask + pymysql监测数据同步中的数据是否完整

一、背景 最近项目搞重构&#xff0c;将原有的系统拆分成了多个子系统。但是有数据表需要在不同系统中数据&#xff0c;同时为了解决项目性能最了一个很简单的方案&#xff0c;就是公共数据存在每个系统之中。 二、分析 分析这些表&#xff0c;这些表相比源数据表&#xff0c;表…

网络编程之XDP和TC

一、TC之于XDP 在前面分析过XDP&#xff0c;今天简单分析一下与其相关的TC&#xff0c;即traffic control,流量控制。在分析XDP时知道其只能用于ingress方向触发&#xff0c;而TC却可以在两个方向即ingress和egress方向触发。也可以简单理解成它可以同时钩住进出两个方向的数据…

Aivis:AI声音模仿系统的创新之旅

在人工智能技术的不断进步中&#xff0c;声音合成技术也迎来了新的发展机遇。Aivis项目正是这一领域的杰出代表&#xff0c;它提供了一个全流程的工具&#xff0c;让用户能够从数据集的创建到学习再到推理&#xff0c;一站式地生成逼真的语音。 Aivis是一个基于Bert-VITS2模型的…

一键取票,YonSuite商旅费控助力企业“消灭报销”

在数字化与智能化并行的时代&#xff0c;企业商旅管理正经历着前所未有的变革。面对传统商旅出行管理中流程复杂、费用不透明等问题&#xff0c;YonSuite商旅费控以其独特的数智化商旅管理平台&#xff0c;为企业提供了一站式的解决方案&#xff0c;特别是其“一键取票”功能&a…

【博客718】时序数据库基石:LSM Tree(log-structured merge-tree)

时序数据库基石&#xff1a;LSM Tree(log-structured merge-tree) 1、为什么需要LSM Tree LSM被设计来提供比传统的B树更好的写操作吞吐量&#xff0c;通过消去随机的本地更新操作来达到这个目标&#xff0c;使得写入都是顺序写&#xff0c;而不是随机写。 那么为什么这是一个…

怎么图片转excel表格?推荐三个方法

怎么图片转excel表格&#xff1f;在信息化高速发展的今天&#xff0c;图片转Excel表格的需求日益凸显&#xff0c;尤其是在职场办公中&#xff0c;这一需求更是显得尤为迫切。为了满足广大用户的需求&#xff0c;市面上涌现出了众多图片转Excel的软件。今天&#xff0c;就为大家…

周五美国股市总结,标普止步四日连涨,纳指五日连创新高,法股单周跌幅两年多最深

美国消费者信心意外下滑至七个月新低&#xff0c;通胀预期反弹&#xff0c;标普大盘脱离历史最高&#xff0c;道指连跌四日&#xff0c;罗素小盘股跌至六周新低&#xff0c;有分析称对经济担忧浮现。全周标普和纳指分别累涨1.6%和3.2%&#xff0c;都是八周里第七周上涨&#xf…

基于SpringBoot+Vue高校自习室预约系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝1W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;还…

Navicat和SQLynx产品功能比较一(整体比较)

Navicat和SQLynx都是数据库管理工具&#xff0c;在过去的二十年中&#xff0c;国内用户主要是使用Navicat偏多&#xff0c;一般是个人简单开发需要&#xff0c;数据量一般不大&#xff0c;开发相对简单。SQLynx是最近几年的数据库管理工具&#xff0c;Web开发&#xff0c;桌面版…

P450Rdb: CYP450数据库--地表最强系列--文献精读24

P450Rdb: A manually curated database of reactions catalyzed by cytochrome P450 enzymes P450Rdb: 一个人工整理的细胞色素P450酶催化反应数据库 http://www.cellknowledge.com.cn/p450rdb/ 还有一篇类似CYP450综述-20年-地表最强系列-文献精读-4 要点&#xff1a; P450…

【服务器硬件由 CPU、RAM、硬盘等组成,选购时需考虑应用需求、预算等。散热、安全、监控与维护亦重要,未来发展趋势包括高性能、低能耗和智能化。】

本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(注明:作者:王文峰…

TypeScript写好了,怎么运行啊!!!

环境搭建 Vs code Ctrlshiftp打开首选项—》打开工作区设置—》搜索Typescript 推荐开启的配置项主要是这几个&#xff1a; Function Like Return Types&#xff0c;显示推导得到的函数返回值类型&#xff1b;Parameter Names&#xff0c;显示函数入参的名称&#xff1b;Par…

基于VSCode和MinGW-w64搭建LVGL模拟开发环境

目录 概述 1 运行环境 1.1 版本信息 1.2 软件安装 1.2.1 下载安装VS Code 1.2.1.1 下载软件 1.2.1.1 安装软件 1.2.2 下载安装MinGW-w64 1.2.2.1 下载软件 1.2.2.2 安装软件 1.2.3 下载安装SDL 1.2.3.1 下载软件 ​1.2.3.2 安装软件 1.2.4 下载安装CMake 1.2.4.…

【ARM 安全系列介绍 3.7 -- SM4 对称加密算】

请阅读【嵌入式开发学习必备专栏 Cache | MMU | AMBA BUS | CoreSight | Trace32 | CoreLink | GCC | CSH | Armv8/v9 系统异常分析】 文章目录 SM4 加密算法简介SM4 工作模式算法步骤加密举例注意事项 Principle of SM4 encryption algorithm SM4 加密算法简介 SM4是一种分组…

Ubuntu20.04-ROS:Noetic安装

根据Ubuntu系统来选择对应的ros版本&#xff0c;在Ubuntu20.04上安装ROS-Noetic为例说明下如何安装ros 一 设置ROS源 / 终端输入 sudo sh -c echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.lis…

PyQt5学习系列之新项目创建并使用widget

PyQt5学习系列之新项目创建并使用widget 前言报错新建项目程序完整程序总结 前言 新建项目&#xff0c;再使用ui转py&#xff0c;无论怎么样都打不开py文件&#xff0c;直接报错。 报错 Connected to pydev debugger (build 233.11799.298)新建项目程序 # Press ShiftF10 to…