LLM大模型: 生成式模型的数学原理和prompt融入image

news/2024/11/20 15:16:37/文章来源:https://www.cnblogs.com/theseventhson/p/18427192

   1、(1)上文介绍了DDPM生成图片的原理和代码测试结果,训练时给样本图片加上gaussian noise,预测时也是预测gaussian noise;

  • 这里为啥要用gaussian distribution?为啥不用其他的分布?
    • 高斯分布相对比较简单,只有两个参数:均值和方差,容易控制
  • 为啥一张随机生成的gaussion noise经过很多次裁剪后能得到想要的图片?数学上的依据是什么?
    • 理论上讲: 任意K个高斯分布按照特定的权重组合,能得到任意曲线,也就是拟合任意的分布

      

    • 假设有K个高斯分布,这K个高斯分布称作混合模型的隐变量则复杂分布的概率分布是:

      

   通过这种Gaussian分布拟合任意分布,这下知道为啥diffusion模型会使用上千个Gaussian noise来生成 image了吧?本质就是利用Gaussian分布的组合生成所需image!两个乘数一个是DDPM中的alpha,另一个是epsilon(预测noise的网络).一张样本图片,比如是3 channel * 28 width * 28 height = 2352个像素点。理论上讲,只要有一个像素点不同,图片就不同。再说直白一点:每个像素点的值刚开始都是随机生成的,生成的值符合Gaussian~(0,1)分布;后续迭代很多次(因为是Gaussian~(0,1)分布,每次生成的数值都较小,99.7%的数值会在(-3,3)之间。为了满足channel的数值范围(一般是0~255),需要多次迭代。比如channel的数值是240,随机生成的noise值是3,那么至少迭代80次才能满足要求),每次迭代都会用新生成的值(也符合Gaussian分布)加减初始值,直到迭代结束(这个思路和"小步快跑"的梯度下降没任何区别)。2352个像素点拼接起来就生成了预测的noise图片!

  (2)怎么求seta?以VAE为例,整个网络结构如下:

  

   Z是隐变量,经过seta网络生成X;如果这个生成的X和原来数据集一样,说明seta是正确的。那么既然x已经发生了,最合理的思路就是让X的概率最大化了,也就是max likelyhood!也就是seta网络要让X生成的概率最大,以此来得到最合适的seta网络参数!

  

  2、实战时,肯定是要加入用户输入的prompt的!怎么严格按照用户的prompt生成image了?transformer架构最初是用来做翻译的,encoder把一种语言的输入转成embedding后,通过cross attention的机制把输入信息转移到decoder用于生成输出的token,这里也需要把输入的prompt信息传递到图片的decoder部分,是不是也能借鉴一下这个思路了?

  用户输入的prompt经过矩阵的线性转换后生成了embedding,在每个resnetblock后都加上一个transformer block(down 和 up 都要加),通过这种方式把用户的prompt信息融入整个unet网络!

        0

   具体代码实现,参考如下:像素点是query,prompt是key和value,做cross attention!注意:为啥要用像素点做attention?生成最终的image,需要每个像素点都参与,只有每个像素点的值对了,最终的image才能正确!为了确保每个像素点都正确,需要把prompt的值融入!

