基于知识蒸馏的去雪、去雾、去雨算法

news2024/11/30 2:33:59

今天来详细学习一篇去雪、去雨、去雾三合一的去噪算法
代码地址:

https://github.com/fingerk28/Two-stage-Knowledge-For-Multiple-Adverse-Weather-Removal

论文地址:

https://openaccess.thecvf.com/content/CVPR2022/papers/Chen_Learning_Multiple_Adverse_Weather_Removal_via_Two-Stage_Knowledge_Learning_and_CVPR_2022_paper.pdf

前言

当下的去雪、去雨、去雾算法主要存在以下问题:

  1. 只能对单一恶劣天气进行去除,无法应用于真实环境
  2. 能够完成多种恶劣天气去除的算法模型十分复杂,不利于部署

针对以上问题,提出基于知识蒸馏的多合一去雪、去雨、去雾算法,构建多教师单学生的学习网络,分别由多个教师网络负责不同恶劣天气的去噪任务,随后将学到的知识进行迁移到学生网络,进而使学生网络在保证模型体积足够小的同时还能拥有媲美教师网络的性能。

其结构图总览如下:

在这里插入图片描述
作者原图:

在这里插入图片描述

创新点概述

1.多教师单学生的两阶段学习策略

在这里插入图片描述

2.协作知识迁移模型

在这里插入图片描述

3.多对比正则化训练策略

在这里插入图片描述

在这里插入图片描述

实验数据集

在这里插入图片描述

代码讲解

从结构上来看,其代码并不复杂,主要分为model(模型文件),utils(配置文件,包含数据集加载与评价),weights(权重文件,包含三个教师网络的预训练权重与一个学生网络的训练结果),train.pyinference.py

训练主体

初始化配置参数

parser = argparse.ArgumentParser()
parser.add_argument('--model', type=str, default='models.MSBDN-RDFF.Net')
parser.add_argument('--dataset_train', type=str, default='utils.dataset.DatasetForTrain')
parser.add_argument('--dataset_valid', type=str, default='utils.dataset.DatasetForValid')
parser.add_argument('--meta_train', type=str, default='./meta/train/')
parser.add_argument('--meta_valid', type=str, default='./meta/valid/')
parser.add_argument('--save-dir', type=str, default="outputs")
parser.add_argument('--max-epoch', type=int, default=25)
parser.add_argument('--warmup-epochs', type=int, default=3)
parser.add_argument('--lr', type=float, default=2e-4)
parser.add_argument('--lr-min', type=float, default=1e-6)
parser.add_argument('--batch-size', type=int, default=32)
parser.add_argument('--num_workers', type=int, default=0)
parser.add_argument('--top-k', type=int, default=3)
parser.add_argument('--val-freq', type=int, default=2)
parser.add_argument('--teachers', default="weights/CSD-teacher.pth  weights/Rain1400-teacher  weights/ITS-OTS-teacher",type=str, nargs='+')
args = parser.parse_args()

writer = SummaryWriter(os.path.join(args.save_dir, 'log'))

设置随机种子,保证程序复现

# Set up random seed
	random_seed = 19870522
	torch.manual_seed(random_seed)
	torch.cuda.manual_seed(random_seed)
	np.random.seed(random_seed)
	random.seed(random_seed)
	print(Back.WHITE + 'Random Seed: {}'.format(random_seed) + Style.RESET_ALL)
	print(Fore.RED + "---------------------------------------------------------------" + Style.RESET_ALL)

获取网络模型与数据集

	# get the net and datasets function
	net_func = get_func(args.model)
	dataset_train_func = get_func(args.dataset_train)
	dataset_valid_func = get_func(args.dataset_valid)

具体get_func方法,以生成model为例

def get_func(path):
	module = path[:path.rfind('.')]#str.rfind(str, beg=0, end=len(string))
	model_name = path[path.rfind('.') + 1:]
	mod = importlib.import_module(module)#导入对象
	net_func = getattr(mod, model_name)#getattr() 函数用于返回一个对象属性值。获取model.MSBDN的Net属性,即生成Net
	return net_func

加载教师网络权重

# load teacher models
teacher_networks = []
for checkpoint_path in args.teachers:
	checkpoint = torch.load(checkpoint_path)
	teacher = net_func().cuda()
	teacher.load_state_dict(checkpoint['state_dict'], strict=True)
	teacher_networks.append(teacher)

