YOLOv8改进 | 知识蒸馏 | 利用模型蒸馏改进YOLOv8进行无损涨点(在线蒸馏 + 离线蒸馏)

一、本文介绍

这篇文章给大家带来的是模型的蒸馏,利用教师模型指导学生模型从而进行模型的涨点,本文的内容不仅可以用于论文中,在目前的绝大多数的工作中模型蒸馏是一项非常重要的技术,所以大家可以仔细学习一下本文的内容,本文从YOLOv8的项目文件为例,进行详细的修改教程, 文章内包括完整的修改教程,针对小白我出了视频修改教程,如果你还不会我提供了修改后的文件大家直接运行即可,所以说不用担心不会适用!模型蒸馏真正的无损涨点,蒸馏你只看这一篇文章就足够了!

欢迎大家订阅我的专栏一起学习YOLO! 

专栏目录:YOLOv8改进有效系列目录 | 包含卷积、主干、检测头、注意力机制、Neck上百种创新机制

专栏回顾:YOLOv8改进系列专栏——本专栏持续复习各种顶会内容——科研必备 

目录

一、本文介绍

二、蒸馏教程

2.1 修改一

2.2 修改二

2.3 修改三

2.4 修改四 

2.5 修改五 

2.6 修改六

2.7 修改七 

2.8 修改八 

2.8 修改九

2.9 修改十

2.10 修改十一

2.11 修改十二

2.12 修改十三

2.13 修改十四

2.14 修改十五

三、使用教程 

3.1 模型蒸馏代码

3.2 开始蒸馏 

四、本文总结


二、蒸馏教程

知识蒸馏的主要方法可以分为三种:基于响应的知识蒸馏(利用教师模型的输出或对最终预测的模仿)、基于特征的知识蒸馏(使用教师模型中间层的特征表示)以及基于关系的知识蒸馏(利用模型内部不同层或不同数据点之间的关系)。每种方法都旨在从大模型中提取有效信息,并通过特定的损失函数将这些信息灌输给学生模型​。

首先,基于模型的知识蒸馏类型包括:

  • 1. 基于响应的蒸馏(Response-based):使用教师模型的最后输出层的信息(如类别概率)来训练学生模型。
  • 2. 基于特征的蒸馏(Feature-based):利用教师模型的中间层特征来指导学生模型。
  • 3. 基于关系的蒸馏(Relation-based):侧重于教师模型内不同特征之间的关系,如特征图之间的相互作用。

蒸馏过程的实施方式:

  • 1. 在线蒸馏(Online distillation):教师模型和学生模型同时训练,学生模型实时学习教师模型的知识。
  • 2. 离线蒸馏(Offline distillation):先训练教师模型,再使用该模型来训练学生模型,学生模型不会影响教师模型。
  • 3. 自蒸馏(Self distillation):模型使用自己的预测作为软标签来提高自己的性能。

知识蒸馏是一个多样化的领域,包括各种不同的方法来优化深度学习模型的性能和大小。从基本的基于响应、特征和关系的蒸馏,到更高级的在线、离线和自蒸馏过程,再到特定的技术如对抗性蒸馏或量化蒸馏等,每一种方法都旨在解决不同的问题和需求。


PS: 开始之前给大家说一下,本文的修改内容涉及的改动比较多(我的教程会很详细的写),我也会出一期视频带大家从头到尾修改一遍,如果你还修改不对我也会提供修改完成版本的代码(直接训练运行即可),但是大家肯定想自己修改到自己的项目里如果你修改过程中的报错,我已经提供很完整的教程了(多种方案给大家选择),所以报错回复我只能是随缘回复(因为本文的内容肯定大家一堆报错会遇到稍微操作不当就是报错),同时本项目仅用于YOLOv8项目上,和本项目无关的报错一律不回复。


👑正式修改教程👑


2.1 修改一

