AIGC专栏9——Scalable Diffusion Models with Transformers (DiT)结构解析

AIGC专栏9——Scalable Diffusion Models with Transformers (DiT)结构解析

  • 学习前言
  • 源码下载地址
  • 网络构建
    • 一、什么是Diffusion Transformer (DiT)
    • 二、DiT的组成
    • 三、生成流程
      • 1、采样流程
        • a、生成初始噪声
        • b、对噪声进行N次采样
        • c、单次采样解析
          • I、预测噪声
          • II、施加噪声
        • d、预测噪声过程中的网络结构解析
          • i、adaLN-Zero结构解析
          • ii、patch分块处理
          • iii、Transformer特征提取
          • iv、上采样
      • 3、隐空间解码生成图片
  • 类别到图像预测过程代码

学习前言

近期Sora大火,它底层是Diffusion Transformer,本质上是使用Transformer结构代替原本的Unet进行噪声预测,好处是统一了文本生成与视频生成的结构。这训练优化和预测优化而言是个好事,因为只需要优化一种结构就够了。虽然觉得OpenAI是大力出奇迹,但还是得学!
在这里插入图片描述

源码下载地址

https://github.com/bubbliiiing/DiT-pytorch

喜欢的可以点个star噢。

网络构建

一、什么是Diffusion Transformer (DiT)

DiT基于扩散模型,所以不免包含不断去噪的过程,如果是图生图的话,还有不断加噪的过程,此时离不开DDPM那张老图,如下:
在这里插入图片描述
DiT相比于DDPM,使用了更快的采样器,也使用了更大的分辨率,与Stable Diffusion一样使用了隐空间的扩散,但可能更偏研究性质一些,没有使用非常大的数据集进行预训练,只使用了imagenet进行预训练。

与Stable Diffusion不同的是,DiT的网络结构完全由Transformer组成,没有Unet中大量的上下采样,结构更为简单清晰。

本文主要是解析一下整个DiT模型的结构组成,并简单一次扩散,多次扩散的流程。本文代码来自于Diffusers,Diffusers代码较为简单清晰,是一个非常好的仓库,学习起来也比较快。

二、DiT的组成

DiT由三大部分组成。
1、Sampler采样器。
2、Variational Autoencoder (VAE) 变分自编码器。
3、UNet 主网络,噪声预测器。

每一部分都很重要,由于DiT的官方版本并没有在 大规模文本图片 的 数据集上训练,只使用了imagenet进行预训练。所以它并没有文本输入,而是以标签作为输入。因此,DiT只能按照类别进行图片生成,可以生成imagenet中的1000类

三、生成流程

在这里插入图片描述
生成流程分为两个部分:
1、生成正态分布向量后进行若干次采样。
2、进行解码。

由于DiT只能按照类别进行图片生成,所以无需对文本进行编码,直接传入类别的对应的id(0-1000)即可指定类别。

