昇思25天学习打卡营第4天|扩散模型

news2024/10/6 0:00:22

文章目录

      • 昇思MindSpore应用实践
        • 基于MindSpore的Diffusion扩散模型
          • 1、Diffusion Models 简介
          • 2、构建 Diffusion Model 的准备工作
          • 3、Attention 机制
          • 4、条件 U-Net
          • 5、Diffusion 正向过程
          • 6、Diffusion 反向过程
          • 7、Diffusion 模型训练
      • Reference

昇思MindSpore应用实践

本系列文章主要用于记录昇思25天学习打卡营的学习心得。

基于MindSpore的Diffusion扩散模型
1、Diffusion Models 简介

关于生成式模型,主要有GANs,VAEs,Flow Models,Diffusion Models,其中最常用的模型主要是生成对抗网络(GANs)和扩散模型(Diffusion Models),两种方法都能从潜在空间的随机噪声中采用并以对抗学习或反向扩散的方式,生成符合真实数据分布的结果,从而应用在各种生成式任务,如:图生图(风格转换)、文生图、语音转换、图像修复等。

在这里插入图片描述

采用GANs的生成方式训练和推理速度相比Diffusion通常较快,但容易产生模式崩塌。重复生成相同结果等问题。而扩散模型的训练更加稳定,且正向加噪过程和反向复原过程都可以通过严格的条件概率和贝叶斯公式推导证明,更具可解释性,缺点是由于需要多步的噪声去除过程,以计算出最符合原始数据分布的像素值从而生成高质量真实类别的图片,速度相比GANs慢。

2、构建 Diffusion Model 的准备工作

以 DDPM 生成图像为例,其前向过程就是给真实图片逐步添加噪声的过程,这个过程是固定的(Frozen),逐渐将高斯噪声添加到图像中,直到最终得到纯噪声。

扩散模型的正向与反向过程示意图:
在这里插入图片描述

实验部分,首先导入必要的mindspore接口:

import math
from functools import partial
%matplotlib inline
import matplotlib.pyplot as plt
from tqdm.auto import tqdm
import numpy as np
from multiprocessing import cpu_count
from download import download

import mindspore as ms
import mindspore.nn as nn
import mindspore.ops as ops
from mindspore import Tensor, Parameter
from mindspore import dtype as mstype
from mindspore.dataset.vision import Resize, Inter, CenterCrop, ToTensor, RandomHorizontalFlip, ToPIL
from mindspore.common.initializer import initializer
from mindspore.amp import DynamicLossScaler

ms.set_seed(0)

def linear_beta_schedule(timesteps):  # 正向扩散时间步设置
    beta_start = 0.0001
    beta_end = 0.02
    return np.linspace(beta_start, beta_end, timesteps).astype(np.float32)

mindspore官方文档中给出了构建一款Diffusion模型的帮助函数和类,用于神经网络的实现:

def rearrange(head, inputs):  # 调整张量分布
    b, hc, x, y = inputs.shape  # 输入形状
    c = hc // head            # 显然是用到了多头注意力机制,将特征维度分割为多个头处理
    return inputs.reshape((b, head, c, x * y))

def rsqrt(x):  # 计算逆平方根
    res = ops.sqrt(x)
    return ops.inv(res)

def randn_like(x, dtype=None):  # 生成与输入张量x形状一致的随机张量
    if dtype is None:
        dtype = x.dtype
    res = ops.standard_normal(x.shape).astype(dtype)
    return res

def randn(shape, dtype=None):  # 生成指定形状的随机张量
    if dtype is None:
        dtype = ms.float32
    res = ops.standard_normal(shape).astype(dtype)
    return res

def randint(low, high, size, dtype=ms.int32):  # 生成一个指定形状的整数随机数张量
    res = ops.uniform(size, Tensor(low, dtype), Tensor(high, dtype), dtype=dtype)
    return res

def exists(x):  # 判断某个变量是否被赋值
    return x is not None

def default(val, d):       # 用于设置默认值
    if exists(val):
        return val
    return d() if callable(d) else d

