当前位置: 首页 > article >正文

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

你有没有想过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画图大师”吧

相关文章:

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

你有没有想过,AI是如何听懂你的指令,画出你想要的东西的?当你对Midjourney输入“一只穿着宇航服的柴犬”,它真的能生成那张图——这背后究竟发生了什么?今天,我将带你亲手实现一个基础的文本控制AI绘图系统…...

机器学习数据预处理:数据标准化(Z-Score)

机器学习数据预处理:数据标准化(Z-Score)超通俗全解 数据标准化是**把所有特征统一变成“均值为0,标准差为1”**的最经典预处理方法,彻底解决量纲不一致、数值差距大的问题,所有对尺度敏感的模型都必须做。…...

【限时技术解禁】:VSCode 2026 Dev Tunnels直连容器的私有化部署方案(绕过GitHub Auth,企业级离线可用)

更多请点击: https://intelliparadigm.com 第一章:VSCode 2026 Dev Tunnels直连容器的技术演进与企业适配价值 VSCode 2026 引入的 Dev Tunnels 原生直连容器能力,标志着远程开发范式从 SSH 代理与端口转发迈向零配置、身份感知、双向加密隧…...

本地GPU预训练Llama模型全流程与优化策略

1. 本地GPU预训练Llama模型全流程解析在自然语言处理领域,Transformer架构已成为大语言模型的事实标准。作为其中的佼佼者,Llama系列模型因其出色的性能和开源特性备受关注。本文将手把手教你如何在本地GPU上完成Llama模型的预训练全流程。1.1 为什么选择…...

深度学习模型集成方法:Bagging实战与优化

1. 深度学习模型集成方法概述在机器学习领域,集成学习(Ensemble Learning)是一种通过组合多个模型的预测结果来提升整体性能的技术。这种方法的核心思想是"三个臭皮匠顶个诸葛亮"——多个模型的集体智慧往往比单个模型表现更好。特别是在深度学习领域&…...

GeniA:大语言模型驱动的生物信息学智能体框架实战指南

1. 项目概述:当AI遇上基因,GeniA如何重塑生物信息学工作流如果你是一名生物信息学研究员、计算生物学家,或者任何需要与高通量测序数据打交道的从业者,那么你一定对“数据洪流”这个词深有体会。从二代测序到三代测序,…...

Transformer位置编码原理与实战技巧详解

1. 位置编码的本质与必要性在传统RNN结构中,序列数据是逐个元素处理的,这种顺序处理方式天然包含了位置信息。但Transformer模型采用并行处理的注意力机制,需要显式地注入位置信息才能理解序列中元素的相对或绝对位置。这就是位置编码&#x…...

神经网络反向传播算法实现与优化指南

1. 神经网络与反向传播算法基础神经网络是机器学习中最强大的工具之一,而反向传播算法则是训练神经网络的核心技术。让我们从一个开发者的角度来理解这个看似复杂的概念。想象你正在教一个孩子识别动物。最初孩子会犯很多错误,但每次错误后你会指出哪里错…...

流体天线阵列与空中计算技术的联合优化实践

1. 流体天线阵列与空中计算技术解析在物联网设备数量爆炸式增长的今天,传统"先通信后计算"的模式正面临严峻挑战。想象一下,当数千个传感器同时向云端发送数据时,不仅会挤占宝贵的无线频谱资源,还会产生难以忍受的通信延…...

3步解密网页视频下载:VideoDownloadHelper智能解析实战指南

3步解密网页视频下载:VideoDownloadHelper智能解析实战指南 【免费下载链接】VideoDownloadHelper Chrome Extension to Help Download Video for Some Video Sites. 项目地址: https://gitcode.com/gh_mirrors/vi/VideoDownloadHelper 你是否曾遇到过这样的…...

NovelClaw:基于动态记忆与可观测架构的AI长篇叙事工作台

1. 项目概述:从“一次性生成”到“可检视的写作工作台”如果你尝试过用大语言模型(LLM)来创作长篇小说,大概率会遇到这样的困境:你给了一个精彩的开头设定,模型也洋洋洒洒生成了几千字。但当你想要继续写第…...

量子计算基础:Hadamard门与CNOT门的原理与应用

1. 量子门基础与物理意义在量子计算中,Hadamard门和CNOT门如同经典计算机中的与、或、非门一样,构成了量子电路的基础构建模块。但与经典比特不同,量子比特(qubit)可以同时处于|0⟩和|1⟩的叠加态,这种特性…...

MPS:用Go语言打造轻量级媒体服务器,让旧安卓设备变身家庭流媒体中心

1. 项目概述:一个为移动设备量身定制的媒体播放服务器如果你和我一样,手头有几台旧手机、平板电脑,或者干脆就是一台性能不那么强劲的安卓电视盒子,想把它们变成家庭媒体中心,那你大概率遇到过和我一样的困境。主流的媒…...

Qwen3-14B开源大模型实战:构建垂直领域微调数据集生成Pipeline

Qwen3-14B开源大模型实战:构建垂直领域微调数据集生成Pipeline 1. 开篇:为什么需要垂直领域数据集 在人工智能领域,通用大模型虽然表现优异,但在特定垂直场景下往往存在"知识盲区"。就像一位博学的教授,虽…...

AI智能体服务化实战:从单体Agent到生产级工具箱架构解析

1. 项目概述:一个为AI智能体服务的工具箱最近在折腾AI智能体(Agent)相关的项目,发现一个挺有意思的现象:很多开发者,包括我自己在内,在初期都会陷入一个“重复造轮子”的困境。每次启动一个新Ag…...

别再重装VSCode了!2026内存优化终极 checklist:12项配置项+8个进程级kill命令+1个自研memory-guard插件