下面给出了一段代码,我们将这段代码找到目录'ultralytics/utils'下创建一个.py文件存放进去,文件的名字我们命名为AddLoss.py,创建好的文件如下图所示->

import torch
import torch.nn as nn
import torch.nn.functional as Fdef is_parallel(model):"""Returns True if model is of type DP or DDP."""return isinstance(model, (nn.parallel.DataParallel, nn.parallel.DistributedDataParallel))def de_parallel(model):"""De-parallelize a model: returns single-GPU model if model is of type DP or DDP."""return model.module if is_parallel(model) else modelclass MimicLoss(nn.Module):def __init__(self, channels_s, channels_t):super(MimicLoss, self).__init__()device = 'cuda' if torch.cuda.is_available() else 'cpu'self.mse = nn.MSELoss()def forward(self, y_s, y_t):"""Forward computation.Args:y_s (list): The student model prediction withshape (N, C, H, W) in list.y_t (list): The teacher model prediction withshape (N, C, H, W) in list.Return:torch.Tensor: The calculated loss value of all stages."""assert len(y_s) == len(y_t)losses = []for idx, (s, t) in enumerate(zip(y_s, y_t)):assert s.shape == t.shapelosses.append(self.mse(s, t))loss = sum(losses)return lossclass CWDLoss(nn.Module):"""PyTorch version of `Channel-wise Distillation for Semantic Segmentation.<https://arxiv.org/abs/2011.13256>`_."""def __init__(self, channels_s, channels_t, tau=1.0):super(CWDLoss, self).__init__()self.tau = taudef forward(self, y_s, y_t):"""Forward computation.Args:y_s (list): The student model prediction withshape (N, C, H, W) in list.y_t (list): The teacher model prediction withshape (N, C, H, W) in list.Return:torch.Tensor: The calculated loss value of all stages."""assert len(y_s) == len(y_t)losses = []for idx, (s, t) in enumerate(zip(y_s, y_t)):assert s.shape == t.shapeN, C, H, W = s.shape# normalize in channel diemensionsoftmax_pred_T = F.softmax(t.view(-1, W * H) / self.tau, dim=1)  # [N*C, H*W]logsoftmax = torch.nn.LogSoftmax(dim=1)cost = torch.sum(softmax_pred_T * logsoftmax(t.view(-1, W * H) / self.tau) -softmax_pred_T * logsoftmax(s.view(-1, W * H) / self.tau)) * (self.tau ** 2)losses.append(cost / (C * N))loss = sum(losses)return lossclass MGDLoss(nn.Module):def __init__(self, channels_s, channels_t, alpha_mgd=0.00002, lambda_mgd=0.65):super(MGDLoss, self).__init__()device = 'cuda' if torch.cuda.is_available() else 'cpu'self.alpha_mgd = alpha_mgdself.lambda_mgd = lambda_mgdself.generation = [nn.Sequential(nn.Conv2d(channel, channel, kernel_size=3, padding=1),nn.ReLU(inplace=True),nn.Conv2d(channel, channel, kernel_size=3, padding=1)).to(device) for channel in channels_t]def forward(self, y_s, y_t):"""Forward computation.Args:y_s (list): The student model prediction withshape (N, C, H, W) in list.y_t (list): The teacher model prediction withshape (N, C, H, W) in list.Return:torch.Tensor: The calculated loss value of all stages."""assert len(y_s) == len(y_t)losses = []for idx, (s, t) in enumerate(zip(y_s, y_t)):assert s.shape == t.shapelosses.append(self.get_dis_loss(s, t, idx) * self.alpha_mgd)loss = sum(losses)return lossdef get_dis_loss(self, preds_S, preds_T, idx):loss_mse = nn.MSELoss(reduction='sum')N, C, H, W = preds_T.shapedevice = preds_S.devicemat = torch.rand((N, 1, H, W)).to(device)mat = torch.where(mat > 1 - self.lambda_mgd, 0, 1).to(device)masked_fea = torch.mul(preds_S, mat)new_fea = self.generation[idx](masked_fea)dis_loss = loss_mse(new_fea, preds_T) / Nreturn dis_lossclass Distill_LogitLoss:def __init__(self, p, t_p, alpha=0.25):t_ft = torch.cuda.FloatTensor if t_p[0].is_cuda else torch.Tensorself.p = pself.t_p = t_pself.logit_loss = t_ft([0])self.DLogitLoss = nn.MSELoss(reduction="none")self.bs = p[0].shape[0]self.alpha = alphadef __call__(self):# per outputassert len(self.p) == len(self.t_p)for i, (pi, t_pi) in enumerate(zip(self.p, self.t_p)):  # layer index, layer predictionsassert pi.shape == t_pi.shapeself.logit_loss += torch.mean(self.DLogitLoss(pi, t_pi))return self.logit_loss[0] * self.alphadef get_fpn_features(x, model, fpn_layers=[15, 18, 21]):y, fpn_feats = [], []with torch.no_grad():model = de_parallel(model)module_list = model.model[:-1] if hasattr(model, "model") else model[:-1]for m in module_list:# if not from previous layerif m.f != -1:x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f]  # from earlier layersx = m(x)y.append(x if m.i in model.save else None)  # save outputif m.i in fpn_layers:fpn_feats.append(x)return fpn_featsdef get_channels(model, fpn_layers=[15, 18, 21]):y, out_channels = [], []p = next(model.parameters())x = torch.zeros((1, 3, 64, 64), device=p.device)with torch.no_grad():model = de_parallel(model)module_list = model.model[:-1] if hasattr(model, "model") else model[:-1]for m in module_list:# if not from previous layerif m.f != -1:x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f]  # from earlier layersx = m(x)y.append(x if m.i in model.save else None)  # save outputif m.i in fpn_layers:out_channels.append(x.shape[1])return out_channelsclass FeatureLoss(nn.Module):def __init__(self, channels_s, channels_t, distiller='cwd'):super(FeatureLoss, self).__init__()device = 'cuda' if torch.cuda.is_available() else 'cpu'self.align_module = nn.ModuleList([nn.Conv2d(channel, tea_channel, kernel_size=1, stride=1, padding=0).to(device)for channel, tea_channel in zip(channels_s, channels_t)])self.norm = [nn.BatchNorm2d(tea_channel, affine=False).to(device)for tea_channel in channels_t]if distiller == 'mimic':self.feature_loss = MimicLoss(channels_s, channels_t)elif distiller == 'mgd':self.feature_loss = MGDLoss(channels_s, channels_t)elif distiller == 'cwd':self.feature_loss = CWDLoss(channels_s, channels_t)else:raise NotImplementedErrordef forward(self, y_s, y_t):assert len(y_s) == len(y_t)tea_feats = []stu_feats = []for idx, (s, t) in enumerate(zip(y_s, y_t)):s = self.align_module[idx](s)s = self.norm[idx](s)t = self.norm[idx](t)tea_feats.append(t)stu_feats.append(s)loss = self.feature_loss(stu_feats, tea_feats)return loss