加载数据集相关配置

# load meta files
	meta_train_paths = sorted(glob(os.path.join(args.meta_train, '*.json')))
	meta_valid_paths = sorted(glob(os.path.join(args.meta_valid, '*.json')))


	# prepare the dataloader
	train_dataset = dataset_train_func(meta_paths=meta_train_paths)
	val_dataset = dataset_valid_func(meta_paths=meta_valid_paths)
	train_loader = DataLoader(dataset=train_dataset, num_workers=args.num_workers, batch_size=args.batch_size,
								drop_last=True, shuffle=True, collate_fn=Collate(n_degrades=len(teacher_networks)))
	val_loader = DataLoader(dataset=val_dataset, num_workers=args.num_workers, batch_size=1, drop_last=False, shuffle=False)

生成CKT知识迁移模块

# Prepare the CKT modules
ckt_modules = nn.ModuleList([])
for c in [64, 128, 256, 256]:
	ckt_modules.append(CKTModule(channel_t=c, channel_s=c, channel_h=c//2, n_teachers=len(teacher_networks)))
ckt_modules = ckt_modules.cuda()

损失函数,即多对比正则化训练策略设计

# prepare the loss function
	criterions = nn.ModuleList([nn.L1Loss(), SCRLoss(), HCRLoss()]).cuda()
# prepare the optimizer and scheduler
	linear_scaled_lr = args.lr * args.batch_size / 16
	optimizer = torch.optim.Adam([{'params': model.parameters()}, {'params': ckt_modules.parameters()}], 
									lr=linear_scaled_lr, betas=(0.9, 0.999), eps=1e-8)
	scheduler_cosine = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, args.max_epoch - args.warmup_epochs, eta_min=args.lr_min)
	scheduler = GradualWarmupScheduler(optimizer, multiplier=1, total_epoch=args.warmup_epochs, after_scheduler=scheduler_cosine)
	scheduler.step()

开启两阶段训练,分为前125epoch的知识收集阶段,后125epoch的知识验证阶段

for epoch in range(start_epoch, args.max_epoch + 1):
	# training
	if epoch <= 125:
		train_kc_stage(model, teacher_networks, ckt_modules, train_loader, optimizer, scheduler, epoch, criterions)
	else:
		train_ke_stage(model, train_loader, optimizer, scheduler, epoch, criterions)

	# validating
	if epoch % args.val_freq == 0:
		psnr, ssim = evaluate(model, val_loader, epoch)
		# Check whether the model is top-k model
		top_k_state = save_top_k(model, optimizer, scheduler, top_k_state, args.top_k, epoch, args.save_dir, psnr=psnr, ssim=ssim)

	torch.save({'epoch': epoch, 'state_dict': model.state_dict(), 'ckt_module': ckt_modules.state_dict(), 
				'optimizer': optimizer.state_dict(), 'scheduler': scheduler.state_dict()}, 
				os.path.join(args.save_dir, 'latest_model'))

评估模块

评估代码不进行梯度更新,且开启eval模式可以大幅减小显存占用
@torch.no_grad()
def evaluate(model, val_loader, epoch):
	print(Fore.GREEN + "==> Evaluating")
	print("==> Epoch {}/{}".format(epoch, args.max_epoch))
	psnr_list, ssim_list = [], []
	model.eval()
	start = time.time()
	pBar = tqdm(val_loader, desc='Evaluating')
	for target, image in pBar:
		if torch.cuda.is_available():
			image = image.cuda()
			target = target.cuda()
		pred = model(image)   		
		psnr_list.append(torchPSNR(pred, target).item())
		ssim_list.append(pytorch_ssim.ssim(pred, target).item())
	print("\nResults")
	print("------------------")
	print("PSNR: {:.3f}".format(np.mean(psnr_list)))
	print("SSIM: {:.3f}".format(np.mean(psnr_list)))
	print("------------------")
	print('Costing time: {:.3f}'.format((time.time()-start)/60))
	print('Current time:', time.strftime("%H:%M:%S", time.localtime()))
	print(Fore.RED + "---------------------------------------------------------------" + Style.RESET_ALL)

	global writer
	writer.add_scalars('PSNR', {'val psnr': np.mean(psnr_list)}, epoch)
	writer.add_scalars('SSIM', {'val ssim': np.mean(ssim_list)}, epoch)

	return np.mean(psnr_list), np.mean(ssim_list)

知识收集阶段训练KC

def train_kc_stage(model, teacher_networks, ckt_modules, train_loader, optimizer, scheduler, epoch, criterions):
	print(Fore.CYAN + "==> Training Stage 1")
	print("==> Epoch {}/{}".format(epoch, args.max_epoch))
	print("==> Learning Rate = {:.6f}".format(optimizer.param_groups[0]['lr']))
	meters = get_meter(num_meters=5)	
	criterion_l1, criterion_scr, _ = criterions
	model.train()
	ckt_modules.train()
	for teacher_network in teacher_networks:
		teacher_network.eval()
	start = time.time()
	pBar = tqdm(train_loader, desc='Training')
	for target_images, input_images in pBar:		
		# Check whether the batch contains all types of degraded data
		if target_images is None: continue
		# move to GPU
		target_images = target_images.cuda()
		input_images = [images.cuda() for images in input_images]
		# Fix all teachers and collect reconstruction results and features from cooresponding teacher
		preds_from_teachers = []
		features_from_each_teachers = []
		with torch.no_grad():
			for i in range(len(teacher_networks)):
				preds, features = teacher_networks[i](input_images[i], return_feat=True)
				preds_from_teachers.append(preds)
				features_from_each_teachers.append(features)	
				
		preds_from_teachers = torch.cat(preds_from_teachers)
		features_from_teachers = []
		for layer in range(len(features_from_each_teachers[0])):
			features_from_teachers.append([features_from_each_teachers[i][layer] for i in range(len(teacher_networks))])
		preds_from_student, features_from_student = model(torch.cat(input_images), return_feat=True)   		
		# Project the features to common feature space and calculate the loss
		PFE_loss, PFV_loss = 0., 0.
		for i, (s_features, t_features) in enumerate(zip(features_from_student, features_from_teachers)):
			t_proj_features, t_recons_features, s_proj_features = ckt_modules[i](t_features, s_features)
			PFE_loss += criterion_l1(s_proj_features, torch.cat(t_proj_features))
			PFV_loss += 0.05 * criterion_l1(torch.cat(t_recons_features), torch.cat(t_features))

		T_loss = criterion_l1(preds_from_student, preds_from_teachers)
		SCR_loss = 0.1 * criterion_scr(preds_from_student, target_images, torch.cat(input_images))
		total_loss = T_loss + PFE_loss + PFV_loss + SCR_loss

		optimizer.zero_grad()
		total_loss.backward()
		optimizer.step()

		meters = update_meter(meters, [total_loss.item(), T_loss.item(), PFE_loss.item(), 
										PFV_loss.item(), SCR_loss.item()])
		pBar.set_postfix({'loss': '{:.3f}'.format(meters[0].avg)})

	
	print("\nResults")
	print("------------------")
	print("Total loss: {:.3f}".format(meters[0].avg))
	print("------------------")
	print('Costing time: {:.3f}'.format((time.time()-start)/60))
	print('Current time:', time.strftime("%H:%M:%S", time.localtime()))
	print(Fore.RED + "---------------------------------------------------------------" + Style.RESET_ALL)

	global writer
	writer.add_scalars('loss', {'train total loss': meters[0].avg}, epoch)
	writer.add_scalars('loss', {'train T loss': meters[1].avg}, epoch)
	writer.add_scalars('loss', {'train PFE loss': meters[2].avg}, epoch)
	writer.add_scalars('loss', {'train PFV loss': meters[3].avg}, epoch)
	writer.add_scalars('loss', {'train SCR loss': meters[4].avg}, epoch)

	writer.add_scalars('lr', {'Model lr': optimizer.param_groups[0]['lr']}, epoch)
	writer.add_scalars('lr', {'CKT lr': optimizer.param_groups[1]['lr']}, epoch)

	scheduler.step()

知识检验阶段训练KE

def train_ke_stage(model, train_loader, optimizer, scheduler, epoch, criterions):
	start = time.time()
	print(Fore.CYAN + "==> Training Stage2")
	print("==> Epoch {}/{}".format(epoch, args.max_epoch))
	print("==> Learning Rate = {:.6f}".format(optimizer.param_groups[0]['lr']))
	meters = get_meter(num_meters=3)
	
	criterion_l1, _, criterion_hcr = criterions

	model.train()

	pBar = tqdm(train_loader, desc='Training')
	for target_images, input_images in pBar:

		# Check whether the batch contains all types of degraded data
		if target_images is None: continue

		# move to GPU
		target_images = target_images.cuda()
		input_images = torch.cat(input_images).cuda()
		
		preds = model(input_images, return_feat=False)   
			
		G_loss = criterion_l1(preds, target_images)
		HCR_loss = 0.2 * criterion_hcr(preds, target_images, input_images)
		total_loss = G_loss + HCR_loss

		optimizer.zero_grad()
		total_loss.backward()
		optimizer.step()

		meters = update_meter(meters, [total_loss.item(), G_loss.item(), HCR_loss.item()])
		pBar.set_postfix({'loss': '{:.3f}'.format(meters[0].avg)})

	
	print("\nResults")
	print("------------------")
	print("Total loss: {:.3f}".format(meters[0].avg))
	print("------------------")
	print('Costing time: {:.3f}'.format((time.time()-start)/60))
	print('Current time:', time.strftime("%H:%M:%S", time.localtime()))
	print(Fore.RED + "---------------------------------------------------------------" + Style.RESET_ALL)

	global writer
	writer.add_scalars('loss', {'train total loss': meters[0].avg}, epoch)
	writer.add_scalars('loss', {'train G loss': meters[1].avg}, epoch)
	writer.add_scalars('loss', {'train HCR loss': meters[2].avg}, epoch)

	writer.add_scalars('lr', {'Model lr': optimizer.param_groups[0]['lr']}, epoch)

	scheduler.step()

model模型结构

在这里插入图片描述

其中,MSBDN即为:Multi-Scale Boosted Dehazing Network,它是一个去雾主干网络,这里用其作为骨干网络

协同知识迁移模块

class CKTModule(nn.Module):
    def __init__(self, channel_t, channel_s, channel_h, n_teachers):
        super().__init__()
        self.teacher_projectors = TeacherProjectors(channel_t, channel_h, n_teachers)
        self.student_projector = StudentProjector(channel_s, channel_h)
    
    def forward(self, teacher_features, student_feature):
        teacher_projected_feature, teacher_reconstructed_feature = self.teacher_projectors(teacher_features)
        student_projected_feature = self.student_projector(student_feature)

        return teacher_projected_feature, teacher_reconstructed_feature, student_projected_feature

多对比正则化训练

硬对比

class SCRLoss(nn.Module):
    def __init__(self):
        super().__init__()
        self.vgg = Vgg19().cuda()
        self.l1 = nn.L1Loss()
        self.weights = [1.0/32, 1.0/16, 1.0/8, 1.0/4, 1.0]

    def forward(self, a, p, n):
        a_vgg, p_vgg, n_vgg = self.vgg(a), self.vgg(p), self.vgg(n)
        loss = 0
        d_ap, d_an = 0, 0
        for i in range(len(a_vgg)):
            d_ap = self.l1(a_vgg[i], p_vgg[i].detach())
            d_an = self.l1(a_vgg[i], n_vgg[i].detach())
            contrastive = d_ap / (d_an + 1e-7)
            loss += self.weights[i] * contrastive

        return loss

软对比

class HCRLoss(nn.Module):
    def __init__(self):
        super().__init__()
        self.vgg = Vgg19().cuda()
        self.l1 = nn.L1Loss()
        self.weights = [1.0/32, 1.0/16, 1.0/8, 1.0/4, 1.0]
    def forward(self, a, p, n):
        a_vgg, p_vgg, n_vgg = self.vgg(a), self.vgg(p), self.vgg(n)
        loss = 0
        d_ap, d_an = 0, 0
        for i in range(len(a_vgg)):
            b, c, h, w = a_vgg[i].shape
            d_ap = self.l1(a_vgg[i], p_vgg[i].detach())
            # a_vgg[i].unsqueeze(1).expand(b, b, c, h, w): a_vgg[i][0, 0] == a_vgg[i][0, 1] == a_vgg[i][0, 2]...
            # n_vgg[i].expand(b, b, c, h, w): a_vgg[i][0] == a_vgg[i][1] == a_vgg[i][2]..., but a_vgg[i][0, 0] != a_vgg[i][0, 1]
            d_an = self.l1(a_vgg[i].unsqueeze(1).expand(b, b, c, h, w), n_vgg[i].expand(b, b, c, h, w).detach())
            contrastive = d_ap / (d_an + 1e-7)
            loss += self.weights[i] * contrastive
        return loss

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

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

相关文章

Python(二):Python简介

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

ELFK——ELK结合filebeat日志分析系统(纵使生活万般磨难,也要笑对生活)

文章目录 一、filebeat二、ELFK1.原理简介 三、部署FilebeatELK1.解压安装2.设置 filebeat 的主配置文件3.启动 filebeat4&#xff0e;在 Logstash 组件所在节点上新建一个 Logstash 配置文件5&#xff0e;测试 一、filebeat Filebeat&#xff0c;轻量级的开源日志文件数据搜集…

高精度电流源怎么用

高精度电流源是一种用于产生高精度、高稳定性和低噪声的直流或交流电流信号的设备。它主要应用于各种实验和测试领域&#xff0c;例如半导体器件测试、传感器校准、精密测量和医疗检测等。高精度电流源的作用是提供可靠的电流输出信号&#xff0c;在实验和测试中获得精确和准确…

聚焦地下停车场污染死角|气体检测仪让您一目了然

由于地下停车场属于封闭式或半封闭式建筑&#xff0c;近年来越来越多高端住宅、办公楼宇的物业管理者收到投诉反应地下停车场的空气质量差的问题。那么地下空气污染有哪些呢&#xff1f; 根据空气监测工程师的检测表明&#xff0c;与地面不同&#xff0c;地下停车场的汽车起动…

一次元数据空间内存溢出的排查记录 | 京东云技术团队

在应用中&#xff0c;我们使用的 SpringData ES的 ElasticsearchRestTemplate来做查询&#xff0c;使用方式不对&#xff0c;导致每次ES查询时都新实例化了一个查询对象&#xff0c;会加载相关类到元数据中。最终长时间运行后元数据出现内存溢出&#xff1b; 问题原因&#xf…

vue 动态引入图片地址的方法

我们直接使用 v-bind 的方式导入无法正常导入 <image :src"item.src" alt""/> 是因为 页面显示为htmlcssjs而vue变成我们可以看到的样子是需要打包变成htmlcssjs的&#xff0c; 在打包过程中将item.src的变量 取出变成/image/icon.svg只是 将地址…

集中式自动抄表系统原理与应用

集中式自动抄表系统是一种自动采集电表、水表、气表等计量数据的系统&#xff0c;其原理是通过一定的通信方式&#xff0c;将计量数据从表端传输到数据采集器&#xff0c;再由数据采集器上传至云端或后台处理系统&#xff0c;从而实现对表数据的自动采集、统计和分析。 集中式…

机器学习之随机森林(Random forest)

1 什么是随机森林 随机森林是一种监督式算法&#xff0c;使用由众多决策树组成的一种集成学习方法&#xff0c;输出是对问题最佳答案的共识。随机森林可用于分类或回归&#xff0c;是一种主流的集成学习算法。 1.1 随机森林算法原理 随机森林中有许多的分类树。我们要将一个输…

【Spring】使用注解读取和存储Bean对象

哈喽&#xff0c;哈喽&#xff0c;大家好~ 我是你们的老朋友&#xff1a;保护小周ღ 谈起Java 圈子里的框架&#xff0c;最年长最耀眼的莫过于 Spring 框架啦&#xff0c;本期给大家带来的是&#xff1a; 将对象存储到 Spring 中、Bean 对象的命名规则、从Spring 中获取bean …

解决github无法拉取submodule子模块的问题

引言 当使用git clone --recursive url 拉取一个配置了子模块的仓库后&#xff0c;会卡住。 同时在使用git clone 拉去https的url时&#xff0c;同样可能会出现一直卡在cloning int reposity...本文提供一个简单的脚本来解决该问题。 前置准备 需要配置好git的相关配置&…

今年第十个零日漏洞,苹果发布紧急更新

苹果于7月10日发布了新一轮快速安全响应 (RSR) 更新&#xff0c;以解决在攻击中利用的一个新零日漏洞。 苹果在iOS和macOS的更新公告中引用了一位匿名安全专家对该漏洞&#xff08;CVE-2023-37450&#xff09;的描述&#xff0c;表示“苹果已获悉有关此漏洞可能已被积极利用的…

自动化测试集成指南 -- 本地单元测试

构建本地单元测试 简介&#xff1a; 单元测试(Unit Test) 是针对 程序的最小单元 来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。一个单元可能是单个程序、类、对象、方法等。 如何区分单元测试和集成测试&#xff0c;一般情况下&#xff0c;单元测试应该不…

jacoco merge 合并代码覆盖率(同一个项目代码没有修改)

相关文章&#xff1a; jacoco代码覆盖率_jacoco覆盖率_做测试的喵酱的博客-CSDN博客 一、背景 前提&#xff1a; 同一个项目&#xff0c;代码没有修改的情况下&#xff0c;合并多个代码覆盖率&#xff0c;实现全量代码覆盖率。 java -jar jacococli.jar merge jacoco.exec …

PFC-34、PMO-78、HD3-AMPS比例控制阀放大器

比例驱动放大器 用于HD2-PS、HD3-PS、HD3-AMPS、HD3-PS8、HD5-PS、HD3-PMO、PMO-78、PRO-M24、AMF-RE、PFC-34、PFC-78、PFP3-78电磁比例阀 DIN 连接器安装 微控制器设计 独立调整&#xff08;斜坡上升 - 斜坡下降&#xff09; 3位LED显示屏 显示和调整实际值&#xff08…

转换成mp4格式的方法有哪些?分享两个给大家!

在数字化的世界中&#xff0c;我们经常需要处理各种格式的视频文件。MP4是一种非常常见的视频格式&#xff0c;由于其优秀的兼容性和较小的文件大小&#xff0c;它被广泛用于在线播放、视频编辑和共享。然而&#xff0c;我们可能会遇到一些非MP4格式的视频文件&#xff0c;这就…

YOLO V5 ROS功能包配置及运行(亲测可用、附ROS功能包源码)

一、 依赖项 1. Ubuntu 18.04 安装opencv 4.2.0/4.6.0链接&#xff1a; 查看当前opencv版本 pkg-config --modversion opencv 安装opencv 4.2.0链接&#xff1a; https://note.youdao.com/s/R6ddu2ou 2. 安装PyTorch 官网链接&#xff1a; https://pytorch.org/get-started…

美创科技获2023年杭州市总部企业认定

日前&#xff0c;2023年度杭州市总部企业认定名单新鲜出炉&#xff0c;美创科技被认定“2023年度杭州市总部企业”。 2023年度杭州市总部企业认定名单(排名不分先后) ‍为进一步扶持、培育和引进总部企业&#xff0c;积极打造全国一流总部经济中心&#xff0c;根据《关于推动杭…

vite性能优化提升开发体验之hmr和预编译

一、vite中的预编译 1. 预编译概念介绍 Vite&#xff0c;一个由Vue.js开发者尤雨溪开发的新型前端构建工具&#xff0c;主要利用了现代浏览器支持的ESM&#xff08;ES模块&#xff09;来进行快速开发。Vite在法语中意为“快”&#xff0c;其中最大的亮点就是其开发服务器启动…

gzyj 安全处理

目录 现场操作指导 解决方案 细节验证 4.1.1.1 4.1.1.2 4.1.1.3 4.1.1.4 4.1.1.5 4.1.2.1 4.1.2.2 4.1.2.3 4.1.2.4 4.1.2.5 4.1.3.1 4.1.3.2 4.1.3.3 4.1.3.4 技术支持可以仅看第一节即可。 现场操作指导 &#xff08;1&#xff09; 升级vms (2) 升级 meshview (3) nm…

从推动到拉动:研发效能提升的第一性原理

导语 |随着企业业务的快速发展&#xff0c;产品迭代速度越来越成为企业发展制胜的关键因素。在业务迅速扩张之下&#xff0c;企业研发团队的规模也在不断壮大。如何有效管理研发团队&#xff0c;又该如何提升企业研发效能&#xff0c;让企业在市场竞争中立于不败之地成为了一堂…