更多请点击: https://intelliparadigm.com 第一章:VSCode 2026内存优化的底层动因与诊断范式 VSCode 2026 版本将内存管理从“被动回收”转向“预测性约束”,其核心动因源于 Electron 28 对 V8 堆快照的细粒度控制能力,以及语言服…...

【VSCode 2026权限控制黄金标准】:为什么头部科技公司已禁用“共享工作区默认读写”?4类角色权限矩阵表免费领取

更多请点击: https://intelliparadigm.com 第一章:VSCode 2026实时协作权限控制全景概览 VSCode 2026 引入了基于角色的细粒度实时协作权限模型(RBAC-RTC),在多人协同编辑同一工作区时,支持文件级、行级乃…...

机器人锂电池完整方案(选型 + 设计 + 厂家推荐)【浩博电池】

机器人锂电池完整方案(选型 设计 厂家推荐)机器人锂电池是机器人系统的核心动力单元,直接影响设备的续航能力、运动性能、安全性与稳定性。不同类型机器人(AGV、巡检机器人、四足机器人、服务机器人、消防机器人等)对…...

GPU显存碎片化暴雷预警!:CUDA 13 Unified Memory + CUDA Graph组合使用导致OOM的4种隐蔽路径与内存池动态调优脚本

更多请点击: https://intelliparadigm.com 第一章:GPU显存碎片化暴雷预警!:CUDA 13 Unified Memory CUDA Graph组合使用导致OOM的4种隐蔽路径与内存池动态调优脚本 CUDA 13 引入的 Unified Memory(UM)自动…...

nanobot效果惊艳:Qwen3-4B-Instruct准确识别并执行Linux系统命令真实截图

nanobot效果惊艳:Qwen3-4B-Instruct准确识别并执行Linux系统命令真实截图 1. nanobot:超轻量级个人AI助手 nanobot是一款受OpenClaw启发的超轻量级个人人工智能助手,仅需约4000行代码就能提供核心代理功能。相比其他类似工具动辄数十万行的…...

Kaggle竞赛入门:4步提升机器学习实战能力

1. 从零开始玩转Kaggle竞赛的实战指南作为一名在数据科学领域摸爬滚打多年的从业者,我清楚地记得第一次接触Kaggle时那种既兴奋又迷茫的感觉。Kaggle作为全球最大的数据科学竞赛平台,汇聚了来自世界各地的机器学习高手,在这里你可以找到最前沿…...

构建Llama风格解码器Transformer:从原理到实践

1. 从零构建类Llama-2/3的解码器专用Transformer模型 在自然语言处理领域,Transformer架构已成为现代大语言模型的基础。与传统Seq2Seq Transformer不同,像Llama-2/3这样的模型采用了更高效的解码器专用架构。这种设计不仅简化了模型结构,还特…...

机器学习模型评估:从指标选择到业务落地的实践指南

1. 机器学习算法评估的核心逻辑评估算法从来不是简单地跑几个指标然后比大小。我在实际项目中见过太多团队把准确率、AUC这些数字当圣旨,结果上线后模型表现一塌糊涂。真正有效的评估需要从业务目标倒推,建立完整的评估体系。评估流程的黄金三角是&#…...

AgentBench:大语言模型智能体综合评估平台深度解析与实践指南

1. 项目概述:AgentBench是什么,以及它为何重要如果你最近在关注大语言模型(LLM)和智能体(Agent)领域,大概率已经听过“THUDM/AgentBench”这个名字。这不仅仅是一个GitHub上的开源项目&#xff…...

软件工程师软技能修炼指南:代码质量、高效协同与问题解决

1. 项目概述:一份写给开发者的“软技能”修炼手册 在技术社区里,我们每天都能看到海量的代码库、框架和工具。但有一个仓库,它不教你写一行代码,却可能比任何技术栈都更能决定你职业发展的上限。这就是我们今天要聊的 mgechev/s…...

NumPy与SciPy科学计算实战:核心功能与性能优化

1. 科学计算的核心工具链在数据处理和算法开发领域,NumPy和SciPy这对黄金组合已经成为了事实上的标准工具包。作为Python科学计算生态系统的基石,它们提供了高效的多维数组操作(NumPy)和丰富的科学计算算法(SciPy&…...

Cubic:无侵入Java应用监控与Arthas动态诊断平台实战

1. 项目概述:Cubic,一个无侵入的应用级问题定位利器在Java应用开发和运维的日常里,最让人头疼的莫过于线上问题定位。日志没打全、监控指标不直观、想动态查看线程状态又不敢轻易重启服务……这些问题相信每个开发者都遇到过。传统的解决方案…...

使用 Rsync 实现服务器数据同步

在当今数据驱动的时代,服务器数据同步是保障业务连续性和数据安全的关键任务。Rsync作为一款高效、灵活的文件同步工具,凭借其增量传输和低资源消耗的特性,成为运维人员的首选方案。无论是跨服务器备份、负载均衡还是灾备恢复,Rsy…...

微信聊天记录完整导出终极指南:3步实现永久保存与智能管理

微信聊天记录完整导出终极指南:3步实现永久保存与智能管理 【免费下载链接】WeChatExporter 一个可以快速导出、查看你的微信聊天记录的工具 项目地址: https://gitcode.com/gh_mirrors/wec/WeChatExporter WeChatExporter是一款专为iOS用户设计的开源工具&a…...

从CVE-2023-XXXX到2026零容忍机制:17个真实工业级漏洞如何被新规范提前封堵(含NASA/JPL内部审计案例节选)

更多请点击: https://intelliparadigm.com 第一章:2026零容忍机制的演进逻辑与工业级合规全景 2026零容忍机制并非突发性政策产物,而是对近十年全球关键基础设施安全事件、AI模型滥用案例及跨国数据治理冲突的系统性响应。其核心逻辑从“事后…...