2.2 修改二

下面的代码我们找到文件'ultralytics/engine/trainer.py'按照我的图片内容复制粘贴到指定位置即可!根据下图进行修改->

在该文件的开头我们先添加两行模块的导入代码,

from ultralytics.utils import IterableSimpleNamespace
from ultralytics.utils.AddLoss import get_fpn_features, Distill_LogitLoss, de_parallel, get_channels, FeatureLoss

        #------------------------------Add-Param-Start---------------self.featureloss = 0self.logitloss = 0self.teacherloss = 0self.distillloss =Noneself.model_t = overrides.get("model_t", None)self.distill_feat_type = "cwd"  # "cwd","mgd","mimic"self.distillonline = False  # False or Trueself.logit_loss = False # False or Trueself.distill_layers = [2, 4, 6, 8, 12, 15, 18, 21]# ------------------------------Add-Param-End-----------------


2.3 修改三

按照图片进行修改即可。

        if self.model_t is not None:for k, v in self.model_t.model.named_parameters():v.requires_grad = Trueself.model_t = self.model_t.to(self.device)


2.4 修改四 

按照图片进行修改即可。

            if self.model_t is not None:self.model_t = nn.parallel.DistributedDataParallel(self.model_t, device_ids=[RANK])


2.5 修改五 