import torch 
from torch import nn 
from config import * 
import math class CrossAttention(nn.Module):def __init__(self,channel,qsize,vsize,fsize,cls_emb_size):super().__init__()self.w_q=nn.Linear(channel,qsize)self.w_k=nn.Linear(cls_emb_size,qsize)self.w_v=nn.Linear(cls_emb_size,vsize)self.softmax=nn.Softmax(dim=-1)self.z_linear=nn.Linear(vsize,channel)self.norm1=nn.LayerNorm(channel)# feed-forward结构self.feedforward=nn.Sequential(nn.Linear(channel,fsize),nn.ReLU(),nn.Linear(fsize,channel))self.norm2=nn.LayerNorm(channel)def forward(self,x,cls_emb): # x:(batch_size,channel,width,height), cls_emb:(batch_size,cls_emb_size)x=x.permute(0,2,3,1) # x:(batch_size,width,height,channel)# 像素是QueryQ=self.w_q(x)   # Q: (batch_size,width,height,qsize)Q=Q.view(Q.size(0),Q.size(1)*Q.size(2),Q.size(3))   # Q: (batch_size,width*height,qsize) 每个像素点都要参与attention的计算# prompt是Key和ValueK=self.w_k(cls_emb) # K: (batch_size,qsize)K=K.view(K.size(0),K.size(1),1) # K: (batch_size,qsize,1)V=self.w_v(cls_emb) # V: (batch_size,vsize)V=V.view(V.size(0),1,V.size(1))  # v: (batch_size,1,vsize)# attention打分矩阵Q*Kattn=torch.matmul(Q,K)/math.sqrt(Q.size(2)) # attn: (batch_size,width*height,1)attn=self.softmax(attn) # attn: (batch_size,width*height,1)# print(attn) # 就一个Key&value,所以Query对其注意力打分总是1分满分# attention输出Z=torch.matmul(attn,V)    # Z: (batch_size,width*height,vsize)Z=self.z_linear(Z)  # Z: (batch_size,width*height,channel)Z=Z.view(x.size(0),x.size(1),x.size(2),x.size(3))   # Z: (batch_size,width,height,channel)# 残差&layerNormZ=self.norm1(Z+x)# Z: (batch_size,width,height,channel)# FeedForwardout=self.feedforward(Z)# Z: (batch_size,width,height,channel)# 残差&layerNormout=self.norm2(out+Z)return out.permute(0,3,1,2)if __name__=='__main__':batch_size=2channel=1qsize=256cls_emb_size=32cross_atn=CrossAttention(channel=1,qsize=256,vsize=128,fsize=512,cls_emb_size=32)x=torch.randn((batch_size,channel,IMG_SIZE,IMG_SIZE))cls_emb=torch.randn((batch_size,cls_emb_size)) # cls_emb_size=32
Z=cross_atn(x,cls_emb)print(Z.size())     # Z: (2,1,48,48)

  把attention机制打包到convblock中:

from torch import nn 
from cross_attn import CrossAttentionclass ConvBlock(nn.Module):def __init__(self,in_channel,out_channel,time_emb_size,qsize,vsize,fsize,cls_emb_size):super().__init__()self.seq1 = nn.Sequential(nn.Conv2d(in_channel,out_channel,kernel_size=3,stride=1,padding=1), # 改通道数,不改大小
            nn.BatchNorm2d(out_channel),nn.ReLU(),)self.time_emb_linear=nn.Linear(time_emb_size,out_channel)    # Time时刻emb转成channel宽,加到每个像素点上self.relu=nn.ReLU()self.seq2=nn.Sequential(nn.Conv2d(out_channel,out_channel,kernel_size=3,stride=1,padding=1), # 不改通道数,不改大小
            nn.BatchNorm2d(out_channel),nn.ReLU(),)# 像素做Query,计算对token的attention,实现分类信息融入图像,不改变图像形状和通道数self.crossattn=CrossAttention(channel=out_channel,qsize=qsize,vsize=vsize,fsize=fsize,cls_emb_size=cls_emb_size)def forward(self,x,t_emb,cls_emb): # t_emb: (batch_size,time_emb_size)x=self.seq1(x)  # 改通道数,不改大小t_emb=self.relu(self.time_emb_linear(t_emb)).view(x.size(0),x.size(1),1,1)   # t_emb: (batch_size,out_channel,1,1) output=self.seq2(x+t_emb)        # 不改通道数,不改大小return self.crossattn(output,cls_emb)   # 图像和prompt embedding做attention

  3、模型微调:市面上可能有已经训练好的模型,但模型的训练数据大概率是通用的数据,并不是某些垂直细分领域的数据,怎么才能加上自己所需垂直领域的数据了?最合适的当然是微调了!微调的方式也有很多:全量参数微调、冻结部分参数微调、lora微调。如果训练数据有限、算力也有限,那么最合适的就是lora微调了!理论上讲,任何线性变换(直白一点就是矩阵乘法啦)都可以旁挂两个m*r和r*n的小矩阵来完成lora微调!但是:这种任务的核心是根据prompt生成image(所以微调的样本肯定也有配对的prompt和image),重点就是融合prompt和image的cross attention了,所以这里直接在cross attention的矩阵乘法旁边外挂新矩阵来达到融合新样本信息的目的

  先找到需要旁挂小矩阵的层:

from unet import UNet
from dataset import train_dataset
from diffusion import forward_diffusion
from config import * 
import torch 
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
import os 
from lora import inject_loraEPOCH=200
BATCH_SIZE=400if __name__=='__main__':# 加载模型model=torch.load('model.pt')# 向nn.Linear层注入Lorafor name,layer in model.named_modules():name_cols=name.split('.')# 找到cross attention中的线性变换,也就是矩阵乘法filter_names=['w_q','w_k','w_v']if any(n in name_cols for n in filter_names) and isinstance(layer,nn.Linear):inject_lora(model,name,layer)# lora权重的加载try:restore_lora_state=torch.load('lora.pt')model.load_state_dict(restore_lora_state,strict=False)except:pass model=model.to(DEVICE)# 冻结非Lora参数for name,param in model.named_parameters():if name.split('.')[-1] not in ['lora_a','lora_b']:  # 非lora部分不计算梯度param.requires_grad=Falseelse:param.requires_grad=Truedataloader=DataLoader(train_dataset,batch_size=BATCH_SIZE,num_workers=4,persistent_workers=True,shuffle=True)   # 数据加载器
optimizer=torch.optim.Adam(filter(lambda x: x.requires_grad==True,model.parameters()),lr=0.001) # 优化器只更新Lorac参数loss_fn=nn.L1Loss() # 损失函数(绝对值误差均值)print(model)writer = SummaryWriter()model.train()n_iter=0for epoch in range(EPOCH):last_loss=0for batch_x,batch_cls in dataloader:# 图像的像素范围转换到[-1,1],和高斯分布对应batch_x=batch_x.to(DEVICE)*2-1# 引导分类IDbatch_cls=batch_cls.to(DEVICE)# 为每张图片生成随机t时刻batch_t=torch.randint(0,T,(batch_x.size(0),)).to(DEVICE)# 生成t时刻的加噪图片和对应噪音batch_x_t,batch_noise_t=forward_diffusion(batch_x,batch_t)# 模型预测t时刻的噪音batch_predict_t=model(batch_x_t,batch_t,batch_cls)# 求损失loss=loss_fn(batch_predict_t,batch_noise_t)# 优化参数
            optimizer.zero_grad()loss.backward()optimizer.step()last_loss=loss.item()writer.add_scalar('Loss/train', last_loss, n_iter)n_iter+=1print('epoch:{} loss={}'.format(epoch,last_loss))# 保存训练好的Lora权重lora_state={}for name,param in model.named_parameters():name_cols=name.split('.')filter_names=['lora_a','lora_b']if any(n==name_cols[-1] for n in filter_names):lora_state[name]=paramtorch.save(lora_state,'lora.pt.tmp')os.replace('lora.pt.tmp','lora.pt')

   旁挂两个小矩阵的实现:

from config import * 
import torch 
from torch import nn
import math # Lora实现,封装linear,替换到父module里
class LoraLayer(nn.Module):def __init__(self,raw_linear,in_features,out_features,r,alpha):super().__init__()self.r=r self.alpha=alphaself.lora_a=nn.Parameter(torch.empty((in_features,r)))self.lora_b=nn.Parameter(torch.zeros((r,out_features)))nn.init.kaiming_uniform_(self.lora_a,a=math.sqrt(5))self.raw_linear=raw_lineardef forward(self,x):    # x:(batch_size,in_features)raw_output=self.raw_linear(x)   lora_output=x@((self.lora_a@self.lora_b)*self.alpha/self.r)    # matmul(x,matmul(lora_a,lora_b)*alpha/r)return raw_output+lora_outputdef inject_lora(model,name,layer):name_cols=name.split('.')# 逐层下探到linear归属的modulechildren=name_cols[:-1]cur_layer=model for child in children:cur_layer=getattr(cur_layer,child)#print(layer==getattr(cur_layer,name_cols[-1]))lora_layer=LoraLayer(layer,layer.in_features,layer.out_features,LORA_R,LORA_ALPHA)setattr(cur_layer,name_cols[-1],lora_layer)

 

 

总结:

1、 机器学习核心是根据输入数据得到所需的输出数据,肯定要对输入数据做各种转换,常见的做法就是matrix multi、active、attention等:

  • matrix multi:旧的向量转移到新的空间
    • 通过更改matrix的参数让新向量的数值适配下游任务
    • 向量长度做调整适配
  • active:特征多维组合后生成新特征,用于下游任务
  • attention:相似度的计算,用于不同网络之间的信息传递与融合

 

参考:

1、https://www.bilibili.com/video/BV19H4y1G73r/?spm_id_from=333.337.search-card.all.click&vd_source=241a5bcb1c13e6828e519dd1f78f35b2