def _check_dtype(d1, d2):  # 检验数据类型是否兼容
    if ms.float32 in (d1, d2):
        return ms.float32
    if d1 == d2:
        return d1
    raise ValueError('dtype is not supported.')

class Residual(nn.Cell):  # 定义残差模块,源自何神CVPR2015的神来之笔
    def __init__(self, fn):
        super().__init__()
        self.fn = fn

    def construct(self, x, *args, **kwargs):
        return self.fn(x, *args, **kwargs) + x

def Upsample(dim):  # 上采样
    return nn.Conv2dTranspose(dim, dim, 4, 2, pad_mode="pad", padding=1)

def Downsample(dim):  # 下采样
    return nn.Conv2d(dim, dim, 4, 2, pad_mode="pad", padding=1)

# 位置向量,源自Transformer中的位置编码,让网络知道它在哪个特定时间步长(噪声水平)上运行
class SinusoidalPositionEmbeddings(nn.Cell):
    def __init__(self, dim):
        super().__init__()
        self.dim = dim
        half_dim = self.dim // 2
        emb = math.log(10000) / (half_dim - 1)
        emb = np.exp(np.arange(half_dim) * - emb)
        self.emb = Tensor(emb, ms.float32)

    def construct(self, x):
        emb = x[:, None] * self.emb[None, :]
        emb = ops.concat((ops.sin(emb), ops.cos(emb)), axis=-1)
        return emb

class Block(nn.Cell):
    def __init__(self, dim, dim_out, groups=1):
        super().__init__()
        self.proj = nn.Conv2d(dim, dim_out, 3, pad_mode="pad", padding=1)
        self.proj = c(dim, dim_out, 3, padding=1, pad_mode='pad')
        self.norm = nn.GroupNorm(groups, dim_out)
        self.act = nn.SiLU()

    def construct(self, x, scale_shift=None):
        x = self.proj(x)
        x = self.norm(x)

        if exists(scale_shift):
            scale, shift = scale_shift
            x = x * (scale + 1) + shift

        x = self.act(x)
        return x

# ResNet/ConvNeXT模块
class ConvNextBlock(nn.Cell):
    def __init__(self, dim, dim_out, *, time_emb_dim=None, mult=2, norm=True):
        super().__init__()
        self.mlp = (
            nn.SequentialCell(nn.GELU(), nn.Dense(time_emb_dim, dim))
            if exists(time_emb_dim)
            else None
        )
        
        self.ds_conv = nn.Conv2d(dim, dim, 7, padding=3, group=dim, pad_mode="pad")
        self.net = nn.SequentialCell(
            nn.GroupNorm(1, dim) if norm else nn.Identity(),
            nn.Conv2d(dim, dim_out * mult, 3, padding=1, pad_mode="pad"),
            nn.GELU(),
            nn.GroupNorm(1, dim_out * mult),
            nn.Conv2d(dim_out * mult, dim_out, 3, padding=1, pad_mode="pad"),
        )

        self.res_conv = nn.Conv2d(dim, dim_out, 1) if dim != dim_out else nn.Identity()

    def construct(self, x, time_emb=None):
        h = self.ds_conv(x)
        if exists(self.mlp) and exists(time_emb):
            assert exists(time_emb), "time embedding must be passed in"
            condition = self.mlp(time_emb)
            condition = condition.expand_dims(-1).expand_dims(-1)
            h = h + condition

        h = self.net(h)
        return h + self.res_conv(x)
3、Attention 机制

接下来是大名鼎鼎的Attention模块,其中使用了多头自注意力机制和线性注意力机制,其时间和内存要求在序列长度上线性缩放,而不是在常规注意力中缩放:

class Attention(nn.Cell):
    def __init__(self, dim, heads=4, dim_head=32):
        super().__init__()
        self.scale = dim_head ** -0.5
        self.heads = heads
        hidden_dim = dim_head * heads

        self.to_qkv = nn.Conv2d(dim, hidden_dim * 3, 1, pad_mode='valid', has_bias=False)
        self.to_out = nn.Conv2d(hidden_dim, dim, 1, pad_mode='valid', has_bias=True)
        self.map = ops.Map()
        self.partial = ops.Partial()

    def construct(self, x):
        b, _, h, w = x.shape
        qkv = self.to_qkv(x).chunk(3, 1)
        q, k, v = self.map(self.partial(rearrange, self.heads), qkv)

        q = q * self.scale

        # 'b h d i, b h d j -> b h i j'
        sim = ops.bmm(q.swapaxes(2, 3), k)
        attn = ops.softmax(sim, axis=-1)
        # 'b h i j, b h d j -> b h i d'
        out = ops.bmm(attn, v.swapaxes(2, 3))
        out = out.swapaxes(-1, -2).reshape((b, -1, h, w))

        return self.to_out(out)

class LayerNorm(nn.Cell):
    def __init__(self, dim):
        super().__init__()
        self.g = Parameter(initializer('ones', (1, dim, 1, 1)), name='g')

    def construct(self, x):
        eps = 1e-5
        var = x.var(1, keepdims=True)
        mean = x.mean(1, keep_dims=True)
        return (x - mean) * rsqrt((var + eps)) * self.g

class LinearAttention(nn.Cell):
    def __init__(self, dim, heads=4, dim_head=32):
        super().__init__()
        self.scale = dim_head ** -0.5
        self.heads = heads
        hidden_dim = dim_head * heads
        self.to_qkv = nn.Conv2d(dim, hidden_dim * 3, 1, pad_mode='valid', has_bias=False)

        self.to_out = nn.SequentialCell(
            nn.Conv2d(hidden_dim, dim, 1, pad_mode='valid', has_bias=True),
            LayerNorm(dim)
        )

        self.map = ops.Map()
        self.partial = ops.Partial()
        
        def construct(self, x):
        b, _, h, w = x.shape
        qkv = self.to_qkv(x).chunk(3, 1)
        q, k, v = self.map(self.partial(rearrange, self.heads), qkv)

        q = ops.softmax(q, -2)
        k = ops.softmax(k, -1)

        q = q * self.scale
        v = v / (h * w)

        # 'b h d n, b h e n -> b h d e'
        context = ops.bmm(k, v.swapaxes(2, 3))
        # 'b h d e, b h d n -> b h e n'
        out = ops.bmm(context.swapaxes(2, 3), q)

        out = out.reshape((b, -1, h, w))
        return self.to_out(out)

DDPM将U-Net的卷积和注意力层在注意力层之前进行组归一化:

class PreNorm(nn.Cell):
    def __init__(self, dim, fn):
        super().__init__()
        self.fn = fn
        self.norm = nn.GroupNorm(1, dim)

    def construct(self, x):
        x = self.norm(x)
        return self.fn(x)
4、条件 U-Net

相比于普通的编解码结构(Encoder-Decoder),U-Net在编码器和解码器之间引入了跳跃连接,极大地改善了梯度流:
在这里插入图片描述
基于mindspore的U-Net实现:
将先前预定义的位置编码、ResNet/ConvNeXT、Self-Attention和组归一化,用于定义现在的神经网络中。
网络获取了一批(batch_size, num_channels, height, width)形状的噪声图像和一批(batch_size, 1)形状的噪音水平作为输入,并返回(batch_size, num_channels, height, width)形状的张量。

网络构建过程如下:

  • 首先,将卷积层应用于噪声图像批上,并计算噪声水平的位置

  • 接下来,应用一系列下采样级。每个下采样阶段由2个ResNet/ConvNeXT块 + groupnorm + attention + 残差连接 + 一个下采样操作组成

  • 在网络的中间,再次应用ResNet或ConvNeXT块,并与attention交织

  • 接下来,应用一系列上采样级。每个上采样级由2个ResNet/ConvNeXT块+ groupnorm + attention + 残差连接 + 一个上采样操作组成

  • 最后,应用ResNet/ConvNeXT块,然后应用卷积层