此处的修改和上面有些不一样,上面的代码都是添加,此处的代码为替换。

        self.optimizer = self.build_optimizer(model=self.model,model_t=self.model_t,distillloss=self.distillloss,distillonline=self.distillonline,name=self.args.optimizer,lr=self.args.lr0,momentum=self.args.momentum,decay=weight_decay,iterations=iterations)


2.6 修改六

修改教程看图片!

        self.model = de_parallel(self.model)if self.model_t is not None:self.model_t = de_parallel(self.model_t)self.channels_s = get_channels(self.model,self.distill_layers)self.channels_t = get_channels(self.model_t,self.distill_layers)self.distillloss = FeatureLoss(channels_s=self.channels_s, channels_t=self.channels_t, distiller= self.distill_feat_type)


2.7 修改七 

修改教程看图片!

            if self.model_t is not None:self.model_t.eval()


2.8 修改八 

修改教程看图片!

                    pred_s= self.model(batch['img'])stu_features = get_fpn_features(batch['img'], self.model,fpn_layers=self.distill_layers)


2.8 修改九

                    if self.model_t is not None:distill_weight = ((1 - math.cos(i * math.pi / len(self.train_loader))) / 2) * (0.1 - 1) + 1with torch.no_grad():pred_t_offline = self.model_t(batch['img'])tea_features = get_fpn_features(batch['img'], self.model_t,fpn_layers=self.distill_layers)  # forwardself.featureloss = self.distillloss(stu_features, tea_features) * distill_weightself.loss += self.featurelossif self.distillonline:self.model_t.train()pred_t_online = self.model_t(batch['img'])for p in pred_t_online:p = p.detach()if i == 0 and epoch == 0:self.model_t.args["box"] = self.model.args.boxself.model_t.args["cls"] = self.model.args.clsself.model_t.args["dfl"] = self.model.args.dflself.model_t.args = IterableSimpleNamespace(**self.model_t.args)self.teacherloss, _ = self.model_t(batch, pred_t_online)if RANK != -1:self.teacherloss *= world_sizeself.loss += self.teacherlossif self.logit_loss:if not self.distillonline:distill_logit = Distill_LogitLoss(pred_s, pred_t_offline)else:distill_logit = Distill_LogitLoss(pred_s, pred_t_online)self.logitloss = distill_logit()self.loss += self.logitloss


2.9 修改十

修改教程看图片!

                    pbar.set_description(('%12s' * 2 + '%12.4g' * (5 + loss_len)) %(f'{epoch + 1}/{self.epochs}', mem, *losses, self.featureloss, self.teacherloss, self.logitloss, batch['cls'].shape[0], batch['img'].shape[-1]))


2.10 修改十一

修改教程看图片!

, model_t, distillloss, distillonline=False,


2.11 修改十二

修改教程看图片!

        if model_t is not None and distillonline:for v in model_t.modules():# print(v)if hasattr(v, 'bias') and isinstance(v.bias, nn.Parameter):  # bias (no decay)g[2].append(v.bias)if isinstance(v, bn):  # weight (no decay)g[1].append(v.weight)elif hasattr(v, 'weight') and isinstance(v.weight, nn.Parameter):  # weight (with decay)g[0].append(v.weight)if model_t is not None and distillloss is not None:for k, v in distillloss.named_modules():# print(v)if hasattr(v, 'bias') and isinstance(v.bias, nn.Parameter):  # bias (no decay)g[2].append(v.bias)if isinstance(v, bn) or 'bn' in k:  # weight (no decay)g[1].append(v.weight)elif hasattr(v, 'weight') and isinstance(v.weight, nn.Parameter):  # weight (with decay)g[0].append(v.weight)

