Transformer vs Stable Diffusion vs LLM模型对比
一 三种模型对比
1 Transformer是一个基础架构,是许多现代AI模型的发送机
2LLM和StableDiffusion是两种不同的顶级车型,分别用于处理语言和图像
3开源是这些模型的发布和协作模式
二 下面我们详细拆解
2.1Transformer一切的基石
本质,一种神经网络架构2017
不是具体的模型,而是一种设计思想,核心创新是自注意力机制,让模型在处理序列数据时,能动态的关注所有部分的重要关系,并行高效的学习。
类比:就像汽车的内燃机或电动平台。是一种基础技术,可以被用来制造各种不同类型的车。
影响:彻底改变了自然语言处理领域,并逐渐扩展到视觉,音频等多模态领域,当今绝大多数先进的LLM都是基于Transformer架构构建的。
2.2LLMvsStableDiffusion不同赛道上的顶级选手
维度 | LLM | StableDiffusion |
核心任务 | 理解和生成人类语言文本,例如,对话,协作,翻译,代码生成。 | 生成和编辑图像,根据文本描述prompt生成图片,或者对现有图片进行修改 |
技术基础 | 主要基于Transformer架构的解码器部分,如GPT系列,或者编码器-解码器部分 | 基于扩散模型架构,结合了Transformer用于理解文本提示,和U-net用于去噪生成图像 |
处理对象 | 离散的符号序列 | 连续的图像数据,像素矩阵 |
工作原理 | 根据上下文预测下一个最可能的词token,自回归生成文本 | 从一个纯随机噪声开始,通过多步去噪过程,逐步将其塑造成符号文本描述的清晰图像 |
代表性模型 | GPT系列,LLaMA系列,ChatGLM,通义千问,文心一言 | StableDiffusion系列及其变体,SDXL,SD1.5等,DALL-E2/3,Midjourney |
输出/输出 | 输入:文本提示,输出,文本 | 输入,文本提示+可选参数,输出图像 |
关键差异点
架构不同:LLM的核心是Transformer;StableDiffusion的核心扩散模型,只是借用Transformer作为其文本理解器
数据模态不同:一个处理文本,一个处理图像
生成逻辑不通:LLM是从左到右的序列预测;StableDiffusion时从噪声到清晰图的迭代去噪。
2.3开源的角色,共同的催化剂
问题中的开源模型是一个关键属性。LLM、StableDiffusion和Transformer本身都受益于开源运动,但程度和方式不同。
Transformer:其论文,<Attention Is All You Need>是彻底开源的,代码实现被广泛公开,称为行业标准。
StableDiffusion:由StabilityAI公司主动开源2002年,这一举动引爆了AI绘画革命,社区可以自由使用,修改,微调模型,催生了海量工具和生态
LLM:情况更复杂
闭源代表:OpenAI的GPT-4,Google的GeminiAdvanced,仅仅提供API,模型细节不公开。
开源代表:Meta的LLaMA系列,MistralAI的Mistral/Mixtral,国产的ChatGLM,Qwen,Baichuan等。这些模型公开了权重和架构,允许研究者和开发者本地部署,微调和研究。
开源的影响:对于LLM和StableDiffusion,开源极大的降低了应用门槛,促进了安全性研究,垂直领域适配,成本下降和技术民主化。
三 Stable Diffusion模型实现示例
#DDPM主程序入口#这个文件是DDPM模型的启动脚本,负责#1配置所有训练/评估参数#2根据配置选择训练或评估模式#3调用相应的训练或评估函数#使用方法:Python Main.py#使用默认配置训练或评估from Diffusion.Trainimporttrain, evaldef main(model_config=None):#主函数:配置参数并启动训练或评估#args:model_config可选的配置字典,如果提供则覆盖默认配置#模式配置字典#包含所有训练和评估所需的超参数modelConfig= {#运行模式"state":"train",#train或eval#train训练模式,从头开始训练或继续训练模型#eval评估模式,加载已训练模型并生成图像#训练超参数"epoch":200,#训练的总轮数"batch_size":80,#每个批次的样本数量"lr":1e-4,#初始学习率,learningrate"multiplier":2.#学习率预热后的倍数,预热后学习率 =lr *multiplier"grad_clip":1.#梯度裁剪阈值,防止梯度爆炸#扩散过程参数"T": 1000,#扩散步数前向过程的步数#扩散过程x_0 -> x_1 -> ... -> x_T#更多步数通常能生成更高质量的图像,但计算成本更高"beta_1": 1e-4, # 初始噪声系数(第 1 步的噪声方差) "beta_T": 0.02, # 最终噪声系数(第 T 步的噪声方差)#beta_t定义了每一步添加的噪声量#通常从很小的值1e-4线性增加到较大的值0.02#这确保了前向过程能逐渐将图像变成纯噪声#UNet模型架构参数"channel": 128,#基础通道数,第一层的通道数"channel_mult":[1,2,3,4]#通道倍数列表#定义了下采样过程中通道数的变化# 例如:[1, 2, 3, 4] 表示: # - 第 1 层:128 通道 # - 第 2 层:128*2 = 256 通道 # - 第 3 层:128*3 = 384 通道 # - 第 4 层:128*4 = 512 通道"attn":[2],#在哪些层级添加注意力机制#例如,[2]表示在第2个分辨率级别添加注意力层#注意力机制能帮助模型捕获长距离依赖,提升生成质量"num_res_blocks": 2,#每个分辨率级别的残差块数量#残差块是,UNet的基本构建单元,包含#时间步嵌入,time embedding#卷积层#可选的注意力层"dropout":0.15#Dropout比率,训练时的正则化#Dropout随即将部分神经元输出置零,防止过拟合#数组相关参数"img_size":32,#图像尺寸#设备配置"device":"cuda:0",#计算设备#cuda0使用第1块GPU如果有#CPU使用CPU训练会很慢#注意DDPM训练需要大量计算,强烈建议使用GPU#模型权重相关"training_load_weight": None, # 训练时加载的预训练权重文件名(None 表示从头训练) "save_weight_dir": "./Checkpoints/", # 模型权重保存目录 "test_load_weight": "ckpt_199_.pt", # 评估时加载的模型权重文件名#图像保存相关 "sampled_dir": "./SampledImgs/", # 生成图像的保存目录 "sampledNoisyImgName": "NoisyNoGuidenceImgs.png", # 初始噪声图像文件名 "sampledImgName": "SampledNoGuidenceImgs.png", # 生成图像文件名 "nrow": 8, # 保存图像时每行显示的图像数量 }#如果提供了自定义配置,则使用自定义配置覆盖默认值if model_config isnot None:modelCOnfig.update(model_config)#根据配置选择运行模式if modelConfig["state"]== "train":train(modelConfig)elseeval(modelConfig) if __name__=='__main__':#程序入口#当直接运行此脚本时,而非作为模块导入,执行main()函数main()
四 训练代码
#DDPMDenoisingDiffusionProbabilistic训练和评估模块#DDPM核心思想#前向过程Forward Process逐步向真实图像添加高斯噪声,直到变成纯噪声#q(x_t | x_{t-1})= N(x_t; sqrt(1 - beta_t) * x_{t-1}, beta_t * I)经过T步后x_t近似为标准高斯分布N(0, I)#反向过程,ReverseProcess训练神经网络学习从噪声逐步去噪,恢复原始图像p_theta(x_{t-1} | x_t)= N(x_{t-1}; )mu_theta(x_t, t), sigma_t^2 * I)#模型预测每一步的噪声,然后从x_t 中减去预测的噪声得到x_{t-1}#训练目标,最小化预测噪声和真实噪声之间的MSE损失,#L=E[||epsilon - epsilon_theta(x_t, t)||^2]#其中,epsilon是前向过程中添加的真实噪声,eplison_theta是模型预测的噪声import osfrom typingimport Dictimporttorchimporttorch.optimas optimfromtqdmimport tqdmfromtorch.utils.dataimport DataLoaderfrom torchvisionimport transformsfrom torchvision.datasetsimportCIFAR10from torchvision.utilsimport save_iamgefrom Diffusionimport GaussianDiffusionSampler, GaussianDiffusionTrainerfrom Diffusion.Modelimport UNetfrom Scheduler import GradualWarmupSchedulerdef train(modelConfig: Dict):#DDPM模型训练函数#训练流程#1 加载数据集CIFAR-10#初始化UNet模型用于预测噪声#设置优化器和学习率调度器#使用GaussianDiffusionTrainer进行训练循环#随机采样时间步#根据t向图像添加噪声得到x_t#模型预测噪声,计算损失并反向传播#args:modelCOnfig:包含所有训练配置参数的字典#设置计算设备,GPU或CPUdevice= torch.device(modelConfig["device"])#数据集准备#加载CIFAR-10训练集#CIFAR-1010类型色图像,每张图像32x32像素dataset= CIFAR10(root=./CIFAR10,数据集保存路径train =True, 使用训练集download=True,如果不存在则自动下载transform= transforms.Compose([transforms.RandomHorizontalFlip(), #随机水平翻转,数据增强transforms.ToTensor(), #转换为张量]0, 1[#归一化到[-1, 1]范围,因为DDPM通常在这个范围内工作#mean = (0.5, 0.5, 0.5),std=(0.5, 0.5, 0.5)将[0, 1]映射到[-1, 1]transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),]))#创建数据加载器dataloader = DataLoader(dataset,batch_size=modelConfig["batch_size"], #批次大小shuffle=True, #每个epoch打乱数据num_workers=4,#数据加载的并行进程数drop_last=True,#丢弃最后一个不完整的批次pin_memory=True#将数据固定在内存中,加速GPU传输)#模型初始化#创建UNet模型#UNet是DDPM的核心网络,用于预测每一步噪声#输入,带噪声的图像x_t和时间步t#输出:预测的噪声epsilon_theta(x_t, t)net_model= UNet(T =modelConfig["T"],扩散步数通常为1000ch = modelConfig["channel"],#基础通道数ch_mult=modelConfig["channel_mult"]#通道倍数列表,如[1,2,3,4]表示下采样时通道数逐渐增加attn = modelConfig["attn"]#在哪些层级添加注意力机制num_res_blocks=modelConfig["num_res_blocks"]#每个分辨率级别的残差块数量dropout=modelConfig["dropout"]#Dropout比率,防止过拟合).to(device)#如果指定了预训练权重,则加载,用于继续训练if modelConfig["trainning_load_weight"]isnot None:net_model.load_state_dict(torch.load(ps.path.join(modelConfig["save_weight_dir"],modelConfig["trainning_load_weight"],map_location=device)))#优化器设置#使用Adam优化器的改进版本,权重衰减更合理optimizer = torch.optim.AdamW(net_model.parameters(),lr=modelConfig["lr"],初始学习率weight_decay=1e-4#L2正则化系数)#学习率调度器设置#余弦退火调度器,学习率按余弦函数从最大值降到最小值cosineScheduler =optim.lr_scheduler.CosineAnnealingLR(optimizer=optimizer,T_max =modelConfig["epoch"],#余弦周期的长度,总epoch数eta_min=0,#最小学习率last_epoch=-1#从初始学习率开始)#预热调度器,在训练初期逐渐增加学习率,然后切换到余弦退火#这有助于训练初期的稳定性warmUpScheduler=GradualWarmupScheduler(optimizer=optimizer,multiplier= modelConfig["multiplier"],#预热后学习率时初始学习率的倍数warm_epoch=modelConfig["epoch"]// 10,#预热epoch数总epoch的10%after_scheduler=cosineScheduler #预热后使用的调度器)#扩散训练器初始化# # GaussianDiffusionTrainer 封装了 DDPM 的前向扩散过程#随机采样时间步t#根据t向图像添加噪声得到x_t#模型预测噪声,计算损失并反向传播#argsmodelConfig包含所有训练配置参数的字典#设置计算设备GPU或CPUdevice =torch.device(modelConfig["device"])#数据集准备#加载CIFAR10训练集#CIFAR-10 10类彩色图像,每张图像32x32像素dataset = CIFAR10(root='./CIFAR10', #数据集保存路径train = True,#使用训练集download=True,#如果不存在则自动下载transform=transforms.Compose([transforms.RandomHorizontaFlip(), #随机水平翻转,数据增强transforms.ToTensor(),#转换为张量0, 1#归一化到[-1, 1]范围,因为DDPM通常在这个范围工作#mean=(0.5, 0.5, 0.5)transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),])#创建数据加载器dataloader= DataLoader(dataset, batch_szie=modelConfig["batch_size"], #批次大小shuffle=True, 每个Epoch打乱数据num_workers=4, #数据加载的并行进程数drop_last=True,丢弃最后一个不完整的批次pin_memory=True#将数据固定在内存中,加速GPU传输)#模型初始化#创建UNet模型#输入:带噪声的图像x_t和时间步t#输出预测的噪声epsilonthetanet_model = UNet(T=modelConfig T扩散步数ch=modelConfigchannel基础通道数ch_mult=modelConfigchannel_mult 通道倍数列表,如1,2,3,4表示下采样时通道数逐渐增加attn = modelCOnfigattn#在哪些层级添加注意力机制num_res_blocks=modelConfig[num_res_blocks]#每个分辨率级别的残差块数量dropout= modelConfig["dropout"]#Dropout比率,防止过拟合).to(device)#如果指定了预训练权重,则加载用于继续训练if modelConfig["training_load_weight"]is not None:net_model.load_state_dict(torch.load(os.path.join(modelConfig["save_weight_dir"],modelConfig["training_load_weight"],map_location=device)))#优化器配置#使用AdamW优化器,Adam的改进版本,权重衰减更合理optimizer =torch.optim.AdamW(net_model.parameters(),lr=modelConfig["lr"], #初始学习率weight_decay=1e-4 #L2正则化系数)#学习率调度器设置#余弦退火调度器,学习率按余弦函数从最大值降到最小值cosineScheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer=optimizer,T_max=modelConfig["epoch"], #余弦周期的长度总epoch数eta_min = 0, #最小学习率last_epoch=-1#从初始学习率开始)#预热调度器,在训练初期逐渐增加学习率,然后切换到余弦退火#这有助于训练初期的稳定性warmUpScheduler= GraduaWarmupScheduler(optimizer=optimizer,multiplier=modelConfig["multiplier"],预热后学习率是初始学习率的倍数warm_epoch=modelConfig["epoch"]// 10, #预热epoch数after_scheduler=cosineScheduler#预热后使用的调度器)#扩散训练器初始化#GaussianDiffusionTrainer封装了DDPM的前向扩散过程#会在训练时#随机采样时间步t~Uniform#根据t计算噪声系数,向x_0添加噪声得到#3返回预测噪声和真实噪声之间的损失trainer= GaussianDiffusionTrainer(net_model,#UNet模型modelConfig["beta_1"]初始噪声系数,通常很小,如1e-4modelConfig["beta_T"],最终噪声系数,modelCOnfig["T"]扩散步数).to(device)#开始训练循环for e in range(modelConfig["epoch"]):#使用tqdm显示训练进度条withtqdm(dataloader, dynamic_ncols=True)as tqdmDataLoader:for images, labels intqdmDataLoader:#清零梯度, optimizer.zero_grad()#将图像移到指定设备,GPUx_0 = images.to(device)#x_0是原始干净图像,形状,batch_size,3, 32, 32#前向传播,计算损失#trainerx_0 内部会#1 随机采样时间步t#2 采样噪声epsilon#3计算x_t=sqrt(alpha_bar_t)* x_0+ sqrt(1 - alpha_bar_t) * epsilon#模型预测噪声epsilon_theta = model(x_t, t)#计算MSE损失loss= ||epsilon - epsilon_theta||^2#返回每个样本的损失,形状,batch_sizeloss= tranier(x_0).sum()/1000.求和后除以1000进行缩放数值稳定性#反向传播,计算梯度loss.backward()#梯度裁剪,防止梯度爆炸#将所有参数的梯度范围裁剪到grad_clip以内torch.nn.utils.clip_grad_norm_(net_model.parameters(),modelCOnfig["grad_clip"])#更新模型参数optimizer.step()#更新进度条显示信息tqdmDataLoader.set_postfix(ordered_dict=("epoch":e, #当前epoch"loss:": loss.item(),#当前批次损失"img shape ":x_0.shape, # 图像形状"LR": optimizer.state_dict()['param_groups'][0]["lr"] # 当前学习率))#每个epoch结束后更新学习率warmUpScheduler.step()#保存模型检查点#每个epoch都保存,方便后续选择最大模型或继续训练torch.save(net_model.state_dict(), os.path.join( modelConfig["save_weight_dir"], 'ckpt_' + str(e) + "_.pt")) defeval(modelConfig: Dict):DDPM模型评估/采样函数评估流程,实际上是生成新图像1加载训练好的模型2从标准高斯分布采样初始噪声x_T 3 使用GaussianDiffusionSampler逐步去噪从t=T 到t=0.,逐步预测并去除噪声最终得到生成的图像x_0#args: modelCOnfig包含所有评估配置参数的字典#使用torch.no_grad()禁用梯度计算,节省内存和加速withtorch.no_grad():device=torch.device(modelCOnfig["device"])#加载训练好的模型#创建模型结构, 评估时dropout设为0model=UNet(T=modelConfig["T"], ch=modelConfig["channel"], ch_mult=modelConfig["channel_mult"], attn=modelConfig["attn"], num_res_blocks=modelConfig["num_res_blocks"], dropout=0. # 评估时不需要 dropout) # 加载模型权重 ckpt = torch.load(os.path.join( modelConfig["save_weight_dir"], modelConfig["test_load_weight"]), map_location=device) model.load_state_dict(ckpt) print("model load weight done.")#设置为评估模式,影响batchNorm, Dropout等层的行为model.eval()#初始化采样器#GaussianDiffusionSampler封装了DDPM的反向去噪过程#会在采样时# 1. 从 x_T ~ N(0, I) 开始 # 2. 从 t=T 到 t=1,逐步预测噪声并去噪 # 3. 最终得到生成的图像 x_0sampler=GaussianDiffusionSampler( model, modelConfig["beta_1"], modelConfig["beta_T"], modelConfig["T"] ).to(device) # ========== 生成图像 ========== # 从标准高斯分布采样初始噪声 # 这是反向过程的起点:x_T ~ N(0, I) noisyImage = torch.randn( size=[modelConfig["batch_size"], 3, 32, 32], # 形状: [batch_size, channels, height, width] device=device ) # 保存初始噪声图像(用于可视化对比) # 将 [-1, 1] 范围转换回 [0, 1] 用于保存 saveNoisy = torch.clamp(noisyImage * 0.5 + 0.5, 0, 1) save_image(saveNoisy, os.path.join( modelConfig["sampled_dir"], modelConfig["sampledNoisyImgName"]), nrow=modelConfig["nrow"]) # nrow: 每行显示的图像数量 # 执行采样过程:从噪声逐步去噪生成图像 # sampler 内部会执行 T 步去噪: # for t in [T, T-1, ..., 1]: # 预测噪声 epsilon_theta = model(x_t, t) # 计算 x_{t-1} 的均值 # 采样 x_{t-1} ~ N(mean, var) # 最终得到 x_0 print("Starting sampling process...") sampledImgs = sampler(noisyImage) # 将生成的图像从 [-1, 1] 范围转换到 [0, 1] 范围,便于保存和可视化 sampledImgs = sampledImgs * 0.5 + 0.5 # [0 ~ 1] # 保存生成的图像 save_image(sampledImgs, os.path.join( modelConfig["sampled_dir"], modelConfig["sampledImgName"]), nrow=modelConfig["nrow"]) print(f"Sampling completed! Images saved to {modelConfig['sampled_dir']}") )