# --------------------------------- #
#   前处理
# --------------------------------- #
# 生成latent
latents = randn_tensor(shape=(batch_size, latent_channels, latent_size, latent_size),generator=generator,device=self._execution_device,dtype=self.transformer.dtype,
)
latent_model_input = torch.cat([latents] * 2) if guidance_scale > 1 else latents# 将输入的label 与 null label进行concat,null label是负向提示类。
class_labels = torch.tensor(class_labels, device=self._execution_device).reshape(-1)
class_null = torch.tensor([1000] * batch_size, device=self._execution_device)
class_labels_input = torch.cat([class_labels, class_null], 0) if guidance_scale > 1 else class_labels# 设置生成的步数
self.scheduler.set_timesteps(num_inference_steps)# --------------------------------- #
#   扩散生成
# --------------------------------- #
# 开始N步扩散的循环
for t in self.progress_bar(self.scheduler.timesteps):if guidance_scale > 1:half = latent_model_input[: len(latent_model_input) // 2]latent_model_input = torch.cat([half, half], dim=0)latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)# 处理timestepstimesteps = tif not torch.is_tensor(timesteps):is_mps = latent_model_input.device.type == "mps"if isinstance(timesteps, float):dtype = torch.float32 if is_mps else torch.float64else:dtype = torch.int32 if is_mps else torch.int64timesteps = torch.tensor([timesteps], dtype=dtype, device=latent_model_input.device)elif len(timesteps.shape) == 0:timesteps = timesteps[None].to(latent_model_input.device)# broadcast to batch dimension in a way that's compatible with ONNX/Core MLtimesteps = timesteps.expand(latent_model_input.shape[0])# 将隐含层特征、时间步和种类输入传入到transformers中noise_pred = self.transformer(latent_model_input, timestep=timesteps, class_labels=class_labels_input).sample# perform guidanceif guidance_scale > 1:# 在通道上做分割,取出生图部分的通道eps, rest = noise_pred[:, :latent_channels], noise_pred[:, latent_channels:]cond_eps, uncond_eps = torch.split(eps, len(eps) // 2, dim=0)half_eps = uncond_eps + guidance_scale * (cond_eps - uncond_eps)eps = torch.cat([half_eps, half_eps], dim=0)noise_pred = torch.cat([eps, rest], dim=1)# 对结果进行分割,取出生图部分的通道if self.transformer.config.out_channels // 2 == latent_channels:model_output, _ = torch.split(noise_pred, latent_channels, dim=1)else:model_output = noise_pred# 通过采样器将这一步噪声施加到隐含层latent_model_input = self.scheduler.step(model_output, t, latent_model_input).prev_sampleif guidance_scale > 1:latents, _ = latent_model_input.chunk(2, dim=0)
else:latents = latent_model_input# --------------------------------- #
#   后处理
# --------------------------------- #
# 通过vae进行解码
latents = 1 / self.vae.config.scaling_factor * latents
samples = self.vae.decode(latents).samplesamples = (samples / 2 + 0.5).clamp(0, 1)# 转化为float32类别
samples = samples.cpu().permute(0, 2, 3, 1).float().numpy()

1、采样流程

a、生成初始噪声

在这里插入图片描述

在生成初始噪声前介绍一下VAE,VAE是变分自编码器,可以将输入图片进行编码,一个高宽原本为256x256x3的图片在使用VAE编码后会变成32x32x4这个4是人为设定的,不必纠结为什么不是3。这个时候我们就使用一个相对简单的矩阵代替原有的256x256x3的图片了,传输与存储成本就很低。在实际要去看的时候,可以对32x32x4的矩阵进行解码,获得256x256x3的图片。

因此,如果 我们要生成一个256x256x3的图片,那么我们只需要初始化一个32x32x4的隐向量,在隐空间进行扩散即可。在隐空间扩散好后,再使用解码器就可以生成256x256x3的图像。

在代码中,我们确实是这么做的,初始噪声的生成函数为randn_tensor,是diffusers自带的一个函数,尽管它写的很长,但实际生成初始噪声的代码只有一行:
在这里插入图片描述

latents = torch.randn(shape, generator=generator, device=rand_device, dtype=dtype, layout=layout).to(device)

代码本来位于diffusers的工具文件中,为了方便查看,我将其复制到nets/pipeline.py中。

b、对噪声进行N次采样

在这里插入图片描述

既然Stable Diffusion是一个不断扩散的过程,那么少不了不断的去噪声,那么怎么去噪声便是一个问题。

在上一步中,我们已经获得了一个latents,它是一个符合正态分布的向量,我们便从它开始去噪声。

在代码中,我们有一个对时间步的循环,会不断的将隐含层向量输入到transformers中进行噪声预测,并且一步一步的去噪。

# --------------------------------- #
#   扩散生成
# --------------------------------- #
# 开始N步扩散的循环
for t in self.progress_bar(self.scheduler.timesteps):if guidance_scale > 1:half = latent_model_input[: len(latent_model_input) // 2]latent_model_input = torch.cat([half, half], dim=0)latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)# 处理timestepstimesteps = tif not torch.is_tensor(timesteps):is_mps = latent_model_input.device.type == "mps"if isinstance(timesteps, float):dtype = torch.float32 if is_mps else torch.float64else:dtype = torch.int32 if is_mps else torch.int64timesteps = torch.tensor([timesteps], dtype=dtype, device=latent_model_input.device)elif len(timesteps.shape) == 0:timesteps = timesteps[None].to(latent_model_input.device)# broadcast to batch dimension in a way that's compatible with ONNX/Core MLtimesteps = timesteps.expand(latent_model_input.shape[0])# 将隐含层特征、时间步和种类输入传入到transformers中noise_pred = self.transformer(latent_model_input, timestep=timesteps, class_labels=class_labels_input).sample# perform guidanceif guidance_scale > 1:# 在通道上做分割,取出生图部分的通道eps, rest = noise_pred[:, :latent_channels], noise_pred[:, latent_channels:]cond_eps, uncond_eps = torch.split(eps, len(eps) // 2, dim=0)half_eps = uncond_eps + guidance_scale * (cond_eps - uncond_eps)eps = torch.cat([half_eps, half_eps], dim=0)noise_pred = torch.cat([eps, rest], dim=1)# 对结果进行分割,取出生图部分的通道if self.transformer.config.out_channels // 2 == latent_channels:model_output, _ = torch.split(noise_pred, latent_channels, dim=1)else:model_output = noise_pred# 通过采样器将这一步噪声施加到隐含层latent_model_input = self.scheduler.step(model_output, t, latent_model_input).prev_sample
c、单次采样解析
I、预测噪声

在进行单次采样前,需要首先判断是否有负向提示类,如果有,我们需要同时处理负向提示类,否则仅仅需要处理正向提示类。实际使用的时候一般都有负向提示类(效果会好一些),所以默认进入对应的处理过程。

在处理负向提示类时,我们对输入进来的隐向量进行复制,一个属于正向提示类(0-999),一个属于负向提示类(1000)。它们是在batch_size维度进行堆叠,二者不会互相影响。然后我们将正向提示类负向提示类(1000)也在batch_size维度堆叠。代码中,如果guidance_scale>1则代表需要负向提示类

# --------------------------------- #
#   前处理
# --------------------------------- #
# 生成latent
latents = randn_tensor(shape=(batch_size, latent_channels, latent_size, latent_size),generator=generator,device=self._execution_device,dtype=self.transformer.dtype,
)
latent_model_input = torch.cat([latents] * 2) if guidance_scale > 1 else latents# 将输入的label 与 null label进行concat,null label是负向提示类。
class_labels = torch.tensor(class_labels, device=self._execution_device).reshape(-1)
class_null = torch.tensor([1000] * batch_size, device=self._execution_device)
class_labels_input = torch.cat([class_labels, class_null], 0) if guidance_scale > 1 else class_labels

堆叠完后,我们将隐向量、步数和类别条件一起传入网络中,将结果在bs维度进行使用chunk进行分割。

因为我们在堆叠时,正向提示类放在了前面。因此分割好后,前半部分cond_eps属于利用正向提示类得到的,后半部分uncond_eps属于利用负向提示类得到的,我们本质上应该扩大正向提示类的影响,远离负向提示类的影响。因此,我们使用cond_eps-uncond_eps计算二者的距离,使用scale扩大二者的距离。在uncond_eps基础上,得到最后的隐向量。

# 堆叠完后,隐向量、步数和prompt条件一起传入网络中,将结果在bs维度进行使用chunk进行分割
e_t_uncond, e_t = self.model.apply_model(x_in, t_in, c_in).chunk(2)
e_t = e_t_uncond + unconditional_guidance_scale * (e_t - e_t_uncond)

此时获得的eps就是通过隐向量和提示类共同获得的预测噪声啦。

II、施加噪声

在获得噪声后,我们还要将获得的新噪声,按照一定的比例添加到原来的原始噪声上。

diffusers的代码并没有将施加噪声的代码写在明面上,而是使用采样器的step方法替代,采样流程与DDIM一致,因此直接参考DDIM公式即可,此前,在Stable Diffusion相关博文中写到过DDIM公式,可以参考对应博文了解一下。

latent_model_input = self.scheduler.step(model_output, t, latent_model_input).prev_sample
d、预测噪声过程中的网络结构解析

这个部分是DiT与Stable Diffusion最大的不同,DiT将网络结构从Unet转换成了Transformers,

i、adaLN-Zero结构解析

Transformers主要做的工作是结合 时间步t 和 类别 计算这一时刻的噪声。此处的Transformers结构与VIT中的Transformers基本一致,但为了融合时间步t和类别,新增了一个Embed层和adaLN-Zero结构。

  • Embed层主要是将输入进来的timestep和label进行向量化。
  • adaLN-Zero则是通过全连接对向量化后的timestep和label进行映射,然后分为6个部分,分别作用于DiT的不同阶段用于缩放(scale)、偏置(shift、bias)与门函数(gate)。

如下是Embed层和adaLN-Zero结构的代码与示意图:

class CombinedTimestepLabelEmbeddings(nn.Module):def __init__(self, num_classes, embedding_dim, class_dropout_prob=0.1):super().__init__()self.time_proj = Timesteps(num_channels=256, flip_sin_to_cos=True, downscale_freq_shift=1)self.timestep_embedder = TimestepEmbedding(in_channels=256, time_embed_dim=embedding_dim)self.class_embedder = LabelEmbedding(num_classes, embedding_dim, class_dropout_prob)def forward(self, timestep, class_labels, hidden_dtype=None):timesteps_proj = self.time_proj(timestep)timesteps_emb = self.timestep_embedder(timesteps_proj.to(dtype=hidden_dtype))  # (N, D)class_labels = self.class_embedder(class_labels)  # (N, D)conditioning = timesteps_emb + class_labels  # (N, D)return conditioningclass AdaLayerNormZero(nn.Module):"""Norm layer adaptive layer norm zero (adaLN-Zero)."""def __init__(self, embedding_dim, num_embeddings):super().__init__()self.emb = CombinedTimestepLabelEmbeddings(num_embeddings, embedding_dim)self.silu = nn.SiLU()self.linear = nn.Linear(embedding_dim, 6 * embedding_dim, bias=True)self.norm = nn.LayerNorm(embedding_dim, elementwise_affine=False, eps=1e-6)def forward(self, x, timestep, class_labels, hidden_dtype=None):emb = self.linear(self.silu(self.emb(timestep, class_labels, hidden_dtype=hidden_dtype)))shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = emb.chunk(6, dim=1)x = self.norm(x) * (1 + scale_msa[:, None]) + shift_msa[:, None]return x, gate_msa, shift_mlp, scale_mlp, gate_mlp

在这里插入图片描述

ii、patch分块处理

在代码中,我们使用一个PatchEmbed类对输入的隐含层向量进行分块,该操作便是VIT中的patchc操作,通过卷积进行类似于下采样的操作,可以减少计算量。
在这里插入图片描述
如下为patch分块处理的代码,核心是使用步长和卷积核大小一样的Conv2d模块进行处理,由于步长和卷积核大小一致,每个图片区域的特征提取过程就不会有重叠

我们初始化生成的隐含层向量为32x32x4。在DiT-XL-2中,patch处理的步长和卷积核大小为2,通道为1152,在处理完成后,特征的通道上升,高宽被压缩,此时我们获得一个16x16x1152的新特征,然后我们将其在长宽上进行平铺,获得一个256x1152的向量,并且加上位置信息。

class PatchEmbed(nn.Module):"""2D Image to Patch Embedding"""def __init__(self,height=224,width=224,patch_size=16,in_channels=3,embed_dim=768,layer_norm=False,flatten=True,bias=True,):super().__init__()num_patches = (height // patch_size) * (width // patch_size)self.flatten = flattenself.layer_norm = layer_normself.proj = nn.Conv2d(in_channels, embed_dim, kernel_size=(patch_size, patch_size), stride=patch_size, bias=bias)if layer_norm:self.norm = nn.LayerNorm(embed_dim, elementwise_affine=False, eps=1e-6)else:self.norm = Nonepos_embed = get_2d_sincos_pos_embed(embed_dim, int(num_patches**0.5))self.register_buffer("pos_embed", torch.from_numpy(pos_embed).float().unsqueeze(0), persistent=False)def forward(self, latent):latent = self.proj(latent)if self.flatten:latent = latent.flatten(2).transpose(1, 2)  # BCHW -> BNCif self.layer_norm:latent = self.norm(latent)return latent + self.pos_embed
iii、Transformer特征提取

此后,我们将向量传入Transformer中进行特征提取,对应图中的DiT Block。

256x1152的特征会通过图中红框的部分,而时间步t 和 类别会通过途中绿框的部分。

红框部分的结构,除了缩放(scale)、偏置(shift、bias)与门函数(gate,对应图中的α,代码中是gate但图中写scale)外,其它部分与VIT一模一样,可参考博文VIT结构解析进行了解,主要工作的模块是Self-Attention和Pointwise Feedforward(MLP)。这两个模块的输入和输出均为256x1152的特征。

而缩放(scale)、偏置(shift、bias)与门函数(gate)分别对应了图中的γ、β和α。通过adaLN-Zero结构获得,γ、β分别在 Self-Attention和Pointwise Feedforward 的处理前 进行特征的 缩放与偏置 ,而Pointwise Feedforward则在 Self-Attention和Pointwise Feedforward 的处理后 进行特征的 缩放。在代码中我添加了中文注释,方便读者区分添加缩放、偏置和门函数的位置

DiT Block的输入和输出特征均为256x1152。
在这里插入图片描述

class BasicTransformerBlock(nn.Module):def __init__(self,dim: int,num_attention_heads: int,attention_head_dim: int,dropout=0.0,cross_attention_dim: Optional[int] = None,activation_fn: str = "geglu",num_embeds_ada_norm: Optional[int] = None,attention_bias: bool = False,only_cross_attention: bool = False,double_self_attention: bool = False,upcast_attention: bool = False,norm_elementwise_affine: bool = True,norm_type: str = "layer_norm",final_dropout: bool = False,):super().__init__().......def forward(self,hidden_states: torch.FloatTensor,attention_mask: Optional[torch.FloatTensor] = None,encoder_hidden_states: Optional[torch.FloatTensor] = None,encoder_attention_mask: Optional[torch.FloatTensor] = None,timestep: Optional[torch.LongTensor] = None,cross_attention_kwargs: Dict[str, Any] = None,class_labels: Optional[torch.LongTensor] = None,):# Notice that normalization is always applied before the real computation in the following blocks.# 1. Self-Attentionif self.use_ada_layer_norm:norm_hidden_states = self.norm1(hidden_states, timestep)elif self.use_ada_layer_norm_zero:# 在norm1中,已经进行了输入特征的缩放与偏置norm_hidden_states, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.norm1(hidden_states, timestep, class_labels, hidden_dtype=hidden_states.dtype)else:norm_hidden_states = self.norm1(hidden_states)cross_attention_kwargs = cross_attention_kwargs if cross_attention_kwargs is not None else {}attn_output = self.attn1(norm_hidden_states,encoder_hidden_states=encoder_hidden_states if self.only_cross_attention else None,attention_mask=attention_mask,**cross_attention_kwargs,)# 在self attention后,再次进行了特征的缩放(gate)if self.use_ada_layer_norm_zero:attn_output = gate_msa.unsqueeze(1) * attn_outputhidden_states = attn_output + hidden_states# 2. Cross-Attentionif self.attn2 is not None:norm_hidden_states = (self.norm2(hidden_states, timestep) if self.use_ada_layer_norm else self.norm2(hidden_states))attn_output = self.attn2(norm_hidden_states,encoder_hidden_states=encoder_hidden_states,attention_mask=encoder_attention_mask,**cross_attention_kwargs,)hidden_states = attn_output + hidden_states# 3. Feed-forwardnorm_hidden_states = self.norm3(hidden_states)# 在mlp前,进行了输入特征的缩放与偏置if self.use_ada_layer_norm_zero:norm_hidden_states = norm_hidden_states * (1 + scale_mlp[:, None]) + shift_mlp[:, None]if self._chunk_size is not None:# "feed_forward_chunk_size" can be used to save memoryif norm_hidden_states.shape[self._chunk_dim] % self._chunk_size != 0:raise ValueError(f"`hidden_states` dimension to be chunked: {norm_hidden_states.shape[self._chunk_dim]} has to be divisible by chunk size: {self._chunk_size}. Make sure to set an appropriate `chunk_size` when calling `unet.enable_forward_chunking`.")num_chunks = norm_hidden_states.shape[self._chunk_dim] // self._chunk_sizeff_output = torch.cat([self.ff(hid_slice) for hid_slice in norm_hidden_states.chunk(num_chunks, dim=self._chunk_dim)],dim=self._chunk_dim,)else:ff_output = self.ff(norm_hidden_states)# 在mlp后,再次进行了特征的缩放(gate)if self.use_ada_layer_norm_zero:ff_output = gate_mlp.unsqueeze(1) * ff_outputhidden_states = ff_output + hidden_statesreturn hidden_states
iv、上采样

虽然这个部分学名可能不叫上采样,但是我觉得用上采样来描述它还是比较合适的,因为我们前面做过patch分块处理,所以隐含层的高宽被压缩,而这一步,则是将隐含层的高宽再还原回去。

在这里我们会对256x1152进行两次全连接+一次LayerNorm,两次全连接的神经元个数分别为2304和patch_size * patch_size * out_channels。第一次全连接目的是扩宽通道数,第二次全链接则是还原高宽。两次全连接后,在DiT-XL-2中,out_channels为8(8可拆分为4 + 4,前面的4用于直接预测噪声,后面的4用于根据 x t − 1 x_{t-1} xt1均值和方差计算KL散度),特征层的shape从256x1152变为256x32。

然后我们会进行一系列shape变换,首先将256x1152变为16x16x2x2x8,然后进行转置变为8x16x2x16x2,然后还原高宽变为8x32x32。此时上采样结束。该部分对应了图中的Linear And Reshape。
在这里插入图片描述

上采样代码如下所示:

# 3. Output
conditioning = self.transformer_blocks[0].norm1.emb(timestep, class_labels, hidden_dtype=hidden_states.dtype
)
shift, scale = self.proj_out_1(F.silu(conditioning)).chunk(2, dim=1)
hidden_states = self.norm_out(hidden_states) * (1 + scale[:, None]) + shift[:, None]
hidden_states = self.proj_out_2(hidden_states)# unpatchify
height = width = int(hidden_states.shape[1] ** 0.5)
hidden_states = hidden_states.reshape(shape=(-1, height, width, self.patch_size, self.patch_size, self.out_channels)
)
hidden_states = torch.einsum("nhwpqc->nchpwq", hidden_states)
output = hidden_states.reshape(shape=(-1, self.out_channels, height * self.patch_size, width * self.patch_size)
)

3、隐空间解码生成图片

通过上述步骤,已经可以多次采样获得结果,然后我们便可以通过隐空间解码生成图片。

隐空间解码生成图片的过程非常简单,将上文多次采样后的结果,使用vae的decode方法即可生成图片。

# --------------------------------- #
#   后处理
# --------------------------------- #
# 通过vae进行解码
latents = 1 / self.vae.config.scaling_factor * latents
samples = self.vae.decode(latents).samplesamples = (samples / 2 + 0.5).clamp(0, 1)# 转化为float32类别
samples = samples.cpu().permute(0, 2, 3, 1).float().numpy()

类别到图像预测过程代码

整体预测代码如下:


import torch
import json
import os
from diffusers import DPMSolverMultistepScheduler, AutoencoderKLfrom nets.transformer_2d import Transformer2DModel
from nets.pipeline import DiTPipeline# 模型路径
model_path = "model_data/DiT-XL-2-256"# 初始化DiT的各个组件
scheduler = DPMSolverMultistepScheduler.from_pretrained(model_path, subfolder="scheduler")
transformer = Transformer2DModel.from_pretrained(model_path, subfolder="transformer")
vae = AutoencoderKL.from_pretrained(model_path, subfolder="vae")
id2label = json.load(open(os.path.join(model_path, "model_index.json"), "r"))['id2label']# 初始化DiT的Pipeline
pipe = DiTPipeline(scheduler=scheduler, transformer=transformer, vae=vae, id2label=id2label)
pipe = pipe.to("cuda")# imagenet种类 对应的 名称
words = ["white shark", "umbrella"]
# 获得imagenet对应的ids
class_ids = pipe.get_label_ids(words)
# 设置seed
generator = torch.manual_seed(42)# pipeline前传
output = pipe(class_labels=class_ids, num_inference_steps=25, generator=generator)# 保存图片
for index, image in enumerate(output.images):image.save(f"output-{index}.png")

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

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

相关文章

kitti数据显示

画出track_id publish_utils.py中 def publish_3dbox(box3d_pub, corners_3d_velos, types, track_ids):marker_array MarkerArray()for i, corners_3d_velo in enumerate(corners_3d_velos):marker Marker()marker.header.frame_id FRAME_IDmarker.header.stamp rospy.T…

Pytorch训练RCAN QAT超分模型

Pytorch训练RCAN QAT超分模型 版本信息测试步骤准备数据集创建容器生成文件列表创建文件列表的代码执行脚本,生成文件列表训练RCAN模型准备工作修改开源代码编写训练代码执行训练脚本可视化本文以RCAN超分模型为例,演示了QAT的训练过程,步骤如下: 先训练FP32模型再加载FP32训练…

【随笔】固态硬盘数据删除无法恢复(开启TRIM),注意数据备份

文章目录 一、序二、机械硬盘和固态硬盘的物理结构与工作原理2.1 机械硬盘2.11 基本结构2.12 工作原理 2.2 固态硬盘2.21 基本结构2.22 工作原理 三、机械硬盘和固态硬盘的垃圾回收机制3.1 机械硬盘GC3.2 固态硬盘GC3.3 TRIM指令开启和关闭 四、做好数据备份 一、序 周末电脑突…

【Qt学习】QLineEdit 控件 属性与实例(登录界面,验证密码,正则表达式)

文章目录 1. 介绍2. 实例使用2.1 登录界面2.2 对比两次密码是否相同2.3 通过按钮显示当前输入的密码(并对2.2进行优化)2.4 结语 3. 正则表达式3.1 QRegExp3.2 验证输入内容 4. 资源代码 1. 介绍 关于 QLineEdit 的详细介绍,可以去查阅官方文…

[计算机网络]--IP协议

前言 作者:小蜗牛向前冲 名言:我可以接受失败,但我不能接受放弃 如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、IP协议…

202432读书笔记|《泰戈尔的诗》——什么事让你大笑,我生命的小蓓蕾

202432读书笔记|《泰戈尔的诗》——什么事让你大笑,我生命的小蓓蕾 《泰戈尔写给孩子的诗(中英双语版)》作者拉宾德拉纳特泰戈尔文 张王哲图,图文并茂的一本书,文字与图画都很美,相得益彰!很值得…

【Memory协议栈】EEPROM Abstraction模块详细介绍

目录 前言 正文 1.功能简介 2.关键概念 3.功能详解 3.1 Addressing scheme and segmentation 3.2 Address calculation 3.3 Limitation of erase / write cycles 3.4 Handling of “immediate” data 3.5 Managing block consistency information 4.关键API定义 4.…

学习磁盘管理

文章目录 一、磁盘接口类型二、磁盘设备的命名三、fdisk分区四、自动挂载五、扩容swap六、GPT分区七、逻辑卷管理八、磁盘配额九、RAID十、软硬链接 一、磁盘接口类型 IDE、SATA、SCSI、SAS、FC(光纤通道) IDE, 该接口是并口。SATA, 该接口是串口。SCS…

golang学习2,golang开发配置国内镜像

go env -w GO111MODULEon go env -w GOPROXYhttps://goproxy.cn,direct

K8S部署Java项目(Gitlab CI/CD自动化部署终极版)

天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。…

websocket入门及应用

websocket When to use a HTTP call instead of a WebSocket (or HTTP 2.0) WebSocket 是基于TCP/IP协议,独立于HTTP协议的通信协议。WebSocket 是双向通讯,有状态,客户端一(多)个与服务端一(多&#xff09…

代码随想录刷题第43天

第一题是最后一块石头的重量IIhttps://leetcode.cn/problems/last-stone-weight-ii/,没啥思路,直接上题解了。本题可以看作将一堆石头尽可能分成两份重量相似的石头,于是问题转化为如何合理取石头,使其装满容量为石头总重量一半的…

【AI Agent系列】【MetaGPT多智能体学习】0. 环境准备 - 升级MetaGPT 0.7.2版本及遇到的坑

之前跟着《MetaGPT智能体开发入门课程》学了一些MetaGPT的知识和实践,主要关注在MetaGPT入门和单智能体部分(系列文章附在文末,感兴趣的可以看下)。现在新的教程来了,新教程主要关注多智能体部分。 本系列文章跟随《M…

Wagtail安装运行并结合内网穿透实现公网访问本地网站界面

文章目录 前言1. 安装并运行Wagtail1.1 创建并激活虚拟环境 2. 安装cpolar内网穿透工具3. 实现Wagtail公网访问4. 固定的Wagtail公网地址 正文开始前给大家推荐个网站,前些天发现了一个巨牛的 人工智能学习网站, 通俗易懂,风趣幽默&#xf…

C++Lambda表达式介绍

C11中引入了Lambda表达式,Lambda表达式是一种匿名函数,它可以在需要函数的地方直接定义和使用,而无需显式地定义一个函数。 lambda表达式 Lambda表达式语法定义 [capture-list](parameters) -> return-type { statement } capture-lis…

SQL Developer 小贴士:PL/SQL语法分析

对于SQL或PL/SQL中的语法错误和警告,SQL Developer可以用不同颜色的下划波浪线显示。 启用语法分析,可以用菜单Tool>Preferences>Code Editor>Completion Insight>Enable Semantic Analysis Info Tips 例如,以下的代码中&…

blender bvh显示关节名称

导入bvh,菜单选择布局,右边出现属性窗口, 在下图红色框依次点击选中,就可以查看bvh关节名称了。

自考《计算机网络原理》考前冲刺

常考选择填空 1、计算机网络的定义:计算机网络是互连的、自治的计算机的集合。 2、协议的定义:协议是网络通信实体之间在数据交换过程中需要遵循的规则或约定 3、协议的3个要素 (1) 语法:定义实体之间交换信息的格式与结构,或…

设计模式六:策略模式

1、策略模式 策略模式定义了一系列的算法,并将每一个算法封装起来,使每个算法可以相互替代,使算法本身和使用算法的客户端分割开来,相互独立。 策略模式的角色: 策略接口角色IStrategy:用来约束一系列具体…

第一次开机开机动画结束后闪白屏

开机动画结束会闪下白屏,再进入launcher 思路 : 分析下从开机动画结束到launcher起来之间的流程步骤 从ZygoteInit.java开始分析 : SystemServer起来后会启动一些核心服务 attachApplication方法中主要创建了Application和Activity 接下里RootActivityC…