​ 


2.12 修改十三

PS:注意此处我们更换了修改的文件了!!

我们找到文件'ultralytics/cfg/__init__.py',按照我的图片进行修改!


2.13 修改十四

PS:注意此处我们更换了修改的文件了!!

我们找到文件'ultralytics/models/yolo/detect/train.py'按照图片进行修改即可!

        return ('\n' + '%12s' *(7 + len(self.loss_names))) % ('Epoch', 'GPU_mem', *self.loss_names, 'dfeaLoss', 'dlineLoss', 'dlogitLoss', 'Instances','Size')


2.14 修改十五

PS:注意此处我们更换了修改的文件了!!

我们找到文件'ultralytics/engine/model.py'按照图片进行修改即可!

​ 

到此处就修改完成了,剩下的就是如何使用进行模型蒸馏了! 


三、使用教程 

模型蒸馏指的是:用训练好的模型(注意是训练好的模型)去教另一个模型!(其中教师模型必须是训练好的权重,学生模型可以是yaml文件也可以是权重文件地址)

在开始之前我们需要准备一个教师模型,我们这里就用YOLOv8l为例,其权重文件可以去官方下载。 

3.1 模型蒸馏代码

PS:需要注意的是,学生模型和教师模型的模型配置文件需要保持一致,也就是说你学生模型假设用了BiFPN那么你的教师模型也需要用BiFPN去训练否则就会报错!

import warnings
warnings.filterwarnings('ignore')
from ultralytics import YOLOif __name__ == '__main__':model_t = YOLO(r'weights/yolov8l.pt')  # 此处填写教师模型的权重文件地址model_t.model.model[-1].set_Distillation = True  # 不用理会此处用于设置模型蒸馏model_s = YOLO(r'ultralytics/cfg/models/v8/yolov8.yaml')  # 学生文件的yaml文件 or 权重文件地址model_s.train(data=r'C:\Users\Administrator\Desktop\Snu77\ultralytics-main\New_GC-DET\data.yaml',  #  将data后面替换你自己的数据集地址cache=False,imgsz=640,epochs=100,single_cls=False,  # 是否是单类别检测batch=1,close_mosaic=10,workers=0,device='0',optimizer='SGD',  # using SGDamp=True,  # 如果出现训练损失为Nan可以关闭ampproject='runs/train',name='exp',model_t=model_t.model)


3.2 开始蒸馏 

我们将蒸馏的代码复制粘贴到一个py文件内,如下图所示!

我们运行蒸馏的py文件即可,模型就会开始训练并且蒸馏。下面的图片就是模型开始训练并且蒸馏,可以看到我们开启了蒸馏损失'dfeaLoss   dlineLoss'

​ 


四、完整文件和视频讲解

百度网盘因为链接很容易过期,如果下面文件过期大家可以提醒我,或者进群,群内我也会上传!

链接:https://pan.baidu.com/s/1_gwwemBDPF6YEoyO2BkTlA?pwd=j8uq 
提取码:j8uq


四、本文总结

到此本文的正式分享内容就结束了,在这里给大家推荐我的YOLOv8改进有效涨点专栏,本专栏目前为新开的平均质量分98分,后期我会根据各种最新的前沿顶会进行论文复现,也会对一些老的改进机制进行补充,如果大家觉得本文帮助到你了,订阅本专栏,关注后续更多的更新~

专栏回顾:YOLOv8改进系列专栏——本专栏持续复习各种顶会内容——科研必备 

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

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

相关文章

Spring Boot 处理过滤器(filter )中抛出的异常