最终,神经网络将层堆叠起来,并构成前向传播。

class Unet(nn.Cell):
    def __init__(
            self,
            dim,
            init_dim=None,
            out_dim=None,
            dim_mults=(1, 2, 4, 8),
            channels=3,
            with_time_emb=True,
            convnext_mult=2,
    ):
        super().__init__()

        self.channels = channels
        init_dim = default(init_dim, dim // 3 * 2)
        self.init_conv = nn.Conv2d(channels, init_dim, 7, padding=3, pad_mode="pad", has_bias=True)

        dims = [init_dim, *map(lambda m: dim * m, dim_mults)]
        in_out = list(zip(dims[:-1], dims[1:]))

        block_klass = partial(ConvNextBlock, mult=convnext_mult)

        if with_time_emb:
            time_dim = dim * 4
            self.time_mlp = nn.SequentialCell(
                SinusoidalPositionEmbeddings(dim), # 位置编码
                nn.Dense(dim, time_dim),
                nn.GELU(),
                nn.Dense(time_dim, time_dim),
            )
        else:
            time_dim = None
            self.time_mlp = None

        self.downs = nn.CellList([])
        self.ups = nn.CellList([])
        num_resolutions = len(in_out)

        for ind, (dim_in, dim_out) in enumerate(in_out):
            is_last = ind >= (num_resolutions - 1)

			# 下采样阶段
            self.downs.append(
                nn.CellList(
                    [
                        block_klass(dim_in, dim_out, time_emb_dim=time_dim),
                        block_klass(dim_out, dim_out, time_emb_dim=time_dim),
                        Residual(PreNorm(dim_out, LinearAttention(dim_out))),
                        Downsample(dim_out) if not is_last else nn.Identity(),
                    ]
                )
            )

        mid_dim = dims[-1]
        self.mid_block1 = block_klass(mid_dim, mid_dim, time_emb_dim=time_dim)
        self.mid_attn = Residual(PreNorm(mid_dim, Attention(mid_dim)))
        self.mid_block2 = block_klass(mid_dim, mid_dim, time_emb_dim=time_dim)

        for ind, (dim_in, dim_out) in enumerate(reversed(in_out[1:])):
            is_last = ind >= (num_resolutions - 1)

			# 上采样阶段
            self.ups.append(
                nn.CellList(
                    [
                        block_klass(dim_out * 2, dim_in, time_emb_dim=time_dim),
                        block_klass(dim_in, dim_in, time_emb_dim=time_dim),
                        Residual(PreNorm(dim_in, LinearAttention(dim_in))),
                        Upsample(dim_in) if not is_last else nn.Identity(),
                    ]
                )
            )

        out_dim = default(out_dim, channels)
        self.final_conv = nn.SequentialCell(
            block_klass(dim, dim), nn.Conv2d(dim, out_dim, 1)
        )

    def construct(self, x, time):  # 前向传播
        x = self.init_conv(x)
        t = self.time_mlp(time) if exists(self.time_mlp) else None
        h = []

        for block1, block2, attn, downsample in self.downs:  # 下采样阶段
            x = block1(x, t)
            x = block2(x, t)
            x = attn(x)
            h.append(x)

            x = downsample(x)

        x = self.mid_block1(x, t)
        x = self.mid_attn(x)
        x = self.mid_block2(x, t)

        len_h = len(h) - 1
        for block1, block2, attn, upsample in self.ups:  # 上采样阶段
            x = ops.concat((x, h[len_h]), 1)
            len_h -= 1
            x = block1(x, t)
            x = block2(x, t)
            x = attn(x)

            x = upsample(x)
        return self.final_conv(x)
5、Diffusion 正向过程

首先使用T=200的时间步长,定义噪声添加过程中需要的各种变量:

# 扩散200步
timesteps = 200

# 定义 beta schedule
betas = linear_beta_schedule(timesteps=timesteps)

# 定义 alphas
alphas = 1. - betas
alphas_cumprod = np.cumprod(alphas, axis=0)
alphas_cumprod_prev = np.pad(alphas_cumprod[:-1], (1, 0), constant_values=1)

sqrt_recip_alphas = Tensor(np.sqrt(1. / alphas))
sqrt_alphas_cumprod = Tensor(np.sqrt(alphas_cumprod))
sqrt_one_minus_alphas_cumprod = Tensor(np.sqrt(1. - alphas_cumprod))

# 计算 q(x_{t-1} | x_t, x_0)
posterior_variance = betas * (1. - alphas_cumprod_prev) / (1. - alphas_cumprod)

p2_loss_weight = (1 + alphas_cumprod / (1 - alphas_cumprod)) ** -0.
p2_loss_weight = Tensor(p2_loss_weight)

def extract(a, t, x_shape):
    b = t.shape[0]
    out = Tensor(a).gather(t, -1)
    return out.reshape(b, *((1,) * (len(x_shape) - 1)))

def q_sample(x_start, t, noise=None):  # 前向扩散过程
    if noise is None:
        noise = randn_like(x_start)
    return (extract(sqrt_alphas_cumprod, t, x_start.shape) * x_start +
            extract(sqrt_one_minus_alphas_cumprod, t, x_start.shape) * noise)

def p_losses(unet_model, x_start, t, noise=None):  # 模型的损失函数
    if noise is None:
        noise = randn_like(x_start)
    x_noisy = q_sample(x_start=x_start, t=t, noise=noise)
    predicted_noise = unet_model(x_noisy, t)

    loss = nn.SmoothL1Loss()(noise, predicted_noise)# todo
    loss = loss.reshape(loss.shape[0], -1)
    loss = loss * extract(p2_loss_weight, t, loss.shape)
    return loss.mean()
6、Diffusion 反向过程

在这里插入图片描述
可以通过上述过程,即Diffusion Models生成高质量的结果,如下图中的人脸图像均是生成出来的,并不是现实中实际的人脸:
在这里插入图片描述

7、Diffusion 模型训练

以Fashion_Mnist为例的 Diffusion 模型训练过程如下,具体完整代码可登录昇思大模型平台扩散模型的Jupyter notebook进行尝试,打印log如下:

# 定义动态学习率
lr = nn.cosine_decay_lr(min_lr=1e-7, max_lr=1e-4, total_step=10*3750, step_per_epoch=3750, decay_epoch=10)

# 定义 Unet模型
unet_model = Unet(
    dim=image_size,
    channels=channels,
    dim_mults=(1, 2, 4,)
)

name_list = []
for (name, par) in list(unet_model.parameters_and_names()):
    name_list.append(name)
i = 0
for item in list(unet_model.trainable_params()):
    item.name = name_list[i]
    i += 1

# 定义优化器
optimizer = nn.Adam(unet_model.trainable_params(), learning_rate=lr)
loss_scaler = DynamicLossScaler(65536, 2, 1000)

# 定义前向过程
def forward_fn(data, t, noise=None):
    loss = p_losses(unet_model, data, t, noise)
    return loss

# 计算梯度
grad_fn = ms.value_and_grad(forward_fn, None, optimizer.parameters, has_aux=False)

# 梯度更新
def train_step(data, t, noise):
    loss, grads = grad_fn(data, t, noise)
    optimizer(grads)
    return loss

import time

epochs = 10

for epoch in range(epochs):
    begin_time = time.time()
    for step, batch in enumerate(dataset.create_tuple_iterator()):
        unet_model.set_train()
        batch_size = batch[0].shape[0]
        t = randint(0, timesteps, (batch_size,), dtype=ms.int32)
        noise = randn_like(batch[0])
        loss = train_step(batch[0], t, noise)

        if step % 500 == 0:
            print(" epoch: ", epoch, " step: ", step, " Loss: ", loss)
    end_time = time.time()
    times = end_time - begin_time
    print("training time:", times, "s")
    # 展示随机采样效果
    unet_model.set_train(False)
    samples = sample(unet_model, image_size=image_size, batch_size=64, channels=channels)
    plt.imshow(samples[-1][5].reshape(image_size, image_size, channels), cmap="gray")
print("Training Success!")

由于时间原因,只训练几个Epoch的可视化效果不是很好,loss未进入收敛状态。Fashion_Mnist单张输入只是28x28x1的shape,可以感受到diffusion的训练速度要比GAN慢了很多。如果时间充足,可以用Asend910尝试多跑几个epoch;
在这里插入图片描述
在这里插入图片描述

Reference

综述 - 扩散模型 - Diffusion Models
MindSpore官方文档-Diffusion扩散模型
annotated-diffusion
由浅入深了解Diffusion Model

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

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

相关文章

图形处理单元(GPU)在现代计算中的应用与挑战(研究论文框架)

摘要:随着高性能计算需求的日益增长,图形处理单元(GPU)已从专业的图形渲染处理器转变为具有高性能并行处理能力的多功能计算平台。本文将探讨GPU的核心优势、编程模型、在不同领域的应用以及面临的挑战和限制。此外,还将讨论GPU技术的未来发展趋势和潜在的研究机会。 关键…

Debian/Ubuntu Linux安装OBS

先决条件 建议使用 xserver-xorg 1.18.4 或更新版本,以避免 OBS 中某些功能(例如全屏投影仪)出现潜在的性能问题。在 Linux 上使用 OBS Studio 需要 OpenGL 3.3(或更高版本)支持。在终端中输入以下内容来检查系统支持…

办公效率新高度:利用办公软件实现文件夹编号批量复制与移动,轻松管理文件

在数字化时代,我们的工作和生活都围绕着海量的数据和文件展开。然而,随着数据量的不断增加,如何高效地管理这些数字资产成为了摆在我们面前的一大难题。今天,我要向您介绍一种革命性的方法——利用办公软件实现文件夹编号批量复制…

揭开统计分析的秘密:独立样本和配对样本T检验实战案例

一、独立样本T检验 1.收集20名学生的自信心值 见下表,试问该指标是否与性别有关?(非参数检验或参数检验) 数据值 性别 1,1,1,1,2,2,1,1&#…

UE5蓝图快速实现打开网页与加群

蓝图节点:启动URL 直接将对应的网址输入,并使用即可快速打开对应的网页,qq、discord等群聊的加入也可以直接通过该节点来完成。 使用后会直接打开浏览器。

装载问题(回溯法)

#include<iostream> using namespace std; int n;//货物的数量 int c;//轮船的总的载重量 int cw;//轮船当前的载重量 int r;//货物的总重量 int w[1000];//n个货物各自的重量 int x[1000];//当前最优解 int bestx[1000];//最优解 int bestw;//货物的最优载重量 void Bac…

Day 48 消息队列集群RabbitMQ

消息队列集群-RabbitMQ 一、消息中间件 中间件 tomcat java web中间件 web容器 mysql php php mysql uwsgi python mysql mycat 数据库中间件 rabbitMQ 消息中间件 1、简介 MQ 全称为&#xff08;Message Queue消息队列&#xff09;。是一种应用程序对应用程序的通信方…

gemini 1.5 flash (node项目)

https://www.npmjs.com/package/google/generative-ai https://ai.google.dev/pricing?hlzh-cn https://aistudio.google.com/app/apikey https://ai.google.dev/gemini-api/docs/models/gemini?hlzh-cn#gemini-1.5-flash https://ai.google.dev/gemini-api/docs/get-started…

vue3+vite+nodejs,通过接口的形式请求后端打包(可打包全部或指定打包组件)

项目地址https://gitee.com/sybb011016/test_build 打包通过按钮的形式请求接口&#xff0c;让后端进行打包&#xff0c;后端使用express-generator搭建模版。前端项目就在npm init vuelatest基础上添加了路由 如果只想打包AboutView组件&#xff0c;首先修改后端接口。 //打…

【计算机毕业设计】基于微信小程序的电子购物系统的设计与实现【源码+lw+部署文档】

包含论文源码的压缩包较大&#xff0c;请私信或者加我的绿色小软件获取 免责声明&#xff1a;资料部分来源于合法的互联网渠道收集和整理&#xff0c;部分自己学习积累成果&#xff0c;供大家学习参考与交流。收取的费用仅用于收集和整理资料耗费时间的酬劳。 本人尊重原创作者…

Hadoop3:Yarn常用Shell命令

一、查看任务 1、查看所有任务 yarn application -list2、根据状态查看任务 语法 yarn application -list -appStates &#xff08;所有状态&#xff1a;ALL、NEW、NEW_SAVING、SUBMITTED、ACCEPTED、RUNNING、FINISHED、FAILED、KILLED&#xff09;例如 yarn application…

品牌推广策划怎么写?看这篇就够了!

品牌推广策划是品牌成功的关键&#xff0c;不仅仅是简单的广告宣传&#xff0c;更是一套全面的策略&#xff0c;通过多个渠道和方式&#xff0c;让品牌形象深入人心&#xff0c;打造成爆款品牌。 本人拥有一家手工酸奶品牌&#xff0c;目前全国也复制了100多家门店&#xff0c…

Excel 宏录制与VBA编程 —— 15、MsgBox参数详解

Msgbox参数具体如下 Msgbox参数使用1 Msgbox参数使用2&#xff08;返回值示例&#xff09; &ensp ;###### 关注 笔者 - jxd

【Mac】Auto Mouse Click for Mac(高效、稳定的鼠标连点器软件)软件介绍

软件介绍 Auto Mouse Click for Mac 是一款专为 macOS 平台设计的自动鼠标点击软件&#xff0c;它可以帮助用户自动化重复的鼠标点击操作&#xff0c;从而提高工作效率。以下是这款软件的主要特点和功能&#xff1a; 1.自动化点击操作&#xff1a;Auto Mouse Click 允许用户录…

聊聊如何制定互联网产品测试策略

提起互联网产品测试&#xff0c;给人的第一感觉那就是一个字“快”&#xff0c;相比于传统行业的软件&#xff0c;更新周期快的一个多月一个版本&#xff0c;慢的半年或一年一个大版本&#xff0c;从测试的角度出发&#xff0c;制定产品的测试策略侧重点有所不一样&#xff0c;…

0基础学JMeter:如何开始简单的WEB压力测试(一)

背景 最近工作上被安排针对Web网站进行性能压测&#xff0c;以评估特定的硬件配置下Web网站可支持的并发用户数。考虑到JMeter是流行的Web性能压测工具&#xff0c;因此趁着这次机会上网查阅了很多关于JMeter的资料&#xff0c;也自己动手进行软件的配置和调测&#xff0c;从最…

华为昇腾NPU实战:LLM ChatGLM2模型推理体验

参考&#xff1a;https://gitee.com/mindspore/mindformers/blob/dev/docs/model_cards/glm2.md#chatglm2-6b 1、安装环境&#xff1a; 昇腾NPU卡对应英伟达GPU卡&#xff0c;CANN对应CUDA底层&#xff1b; mindspore对应pytorch&#xff1b;mindformers对应transformers 本…

2025艺考时间线来啦!所有艺考生码住!

2025届艺考生们的征途即将启程。对于每一个即将参加艺考的考生和家长来说&#xff0c;梳理艺考时间节点是尤为重要的。 对于艺考生而言&#xff0c;更早的规划意味着更充分的准备时间&#xff0c;更扎实的专业能力。补齐艺考信息差&#xff0c;以下2025艺考时间线一定要看明白…

【教学类65-04】20240625秘密花园涂色书04(通义万相)(图纸16:9,A4横板1张,一大168张纸168份)

背景需求 【教学类65-01】20240622秘密花园涂色书01&#xff08;通义万相&#xff09;&#xff08;A4横版2张&#xff0c;一大3小 38张纸76份&#xff09;-CSDN博客文章浏览阅读118次。【教学类65-01】20240622秘密花园涂色书01&#xff08;通义万相&#xff09;&#xff08;A…