【AI学习】RadioDiff:代码学习

之前学习了RadioDiff这篇论文,最近在复刻相关代码。

这段代码实现了一个基于潜在扩散模型(Latent Diffusion Model, LDM)的训练框架。借助DeepSeek总体学习一下:

1. 整体结构

代码主要分为以下几个部分:

  • 参数解析和配置加载(parse_args, load_conf
  • 主训练函数(main
  • 训练器类(Trainer
  • 主程序入口

2. 主要组件

2.1 模型架构
  1. AutoencoderKL:变分自编码器,用于将图像编码到潜在空间
  2. Unet:条件UNet模型,用于扩散过程
  3. LatentDiffusion:将VAE和UNet组合在一起的潜在扩散模型
2.2 训练流程
  1. 加载配置文件和模型
  2. 准备数据集和数据加载器
  3. 初始化训练器
  4. 执行训练循环

3. 详细解释

3.1 参数和配置
def parse_args():parser = argparse.ArgumentParser(description="training vae configure")parser.add_argument("--cfg", help="experiment configure file name", type=str, required=True)args = parser.parse_args()args.cfg = load_conf(args.cfg)return args
  • 使用argparse解析命令行参数,主要接受一个配置文件路径
  • load_conf函数加载YAML配置文件
3.2 主函数
def main(args):cfg = CfgNode(args.cfg)# 初始化VAE模型first_stage_model = AutoencoderKL(...)# 初始化UNet模型if model_cfg.model_name == 'cond_unet':from denoising_diffusion_pytorch.mask_cond_unet import Unetunet = Unet(...)# 初始化扩散模型if model_cfg.model_type == 'const_sde':from denoising_diffusion_pytorch.ddm_const_sde import LatentDiffusionldm = LatentDiffusion(...)# 统计模型参数vae_params = count_params(first_stage_model)unet_params = count_params(unet)ldm_total_params = count_params(ldm)# 准备数据集和数据加载器if data_cfg['name'] == 'edge':dataset = EdgeDataset(...)dl = DataLoader(...)# 初始化训练器并开始训练trainer = Trainer(...)trainer.train()
3.3 Trainer类

Trainer类负责整个训练过程的管理,主要功能包括:

  1. 初始化

    • 设置混合精度训练
    • 准备模型、数据加载器、优化器
    • 初始化EMA(指数移动平均)模型
    • 设置学习率调度器
  2. 训练循环

    def train(self):while self.step < self.train_num_steps:# 梯度累积for ga_ind in range(self.gradient_accumulate_every):batch = next(self.dl)# 训练步骤loss, log_dict = self.model.training_step(batch)# 反向传播self.accelerator.backward(loss)# 参数更新self.opt.step()self.opt.zero_grad()self.lr_scheduler.step()# EMA更新self.ema.update()# 定期保存和采样if self.step % self.save_and_sample_every == 0:self.save(milestone)# 生成样本图像all_images, *_ = self.model.sample(...)tv.utils.save_image(all_images, ...)
    
  3. 关键功能

    • 梯度累积:通过多次前向传播累积梯度再更新参数
    • 混合精度训练:使用accelerator自动管理
    • EMA:保持模型的平滑版本,提高稳定性
    • 定期采样:生成样本图像监控训练进度
3.4 模型保存和加载
def save(self, milestone):data = {'step': self.step,'model': self.accelerator.get_state_dict(self.model),'opt': self.opt.state_dict(),'ema': self.ema.state_dict(),...}torch.save(data, f'model-{milestone}.pt')def load(self, milestone):data = torch.load(...)model.load_state_dict(data['model'])self.opt.load_state_dict(data['opt'])...

4. 关键技术点

  1. 潜在扩散模型

    • 先在潜在空间进行扩散过程,再通过VAE解码
    • 降低计算成本,提高生成质量
  2. 条件生成

    • UNet支持条件输入(cond_in_dim, cond_dim等参数)
    • 可用于图像到图像转换任务
  3. 分布式训练

    • 使用Accelerator库简化多GPU训练
    • 支持混合精度训练
  4. 训练技巧

    • EMA模型平滑
    • 学习率调度
    • 梯度裁剪

5. 配置文件

代码通过YAML文件配置模型和训练参数,包括:

  • 模型架构参数
  • 训练超参数
  • 数据路径和预处理设置

6. 应用场景

这段代码适用于:

  • 图像生成
  • 图像修复
  • 图像到图像转换
  • 其他基于扩散模型的生成任务

总结来说,这是一个完整的潜在扩散模型训练框架,包含了从数据加载、模型定义、训练循环到结果保存的全流程实现,支持分布式训练和各种训练优化技术。

完整代码

import yaml
import argparse
import math
import torch
from lib import loaders
import torch.nn as nn
from tqdm.auto import tqdm
from denoising_diffusion_pytorch.ema import EMA
from accelerate import Accelerator, DistributedDataParallelKwargs
from torch.utils.tensorboard import SummaryWriter
from denoising_diffusion_pytorch.utils import *
import torchvision as tv
from denoising_diffusion_pytorch.encoder_decoder import AutoencoderKL
# from denoising_diffusion_pytorch.transmodel import TransModel
from denoising_diffusion_pytorch.data import *
from torch.utils.data import DataLoader
from multiprocessing import cpu_count
from fvcore.common.config import CfgNode
from taming.modules.util import count_params
os.environ["CUDA_VISIBLE_DEVICES"]="1"def parse_args():parser = argparse.ArgumentParser(description="training vae configure")parser.add_argument("--cfg", help="experiment configure file name", type=str, required=True)# parser.add_argument("")args = parser.parse_args()args.cfg = load_conf(args.cfg)return argsdef load_conf(config_file, conf={}):with open(config_file) as f:exp_conf = yaml.load(f, Loader=yaml.FullLoader)for k, v in exp_conf.items():conf[k] = vreturn confdef main(args):cfg = CfgNode(args.cfg)# logger = create_logger(root_dir=cfg['out_path'])# writer = SummaryWriter(cfg['out_path'])model_cfg = cfg.modelfirst_stage_cfg = model_cfg.first_stagefirst_stage_model = AutoencoderKL(ddconfig=first_stage_cfg.ddconfig,lossconfig=first_stage_cfg.lossconfig,embed_dim=first_stage_cfg.embed_dim,ckpt_path=first_stage_cfg.ckpt_path,)if model_cfg.model_name == 'cond_unet':from denoising_diffusion_pytorch.mask_cond_unet import Unetunet_cfg = model_cfg.unetunet = Unet(dim=unet_cfg.dim,channels=unet_cfg.channels,dim_mults=unet_cfg.dim_mults,learned_variance=unet_cfg.get('learned_variance', False),out_mul=unet_cfg.out_mul,cond_in_dim=unet_cfg.cond_in_dim,cond_dim=unet_cfg.cond_dim,cond_dim_mults=unet_cfg.cond_dim_mults,window_sizes1=unet_cfg.window_sizes1,window_sizes2=unet_cfg.window_sizes2,fourier_scale=unet_cfg.fourier_scale,cfg=unet_cfg,)else:raise NotImplementedErrorif model_cfg.model_type == 'const_sde':from denoising_diffusion_pytorch.ddm_const_sde import LatentDiffusionelse:raise NotImplementedError(f'{model_cfg.model_type} is not surportted !')ldm = LatentDiffusion(model=unet,auto_encoder=first_stage_model,train_sample=model_cfg.train_sample,image_size=model_cfg.image_size,timesteps=model_cfg.timesteps,sampling_timesteps=model_cfg.sampling_timesteps,loss_type=model_cfg.loss_type,objective=model_cfg.objective,scale_factor=model_cfg.scale_factor,scale_by_std=model_cfg.scale_by_std,scale_by_softsign=model_cfg.scale_by_softsign,default_scale=model_cfg.get('default_scale', False),input_keys=model_cfg.input_keys,ckpt_path=model_cfg.ckpt_path,ignore_keys=model_cfg.ignore_keys,only_model=model_cfg.only_model,start_dist=model_cfg.start_dist,perceptual_weight=model_cfg.perceptual_weight,use_l1=model_cfg.get('use_l1', True),cfg=model_cfg,)# 统计VAE(AutoencoderKL)vae_params = count_params(first_stage_model)print(f"扩散模型中的VAE参数量: {vae_params / 1e6:.2f}M")# 统计UNetunet_params = count_params(unet)print(f"UNet参数量: {unet_params:,}")print(f"UNet参数量: {unet_params / 1e6:.2f}M")# 统计整个扩散模型(包括UNet、VAE等)ldm_total_params = count_params(ldm)print(f"扩散模型总参数量: {ldm_total_params:,}")print(f"扩散模型总参数量: {ldm_total_params / 1e6:.2f}M")data_cfg = cfg.dataif data_cfg['name'] == 'edge':dataset = EdgeDataset(data_root=data_cfg.img_folder,image_size=model_cfg.image_size,augment_horizontal_flip=data_cfg.augment_horizontal_flip,cfg=data_cfg)elif data_cfg['name'] == 'radio':dataset = loaders.RadioUNet_c(phase="train")else:raise NotImplementedErrordl = DataLoader(dataset, batch_size=data_cfg.batch_size, shuffle=True, pin_memory=True,num_workers=data_cfg.get('num_workers', 2))train_cfg = cfg.trainertrainer = Trainer(ldm, dl, train_batch_size=data_cfg.batch_size,gradient_accumulate_every=train_cfg.gradient_accumulate_every,train_lr=train_cfg.lr, train_num_steps=train_cfg.train_num_steps,save_and_sample_every=train_cfg.save_and_sample_every, results_folder=train_cfg.results_folder,amp=train_cfg.amp, fp16=train_cfg.fp16, log_freq=train_cfg.log_freq, cfg=cfg,resume_milestone=train_cfg.resume_milestone,train_wd=train_cfg.get('weight_decay', 1e-4))if train_cfg.test_before:if trainer.accelerator.is_main_process:with torch.no_grad():for datatmp in dl:breakif isinstance(trainer.model, nn.parallel.DistributedDataParallel):all_images, *_ = trainer.model.module.sample(batch_size=datatmp['cond'].shape[0],cond=datatmp['cond'].to(trainer.accelerator.device),mask=datatmp['ori_mask'].to(trainer.accelerator.device) if 'ori_mask' in datatmp else None)elif isinstance(trainer.model, nn.Module):all_images, *_ = trainer.model.sample(batch_size=datatmp['cond'].shape[0],cond=datatmp['cond'].to(trainer.accelerator.device),mask=datatmp['ori_mask'].to(trainer.accelerator.device) if 'ori_mask' in datatmp else None)# all_images = torch.cat(all_images_list, dim = 0)nrow = 2 ** math.floor(math.log2(math.sqrt(data_cfg.batch_size)))tv.utils.save_image(all_images, str(trainer.results_folder / f'sample-{train_cfg.resume_milestone}_{model_cfg.sampling_timesteps}.png'), nrow=nrow)torch.cuda.empty_cache()trainer.train()passclass Trainer(object):def __init__(self,model,data_loader,train_batch_size=16,gradient_accumulate_every=1,train_lr=1e-4,train_wd=1e-4,train_num_steps=100000,save_and_sample_every=1000,num_samples=25,results_folder='./results',amp=False,fp16=False,split_batches=True,log_freq=20,resume_milestone=0,cfg={},):super().__init__()ddp_handler = DistributedDataParallelKwargs(find_unused_parameters=True)self.accelerator = Accelerator(split_batches=split_batches,mixed_precision='fp16' if fp16 else 'no',kwargs_handlers=[ddp_handler],)self.enable_resume = cfg.trainer.get('enable_resume', False)self.accelerator.native_amp = ampself.model = modelassert has_int_squareroot(num_samples), 'number of samples must have an integer square root'self.num_samples = num_samplesself.save_and_sample_every = save_and_sample_everyself.batch_size = train_batch_sizeself.gradient_accumulate_every = gradient_accumulate_everyself.log_freq = log_freqself.train_num_steps = train_num_stepsself.image_size = model.image_size# dataset and dataloader# self.ds = Dataset(folder, mask_folder, self.image_size, augment_horizontal_flip = augment_horizontal_flip, convert_image_to = convert_image_to)# dl = DataLoader(self.ds, batch_size = train_batch_size, shuffle = True, pin_memory = True, num_workers = cpu_count())dl = self.accelerator.prepare(data_loader)self.dl = cycle(dl)# optimizerself.opt = torch.optim.AdamW(filter(lambda p: p.requires_grad, model.parameters()),lr=train_lr, weight_decay=train_wd)lr_lambda = lambda iter: max((1 - iter / train_num_steps) ** 0.96, cfg.trainer.min_lr)self.lr_scheduler = torch.optim.lr_scheduler.LambdaLR(self.opt, lr_lambda=lr_lambda)# for logging results in a folder periodicallyif self.accelerator.is_main_process:self.results_folder = Path(results_folder)self.results_folder.mkdir(exist_ok=True, parents=True)self.ema = EMA(model, ema_model=None, beta=0.999,update_after_step=cfg.trainer.ema_update_after_step,update_every=cfg.trainer.ema_update_every)# step counter stateself.step = 0# prepare model, dataloader, optimizer with acceleratorself.model, self.opt, self.lr_scheduler = \self.accelerator.prepare(self.model, self.opt, self.lr_scheduler)self.logger = create_logger(root_dir=results_folder)self.logger.info(cfg)self.writer = SummaryWriter(results_folder)self.results_folder = Path(results_folder)resume_file = str(self.results_folder / f'model-{resume_milestone}.pt')if os.path.isfile(resume_file):self.load(resume_milestone)def save(self, milestone):if not self.accelerator.is_local_main_process:returnif self.enable_resume:data = {'step': self.step,'model': self.accelerator.get_state_dict(self.model),'opt': self.opt.state_dict(),'lr_scheduler': self.lr_scheduler.state_dict(),'ema': self.ema.state_dict(),'scaler': self.accelerator.scaler.state_dict() if exists(self.accelerator.scaler) else None}# data_only_model = {'ema': self.ema.state_dict(),}torch.save(data, str(self.results_folder / f'model-{milestone}.pt'))else:data = {'model': self.accelerator.get_state_dict(self.model),}torch.save(data, str(self.results_folder / f'model-{milestone}.pt'))def load(self, milestone):assert self.enable_resume; 'resume is available only if self.enable_resume is True !'accelerator = self.acceleratordevice = accelerator.devicedata = torch.load(str(self.results_folder / f'model-{milestone}.pt'),map_location=lambda storage, loc: storage)model = self.accelerator.unwrap_model(self.model)model.load_state_dict(data['model'])if 'scale_factor' in data['model']:model.scale_factor = data['model']['scale_factor']self.step = data['step']self.opt.load_state_dict(data['opt'])self.lr_scheduler.load_state_dict(data['lr_scheduler'])if self.accelerator.is_main_process:self.ema.load_state_dict(data['ema'])if exists(self.accelerator.scaler) and exists(data['scaler']):self.accelerator.scaler.load_state_dict(data['scaler'])def train(self):accelerator = self.acceleratordevice = accelerator.devicewith tqdm(initial=self.step, total=self.train_num_steps, disable=not accelerator.is_main_process) as pbar:while self.step < self.train_num_steps:total_loss = 0.total_loss_dict = {'loss_simple': 0., 'loss_vlb': 0., 'total_loss': 0., 'lr': 5e-5}for ga_ind in range(self.gradient_accumulate_every):# data = next(self.dl).to(device)batch = next(self.dl)for key in batch.keys():if isinstance(batch[key], torch.Tensor):batch[key].to(device)if self.step == 0 and ga_ind == 0:if isinstance(self.model, nn.parallel.DistributedDataParallel):self.model.module.on_train_batch_start(batch)else:self.model.on_train_batch_start(batch)with self.accelerator.autocast():if isinstance(self.model, nn.parallel.DistributedDataParallel):loss, log_dict = self.model.module.training_step(batch)else:loss, log_dict = self.model.training_step(batch)loss = loss / self.gradient_accumulate_everytotal_loss += loss.item()loss_simple = log_dict["train/loss_simple"].item() / self.gradient_accumulate_everyloss_vlb = log_dict["train/loss_vlb"].item() / self.gradient_accumulate_everytotal_loss_dict['loss_simple'] += loss_simpletotal_loss_dict['loss_vlb'] += loss_vlbtotal_loss_dict['total_loss'] += total_loss# total_loss_dict['s_fact'] = self.model.module.scale_factor# total_loss_dict['s_bias'] = self.model.module.scale_biasself.accelerator.backward(loss)total_loss_dict['lr'] = self.opt.param_groups[0]['lr']describtions = dict2str(total_loss_dict)describtions = "[Train Step] {}/{}: ".format(self.step, self.train_num_steps) + describtionsif accelerator.is_main_process:pbar.desc = describtionsif self.step % self.log_freq == 0:if accelerator.is_main_process:# pbar.desc = describtions# self.logger.info(pbar.__str__())self.logger.info(describtions)accelerator.clip_grad_norm_(filter(lambda p: p.requires_grad, self.model.parameters()), 1.0)# pbar.set_description(f'loss: {total_loss:.4f}')accelerator.wait_for_everyone()self.opt.step()self.opt.zero_grad()self.lr_scheduler.step()if accelerator.is_main_process:self.writer.add_scalar('Learning_Rate', self.opt.param_groups[0]['lr'], self.step)self.writer.add_scalar('total_loss', total_loss, self.step)self.writer.add_scalar('loss_simple', loss_simple, self.step)self.writer.add_scalar('loss_vlb', loss_vlb, self.step)accelerator.wait_for_everyone()self.step += 1# if self.step >= int(self.train_num_steps * 0.2):if accelerator.is_main_process:self.ema.to(device)self.ema.update()if self.step != 0 and self.step % self.save_and_sample_every == 0:milestone = self.step // self.save_and_sample_everyself.save(milestone)self.model.eval()# self.ema.ema_model.eval()with torch.no_grad():# img = self.dl# batches = num_to_groups(self.num_samples, self.batch_size)# all_images_list = list(map(lambda n: self.model.module.validate_img(ns=self.batch_size), batches))if isinstance(self.model, nn.parallel.DistributedDataParallel):# all_images = self.model.module.sample(batch_size=self.batch_size)all_images, *_ = self.model.module.sample(batch_size=batch['cond'].shape[0],cond=batch['cond'],mask=batch['ori_mask'] if 'ori_mask' in batch else None)elif isinstance(self.model, nn.Module):# all_images = self.model.sample(batch_size=self.batch_size)all_images, *_ = self.model.sample(batch_size=batch['cond'].shape[0],cond=batch['cond'],mask=batch['ori_mask'] if 'ori_mask' in batch else None)# all_images = torch.clamp((all_images + 1.0) / 2.0, min=0.0, max=1.0)# all_images = torch.cat(all_images_list, dim = 0)# nrow = 2 ** math.floor(math.log2(math.sqrt(self.batch_size)))nrow = 2 ** math.floor(math.log2(math.sqrt(batch['cond'].shape[0])))tv.utils.save_image(all_images, str(self.results_folder / f'sample-{milestone}.png'), nrow=nrow)self.model.train()accelerator.wait_for_everyone()pbar.update(1)accelerator.print('training complete')if __name__ == "__main__":args = parse_args()main(args)pass

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

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

相关文章

【专题十七】多源 BFS

&#x1f4dd;前言说明&#xff1a; 本专栏主要记录本人的基础算法学习以及LeetCode刷题记录&#xff0c;按专题划分每题主要记录&#xff1a;&#xff08;1&#xff09;本人解法 本人屎山代码&#xff1b;&#xff08;2&#xff09;优质解法 优质代码&#xff1b;&#xff…

京东零售在智能供应链领域的前沿探索与技术实践

近日&#xff0c;“智汇运河 智算未来”2025人工智能创新创业大会在杭州召开。香港工程科学院院士、香港大学副校长、研究生院院长、讲座教授、京东零售供应链首席科学家申作军教授与供应链算法团队技术总监戚永志博士受邀出席并担任《AI智慧物流与供应链分享会》联席主席&…

MyBatisPlus之CRUD接口(IService与BaseMapper)

MyBatisPlus之CRUD接口—IService与BaseMapper一、BaseMapper与IService的关系二、BaseMapper核心方法详解2.1 新增操作&#xff08;Insert&#xff09;2.2 查询操作&#xff08;Select&#xff09;2.3 更新操作&#xff08;Update&#xff09;2.4 删除操作&#xff08;Delete&…

axios请求的取消

axios请求的取消解决&#xff1a;axios请求的取消解决&#xff1a;axios请求的取消 在使用 Axios 发起请求时&#xff0c;有时候你可能需要取消这些请求&#xff0c;比如当组件销毁时或者用户操作导致不再需要获取之前发起的请求结果。Axios 支持通过 Cancel Token 取消请求。 …

深入理解C++中的Lazy Evaluation:延迟计算的艺术

在编程世界里&#xff0c;“最好的运算就是从未执行的运算” —— 这句话深刻揭示了性能优化的核心思路。如果一个计算过程最终不会被使用&#xff0c;那么提前执行它就是纯粹的资源浪费。这种思想衍生出了 Lazy Evaluation&#xff08;缓式评估&#xff09; 技术&#xff1a;延…

php完整处理word中表单数据的方法

使用php基础方式实现word中表单处理<?php/*** zipFile 类用于处理 .docx 文件的解压、修改和重新打包*/ class zipFile {/** var ZipArchive ZIP 文件对象 */private $zipFile;/** var string 临时目录路径 */private $tempDir;/** var string 嵌入的 Excel 文件临时目录路…

Node.js 操作 MongoDB

目录 Node.js 操作 MongoDB 一、什么是 MongoDB&#xff1f; 二、MongoDB 的功能概览 三、MongoDB 的安装与启动 安装 MongoDB&#xff08;以本地安装为例&#xff09; 启动 MongoDB 四、Node.js 如何连接 MongoDB&#xff1f; 使用 Mongoose ODM 工具 建立连接 五、…

先学Python还是c++?

选择先学Python还是C&#xff0c;取决于你的学习目标、应用场景和职业规划。以下是两者的对比分析和建议&#xff0c;帮助你做出更适合自己的选择&#xff1a;一、核心差异对比维度PythonC学习曲线简单易上手&#xff08;语法接近自然语言&#xff09;复杂&#xff08;需理解指…

Trae + Notion MCP:将你的Notion数据库升级为智能对话机器人

前言 Notion作为一款功能强大的信息管理工具&#xff0c;被广泛用于项目跟踪、知识库构建和数据整理。然而&#xff0c;随着数据量的增长&#xff0c;我们常常会发现自己陷入了重复和繁琐的操作中。比如&#xff0c;为了找到符合特定条件的几条数据&#xff0c;需要在庞大的数…

【iOS】retain/release底层实现原理

文章目录前言前情知识retain和release的实现原理&#xff08;MRC手动管理&#xff09;retain&#xff08;MRC手动管理&#xff09;retain源码内联函数rootRetain源码相关的sidetable_tryRetain()方法retain底层工作流程总结releaserelease源码内联函数rootRelease源码小结前言 …

文件同步神器-rsync命令讲解

rsync 是一个强大的文件同步与传输工具&#xff0c;广泛用于本地或远程服务器之间的高效文件备份、镜像或同步。其核心优势是通过增量传输​&#xff08;仅传输文件差异部分&#xff09;和压缩减少数据传输量&#xff0c;同时支持保留文件元数据&#xff08;如权限、时间戳、所…

Rust: 工具链版本更新

遇到 cargo build --release 错误&#xff0c;比如&#xff0c;当前 Rust 工具链版本&#xff08;1.78.0&#xff09;低于依赖项所需的最低版本&#xff08;部分依赖要求 ≥1.82.0&#xff09;。以下是系统化的解决方案&#xff1a; &#x1f527; 一、升级 Rust 工具链&#x…

Prompt-to-Prompt| 修改Attention会有“反向传播”或梯度计算?

需要注意的几个问题&#xff1a;额外计算开销&#xff1a;Cross-Attention Control原因&#xff1a;Prompt-to-Prompt的编辑方法需要动态干预交叉注意力&#xff08;Cross-Attention&#xff09;层的权重&#xff0c;这会引入额外的计算和显存占用&#xff1a;需要缓存注意力矩…

电商API接口的优势、数据采集方法及功能说明

一、电商API接口的核心优势1. 高效性与准确性数据采集效率&#xff1a;API通过标准化参数&#xff08;如商品ID、类目&#xff09;直接获取结构化数据&#xff08;JSON/XML&#xff09;&#xff0c;无需解析HTML&#xff0c;减少误差。例如&#xff0c;采集1000条商品信息&…

iOS企业签名掉签,iOS企业签名掉签了怎么办?

不能上架到App Store的iOS应用 &#xff0c;几乎每一个开发者的选择都是通过iOS签名这种内测渠道来完成APP的上架任务&#xff0c;最常用的就是企业签名、超级签名以及TF上架&#xff0c;其中最受欢迎的当属于企业签名了。不过企业签名会出现掉签的现象&#xff0c;那么企业签名…

存储成本深度优化:冷热分层与生命周期管理——从视频平台年省200万实践解析智能存储架构

一、冷热分层&#xff1a;存储成本优化的核心逻辑1.1 数据访问的“二八定律”据行业统计&#xff0c;80%的访问集中在20%的热数据上&#xff0c;而超过90天的历史数据访问频率下降70%以上。某视频平台存储超10PB媒体文件&#xff0c;未分层前年存储成本高达680万元&#xff0c;…

Java设计模式之《备忘录模式》

目录 1. 概念 1.1、定义 1.2、适用场景 2、角色划分 3、实现 1、Originator&#xff08;发起人&#xff09; 2、Memento&#xff08;备忘录&#xff09; 3、Caretaker&#xff08;管理者&#xff09; 4、使用示例 4、优缺点 4.1、优点 4.2、缺点 前言 备忘录模式是…

SpringBoot 多环境配置

在实际项目开发中&#xff0c;不同环境往往有不同的配置需求&#xff1a; 开发环境&#xff08;dev&#xff09;&#xff1a;本地调试&#xff0c;连接测试数据库&#xff1b;测试环境&#xff08;test&#xff09;&#xff1a;接口联调&#xff0c;接近真实场景&#xff1b;生…

延凡智慧医院数字孪生平台

延凡智慧医院数字孪生平台是延凡科技依托物联网、数字孪生、AI 算法及边缘计算技术打造的医疗场景全要素数字化解决方案&#xff0c;通过构建医院物理实体与虚拟空间的实时映射&#xff0c;实现医疗资源优化、运营效率提升及患者体验升级。一、平台价值&#xff08;一&#xff…

谈谈WebAssembly、PWA、Web Workers的作用和场景

WebAssembly、PWA 和 Web Workers 是现代 Web 开发中提升性能、扩展能力的重要技术&#xff0c;各自解决不同场景的问题&#xff0c;以下结合实际使用经验分析&#xff1a;一、WebAssembly&#xff08;Wasm&#xff09;&#xff1a;高性能代码执行作用&#xff1a;WebAssembly …