前言&#xff1a; 在改造老项目登录功能的时候&#xff0c;使用了过滤器对 token 进行有效性验证&#xff0c;验证通过继续进行业务请求&#xff0c;验证不通过则抛出校验异常。 过程&#xff1a; 技术方案拟定后&#xff0c;就着手开始改造&#xff0c;一切都很顺畅&#x…

大数据平台搭建2024(二)

二&#xff1a;Hive安装 只在node01上操作 1 安装MySQL 8.0 最小化安装需要安装这个 yum install -y wget1-1 下载MySQL的yum源 wget http://dev.mysql.com/get/mysql80-community-release-el7-7.noarch.rpm检查是否安装成功 rpm -qpl mysql80-community-release-el7-7.n…

【YOLO系列PR、F1绘图】更改v5、v7、v8(附v8训练、验证方式),实现调用val.py或者test.py后生成pr.csv,然后再整合绘制到一张图上(使用matplotlib绘制)

目录 1. 前提 效果图2. 更改步骤2.1 得到PR_curve.csv和F1_curve.csv2.1.1 YOLOv7的更改2.1.1.1 得到PR_curve.csv2.2.1.2 得到F1_curve.csv 2.1.2 YOLOv5的更改&#xff08;v6.1版本&#xff09;2.1.3 YOLOv8的更改&#xff08;附训练、验证方式&#xff09; 2.2 绘制PR曲线 …

牛客-环形链表的约瑟夫问题

目录 题目 描述 示例1 示例2 图解 代码(解析在注释中) 题目 描述 编号为 1 到 n 的 n 个人围成一圈。从编号为 1 的人开始报数&#xff0c;报到 m 的人离开。 下一个人继续从 1 开始报数。 n-1 轮结束以后&#xff0c;只剩下一个人&#xff0c;问最后留下的这个人编号…

zabbix 自动发现与自动注册 部署 zabbix 代理服务器

zabbix 自动发现&#xff08;对于 agent2 是被动模式&#xff09; zabbix server 主动的去发现所有的客户端&#xff0c;然后将客户端的信息登记在服务端上。 缺点是如果定义的网段中的主机数量多&#xff0c;zabbix server 登记耗时较久&#xff0c;且压力会较大。1.确保客户端…

Qt | 事件第二节

Qt | 事件第一节书接上回 四、事件的接受和忽略 1、事件可以被接受或忽略,被接受的事件不会再传递给其他对象,被忽略的事件会被传递给其他对象处理,或者该事件被丢弃(即没有对象处理该事件) 2、使用 QEvent::accept()函数表示接受一个事件,使用 QEvent::ignore()函数表示…

认识异常(1)

❤️❤️前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; hellohello~&#xff0c;大家好&#x1f495;&#x1f495;&#xff0c;这里是E绵绵呀✋✋ &#xff0c;如果觉得这篇文章还不错的话还请点赞❤️❤️收藏&#x1f49e; &#x1f49e; 关注&#x1f4a5;&a…

OpenHarmony南向开发实例:【游戏手柄】

介绍 基于TS扩展的声明式开发范式编程语言&#xff0c;以及OpenHarmony的分布式能力实现的一个手柄游戏。 完成本篇Codelab需要两台开发板&#xff0c;一台开发板作为游戏端&#xff0c;一台开发板作为手柄端&#xff0c;实现如下功能&#xff1a; 游戏端呈现飞机移动、发射…

python生成二维码

要在Python中生成二维码&#xff0c;可以使用第三方库qrcode。首先&#xff0c;确保已经安装了qrcode库&#xff1a; pip install qrcode然后&#xff0c;使用以下代码生成二维码&#xff1a; import qrcodedata "https://mp.csdn.net/mp_blog/creation/editor?spm100…

项目7-音乐播放器2(上传音乐+查询音乐+拦截器)

0.加入拦截器 之后就不用对用户是否登录进行判断了 0.1 定义拦截器 0.2 注册拦截器 生效 1.上传音乐的接口设计 请求&#xff1a; { post, /music/upload {singer&#xff0c;MultipartFile file}&#xff0c; } 响应&#xff1a; { "status": 0, "message&…

