【AIGC系列】4:Stable Diffusion应用实践和代码分析
AIGC系列博文:
【AIGC系列】1:自编码器(AutoEncoder, AE)
【AIGC系列】2:DALL·E 2模型介绍(内含扩散模型介绍)
【AIGC系列】3:Stable Diffusion模型原理介绍
【AIGC系列】4:Stable Diffusion应用实践和代码分析
【AIGC系列】5:视频生成模型数据处理和预训练流程介绍(Sora、MovieGen、HunyuanVideo)
目录
- 1 AutoEncoder
- 2 CLIP text encoder
- 3 UNet
- 4 应用
- 4.1 文生图
- 4.2 图生图
- 4.3 图像inpainting
- 5 其他
上一篇博文我们学习了Stable Diffusion的原理,这一篇我们继续深入了解Stable Diffusion的应用实践和代码分析。
1 AutoEncoder
SD采用基于KL-reg的autoencoder,当输入图像为512x512时将得到64x64x4大小的latent。autoencoder模型是在OpenImages数据集上基于256x256大小训练的,但是由于模型是全卷积结构的(基于ResnetBlock),所以可以扩展应用在尺寸>256的图像上。
下面我们使用diffusers库来加载autoencoder模型,实现图像的压缩和重建,代码如下:
import torch
from diffusers import AutoencoderKL
import numpy as np
from PIL import Imageprint(torch.cuda.is_available())#加载模型: autoencoder可以通过SD权重指定subfolder来单独加载
print("Start...")
autoencoder = AutoencoderKL.from_pretrained("runwayml/stable-diffusion-v1-5", subfolder="vae")
autoencoder.to("cuda", dtype=torch.float16)
print("Get weight successfully")# 读取图像并预处理
# raw_image = Image.open("liuyifei.jpg").convert("RGB").resize((256, 256))
raw_image = Image.open("liuyifei.jpg").convert("RGB")
image = np.array(raw_image).astype(np.float32) / 127.5 - 1.0
image = image[None].transpose(0, 3, 1, 2)
image = torch.from_numpy(image) # 压缩图像为latent并重建
with torch.inference_mode(): latent = autoencoder.encode(image.to("cuda", dtype=torch.float16)).latent_dist.sample() rec_image = autoencoder.decode(latent).sample rec_image = (rec_image / 2 + 0.5).clamp(0, 1) rec_image = rec_image.cpu().permute(0, 2, 3, 1).numpy() rec_image = (rec_image * 255).round().astype("uint8") rec_image = Image.fromarray(rec_image[0]) rec_image.save("liuyifei_re.jpg")
重建效果如下所示,对比手表上的文字,可以看出,autoencoder将图片压缩到latent后再重建其实是有损的。
为了改善这种畸变,stabilityai在发布SD 2.0时同时发布了两个在LAION子数据集上精调的autoencoder,注意这里只精调autoencoder的decoder部分,SD的UNet在训练过程只需要encoder部分,所以这样精调后的autoencoder可以直接用在先前训练好的UNet上(这种技巧还是比较通用的,比如谷歌的Parti也是在训练好后自回归生成模型后,扩大并精调ViT-VQGAN的decoder模块来提升生成质量)。我们也可以直接在diffusers中使用这些autoencoder,比如mse版本(采用mse损失来finetune的模型):
autoencoder = AutoencoderKL.from_pretrained("stabilityai/sd-vae-ft-mse/")
2 CLIP text encoder
SD采用CLIP text encoder来对输入的文本生成text embeddings,采用的CLIP模型是clip-vit-large-patch14,该模型的text encoder层数为12,特征维度为768,模型参数大小是123M。文本输入text encoder后得到最后的hidden states特征维度大小为77x768(77是token的数量),这个细粒度的text embeddings将以cross attention的方式输入UNet中。
在transofmers库中,使用CLIP text encoder的代码如下:
from transformers import CLIPTextModel, CLIPTokenizer text_encoder = CLIPTextModel.from_pretrained("runwayml/stable-diffusion-v1-5", subfolder="text_encoder").to("cuda")
# text_encoder = CLIPTextModel.from_pretrained("openai/clip-vit-large-patch14").to("cuda")
tokenizer = CLIPTokenizer.from_pretrained("runwayml/stable-diffusion-v1-5", subfolder="tokenizer")
# tokenizer = CLIPTokenizer.from_pretrained("openai/clip-vit-large-patch14") # 对输入的text进行tokenize,得到对应的token ids
prompt = "a photograph of an astronaut riding a horse"
text_input_ids = tokenizer( prompt, padding="max_length", max_length=tokenizer.model_max_length, truncation=True, return_tensors="pt"
).input_idsprint(f" \n\n text_input_ids: {text_input_ids} \n\n")# 将token ids送入text model得到77x768的特征
text_embeddings = text_encoder(text_input_ids.to("cuda"))[0]
print(f" \n\n text_embeddings: {text_embeddings} \n\n")
输出如下:
text_input_ids: tensor([[49406, 320, 8853, 539, 550, 18376, 6765, 320, 4558, 49407,49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407,49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407,49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407,49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407,49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407,49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407,49407, 49407, 49407, 49407, 49407, 49407, 49407]])text_embeddings: tensor([[[-0.3884, 0.0229, -0.0522, ..., -0.4899, -0.3066, 0.0675],[ 0.0290, -1.3258, 0.3085, ..., -0.5257, 0.9768, 0.6652],[ 0.4595, 0.5617, 1.6663, ..., -1.9515, -1.2307, 0.0104],...,[-3.0421, -0.0656, -0.1793, ..., 0.3943, -0.0190, 0.7664],[-3.0551, -0.1036, -0.1936, ..., 0.4236, -0.0189, 0.7575],[-2.9854, -0.0832, -0.1715, ..., 0.4355, 0.0095, 0.7485]]],device='cuda:0', grad_fn=<NativeLayerNormBackward0>)
值得注意的是,这里的tokenizer最大长度为77(CLIP训练时所采用的设置),当输入text的tokens数量超过77后,将进行截断,如果不足则进行paddings,这样将保证无论输入任何长度的文本(甚至是空文本)都得到77x768大小的特征。在上面的例子里,输入的tokens数量少于77,所以后面都padding了id为49407的token。
在训练SD的过程中,CLIP text encoder模型是冻结的。在早期的工作中,比如OpenAI的GLIDE和latent diffusion中的LDM均采用一个随机初始化的tranformer模型来提取text的特征,但是最新的工作都是采用预训练好的text model。比如谷歌的Imagen采用纯文本模型T5 encoder来提出文本特征,而SD则采用CLIP text encoder,预训练好的模型往往已经在大规模数据集上进行了训练,它们要比直接采用一个从零训练好的模型要好。
3 UNet
SD的扩散模型是一个860M的UNet,其主要结构如下图所示,其中encoder部分包括3个CrossAttnDownBlock2D模块和1个DownBlock2D模块,而decoder部分包括1个UpBlock2D模块和3个CrossAttnUpBlock2D模块,中间还有一个UNetMidBlock2DCrossAttn模块。
encoder和decoder两个部分是完全对应的,中间有skip connection。3个CrossAttnDownBlock2D模块最后均有一个2x的downsample操作,而DownBlock2D模块是不包含下采样的。
其中CrossAttnDownBlock2D模块的主要结构如下图所示,text condition将通过CrossAttention模块嵌入进来,此时Attention的query是UNet的中间特征,而key和value则是text embeddings。
SD和DDPM一样采用预测noise的方法来训练UNet,其训练损失也和DDPM一样。基于diffusers库,我们可以实现SD的训练,其核心代码如下:
import torch
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
from PIL import Image
from diffusers import AutoencoderKL, UNet2DConditionModel, DDPMScheduler
from transformers import CLIPTextModel, CLIPTokenizer
import torch.nn.functional as F# 自定义Dataset类
class CustomImageTextDataset(Dataset):def __init__(self, image_paths, text_descriptions, transform=None):self.image_paths = image_pathsself.text_descriptions = text_descriptionsself.transform = transformdef __len__(self):return len(self.image_paths)def __getitem__(self, idx):image_path = self.image_paths[idx]text_description = self.text_descriptions[idx]# 加载图像image = Image.open(image_path).convert("RGB")if self.transform:image = self.transform(image)return {'image': image,'text': text_description}# 数据准备
image_paths = ["path/to/image1.jpg", "path/to/image2.jpg"] # 替换为实际的图像路径
text_descriptions = ["description for image1", "description for image2"] # 替换为实际的文本描述# 图像转换(预处理)
transform = transforms.Compose([transforms.Resize((256, 256)), # 调整大小transforms.ToTensor(), # 转换为张量
])# 创建数据集实例
dataset = CustomImageTextDataset(image_paths=image_paths, text_descriptions=text_descriptions, transform=transform)# 创建DataLoader
train_dataloader = DataLoader(dataset, batch_size=4, shuffle=True, num_workers=0)# 加载autoencoder
vae = AutoencoderKL.from_pretrained("runwayml/stable-diffusion-v1-5", subfolder="vae")
# 加载text encoder
text_encoder = CLIPTextModel.from_pretrained("runwayml/stable-diffusion-v1-5", subfolder="text_encoder")
tokenizer = CLIPTokenizer.from_pretrained("runwayml/stable-diffusion-v1-5", subfolder="tokenizer")model_config = {"sample_size": 32,"in_channels": 4,"out_channels": 4,"down_block_types": ("DownBlock2D", "CrossAttnDownBlock2D", "CrossAttnDownBlock2D", "CrossAttnDownBlock2D"),"up_block_types": ("UpBlock2D", "CrossAttnUpBlock2D", "CrossAttnUpBlock2D", "CrossAttnUpBlock2D"),"block_out_channels": (320, 640, 1280, 1280),"layers_per_block": 2,"cross_attention_dim": 768,"attention_head_dim": 8,
}
# 初始化UNet
unet = UNet2DConditionModel(**model_config)# 定义scheduler
noise_scheduler = DDPMScheduler(beta_start=0.00085,beta_end=0.012,beta_schedule="scaled_linear",num_train_timesteps=1000
)# 冻结vae和text_encoder
vae.requires_grad_(False)
text_encoder.requires_grad_(False)opt = torch.optim.AdamW(unet.parameters(), lr=1e-4)# 训练循环
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
unet.to(device)
vae.to(device)
text_encoder.to(device)for epoch in range(10): # 假设训练10个epochunet.train()for step, batch in enumerate(train_dataloader):with torch.no_grad():# 将image转到latent空间latents = vae.encode(batch["image"].to(device)).latent_dist.sample()# rescaling latentslatents = latents * vae.config.scaling_factor# 提取text embeddingstext_input_ids = tokenizer(batch["text"],padding="max_length",max_length=tokenizer.model_max_length,truncation=True,return_tensors="pt").input_ids.to(device)text_embeddings = text_encoder(text_input_ids)[0]# 随机采样噪音noise = torch.randn_like(latents)bsz = latents.shape[0]# 随机采样timesteptimesteps = torch.randint(0, noise_scheduler.num_train_timesteps, (bsz,), device=device).long()# 将noise添加到latent上,即扩散过程noisy_latents = noise_scheduler.add_noise(latents, noise, timesteps)# 预测noise并计算lossmodel_pred = unet(noisy_latents, timesteps, encoder_hidden_states=text_embeddings).sampleloss = F.mse_loss(model_pred.float(), noise.float(), reduction="mean")opt.zero_grad()loss.backward()opt.step()if step % 10 == 0:print(f"Epoch {epoch}, Step {step}, Loss: {loss.item()}")# 在训练完成后保存模型
model_save_path = 'path/to/your/unet_model.pth'
torch.save(unet.state_dict(), model_save_path)
print(f"Model has been saved to {model_save_path}")optimizer_save_path = 'path/to/your/optimizer.pth'
torch.save(opt.state_dict(), optimizer_save_path)
print(f"Optimizer state has been saved to {optimizer_save_path}")# 加载模型进行推理或继续训练
unet_load_path = 'path/to/your/unet_model.pth'
unet_loaded = UNet2DConditionModel(**model_config) # 创建一个与原模型结构相同的实例
unet_loaded.load_state_dict(torch.load(unet_load_path))
unet_loaded.to(device)
unet_loaded.eval() # 设置为评估模式# 恢复优化器的状态以继续训练
opt_load_path = 'path/to/your/optimizer.pth'
opt_loaded = torch.optim.AdamW(unet_loaded.parameters(), lr=1e-4) # 创建一个新的优化器实例
opt_loaded.load_state_dict(torch.load(opt_load_path))# 使用unet_loaded进行推理或者用opt_loaded继续训练。
注意的是SD的noise scheduler虽然也是采用一个1000步长的scheduler,但是不是linear的,而是scaled linear,具体的计算如下所示:
betas = torch.linspace(beta_start**0.5, beta_end**0.5, num_train_timesteps, dtype=torch.float32) ** 2
在训练条件扩散模型时,往往会采用Classifier-Free Guidance (CFG),即在训练条件扩散模型的同时也训练一个无条件的扩散模型,同时在采样阶段将条件控制下预测的噪音和无条件下的预测噪音组合在一起来确定最终的噪音,CFG对于提升条件扩散模型的图像生成效果是至关重要的。
4 应用
4.1 文生图
根据文本生成图像是文生图的最核心的功能,SD的文生图的推理流程图:首先根据输入text用text encoder提取text embeddings,同时初始化一个随机噪音noise(latent上的,512x512图像对应的noise维度为64x64x4),然后将text embeddings和noise送入扩散模型UNet中生成去噪后的latent,最后送入autoencoder的decoder模块得到生成的图像。
使用diffusers库,我们可以直接调用StableDiffusionPipeline来实现文生图,具体代码如下所示:
import torch
from diffusers import StableDiffusionPipeline
from PIL import Image # 组合图像,生成grid
def image_grid(imgs, rows, cols): assert len(imgs) == rows*cols w, h = imgs[0].size grid = Image.new('RGB', size=(cols*w, rows*h)) grid_w, grid_h = grid.size for i, img in enumerate(imgs): grid.paste(img, box=(i%cols*w, i//cols*h)) return grid # 加载文生图pipeline
pipe = StableDiffusionPipeline.from_pretrained( "runwayml/stable-diffusion-v1-5", # 或者使用 SD v1.4: "CompVis/stable-diffusion-v1-4" torch_dtype=torch.float16
).to("cuda") # 输入text,这里text又称为prompt
prompts = [ "a photograph of an astronaut riding a horse", "A cute otter in a rainbow whirlpool holding shells, watercolor", "An avocado armchair", "A white dog wearing sunglasses"
] generator = torch.Generator("cuda").manual_seed(42) # 定义随机seed,保证可重复性 # 执行推理
images = pipe( prompts, height=512, width=512, num_inference_steps=50, guidance_scale=7.5, negative_prompt=None, num_images_per_prompt=1, generator=generator
).images # 保存每个单独的图片
for idx, img in enumerate(images):img.save(f"image_{idx}.png")# 创建并保存组合后的网格图
grid = image_grid(images, rows=1, cols=len(prompts))
grid.save("combined_images.png")
print("所有图片已保存到本地。")
生成的结果如下:
重要参数说明:
-
指定width和height来决定生成图像的大小:前面说过SD最后是在512x512尺度上训练的,所以生成512x512尺寸效果是最好的,但是实际上SD可以生成任意尺寸的图片:一方面autoencoder支持任意尺寸的图片的编码和解码,另外一方面扩散模型UNet也是支持任意尺寸的latents生成的(UNet是卷积+attention的混合结构)。但是生成512x512以外的图片会存在一些问题,比如生成低分辨率图像时,图像的质量大幅度下降等等。
-
num_inference_steps:指推理过程中的去噪步数或者采样步数。SD在训练过程采用的是步数为1000的noise scheduler,但是在推理时往往采用速度更快的scheduler:只需要少量的采样步数就能生成不错的图像,比如SD默认采用PNDM scheduler,它只需要采样50步就可以出图。当然我们也可以换用其它类型的scheduler,比如DDIM scheduler和DPM-Solver scheduler。我们可以在diffusers中直接替换scheduler,比如我们想使用DDIM:
from diffusers import DDIMScheduler # 注意这里的clip_sample要关闭,否则生成图像存在问题,因为不能对latent进行clip
pipe.scheduler = DDIMScheduler.from_config(pipe.scheduler.config, clip_sample=False)
-
guidance_scale:当CFG的guidance_scale越大时,生成的图像应该会和输入文本更一致。SD默认采用的guidance_scale为7.5。但是过大的guidance_scale也会出现问题,主要是由于训练和测试的不一致,过大的guidance_scale会导致生成的样本超出范围。
-
negative_prompt:这个参数和CFG有关,去噪过程的噪音预测不仅仅依赖条件扩散模型,也依赖无条件扩散模型,这里的negative_prompt便是无条件扩散模型的text输入,前面说过训练过程中我们将text置为空字符串来实现无条件扩散模型,所以这里negative_prompt = None 。但是有时候我们可以使用不为空的negative_prompt来避免模型生成的图像包含不想要的东西,因为从上述公式可以看到这里的无条件扩散模型是我们想远离的部分。
4.2 图生图
图生图(image2image)是对文生图功能的一个扩展,这个功能来源于SDEdit这个工作,其核心思路也非常简单:给定一个笔画的色块图像,可以先给它加一定的高斯噪音(执行扩散过程)得到噪音图像,然后基于扩散模型对这个噪音图像进行去噪,就可以生成新的图像,但是这个图像在结构和布局和输入图像基本一致。
相比文生图流程来说,这里的初始latent不再是一个随机噪音,而是由初始图像经过autoencoder编码之后的latent加高斯噪音得到,这里的加噪过程就是扩散过程。要注意的是,去噪过程的步数要和加噪过程的步数一致,就是说你加了多少噪音,就应该去掉多少噪音,这样才能生成想要的无噪音图像。
在diffusers中,我们可以使用StableDiffusionImg2ImgPipeline来实现文生图,具体代码如下所示:
import torch
from diffusers import StableDiffusionImg2ImgPipeline
from PIL import Image# 加载图生图pipeline
model_id = "runwayml/stable-diffusion-v1-5"
pipe = StableDiffusionImg2ImgPipeline.from_pretrained(model_id, torch_dtype=torch.float16).to("cuda")# 读取初始图片
init_image = Image.open("liuyifei.jpg").convert("RGB").resize((512, 512))
print(init_image.size)
init_image.save("liuyifei_512.jpg")# 推理
prompt = "A girl wearing a hat on her head."
generator = torch.Generator(device="cuda").manual_seed(2023)image = pipe(prompt=prompt,image=init_image,strength=0.8,guidance_scale=7.5,generator=generator
).images[0]# 保存生成的图像
output_path = "generated_liuyifei.jpg"
image.save(output_path)
print(f"Generated image saved to {output_path}")
原始图片:
效果如下:
相比文生图的pipeline,图生图的pipeline还多了一个参数strength,这个参数介于0-1之间,表示对输入图片加噪音的程度,这个值越大加的噪音越多,对原始图片的破坏也就越大,当strength=1时,其实就变成了一个随机噪音,此时就相当于纯粹的文生图pipeline了。
4.3 图像inpainting
图像inpainting和图生图一样也是文生图功能的一个扩展。SD的图像inpainting不是用在图像修复上,而是主要用在图像编辑上:给定一个输入图像和想要编辑的区域mask,我们想通过文生图来编辑mask区域的内容。
它和图生图一样,首先将输入图像通过autoencoder编码为latent,然后加入一定的高斯噪音生成noisy latent,再进行去噪生成图像,但是这里为了保证mask以外的区域不发生变化,在去噪过程的每一步,都将扩散模型预测的noisy latent用真实图像同level的nosiy latent替换。
在diffusers中,使用StableDiffusionInpaintPipelineLegacy可以实现文本引导下的图像inpainting,具体代码如下所示:
import torch
from diffusers import StableDiffusionInpaintPipelineLegacy
from PIL import Image # 加载inpainting pipeline
model_id = "runwayml/stable-diffusion-v1-5"
pipe = StableDiffusionInpaintPipelineLegacy.from_pretrained(model_id, torch_dtype=torch.float16).to("cuda") # 读取输入图像和输入mask
input_image = Image.open("overture-creations-5sI6fQgYIuo.png").resize((512, 512))
input_mask = Image.open("overture-creations-5sI6fQgYIuo_mask.png").resize((512, 512)) # 执行推理
prompt = ["a mecha robot sitting on a bench", "a cat sitting on a bench"]
generator = torch.Generator("cuda").manual_seed(0) with torch.autocast("cuda"): images = pipe( prompt=prompt, image=input_image, mask_image=input_mask, num_inference_steps=50, strength=0.75, guidance_scale=7.5, num_images_per_prompt=1, generator=generator, ).images # 保存每个单独的图片
for idx, img in enumerate(images):img.save(f"image_{idx}.png")print("所有图片已保存到本地。")
5 其他
Colab上开源的Stable Diffusion 2.1 GUI:stable_diffusion_2_0.ipynb。
最强大且模块化的具有图形/节点界面的稳定扩散GUI:ComfyUI。
Huggingface模型库:https://huggingface.co/stabilityai。
Huggingface的Diffuser库:https://github.com/huggingface/diffusers。
相关文章:

【AIGC系列】4:Stable Diffusion应用实践和代码分析
AIGC系列博文: 【AIGC系列】1:自编码器(AutoEncoder, AE) 【AIGC系列】2:DALLE 2模型介绍(内含扩散模型介绍) 【AIGC系列】3:Stable Diffusion模型原理介绍 【AIGC系列】4࿱…...
小米火龙CPU和其他几代温度太高的CPU是由谁代工的
小米火龙CPU”并非小米自研芯片,而是指搭载在小米手机上的部分高通骁龙处理器因发热问题被调侃为“火龙”。以下是几款被称为“火龙”的高通CPU及其代工情况: 骁龙810 骁龙810是高通历史上最著名的“火龙”之一,采用台积电20nm工艺代工。由于…...

在 ASP.NET Core 中压缩并减少图像的文件大小
示例代码:https://download.csdn.net/download/hefeng_aspnet/90294127 在当今的数字时代,图像是 Web 应用程序和用户体验不可或缺的一部分。但是,处理大型图像文件可能会导致网页加载缓慢和更高的存储费用。为了解决这个问题,在…...

网络流算法: Dinic算法
图论相关帖子 基本概念图的表示: 邻接矩阵和邻接表图的遍历: 深度优先与广度优先拓扑排序图的最短路径:Dijkstra算法和Bellman-Ford算法最小生成树二分图多源最短路径强连通分量欧拉回路和汉密尔顿回路网络流算法: Edmonds-Karp算法网络流算法: Dinic算法 环境要求 本文所用…...

【Godot4.3】自定义简易菜单栏节点ETDMenuBar
概述 Godot中的菜单创建是一个复杂的灾难性工作,往往无从下手,我也是不止一次尝试简化菜单的创建。 从自己去年的发明“简易树形数据”用于简化Tree控件获得灵感,于是尝试编写了用于表示菜单数据的EasyMenuData类,以及对应的纯文…...

如何杀死僵尸进程?没有那个进程?
在题主跑代码的时候遇到了这样一种很奇怪的问题: 可以看到显卡0没有跑任何程序但是还是被占据着大量显存,这种进程称为“僵尸进程”,并且当我想kill它的时候,出现下面这种情况: 查过各种资料,最后我的解决…...

Solana 核心概念全解析:账户、交易、合约与租约,高流量区块链技术揭秘!
目录 1.Solana 核心概念简述 1.1. 账户(Account) 1.2. 交易(Transaction) 1.3. 交易指令(Instruction) 1.4. SPL 代币 1.5. 合约(Program) 1.6. 租约(Rent&#x…...

Leetcode-853. Car Fleet [C++][Java]
目录 一、题目描述 二、解题思路 Leetcode-853. Car Fleethttps://leetcode.com/problems/car-fleet/description/ 一、题目描述 There are n cars at given miles away from the starting mile 0, traveling to reach the mile target. You are given two integer array …...

012 rocketmq事务消息
文章目录 事务消息概念介绍交互流程事务消息原理TransactionListener接⼝TransactionProducer.javaTransactionConsumer.java 事务消息 内置topic中的消息对消费者不可见 本地事务mq消息事务消息 消息队列 RocketMQ 版提供的分布式事务消息适⽤于所有对数据最终⼀致性有强需求…...
ChatGPT与DeepSeek:开源与闭源的AI模型之争
目录 一、模型架构与技术原理 二、性能能力与应用场景 三、用户体验与部署灵活性 四、成本与商业模式 五、未来展望与市场影响 六、总结 随着人工智能技术的飞速发展,ChatGPT和DeepSeek作为两大领先的AI语言模型,成为了行业内外关注的焦点。它们在…...

Ollama的底层实现原理分析
一、背景 Ollama我们可以很方便的对DeepSeek等开源大模型进行部署,几条命令便能部署一个本地大模型服务,降低了非专业大模型开发者的门槛。 我们从中可以看到类似Docker的影子,ollama run 、ollama list等等,拉取对应大模型镜像&a…...
nginx 动态计算拦截非法访问ip
需求:在Nginx上实现一个动态拦截IP的方法,具体是当某个IP在1分钟内访问超过60次时,将其加入Redis并拦截,拦截时间默认1天。 技术选型:使用NginxLuaRedis的方法。这种方案通过Lua脚本在Nginx处理请求时检查Redis中的黑…...

商业秘密维权有哪些成本开支?
企业商业秘密百问百答之六十三:商业秘密维权费用项目有哪些? 在商业秘密维权过程中,原告可能需要支付多种费用,一般费用项目包括: 1、诉讼费。诉讼费是向法院支付的费用,包括起诉费、案件受理费等。这些费…...

使用UA-SPEECH和TORGO数据库验证自动构音障碍语音分类方法
使用UA-SPEECH和TORGO数据库验证自动构音障碍语音分类方法 引言 原文:On using the UA-Speech and TORGO databases to validate automatic dysarthric speech classification approaches 构音障碍简介 构音障碍是一种由于脑损伤或神经疾病(如脑瘫、肌萎缩侧索硬化症、帕金森…...
WebSocketHandler 是 Spring Framework 中用于处理 WebSocket 通信的接口
WebSocketHandler 是 Spring Framework 中用于处理 WebSocket 通信的接口,其主要作用是定义了如何处理 WebSocket 的各种事件和消息。以下是 WebSocketHandler 的主要作用和功能: ### 1. 处理 WebSocket 生命周期事件 WebSocketHandler 定义了多个方法来…...

Pikachu
一、网站搭建 同样的,先下载安装好phpstudy 然后启动Apache和Mysql 然后下载pikachu,解压到phpstudy文件夹下的www文件 然后用vscode打开pikachu中www文件夹下inc中的config.inc.php 将账户和密码改为和phpstudy中的一致(默认都是root&…...
如何使用 Jenkins 实现 CI/CD 流水线:从零开始搭建自动化部署流程
如何使用 Jenkins 实现 CI/CD 流水线:从零开始搭建自动化部署流程 在软件开发过程中,持续集成(CI)和持续交付(CD)已经成为现代开发和运维的标准实践。随着代码的迭代越来越频繁,传统的手动部署方式不仅低效,而且容易出错。为了提高开发效率和代码质量,Jenkins作为一款…...

Vue.js 学习笔记
文章目录 前言一、Vue.js 基础概念1.1 Vue.js 简介1.2 Vue.js 的特点1.3 Vue.js 基础示例 二、Vue.js 常用指令2.1 双向数据绑定(v-model)2.2 条件渲染(v-if 和 v-show)2.3 列表渲染(v-for)2.4 事件处理&am…...
数据存储:一文掌握RabbitMQ的详细使用
文章目录 一、RabbitMQ简介二、RabbitMQ的概述2.1 基本概念2.2 实际应用场景三、RabbitMQ的安装与配置3.1 安装RabbitMQ3.2 启用管理插件四、使用Python操作RabbitMQ4.1 安装Pika库4.2 生产者示例4.3 消费者示例4.4 发布/订阅模式示例五、RabbitMQ的高级特性5.1 消息持久化5.2 …...

辛格迪客户案例 | 祐儿医药科技GMP培训管理(TMS)项目
01 项目背景:顺应行业趋势,弥补管理短板 随着医药科技行业的快速发展,相关法规和标准不断更新,对企业的质量管理和人员培训提出了更高要求。祐儿医药科技有限公司(以下简称“祐儿医药”)作为一家专注于创新…...

UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...

idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...

【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...