扩散模型学习——代码学习

news2024/11/19 18:35:30

文章目录

    • 引言
    • 正文
      • UNet网络结构
      • 训练方法
      • DDPM采样方法讲解
      • Context上下文信息添加
      • DDIM的方法详解
    • 总结
    • 参考

引言

  • 这是第一次接触扩散模型,为了学习,这里好好分析一下他的代码

正文

UNet网络结构

  • 这部分主要是定义一下网络结构,以及相关的网络超参数
  • 具体网络结构的图片如下

在这里插入图片描述
下述为网络结构各个层的定义

  • 结合定义和模型的具体输出,会更加理解
class ContextUnet(nn.Module):
    def __init__(self, in_channels, n_feat=256, n_cfeat=10, height=28):  # cfeat - context features
        super(ContextUnet, self).__init__()

        # number of input channels, number of intermediate feature maps and number of classes
        # 输入通道数
        self.in_channels = in_channels
        # 映射特征数量
        self.n_feat = n_feat
        # 生成类别数
        self.n_cfeat = n_cfeat
        # 生成的是方形图,并且输入必须能够被4整除
        self.h = height  #assume h == w. must be divisible by 4, so 28,24,20,16...

        # Initialize the initial convolutional layer
        self.init_conv = ResidualConvBlock(in_channels, n_feat, is_res=True)

        # 初始化下采样层
        self.down1 = UnetDown(n_feat, n_feat)        # down1 #[10, 256, 8, 8]
        self.down2 = UnetDown(n_feat, 2 * n_feat)    # down2 #[10, 256, 4,  4]
        
         # original: self.to_vec = nn.Sequential(nn.AvgPool2d(7), nn.GELU())
        # 仅仅进行平均池化,并没有改变他的通道数
        self.to_vec = nn.Sequential(nn.AvgPool2d((4)), nn.GELU())

        # Embed the timestep and context labels with a one-layer fully connected neural network
        # 定义两个嵌入层,将时间戳信息和上下文消息都转为对应的embedding向量
        # 这里仅仅是改变通道数,并没有改变上下文信息的特征
        self.timeembed1 = EmbedFC(1, 2*n_feat)
        self.timeembed2 = EmbedFC(1, 1*n_feat)
        self.contextembed1 = EmbedFC(n_cfeat, 2*n_feat)
        self.contextembed2 = EmbedFC(n_cfeat, 1*n_feat)

        # Initialize the up-sampling path of the U-Net with three levels
        # 并不改变通道数,仅仅是进行上采样
        self.up0 = nn.Sequential(
            nn.ConvTranspose2d(2 * n_feat, 2 * n_feat, self.h//4, self.h//4), # up-sample  
            nn.GroupNorm(8, 2 * n_feat), # normalize                       
            nn.ReLU(),
        )
        
        # 降低通道数,并进行上采样,同下
        self.up1 = UnetUp(4 * n_feat, n_feat)
        # 降低通道数,并进行上采样,这里输入通道和up1的输出通道不同,是因为还有上下文信息和之前下采样的输出
        self.up2 = UnetUp(2 * n_feat, n_feat)

        # 初始化最终的卷积层,将最终的输出映射为和输入相同大小
        self.out = nn.Sequential(
            nn.Conv2d(2 * n_feat, n_feat, 3, 1, 1), # reduce number of feature maps   #in_channels, out_channels, kernel_size, stride=1, padding=0
            nn.GroupNorm(8, n_feat), # normalize
            nn.ReLU(),
            nn.Conv2d(n_feat, self.in_channels, 3, 1, 1), # map to same number of channels as input
        )

网络结构的每一层参数如下

# 初始化卷积层
init_conv.conv1.0.weight torch.Size([256, 3, 3, 3])
init_conv.conv1.0.bias torch.Size([256])
init_conv.conv1.1.weight torch.Size([256])
init_conv.conv1.1.bias torch.Size([256])
init_conv.conv2.0.weight torch.Size([256, 256, 3, 3])
init_conv.conv2.0.bias torch.Size([256])
init_conv.conv2.1.weight torch.Size([256])
init_conv.conv2.1.bias torch.Size([256])

# 下采样层一
down1.model.0.conv1.0.weight torch.Size([256, 256, 3, 3])
down1.model.0.conv1.0.bias torch.Size([256])
down1.model.0.conv1.1.weight torch.Size([256])
down1.model.0.conv1.1.bias torch.Size([256])
down1.model.0.conv2.0.weight torch.Size([256, 256, 3, 3])
down1.model.0.conv2.0.bias torch.Size([256])
down1.model.0.conv2.1.weight torch.Size([256])
down1.model.0.conv2.1.bias torch.Size([256])
down1.model.1.conv1.0.weight torch.Size([256, 256, 3, 3])
down1.model.1.conv1.0.bias torch.Size([256])
down1.model.1.conv1.1.weight torch.Size([256])
down1.model.1.conv1.1.bias torch.Size([256])
down1.model.1.conv2.0.weight torch.Size([256, 256, 3, 3])
down1.model.1.conv2.0.bias torch.Size([256])
down1.model.1.conv2.1.weight torch.Size([256])
down1.model.1.conv2.1.bias torch.Size([256])

# 下采样层二
down2.model.0.conv1.0.weight torch.Size([512, 256, 3, 3])
down2.model.0.conv1.0.bias torch.Size([512])
down2.model.0.conv1.1.weight torch.Size([512])
down2.model.0.conv1.1.bias torch.Size([512])
down2.model.0.conv2.0.weight torch.Size([512, 512, 3, 3])
down2.model.0.conv2.0.bias torch.Size([512])
down2.model.0.conv2.1.weight torch.Size([512])
down2.model.0.conv2.1.bias torch.Size([512])
down2.model.1.conv1.0.weight torch.Size([512, 512, 3, 3])
down2.model.1.conv1.0.bias torch.Size([512])
down2.model.1.conv1.1.weight torch.Size([512])
down2.model.1.conv1.1.bias torch.Size([512])
down2.model.1.conv2.0.weight torch.Size([512, 512, 3, 3])
down2.model.1.conv2.0.bias torch.Size([512])
down2.model.1.conv2.1.weight torch.Size([512])
down2.model.1.conv2.1.bias torch.Size([512])

# 时间上下文信息embedding
timeembed1.model.0.weight torch.Size([512, 1])
timeembed1.model.0.bias torch.Size([512])
timeembed1.model.2.weight torch.Size([512, 512])
timeembed1.model.2.bias torch.Size([512])
timeembed2.model.0.weight torch.Size([256, 1])
timeembed2.model.0.bias torch.Size([256])
timeembed2.model.2.weight torch.Size([256, 256])
timeembed2.model.2.bias torch.Size([256])

# 上下文信息的embedding
contextembed1.model.0.weight torch.Size([512, 10])
contextembed1.model.0.bias torch.Size([512])
contextembed1.model.2.weight torch.Size([512, 512])
contextembed1.model.2.bias torch.Size([512])
contextembed2.model.0.weight torch.Size([256, 10])
contextembed2.model.0.bias torch.Size([256])
contextembed2.model.2.weight torch.Size([256, 256])
contextembed2.model.2.bias torch.Size([256])

# 上采样零层,如果不用加上上下文信息,这层完全没有必要,现在是加上了。
up0.0.weight torch.Size([512, 512, 7, 7])
up0.0.bias torch.Size([512])
up0.1.weight torch.Size([512])
up0.1.bias torch.Size([512])
up1.model.0.weight torch.Size([1024, 256, 2, 2])
up1.model.0.bias torch.Size([256])

# 上采样一层
up1.model.1.conv1.0.weight torch.Size([256, 256, 3, 3])
up1.model.1.conv1.0.bias torch.Size([256])
up1.model.1.conv1.1.weight torch.Size([256])
up1.model.1.conv1.1.bias torch.Size([256])
up1.model.1.conv2.0.weight torch.Size([256, 256, 3, 3])
up1.model.1.conv2.0.bias torch.Size([256])
up1.model.1.conv2.1.weight torch.Size([256])
up1.model.1.conv2.1.bias torch.Size([256])
up1.model.2.conv1.0.weight torch.Size([256, 256, 3, 3])
up1.model.2.conv1.0.bias torch.Size([256])
up1.model.2.conv1.1.weight torch.Size([256])
up1.model.2.conv1.1.bias torch.Size([256])
up1.model.2.conv2.0.weight torch.Size([256, 256, 3, 3])
up1.model.2.conv2.0.bias torch.Size([256])
up1.model.2.conv2.1.weight torch.Size([256])
up1.model.2.conv2.1.bias torch.Size([256])

# 上采样二层
up2.model.0.weight torch.Size([512, 256, 2, 2])
up2.model.0.bias torch.Size([256])
up2.model.1.conv1.0.weight torch.Size([256, 256, 3, 3])
up2.model.1.conv1.0.bias torch.Size([256])
up2.model.1.conv1.1.weight torch.Size([256])
up2.model.1.conv1.1.bias torch.Size([256])
up2.model.1.conv2.0.weight torch.Size([256, 256, 3, 3])
up2.model.1.conv2.0.bias torch.Size([256])
up2.model.1.conv2.1.weight torch.Size([256])
up2.model.1.conv2.1.bias torch.Size([256])
up2.model.2.conv1.0.weight torch.Size([256, 256, 3, 3])
up2.model.2.conv1.0.bias torch.Size([256])
up2.model.2.conv1.1.weight torch.Size([256])
up2.model.2.conv1.1.bias torch.Size([256])
up2.model.2.conv2.0.weight torch.Size([256, 256, 3, 3])
up2.model.2.conv2.0.bias torch.Size([256])
up2.model.2.conv2.1.weight torch.Size([256])
up2.model.2.conv2.1.bias torch.Size([256])

# 最终的输出层,将输出的通道进行调整为3
out.0.weight torch.Size([256, 512, 3, 3])
out.0.bias torch.Size([256])
out.1.weight torch.Size([256])
out.1.bias torch.Size([256])
out.3.weight torch.Size([3, 256, 3, 3])
out.3.bias torch.Size([3])

当前网络每一层输出的张量情况

# 输入的图片为[32,3,28,28]=[batch_size,channel,height,width]
# 提取特征,扩充通道数
Layer: ResidualConvBlock
Input shape: torch.Size([32, 3, 28, 28])
Output shape: torch.Size([32, 64, 28, 28])
==============================
# 下采样层一:尺寸减半,通道数不变
Layer: UnetDown
Input shape: torch.Size([32, 64, 28, 28])
Output shape: torch.Size([32, 64, 14, 14])
==============================
# 下采样层二:尺寸减半,通道数翻倍
Layer: UnetDown
Input shape: torch.Size([32, 64, 14, 14])
Output shape: torch.Size([32, 128, 7, 7])
==============================
# 还是对输入的特征图进行下采样,是4*4的方格进行下采样
Layer: Sequential
Input shape: torch.Size([32, 128, 7, 7])
Output shape: torch.Size([32, 128, 1, 1])
==============================

# 下述四层为上下文信息处理层,分别处理上下文类别信息和时间序列信息,分层加入到模型中
# 下述为特征上下文信息,每一个样本都有自己的特征上下文
Layer: EmbedFC
Input shape: torch.Size([32, 5])
Output shape: torch.Size([32, 128])
==============================
# 下述为时间序列上下文,所有样本的时间序列是统一的
Layer: EmbedFC
Input shape: torch.Size([1, 1, 1, 1])
Output shape: torch.Size([1, 128])
==============================
# 下述为经过扩展的样本上下文,用于加到第二个上采样层
Layer: EmbedFC
Input shape: torch.Size([32, 5])
Output shape: torch.Size([32, 64])
==============================
# 下述为经过扩展的时间序列信息,用于加到第二个上采样层
Layer: EmbedFC
Input shape: torch.Size([1, 1, 1, 1])
Output shape: torch.Size([1, 64])
==============================

# 上采样层零:扩展维度,对应两个下采样层下的第一个卷积层
Layer: Sequential
Input shape: torch.Size([32, 128, 1, 1])
Output shape: torch.Size([32, 128, 7, 7])
==============================
# 上采样层一
Layer: UnetUp
Input shape: torch.Size([32, 128, 7, 7])
Output shape: torch.Size([32, 64, 14, 14])
==============================
# 上采样层二
Layer: UnetUp
Input shape: torch.Size([32, 64, 14, 14])
Output shape: torch.Size([32, 64, 28, 28])
==============================
# 输出调整层,将输出的信道调整为原始图层
Layer: Sequential
Input shape: torch.Size([32, 128, 28, 28])
Output shape: torch.Size([32, 3, 28, 28])
==============================

网络各层的连接方式

  • 这里最好对照着图片看,会更加清晰,知道他这个网络模型的各个层级之间如何记性沟通。
  • 整体来说,下采样比较简单,上采样比较复杂,因为涉及到添加对应下采样层的输出还有上下文信息、时间序列信息等,所以需要好好看看。
  • 不过可以学到,如何添加额外信息的
 def forward(self, x, t, c=None):
        """
        x : (batch, n_feat, h, w) : input image
        t : (batch, n_cfeat)      : time step
        c : (batch, n_classes)    : context label
        """
        # x is the input image, c is the context label, t is the timestep, context_mask says which samples to block the context on
        
        '''下采样过程'''
        # 将输入的图片传入初始化卷积层中
        x = self.init_conv(x)
        # 将结果传入下采样层
        down1 = self.down1(x)       #[10, 256, 8, 8]
        down2 = self.down2(down1)   #[10, 256, 4, 4]
        
        # 将特征映射为向量
        hiddenvec = self.to_vec(down2)
        
        '''上采样过程'''
        # mask out context if context_mask == 1
        # 判定是否有上下文信息
        if c is None:
            c = torch.zeros(x.shape[0], self.n_cfeat).to(x)
            
        # 将上下文信息context information还有timestep转为embedding
        cemb1 = self.contextembed1(c).view(-1, self.n_feat * 2, 1, 1)     # (batch, 2*n_feat, 1,1)
        temb1 = self.timeembed1(t).view(-1, self.n_feat * 2, 1, 1)
        cemb2 = self.contextembed2(c).view(-1, self.n_feat, 1, 1)
        temb2 = self.timeembed2(t).view(-1, self.n_feat, 1, 1)
        #print(f"uunet forward: cemb1 {cemb1.shape}. temb1 {temb1.shape}, cemb2 {cemb2.shape}. temb2 {temb2.shape}")

        
        # 上采样过程,分别和对应下采样对应层和对应上下文信息加入到每一个上采样层中
        up1 = self.up0(hiddenvec)
        up2 = self.up1(cemb1*up1 + temb1, down2)  # add and multiply embeddings
        up3 = self.up2(cemb2*up2 + temb2, down1)
        out = self.out(torch.cat((up3, x), 1))
        return out

训练方法

  • 这里需要明白训练公式,通过公式推导,书写代码,需要明确如下参数
    • α ‾ \overline{\alpha} α 表示若干个 α t \alpha_t αt的连乘
    • ξ θ \xi_\theta ξθ 表示预测的噪声,另外一个表示实际生成的噪声
      在这里插入图片描述
      下述为定义增加噪声的过程
# helper function: perturbs an image to a specified noise level
def perturb_input(x, t, noise):
    # 前向传播公示
    return ab_t.sqrt()[t, None, None, None] * x + (1 - ab_t[t, None, None, None]) * noise

下述为具体的训练代码

# training without context code

# set into train mode
nn_model.train()

for ep in range(n_epoch):
    print(f'epoch {ep}')
    
    # linearly decay learning rate
    # 定义学习率进行线性衰减
    optim.param_groups[0]['lr'] = lrate*(1-ep/n_epoch)
    
    # 加载进度条
    pbar = tqdm(dataloader, mininterval=2 )
    for x, _ in pbar:   # x: images
        optim.zero_grad()
        x = x.to(device)
        
        # perturb data
        # 给当前的图片增加噪声
        noise = torch.randn_like(x) # 随机生成噪声
        t = torch.randint(1, timesteps + 1, (x.shape[0],)).to(device) # 随机生成timestep
        x_pert = perturb_input(x, t, noise) # 增加噪声扰动
        
        # use network to recover noise
        # 使用网络去预测噪声
        pred_noise = nn_model(x_pert, t / timesteps)
        
        # loss is mean squared error between the predicted and true noise
        # 使用MSE计算损失
        loss = F.mse_loss(pred_noise, noise)
        loss.backward()
        
        optim.step()

    # save model periodically
    # 按照周期保存模型
    if ep%4==0 or ep == int(n_epoch-1):
        if not os.path.exists(save_dir):
            os.mkdir(save_dir)
        torch.save(nn_model.state_dict(), save_dir + f"model_{ep}.pth")
        print('saved model at ' + save_dir + f"model_{ep}.pth")

DDPM采样方法讲解

  • 在这个基础的扩散模型中,最为重要的是denoise_add_noise方法,该方法主要是先如下功能
    • 生成model预测的噪声,从原来数据中减去模型预测的噪声
    • 添加新的额外的噪声,防止训练崩溃
  • 这里的采样方法完全是按照公式进行展开的,重要的是几个参数的构建方法
    • 下属方法中的a_t是公式中的 α t \sqrt\alpha_t α t,

在这里插入图片描述

# construct DDPM noise schedule
# 构建DDPM的计算模式
# 定义 \beta_t  ,表示从零到一的若干均匀分布的小数,有几个时间步骤,就有几个
b_t = (beta2 - beta1) * torch.linspace(0, 1, timesteps + 1, device=device) + beta1

# 计算\alpha_t 得值
a_t = 1 - b_t
# 这里是通过取对数,然后再去指数,来避免小数连乘的溢出。
ab_t = torch.cumsum(a_t.log(), dim=0).exp()
# 确保x_0的连续性
ab_t[0] = 1

# helper function; removes the predicted noise (but adds some noise back in to avoid collapse)
# 祛除模型预测的噪声,并且添加一些额外的噪声,避免过拟合
def denoise_add_noise(x, t, pred_noise, z=None):

    # 重参数化,实现对特定复杂分布的采样,z是从高斯分布进行的正常采样
    if z is None:
        z = torch.randn_like(x)
    noise = b_t.sqrt()[t] * z
    
    # 公式的前半项,x是当前timestep的情况,这里完全是按照公式进行推倒的
    mean = ((x - pred_noise * ((1 - a_t[t]) / (1 - ab_t[t]).sqrt())) # 减去预测噪声
            / a_t[t].sqrt())
    
    # 增加额外的噪声,防止过拟合
    return mean + noise
  • 上述方法完全是按照对应的公示进行展开的,看过了推导之后,发现对于整个公式的理解更加明确。

  • 下述为整体的采样过程

    • 对于每一张图片,都是多次迭代,并且逐步减去噪声
# sample using standard algorithm
@torch.no_grad()
def sample_ddpm(n_sample, save_rate=20):
    # x_T ~ N(0, 1), sample initial noise
    samples = torch.randn(n_sample, 3, height, height).to(device)  

    # array to keep track of generated steps for plotting
    intermediate = [] 
    for i in range(timesteps, 0, -1):
        print(f'sampling timestep {i:3d}', end='\r')

        # reshape time tensor
        t = torch.tensor([i / timesteps])[:, None, None, None].to(device)

        # sample some random noise to inject back in. For i = 1, don't add back in noise
        z = torch.randn_like(samples) if i > 1 else 0

        eps = nn_model(samples, t)    # predict noise e_(x_t,t)
        samples = denoise_add_noise(samples, i, eps, z)
        if i % save_rate ==0 or i==timesteps or i<8:
            intermediate.append(samples.detach().cpu().numpy())

    intermediate = np.stack(intermediate)
    return samples, intermediate

Context上下文信息添加

  • 关于上下文的添加,在之前的模型定义中ContextUNet是说明了上下文添加具体网络结构,这里就专门讲讲如何在采样过程中,增加对应的上下文信息
    • 就是在之前定义model的forward参数中增加了一个参数c
# sample with context using standard algorithm
@torch.no_grad()
def sample_ddpm_context(n_sample, context, save_rate=20):
    # x_T ~ N(0, 1), sample initial noise
    samples = torch.randn(n_sample, 3, height, height).to(device)  

    # array to keep track of generated steps for plotting
    intermediate = [] 
    for i in range(timesteps, 0, -1):
        print(f'sampling timestep {i:3d}', end='\r')

        # reshape time tensor
        t = torch.tensor([i / timesteps])[:, None, None, None].to(device)

        # sample some random noise to inject back in. For i = 1, don't add back in noise
        z = torch.randn_like(samples) if i > 1 else 0
        
        # 和之前一样,就是增加了对应的上下文信息
        eps = nn_model(samples, t, c=context)    # predict noise e_(x_t,t, ctx)
        samples = denoise_add_noise(samples, i, eps, z)
        if i % save_rate==0 or i==timesteps or i<8:
            intermediate.append(samples.detach().cpu().numpy())

    intermediate = np.stack(intermediate)
    return samples, intermediate

DDIM的方法详解

  • DDIM和DDPM二者在前向传播的过程中,是完全相同的,所以他们的模型定义是相同的,完全可以共用的。
  • 但是他们的采样过程是不同,DDIM能够实现跨步采样,速度更快,他是基于任意分布假设,并不是基于马卡洛夫链,所以不用逐步推理。具体算法描述如下

在这里插入图片描述
具体代码如下,下述要结合对应的采样公式,来实现对应的代码

# construct DDPM noise schedule
b_t = (beta2 - beta1) * torch.linspace(0, 1, timesteps + 1, device=device) + beta1
a_t = 1 - b_t
ab_t = torch.cumsum(a_t.log(), dim=0).exp()    
ab_t[0] = 1

# 下述为根据采样公式写出的采样函数
# t是当前的状态数量
# t-prev是根据当前状态t,需要预测prev向前的内容
def denoise_ddim(x, t, t_prev, pred_noise):
    ab = ab_t[t]
    ab_prev = ab_t[t_prev]
    
    x0_pred = ab_prev.sqrt() / ab.sqrt() * (x - (1 - ab).sqrt() * pred_noise)
    dir_xt = (1 - ab_prev).sqrt() * pred_noise

    return x0_pred + dir_xt

# 具体调用采样过程
# sample quickly using DDIM
@torch.no_grad()
def sample_ddim(n_sample, n=20):
    # x_T ~ N(0, 1), sample initial noise
    samples = torch.randn(n_sample, 3, height, height).to(device)  

    # array to keep track of generated steps for plotting
    intermediate = [] 
    step_size = timesteps // n
    for i in range(timesteps, 0, -step_size):
        print(f'sampling timestep {i:3d}', end='\r')

        # reshape time tensor
        t = torch.tensor([i / timesteps])[:, None, None, None].to(device)

        eps = nn_model(samples, t)    # predict noise e_(x_t,t)
        samples = denoise_ddim(samples, i, i - step_size, eps)
        intermediate.append(samples.detach().cpu().numpy())

    intermediate = np.stack(intermediate)
    return samples, intermediate

总结

  • 之前的学习方式有点问题,在扩散模型这里就卡了差不多一周,看公式推导,看相关的代码,学习相关的数学推理,还没有将当前模块嵌入到对应的模型进行测试,效率被大大降低了,所以对于DDIM的学习就简单很多。

参考

  • AIGC爆火的背后——扩散模型DDPM浅析
  • 扩散模型探索:DDIM 笔记与思考

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

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

相关文章

免费领取!TikTok Shop “全托管”黑五大促官方备战指南来啦!

黑五网一大促即将来袭&#xff0c;自“全托管”模式上线以来&#xff0c;TikTok for Business在沙特阿拉伯和英国市场开展了古尔邦节大促、夏季大促、返校季大促等活动&#xff0c;今年更是会借着黑五网一大促之际&#xff0c;首次覆盖美国市场&#xff0c;为全托管商家带来全球…

k8s的coreDNS添加自定义hosts

1.ack的hosts不会继承宿主机的hosts&#xff0c;而工作中有一个域名默认是走内网解析&#xff0c;内网被限制访问了&#xff0c;只能在coreDNS中加一个hosts解析域名 2.编辑configmap (coredns) kubectl edit configmap -n kube-system coredns 增加hosts节点 Corefile: |.:53…

由浅到深 : Self-Attention (自注意力机制)

Self-Attention 看到下面的第一个saw是动词&#xff0c;第二个saw是名词。 因为第一个saw和第二个saw在形式上没有任何差别。如果任务是进行词性的判断&#xff0c;把上面的词直接输入给神经网络&#xff0c;那么它肯定不能够正确分析。 想要正确分析词性&#xff0c;那么该…

谈谈你对Spring6.0的理解

谈谈你对Spring6.0的理解 一&#xff0c;介绍 Spring 6.0是Spring框架的一个重要版本&#xff0c;它提供了许多新特性和改进&#xff0c;以帮助开发者更高效地完成工作。以下是我对Spring 6.0的一些理解&#xff1a; 反应式编程支持&#xff1a;Spring 6.0对反应式编程提供了…

Pico示波器 汽车振动异响(NVH)解决方案

汽车振动异响故障可能有多个潜在原因&#xff0c;包括发动机、传动系统、悬挂系统、制动系统等等。技师需要对汽车各个方面有全面的了解&#xff0c;才能更好地进行故障排查。 振动和异响可能由多个因素引起。例如&#xff0c;可能是零部件损坏、松脱、磨损或者不正确安装。这…

ResNet: Deep Residual Learning for Image Recognition(CVPR 2015)

正常来说&#xff0c;plain网络&#xff08;简单的堆叠层数&#xff09;会随着网络的加深错误率变低的&#xff0c;但事实上训练和测试错误率都升高了&#xff0c;这就是网络的退化问题。 此外&#xff0c;网络加深会出现梯度消失或梯度爆炸&#xff0c;妨碍收敛&#xff0c;但…

HarmonyOS 音频开发指导:使用 AudioRenderer 开发音频播放功能

AudioRenderer 是音频渲染器&#xff0c;用于播放 PCM&#xff08;Pulse Code Modulation&#xff09;音频数据&#xff0c;相比 AVPlayer 而言&#xff0c;可以在输入前添加数据预处理&#xff0c;更适合有音频开发经验的开发者&#xff0c;以实现更灵活的播放功能。 开发指导…

node-sass安装报错

node-sass安装竟然报了python的错 先说最终解决方案 将node换成14版本 声明&#xff1a;个人总结&#xff0c;思路到哪写到哪&#xff0c;不喜请轻喷&#x1f602; 参考 https://blog.csdn.net/ccm_2354586864 可恶的node-sass让我不得不在周末自愿加班&#x1f620; 我的…

优化mybatisPlus批量新增,新增mapper层批量新增方法,附带代码生成vm模板。

文章目录 一、主题二、效果展示三、mybatis-plus源码分析四、优化思路五、代码优化方法一&#xff1a;修改批量新增SQL方法二&#xff1a;使用insertBatchSomeColumn 方法 一、主题 经发现&#xff0c;mybatisPlus只有Service中存在saveBatch()方法&#xff0c;而Mapper中不存在…

博客后台模块续更(五)

十一、后台模块-菜单列表 菜单指的是权限菜单&#xff0c;也就是一堆权限字符串 1. 查询菜单 1.1 接口分析 需要展示菜单列表&#xff0c;不需要分页。可以针对菜单名进行模糊查询。也可以针对菜单的状态进行查询。菜单要按照父菜单id和orderNum进行排序 请求方式 请求路径…

从0开始学云计算之服务器:服务的定义,特点,应用场景,分类

服务器定义 服务器是计算机的一种。它比普通计算机运行速度更快、负载更高且价格更高。 服务器的英文名称为“Server”&#xff0c;是指在网络上提供各种服务的高性能计算机。作为网络的节点&#xff0c;存储、处理网络上80%的数据、信息&#xff0c;因此也被称为x络的灵魂。 …

三、机器学习基础知识:Python常用机器学习库(Pandas)

文章目录 1、Pandas2、Series数据结构2.1 Series的创建2.2 Series的索引与访问2.3 Series的常见操作 3、DataFrame对象3.1 DataFrame的创建与索引3.2 DataFrame的访问3.3 DataFrame的常见操作 4、数据对齐5、缺失数据处理6、Pandas存取文件 1、Pandas Pandas 库是以Numpy库为基…

互联网医院牌照|互联网医院牌照资质申请流程和所需材料

那么不同的企业基于不同的业务板块&#xff0c;所需要搭建的互联网医院也存在差异&#xff0c;平衡好自身业务及互联网医疗建设二者间的关系&#xff0c;是企业建设互医的基础需求&#xff0c;那么互联网医院牌照办理流程和材料都包含哪些呢&#xff1f;接下来就给大家介绍下。…

短视频矩阵系统软件源码

短视频矩阵系统软件源码 视频成为获得免费流量最便宜的渠道&#xff0c;平台给所有视频最基础的保底流量。如果按照一个视频最低500流量计算&#xff0c;5个账户就是2500的流量&#xff0c;200个视频就是50W流量&#xff0c;如果从其他渠道获得50W流量是个很困难的事情。短视频…

第四章 文件管理 三、文件目录

目录 一、文件控制块 1、定义&#xff1a; 2、要进行哪些操作 &#xff08;1&#xff09;搜索: &#xff08;2&#xff09;创建文件: &#xff08;3&#xff09;删除文件: &#xff08;4&#xff09;显示目录: &#xff08;5&#xff09;修改目录: 二、目录结构 1、单…

QSOP24封装的语音芯片优势列举

1. 封装紧凑&#xff1a;QSOP24封装采用了更小的封装尺寸&#xff0c;相比于其他大型封装&#xff0c;它的体积更小、尺寸更紧凑。这使得它在空间受限的应用中更加适用&#xff0c;可以方便地集成到小型或薄型设备中&#xff0c;提供更高的设计灵活性。 2. 强大的功能&#xf…

Go之流程控制大全: 细节、示例与最佳实践

引言 在计算机编程中&#xff0c;流程控制是核心的组成部分&#xff0c;它决定了程序应该如何根据给定的情况执行或决策。以下是Go语言所支持的流程控制结构的简要概览&#xff1a; 流程控制类型代码if-else条件分支if condition { } else { }for循环for initialization; con…

el-table相同的值合并单元格+多级表头

<el-table border :data"costList" :span-method"objectSpanMethod" ><el-table-column label"类别" prop"类别" width"120"> </el-table-column><el-table-column label"费用科目" prop&qu…

如何给照片添加水印?请看下面3个简单教程

如何给照片添加水印&#xff1f;随着智能手机的普及和不断提升的拍摄技术&#xff0c;如今人们可以轻松使用手机进行高质量的照片拍摄。从老人到小孩&#xff0c;每个人都可以在日常生活中捕捉到美好瞬间&#xff0c;并将其记录下来。作为一种表达自己的方式&#xff0c;现在手…

重拾潜行初心,用艾尔莎EA B760M-E D5玩转《刺客信条:幻景》

不知不觉&#xff0c;《刺客信条》系列游戏已经推出了有近20部作品。为了体现游戏的差异化&#xff0c;育碧经常会在新作上进行不同程度的玩法修改&#xff0c;然而在多部作品的变化与改动以后&#xff0c;许多玩家已经开始出现了审美疲劳&#xff0c;并怀念起了初代经典而纯粹…