Promise简单概述

一. Promise是什么&#xff1f; 理解 1.抽象表达&#xff1a; Promise是一门新的技术(ES6规范) Promise是JS中进行异步编程的新解决方案(旧方案是单纯使用回调函数) 异步编程&#xff1a;包括fs文件操作&#xff0c;数据库操作(Mysql)&#xff0c;AJAX&#xff0c;定时器 2.具…

RK3568笔记二十二:基于TACO的垃圾检测和识别

若该文为原创文章&#xff0c;转载请注明原文出处。 基于TACO数据集&#xff0c;使用YOLOv8分割模型进行垃圾检测和识别&#xff0c;并在ATK-RK3568上部署运行。 一、环境 1、测试训练环境&#xff1a;AutoDL. 2、平台&#xff1a;rk3568 3、开发板: ATK-RK3568正点原子板子…

大创项目推荐 深度学习YOLOv5车辆颜色识别检测 - python opencv

文章目录 1 前言2 实现效果3 CNN卷积神经网络4 Yolov56 数据集处理及模型训练5 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习YOLOv5车辆颜色识别检测 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0…

前端开发框架BootStrap

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl BootStrap概述 Bootstrap是一个开源的前端框架&#xff0c;它由Twitter的设计师和开发者创建并维护。Bootstrap提供了许多现成的Web组件&#xff0c;可帮助开发者快速设计和…

36、二叉树-二叉树的中序遍历

思路&#xff1a; 二叉树的遍历可以有 前序&#xff0c;中序&#xff0c;后序&#xff0c;层序遍历。 前序&#xff1a;头左右中序&#xff1a;左头右后序&#xff1a;左右头层序:从左往右依次遍历 实现方式&#xff1a; 递归通过栈结构便于回溯 代码如下&#xff1a; c…

软件开发安全设计方案

2.1.应用系统架构安全设计要求 2.2.应用系统软件功能安全设计要求 2.3.应用系统存储安全设计要求 2.4.应用系统通讯安全设计要求 2.5.应用系统数据库安全设计要求 2.6.应用系统数据安全设计要求 软件开发全资料获取&#xff1a;软件开发全套资料_软件开发资料-CSDN博客https://…

【C++学习】C++IO流

这里写目录标题 &#x1f680;C语言的输入与输出&#x1f680;什么是流&#x1f680;CIO流&#x1f680;C标准IO流&#x1f680;C文件IO流 &#x1f680;C语言的输入与输出 C语言中我们用到的最频繁的输入输出方式就是scanf ()与printf()。 scanf(): 从标准输入设备(键盘)读取…

Go Plugin:动态模块的加载与问题解析_go语言加载动态库的工具(1)

先自我介绍一下&#xff0c;小编浙江大学毕业&#xff0c;去过华为、字节跳动等大厂&#xff0c;目前阿里P7 深知大多数程序员&#xff0c;想要提升技能&#xff0c;往往是自己摸索成长&#xff0c;但自己不成体系的自学效果低效又漫长&#xff0c;而且极易碰到天花板技术停滞…

flutter书架形式格口的动态创建(行、列数,是否全选的配置)

根据传入的行列数创建不同格口数量的书架 左图&#xff1a;5行3列、右图&#xff1a;3行3列 代码 import package:jade/bean/experienceStation/ExpCellSpecsBean.dart; import package:jade/configs/PathConfig.dart; import package:jade/utils/DialogUtils.dart; import p…

springboot整合dubbo实现RPC服务远程调用

一、dubbo简介 1.什么是dubbo Apache Dubbo是一款微服务开发框架&#xff0c;他提供了RPC通信与微服务治理两大关键能力。有着远程发现与通信的能力&#xff0c;可以实现服务注册、负载均衡、流量调度等服务治理诉求。 2.dubbo基本工作原理 Contaniner:容器Provider&#xf…