从零开始打造AI画图大师:条件扩散模型完整实现与无分类器指引详解

news2026/4/27 14:34:12
你有没有想过AI是如何听懂你的指令画出你想要的东西的当你对Midjourney输入“一只穿着宇航服的柴犬”它真的能生成那张图——这背后究竟发生了什么今天我将带你亲手实现一个基础的文本控制AI绘图系统。虽然我们做的是“数字0~9”的控制但原理和那些动辄几十亿参数的大模型完全一致。一、更上一层楼让AI听懂你的“命令”在之前的项目中我们的扩散模型虽然能生成MNIST手写数字但它是完全不受控的——你无法告诉它“我要一个数字5”它生成什么全凭运气。1.1 核心思维从无条件到有条件想象一下你请一个画家画画无条件扩散模型你告诉画家“随便画点啥”。他画什么你都只能接受完全看他的心情。条件扩散模型你告诉画家“给我画一个数字8”。他听懂了你的指令专门为你创作一个8。这就是条件扩散模型的核心思想——我们在神经网络中引入了一个额外的输入也就是条件y告诉模型“我想要什么”。条件y可以是各种各样的东西一个数字标签就像我们今天要做的MNIST手写数字、一段文本描述、一张低分辨率的图像这就是超分辨率技术甚至是边缘检测图或姿态关键点。1.2 简单的实现思路如何让模型消化这个“条件”呢关键是把y变成它能理解的数学形式。神经网络不认识“5”这个整数就像你不认识外星文一样。我们需要一个翻译官把“5”翻译成神经网络能理解的向量。这个翻译官在深度学习里叫做嵌入层Embedding Layer。具体的实现思路是这样的我们首先仍然使用正弦位置编码把时间步信息t比如当前是第100步去噪变成模型能理解的向量。然后我们也用一个嵌入层把输入的指令y比如数字“5”也变成一个特征向量。最后简单粗暴但极其有效把这两个向量相加此时模型接收到的信息就同时包含了“时间信息”和“用户指令”。模型自然就知道在这个时间点它应该朝着“生成5”的方向去努力了。这个方法的优雅之处在于它对原始模型结构的改动极其微小但效果却立竿见影。二、代码实战一打造能听懂指令的AI画师纸上得来终觉浅绝知此事要躬行。下面我们动手实现上面讲的条件扩散模型。这里有一个细节需要注意在本节及后续的所有代码实现中DDPM的前向加噪过程使用了“累积极大值” \bar{\alpha}t而在反向去噪计算 \mu\theta 时则使用“原始值” \alpha_t 与上一节的数学推导完全一致。denoise() 函数也必须同时接收这两个参数。import math import torch import torchvision import matplotlib.pyplot as plt from torchvision import transforms from torch.utils.data import DataLoader from torch.optim import Adam import torch.nn.functional as F from torch import nn from tqdm import tqdm # 超参数设置 img_size 28 # MNIST图像尺寸 28x28 batch_size 128 # 批次大小 num_timesteps 1000 # 扩散步数DDPM的标准配置 epochs 10 # 训练轮数 lr 1e-3 # 学习率 device cuda if torch.cuda.is_available() else cpu # 辅助函数 def show_images(images, labelsNone, rows2, cols10): 展示生成的图像带标签 fig plt.figure(figsize(cols, rows)) i 0 for r in range(rows): for c in range(cols): ax fig.add_subplot(rows, cols, i 1) plt.imshow(images[i], cmapgray) if labels is not None: ax.set_xlabel(labels[i].item()) ax.get_axes().set_ticklabels([]) ax.get_axes().set_ticks([]) i 1 plt.tight_layout() plt.show() def _pos_encoding(time_idx, output_dim, devicecpu): 为单个时间步生成正弦位置编码 t, D time_idx, output_dim v torch.zeros(D, devicedevice) i torch.arange(0, D, devicedevice) # 关键的计算公式div_term 10000^(2i/D) div_term torch.exp(i / D * math.log(10000)) # 偶数位用正弦奇数位用余弦 v[0::2] torch.sin(t / div_term[0::2]) v[1::2] torch.cos(t / div_term[1::2]) return v def pos_encoding(timesteps, output_dim, devicecpu): 为批次中的所有时间步生成正弦位置编码 batch_size len(timesteps) device timesteps.device v torch.zeros(batch_size, output_dim, devicedevice) for i in range(batch_size): v[i] _pos_encoding(timesteps[i], output_dim, devicedevice) return v # 卷积块带时间嵌入 class ConvBlock(nn.Module): def __init__(self, in_ch, out_ch, time_embed_dim): super().__init__() # 双卷积层Conv - BN - ReLU - Conv - BN - ReLU self.convs nn.Sequential( nn.Conv2d(in_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(), nn.Conv2d(out_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU() ) # MLP将时间嵌入映射到合适的特征维度 self.mlp nn.Sequential( nn.Linear(time_embed_dim, in_ch), nn.ReLU(), nn.Linear(in_ch, in_ch) ) def forward(self, x, v): N, C, _, _ x.shape # 时间嵌入经过MLP后 reshape 为 (N, C, 1, 1) v self.mlp(v) v v.view(N, C, 1, 1) # 将时间嵌入加到输入上特征调制 y self.convs(x v) return y # 条件U-Net模型 class UNetCond(nn.Module): def __init__(self, in_ch1, time_embed_dim100, num_labelsNone): super().__init__() self.time_embed_dim time_embed_dim # U-Net的编码器下采样路径 self.down1 ConvBlock(in_ch, 64, time_embed_dim) # 28 - 28保留尺寸经池化-14 self.down2 ConvBlock(64, 128, time_embed_dim) # 14 - 14保留尺寸经池化-7 # 瓶颈层最低分辨率 self.bot1 ConvBlock(128, 256, time_embed_dim) # 7 - 7 # 解码器上采样路径 self.up2 ConvBlock(128 256, 128, time_embed_dim) # 7 - 14拼接来自down2的特征 self.up1 ConvBlock(128 64, 64, time_embed_dim) # 14 - 28拼接来自down1的特征 # 输出层 self.out nn.Conv2d(64, in_ch, 1) # 1x1卷积输出噪声预测 self.maxpool nn.MaxPool2d(2) # 2倍下采样 self.upsample nn.Upsample(scale_factor2, modebilinear) # 2倍上采样 # 关键处理标签的嵌入层 if num_labels is not None: # 将整数标签0-9转换为 time_embed_dim 维的向量 self.label_emb nn.Embedding(num_labels, time_embed_dim) def forward(self, x, timesteps, labelsNone): # 1. 将时间步转换为正弦位置编码 t pos_encoding(timesteps, self.time_embed_dim) # 2. 如果有标签将标签转换为嵌入并加到时间编码上 if labels is not None: # label_emb(labels) 的形状是 (batch_size, time_embed_dim) # 直接加到时间编码上两个信号融合 t self.label_emb(labels) # 3. U-Net 前向传播 # 编码器路径 x1 self.down1(x, t) # 保存用于跳跃连接 x self.maxpool(x1) # 下采样 x2 self.down2(x, t) # 保存用于跳跃连接 x self.maxpool(x2) # 下采样 # 瓶颈层 x self.bot1(x, t) # 解码器路径带跳跃连接 x self.upsample(x) x torch.cat([x, x2], dim1) # 拼接跳跃连接 x self.up2(x, t) x self.upsample(x) x torch.cat([x, x1], dim1) # 拼接跳跃连接 x self.up1(x, t) # 输出噪声预测 x self.out(x) return x去噪扩散封装器Diffuser封装正向加噪与反向去噪流程。class Diffuser: def __init__(self, num_timesteps1000, beta_start0.0001, beta_end0.02, devicecpu): self.num_timesteps num_timesteps self.device device # 线性噪声调度beta从0.0001线性增加到0.02DDPM论文的原始配置 self.betas torch.linspace(beta_start, beta_end, num_timesteps, devicedevice) self.alphas 1 - self.betas # alpha_t 1 - beta_t self.alpha_bars torch.cumprod(self.alphas, dim0) # \bar{alpha}_t Π alpha_{1..t} def add_noise(self, x_0, t): 前向扩散向干净图像添加噪声得到 x_t # t 从 1 到 T索引需要 -1 才能对齐 alpha_bars[0] 对应 t1 t_idx t - 1 alpha_bar self.alpha_bars[t_idx] # reshape 为 (N, 1, 1, 1) 用于广播 alpha_bar alpha_bar.view(alpha_bar.size(0), 1, 1, 1) # 生成高斯噪声并与干净图像按公式混合 noise torch.randn_like(x_0, deviceself.device) # x_t sqrt(alpha_bar) * x_0 sqrt(1 - alpha_bar) * noise x_t torch.sqrt(alpha_bar) * x_0 torch.sqrt(1 - alpha_bar) * noise return x_t, noise def denoise(self, model, x, t, labels): 反向扩散从 x_t 去噪得到 x_{t-1} t_idx t - 1 # 获取当前时间步的关键参数 alpha self.alphas[t_idx] # alpha_t alpha_bar self.alpha_bars[t_idx] # \bar{alpha}_t alpha_bar_prev self.alpha_bars[t_idx-1] # \bar{alpha}_{t-1}t1时自动处理 N alpha.size(0) alpha alpha.view(N, 1, 1, 1) alpha_bar alpha_bar.view(N, 1, 1, 1) alpha_bar_prev alpha_bar_prev.view(N, 1, 1, 1) # 使用模型预测噪声 model.eval() with torch.no_grad(): eps model(x, t, labels) # 【关键】同时传入 labels model.train() # 计算去噪均值 mu # mu (x - ( (1 - alpha) / sqrt(1 - alpha_bar) ) * eps) / sqrt(alpha) mu (x - ((1 - alpha) / torch.sqrt(1 - alpha_bar)) * eps) / torch.sqrt(alpha) std torch.sqrt((1 - alpha) * (1 - alpha_bar_prev) / (1 - alpha_bar)) # 添加噪声DDPM采样时的随机性 noise torch.randn_like(x, deviceself.device) noise[t 1] 0 # t1时是最后一步不添加噪声 return mu noise * std def reverse_to_img(self, x): 将张量数据转换为可显示的 PIL 图像 x x * 255 x x.clamp(0, 255) x x.to(torch.uint8) x x.cpu() to_pil transforms.ToPILImage() return to_pil(x) def sample(self, model, x_shape(20, 1, 28, 28), labelsNone): 从随机噪声开始逐步去噪生成图像 batch_size x_shape[0] x torch.randn(x_shape, deviceself.device) # 纯随机噪声开始 if labels is None: # 如果没给标签就随机生成0~9的标签 labels torch.randint(0, 10, (batch_size,), deviceself.device) # 从 T 步逐步去噪到 1 步 for i in tqdm(range(self.num_timesteps, 0, -1)): t torch.tensor([i] * batch_size, deviceself.device, dtypetorch.long) x self.denoise(model, x, t, labels) # 转换格式并返回 images [self.reverse_to_img(x[i]) for i in range(batch_size)] return images, labels # 数据加载 preprocess transforms.ToTensor() dataset torchvision.datasets.MNIST(root../datasets, downloadTrue, transformpreprocess) dataloader DataLoader(dataset, batch_sizebatch_size, shuffleTrue) # 初始化模型和优化器 diffuser Diffuser(num_timesteps, devicedevice) model UNetCond(num_labels10) # 10个类别数字0-9 model.to(device) optimizer Adam(model.parameters(), lrlr) # 训练循环 losses [] for epoch in range(epochs): loss_sum 0.0 cnt 0 # 每个 epoch 结束后生成一组图像观察训练进展 images, labels diffuser.sample(model) show_images(images, labels) for images, labels in tqdm(dataloader): optimizer.zero_grad() x images.to(device) labels labels.to(device) # 【关键】训练时也要提供标签 t torch.randint(1, num_timesteps1, (len(x),), devicedevice) # 添加噪声并预测 x_noisy, noise diffuser.add_noise(x, t) noise_pred model(x_noisy, t, labels) # 【关键】模型同时接收t和labels loss F.mse_loss(noise, noise_pred) loss.backward() optimizer.step() loss_sum loss.item() cnt 1 loss_avg loss_sum / cnt losses.append(loss_avg) print(fEpoch {epoch} | Loss: {loss_avg:.4f}) # 最终生成展示 images, labels diffuser.sample(model) show_images(images, labels)运行结果经过短短10轮训练模型已经学会了根据标签生成对应的数字。虽然边缘还有些模糊但它确实成功理解了你的“指令”。三、AI的进阶之路从得分函数到分类器指引上一步的模型虽然能工作了但它有时候会“偷懒”不那么看重你给的条件甚至可能会忽略。为了解决这个问题我们需要引入一种更强大的技术它的名字听起来很学术但原理非常直观这就是——指引Guidance。3.1 得分函数——AI内部的“导航仪”在讨论指引之前我们需要了解一下扩散模型内部是怎么工作的。扩散模型内部有一个重要的概念叫做得分函数它是模型判断“这像不像一张真实图像”的内部标尺。数学上定义为对数概率密度相对于输入数据向量的梯度。一句话理解得分函数想象你在一个黑暗的山谷里探索你蒙着眼睛目标是走到谷底。得分函数就像你脚下感知坡度的触觉——它会告诉你哪个方向是“下坡”哪里是“上坡” 。模型就是循着这个“下坡方向”一步步把噪声“修”成干净图像数据点会自然聚集在概率高密度的谷底。噪声预测模型 \epsilon_\theta(x_t, t) 本质上就是局部梯度的另一种表达形式存在一个负常数倍关系因此它其实就在扮演得分函数 s_\theta(x_t, t) 的角色。这也再次验证了扩散模型与基于得分的生成模型是高度统一的对噪声的预测本质上等效于对得分的预测。3.2 分类器指引——给AI装上“GPS”既然得分函数告诉模型“往哪边走是对的”那如果我们用分类器告诉模型“往条件 y 的方向走”不就行了吗这正是分类器指引的思路。这条方向其实就是条件分类器对当前图像的梯度\nabla_{x_t} \log p(y|x_t)。无条件得分\nabla_{x_t} \log p(x_t)模型觉得哪条路自然分类器梯度\nabla_{x_t} \log p(y|x_t)分类器觉得哪条路更符合 y将这两股力量按公式“有条件得分无条件得分γ×分类器梯度”融合模型就能在保持自然的同时坚决朝着指令 y 前进。缺点也很明显你必须额外训练一个独立的分类器。而且这个分类器要处理“加了噪声的模糊图”和常规训练好的分类器很难完美兼容。3.3 无分类器指引——一个模型干两份活既然训练一个独立分类器这么麻烦能不能用一个模型同时学会“无条件生成”和“有条件生成”然后在生成时把两者结合起来这就是大名鼎鼎的无分类器指引Classifier-Free Guidance简称CFG的核心思想。原理其实简单到令人惊讶我们在训练时让模型以一定比例随机丢掉条件信息。比如10%的概率把labels设为None让模型在这种情况下进行无条件训练其余90%的概率正常传labels进行有条件训练。当传入labelsNone时模型只根据时间步去噪学到的就是“无条件得分”。当传入具体labels时模型同时利用时间步和标签去噪学到的就是“有条件得分”。最后在生成时CFG按以下公式将两者结合最终预测无条件预测γ×(有条件预测−无条件预测)最终预测无条件预测γ×(有条件预测−无条件预测)\gamma称为Guidance Scale越大模型就越“听话”生成的图像更贴合你的指令。\gamma 越小模型就越“自由”生成的图像更有创意和多样性。这种方法的优势在于不依赖任何外部预训练分类器只需一个模型训练极其简单生成时又能精准控制“听话”的程度。补充两条进阶视角近年来学术界仍在持续优化 CFG 的底层理论——例如 2025 年底的研究已开始分析声音信号与几何纠缠的根本原因另一组工作则专门解析线性 CFG 所内含的“均值偏移”和“类别特征放大”机制。你熟悉的“反向提示词”技术在数学上恰好对应这种无分类器指引把不需要的信息对应的条件 p(y|x_t) 低维嵌入Φ放到无条件部分里让模型在生成时“踩刹车”绕过它。四、代码实战二无分类器指引的完整实现现在我们把上面讲的理论转化为可运行的代码看看 CFG 到底有多简单、多强大。核心改动有三点训练时随机丢弃条件以一定概率比如10%将labels设为None让模型在无条件模式下训练。生成时使用CFG公式同时计算model(x, t, labels)有条件预测和model(x, t)无条件预测然后按 γ 系数混合。配置可供调节的引导系数 γγ 越大生成结果越“听指令”γ 越小结果越有随机多样性。import math import numpy as np import torch import torchvision import matplotlib.pyplot as plt from torchvision import transforms from torch.utils.data import DataLoader from torch.optim import Adam import torch.nn.functional as F from torch import nn from tqdm import tqdm # 超参数 img_size 28 batch_size 128 num_timesteps 1000 epochs 10 lr 1e-3 device cuda if torch.cuda.is_available() else cpu def show_images(images, labelsNone, rows2, cols10): fig plt.figure(figsize(cols, rows)) i 0 for r in range(rows): for c in range(cols): ax fig.add_subplot(rows, cols, i 1) plt.imshow(images[i], cmapgray) if labels is not None: ax.set_xlabel(labels[i].item()) ax.get_axes().set_ticklabels([]) ax.get_axes().set_ticks([]) i 1 plt.tight_layout() plt.show() def _pos_encoding(time_idx, output_dim, devicecpu): t, D time_idx, output_dim v torch.zeros(D, devicedevice) i torch.arange(0, D, devicedevice) div_term torch.exp(i / D * math.log(10000)) v[0::2] torch.sin(t / div_term[0::2]) v[1::2] torch.cos(t / div_term[1::2]) return v def pos_encoding(timesteps, output_dim, devicecpu): batch_size len(timesteps) device timesteps.device v torch.zeros(batch_size, output_dim, devicedevice) for i in range(batch_size): v[i] _pos_encoding(timesteps[i], output_dim, device) return v class ConvBlock(nn.Module): def __init__(self, in_ch, out_ch, time_embed_dim): super().__init__() self.convs nn.Sequential( nn.Conv2d(in_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(), nn.Conv2d(out_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU() ) self.mlp nn.Sequential( nn.Linear(time_embed_dim, in_ch), nn.ReLU(), nn.Linear(in_ch, in_ch) ) def forward(self, x, v): N, C, _, _ x.shape v self.mlp(v) v v.view(N, C, 1, 1) y self.convs(x v) return y class UNetCond(nn.Module): def __init__(self, in_ch1, time_embed_dim100, num_labelsNone): super().__init__() self.time_embed_dim time_embed_dim self.down1 ConvBlock(in_ch, 64, time_embed_dim) self.down2 ConvBlock(64, 128, time_embed_dim) self.bot1 ConvBlock(128, 256, time_embed_dim) self.up2 ConvBlock(128 256, 128, time_embed_dim) self.up1 ConvBlock(128 64, 64, time_embed_dim) self.out nn.Conv2d(64, in_ch, 1) self.maxpool nn.MaxPool2d(2) self.upsample nn.Upsample(scale_factor2, modebilinear) if num_labels is not None: self.label_emb nn.Embedding(num_labels, time_embed_dim) def forward(self, x, timesteps, labelsNone): t pos_encoding(timesteps, self.time_embed_dim) if labels is not None: t self.label_emb(labels) x1 self.down1(x, t) x self.maxpool(x1) x2 self.down2(x, t) x self.maxpool(x2) x self.bot1(x, t) x self.upsample(x) x torch.cat([x, x2], dim1) x self.up2(x, t) x self.upsample(x) x torch.cat([x, x1], dim1) x self.up1(x, t) x self.out(x) return x # 带 CFG 的 Diffuser class Diffuser: def __init__(self, num_timesteps1000, beta_start0.0001, beta_end0.02, devicecpu): self.num_timesteps num_timesteps self.device device self.betas torch.linspace(beta_start, beta_end, num_timesteps, devicedevice) self.alphas 1 - self.betas self.alpha_bars torch.cumprod(self.alphas, dim0) def add_noise(self, x_0, t): t_idx t - 1 alpha_bar self.alpha_bars[t_idx] alpha_bar alpha_bar.view(alpha_bar.size(0), 1, 1, 1) noise torch.randn_like(x_0, deviceself.device) x_t torch.sqrt(alpha_bar) * x_0 torch.sqrt(1 - alpha_bar) * noise return x_t, noise def denoise(self, model, x, t, labels, gamma): 带 CFG 的去噪函数 —— 最关键的部分 t_idx t - 1 alpha self.alphas[t_idx] alpha_bar self.alpha_bars[t_idx] alpha_bar_prev self.alpha_bars[t_idx-1] N alpha.size(0) alpha alpha.view(N, 1, 1, 1) alpha_bar alpha_bar.view(N, 1, 1, 1) alpha_bar_prev alpha_bar_prev.view(N, 1, 1, 1) model.eval() with torch.no_grad(): eps_cond model(x, t, labels) # 有条件预测 eps_uncond model(x, t) # 无条件预测 # CFG 核心公式最终预测 无条件 gamma * (有条件 - 无条件) eps eps_uncond gamma * (eps_cond - eps_uncond) model.train() noise torch.randn_like(x, deviceself.device) noise[t 1] 0 mu (x - ((1-alpha) / torch.sqrt(1-alpha_bar)) * eps) / torch.sqrt(alpha) std torch.sqrt((1-alpha) * (1-alpha_bar_prev) / (1-alpha_bar)) return mu noise * std def reverse_to_img(self, x): x x * 255 x x.clamp(0, 255) x x.to(torch.uint8) x x.cpu() to_pil transforms.ToPILImage() return to_pil(x) def sample(self, model, x_shape(20, 1, 28, 28), labelsNone, gamma3.0): 生成函数带 CFG 的引导系数 gamma batch_size x_shape[0] x torch.randn(x_shape, deviceself.device) if labels is None: labels torch.randint(0, 10, (batch_size,), deviceself.device) for i in tqdm(range(self.num_timesteps, 0, -1)): t torch.tensor([i] * batch_size, deviceself.device, dtypetorch.long) x self.denoise(model, x, t, labels, gamma) images [self.reverse_to_img(x[i]) for i in range(batch_size)] return images, labels # 数据加载 preprocess transforms.ToTensor() dataset torchvision.datasets.MNIST(root./data, downloadTrue, transformpreprocess) dataloader DataLoader(dataset, batch_sizebatch_size, shuffleTrue) # 初始化 diffuser Diffuser(num_timesteps, devicedevice) model UNetCond(num_labels10) model.to(device) optimizer Adam(model.parameters(), lrlr) # 训练关键随机丢弃条件 losses [] for epoch in range(epochs): loss_sum 0.0 cnt 0 # 每轮结束后生成一次观察 gamma 效果可以尝试 gamma1.5, 3.0, 5.0 images, labels diffuser.sample(model, gamma3.0) show_images(images, labels) for images, labels in tqdm(dataloader): optimizer.zero_grad() x images.to(device) labels labels.to(device) t torch.randint(1, num_timesteps1, (len(x),), devicedevice) # 关键改动随机丢弃标签 # 10% 的概率进行无条件训练让模型学会没有标签时也能去噪 if np.random.random() 0.1: labels None x_noisy, noise diffuser.add_noise(x, t) noise_pred model(x_noisy, t, labels) loss F.mse_loss(noise, noise_pred) loss.backward() optimizer.step() loss_sum loss.item() cnt 1 loss_avg loss_sum / cnt losses.append(loss_avg) print(fEpoch {epoch} | Loss: {loss_avg:.4f}) # 最终生成展示 images, labels diffuser.sample(model, gamma3.0) show_images(images, labels)运行结果解读你可以尝试修改 sample() 函数中的 gamma 参数来感受它的魔力gamma 1.0相当于不加引导模型自由发挥。gamma 3.0模型比较听指令生成结果与标签高度一致。gamma 5.0极度听从指令但可能会牺牲一些图像的自然度和多样性。这种一拉滑块就能控制“听话程度”的体验就是 CFG 最迷人的地方。五、登堂入室从MNIST到Stable Diffusion的广阔天地我们已经从零搭建了一个能听懂数字指令的MNIST手写体生成器但这只是万里长征的第一步。当我们放眼现代顶尖的AI绘画系统如Stable Diffusion会发现它们虽然体量巨大但其底层控制逻辑与我们今天搭建的模型惊人地相似。在像素空间运行太慢了直接在 1024×1024 大小的图像上计算对算力的消耗是不可思议的。潜在扩散模型LDM的解决方案先将图像压缩到一个只有原图几十分之一大小的潜在空间Latent Space在压缩空间里进行所有复杂的扩散与去噪计算最后再解压回原始尺寸。文本编码器的进化我们用的是简单的nn.Embedding数字标签而现代模型通常使用CLIP等大规模预训练模型作为文本编码器将任何自然语言比如“一只穿宇航服的柴犬”转换成模型能理解的向量。ControlNet与生成控制力天花板利用ControlNet等附加控制模块你甚至可以通过边缘图、深度图甚至人体姿态骨架来精确控制图像的构图和内容。六、总结今天我们完成了一段从理论到实践的完整旅程理解核心原理条件扩散模型通过在去噪网络中添加额外的条件输入如文本、标签等实现了可控的AI图像生成。亲手实现模型我们用PyTorch从零搭建了一个带条件U-Net的扩散模型成功实现了MNIST数字的条件生成。掌握无分类器指引我们深入剖析了CFG技术的原理并实现了通过一个γ系数就能精准控制“听话程度”的强大功能。展望未来世界以Stable Diffusion为代表的现代模型利用潜在空间扩散和文本编码器实现了更高分辨率、更强大的可控生成能力。你已经不再是AI绘画的门外汉而是一个掌握了核心底层技术的搭建者。现在去创造属于你自己的“AI画图大师”吧

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

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

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

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

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…