2、https://nn.labml.ai/diffusion/stable_diffusion/model/unet.html  

3、https://deepsense.ai/diffusion-models-in-practice-part-1-the-tools-of-the-trade/  

4、https://aitechtogether.com/python/77485.html

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

P3311 [SDOI2014] 数数

参考题解做法。 题目思路 数位 dp + AC 自动机好题。 直接往下递归,dfs(u, ver, limit, st) 表示目前在数字 \(n\) 的第 \(u\) 位进行讨论,\(ver\) 表示当前在 AC 自动机上的节点,\(limit\) 是是否步步紧逼 \(n\),只要位数不足 \(n\) 的位数或者有一位小于 \(n\) 的那一位就…

实验1

任务1 源代码1 #include <stdio.h> 2 int main() 3 { 4 printf("o \n"); 5 printf("<H>\n"); 6 printf("I I\n"); 7 8 return 0; 9 }

initrdinit进程

initrd的全名是 init ramdisk,是一个启动时存在于内存的文件系统。 kernal 到 initrd的流程在GRUB加载kernel时,kernel会先在内存中制造一个rootfs当做临时的空间供系统使用,接下来,kernel便会将initrd当做是一个系统,将其mount到rootfs上启动。 引入initrd的目的是为了把…

如何部署北斗定位应用,基于国产自主架构LS2K1000LA-i处理器平台

北斗卫星导航系统(以下简称北斗系统)是着眼于国内经济社会发展需要,自主建设、独立运行的卫星导航系统。经过多年发展,北斗系统已成为面向全球用户提供全天候、全天时、高精度定位、导航与授时服务的重要新型基础设施。图 1 北斗定位系统的应用优势 强可控:北斗系统是国内…

ChatGPT 向更多用户推出高级语音模式:支持 50 种语言;字节发布两款新视频生成大模型丨 RTE 开发者日报

开发者朋友们大家好:这里是 「RTE 开发者日报」 ,每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE(Real-Time Engagement) 领域内「有话题的新闻」、「有态度的观点」、「有意思的数据」、「有思考的文章」、「有看点的会议」,但内容仅代表编辑的个人观点…

题解:CF573D Bear and Cavalry

CF因为这是远古题目,所以根据现在的评测机速度,用 \(O(nq)\) 的做法也是可以过的。 也就是说,我们可以每次操作直接修改对应位置上的数字,然后设计一种 \(O(n)\) 的算法求解答案。 这道题类似资源分配型动态规划,所以我们可以设 \(dp_i\) 表示分配前 \(i\) 个人的答案。 直…

题解:AT_abc204_e [ABC204E] Rush Hour 2

LG变形的 dijkstra。 先思考什么情况下需要等待以及等待多长时间最优。我们把题目上的计算方法按照当前的时间 \(t\) 和通过所需的时间 \(f(t)\) 列个函数关系: \[f(t)=t+c+\lfloor \frac{d}{t+1}\rfloor \]然后用 Desmos 画个图可以得到图像(其实就是对勾函数):因为 \(c,d…

Rust字符串类型全解析

字符串是每种编程语言都绕不开的类型, 不过,在Rust中,你会看到远比其他语言更加丰富多样的字符串类型。 如下图:为什么Rust中需要这么多种表示字符串的类型呢? 初学Rust时,可能无法理解为什么要这样设计?为什么要给使用字符串带来这么多不必要的复杂性? 其实,Rust中对…

AI自动生成代码注释

在vscode 中安装 TONGYI Lingma

通过 Tampermonkey 实现学习通全自动刷课

本文介绍了如何使用 Tampermonkey 这一流行的用户脚本管理器,通过其脚本库实现学习通的全自动刷课。文章详细讲解了 Tampermonkey 的安装步骤、OCS 脚本的配置方法,以及题库的使用流程,帮助读者高效完成学习任务。在学习过程中,自动化工具能大大提升学习效率。Tampermonkey…

KBU1010-ASEMI单向整流桥KBU1010

KBU1010-ASEMI单向整流桥KBU1010编辑:ll KBU1010-ASEMI单向整流桥KBU1010 型号:KBU1010 品牌:ASEMI 封装:KBU-4 批号:2024+ 类型:单向整流桥 电流(ID):10A 电压(VF):1000V 安装方式:直插式封装 特性:大功率、整流扁桥 产品引线数量:4 产品内部芯片个数:4 产品内部…