目录
一、概述
二、PhotoDoodle
1、OmniEditor的预训练
2、DiT重点
3、无噪声条件范式与CFM
4、EditLoRA
4.1关于LoRA
4.2关于EditLoRA
三、相关工作
一、概述
风格化图像编辑的论文!
介绍了PhotoDoodle,一个基于扩散模型的图像编辑框架,旨在通过少量示例学习艺术家的风格,实现照片的创意涂鸦(如添加装饰元素、艺术化处理),同时保持背景的完整性和一致性。
该论文的动机:将普通图片转换成富有创意的艺术作品(比如添加手绘线条,几何图案,3D效果等),但以往的传统方法依赖于艺术家的手动操作,耗时且门槛高。
现有方法不足之处:
- 全局风格迁移(如风格迁移模型)会全局修改图像纹理和颜色,导致背景内容被破坏,如人脸变成油画风格而失去真实感。
- 局部修复方法(如Inpainting)依赖精确遮罩标注,无法自动生成复杂元素,或者生成结果可能与背景透视或语义不匹配。
- 难以从小样本学习:艺术家的独特风格需要从少量示例(30-50对图像)中高效捕捉,但现有的方法容易产生过拟合或欠拟合。
PhotoDoodle提出的创新点:
- 提出PhotoDoodle框架:一个基于DiT的少样本学习模型,并实现保证背景完整性同时进行特有的风格编辑
- 噪音无关的条件范式:保留原图像的高频细节
- EditLoRA:参数高效的风格微调方法,避免过拟合
- 位置编码克隆:确保生成元素与背景的空间对齐
- 高质量数据集:6种艺术风格、300+ pairs的photoDoodle数据集
二、PhotoDoodle
PhotoDoodle分为两个部分,OmniEditor的预训练,EditorLoRA的微调。

1、OmniEditor的预训练
对于内部DiT的优化在第二点进行展开介绍。
看上面的图训练过程。
输入:
- 源图像(I_src) → VAE编码 → 源图像隐变量 z_src
- 目标图像(I_tar) → VAE编码 → 隐变量 z_0(真实目标隐变量)
- 文本指令(T) → CLIP编码 → 文本嵌入 c_text
- 前向扩散 对z_0添加随机噪声得到z_t(带噪声的隐变量)
- 将源图像隐变量 z_src、带噪声的隐变量z_t、c_text三者concat输入到DiT中
输出:
- 扩散模型(DiT)预测噪声 ε_θ,监督目标是让预测噪声逼近真实噪声 ε。
损失函数:(传统的去噪方式)
最小化预测噪声ε_θ与真实噪声ε之间的MSE:
推理过程:(传统的去噪方式)
- 源图像(I_src) → VAE编码 → 源图像隐变量 z_src
- 文本指令(T) → CLIP编码 → 文本嵌入 c_text
- 将源图像隐变量 z_src、文本嵌入c_text和高斯噪声z_T三者concat输入到DiT中进行多轮去噪,也就是说DiT预测出噪声ε_θ就要从z_T中去除这一部分,然后重新concat,输入到DiT中,反复去噪,最终将高斯噪声那一部分分离开得到z_0'
- 解码生成图像I_tar'=VAEdecoder(z_0')
2、DiT重点
(1)位置编码克隆(PE Cloning)
将源图像隐变量z_src的位置编码(空间坐标信息)复制到带噪声的隐变量z_t上,确保生成内容与源图像对齐。
这一步在哪里做呢?当然是在DiT的自注意力机制后,先接RoPE编码后得到位置编码,再进行位置编码克隆。
(2)交叉注意力机制
就是文中提到的MM attention,也就是在DiT的cross-attention中c_text作为Query,上一部分图像隐变量作为key/value。
我的疑惑?为什么在DiT之前将z_src,z_tar,c_text拼接在一起输入,交叉注意力时是再次引入c_text,还是拆开c_text部分。
后续理解:首先所谓的拼接,应该是均作为DiT的输入的意思(z_src,z_tar拼接在一起,c_text作为交叉注意力部分的输入),而且c_text应该是一维的,z_src和z_tar是二维的本身不能拼接。
3、无噪声条件范式与CFM
(1)CFM
在PhotoDoodle中并没有使用扩散模型的方式,而是条件流的方式。
构建一个线性插值概率路径(从z_src到z_tar):
向量场预测(速度场预测):
目的是预测z_t到z_tar的速度场
真实的速度场:(概率路径对t求导):
损失函数(使用CFM后的损失函数):
(2)无噪声条件
定义一个与z_src相同表达对齐论文符号,也就是源图像经过VAE后的隐变量。
无噪声条件就是指,相比于扩散模型引入了高斯噪声作为隐变量,CFM方法并不需要引入高斯噪声,这样可以确保生成内容与源图像的背景和结构严格对齐,完全依靠z_src和c_text来驱动,或者说是输入图像I_src和文本T的数据引导。
4、EditLoRA
EditLoRA目标是通过少量示例(30-50 pairs)来学习艺术家风格,原理就是LoRA。
4.1关于LoRA
LoRA的位置是并联在原模型某一部分的预训练权重处,在训练过程中冻结左侧原模型预训练权重,训练右侧LoRA,推理过程冻结所有参数,对模型结构不进行修改,不去除预训练模型进行推理。
LoRA的核心思想是,通过在原始权重矩阵旁并联一个低秩的适配矩阵,来对原来模型进行微调,另一个是在SD模型中,可以对A场景下训练一个LoRA,之后将A LoRA植入B模型,可以使B模型学习到A模型的信息。
什么是低秩适配矩阵?
对于一个训练二维图像相关的预训练权重,我们可以看做是一个二维矩阵,比如100*100维,那么我们可以利用一个100*rank的A矩阵和rank*100的矩阵的乘积来代替这个100*100维的矩阵。其中在训练过程中A矩阵作kaiming均匀分布初始化,避免梯度消失或爆炸。B矩阵初始化为0矩阵,确保训练开始时低秩矩阵贡献为0,逐步学习。scale作为这个LoRA与原预训练权重merge时的比例,过大会覆盖原有的权重。
LoRA公式:
一个简单的LoraLInear示例,在这个类中已经把预训练模型(线性层)的结构写入了,但冻结了该部分的权重。
class LoRALinear(nn.Module):
def __init__(self, in_features, out_features, merge, rank=16, lora_alpha=16, dropout=0.5):
super(LoRALinear, self).__init__()
self.in_features = in_features
self.out_features = out_features
self.merge = merge
self.rank = rank
self.dropout_rate = dropout
self.lora_alpha = lora_alpha
self.linear = nn.Linear(in_features, out_features)
if rank > 0:
# 修正维度:A (in_features, r), B (r, out_features)
self.lora_a = nn.Parameter(torch.zeros(in_features, rank))
self.lora_b = nn.Parameter(torch.zeros(rank, out_features))
self.scale = self.lora_alpha / self.rank #通过lora_alpha计算scale
self.linear.weight.requires_grad = False
if self.dropout_rate > 0:
self.dropout = nn.Dropout(self.dropout_rate)
else:
self.dropout = nn.Identity()
self.initial_weights()
def initial_weights(self):
# 初始化 A 和 B
nn.init.kaiming_uniform_(self.lora_a, a=math.sqrt(5)) #kaiming初始化lora_a
nn.init.zeros_(self.lora_b) #0初始化lora_b
def forward(self, x):
if self.rank > 0 and self.merge:
# 修正矩阵乘法和转置
delta_w = (self.lora_a @ self.lora_b).T * self.scale
merged_weight = self.linear.weight + delta_w
output = F.linear(x, merged_weight, self.linear.bias)
output = self.dropout(output)
return output
else:
return self.dropout(self.linear(x))
对于原来的fc层(也就是linear层)替换成并联lora的loralinear层。
# 示例:替换预训练模型的线性层为LoRALinear
original_layer = model.fc # 假设原模型有一个全连接层
lora_layer = LoRALinear(
in_features=768,
out_features=512,
merge=False,
rank=16,
lora_alpha=32
)
model.fc = lora_layer
# 训练时:仅训练LoRA参数
optimizer = torch.optim.Adam(lora_layer.parameters(), lr=1e-3)
# 推理时:合并权重
lora_layer.merge = True
output = model(input_data)
4.2关于EditLoRA
流程:OmniEditor预训练模型冻结所有参数,在DiT部分的交叉注意力层处并联一个LoRA结构。
重新提一遍OmniEditor的训练过程!
首先VAE是自己训练的,text encoder肯定是预训练好的可能是clip,DIT使用的是Flux.1 Dev模型的DiT部分权重
然后利用SeedEdit数据集在原有DiT权重下,接入一个rank=256的高秩LoRA模型,扩散过程替换成流匹配过程,开始训练。
之后将这个rank=256的LoRA融入这个OmniEditor模块。
EditLoRA微调部分!
再次利用一个小规模风格化数据集(50对)来额外训练一个rank=128的低秩LoRA模块,并冻结以往OmniEditor的所有参数,仅训练低秩LoRA。
将低秩LoRA合并到基础模型中,结束所有训练。
三、相关工作
近期文本引导图像编辑有三个paradigms:全局描述(prompt2prompt、Imagic、EditWorld、ZONE,通过文本描述和图像区域之间跨模态对齐来实现细粒度操作,但需要对目标属性进行细致的文本规范),局部描述引导(Blended Diffusion、Imagen Editor通过显式掩码注释和区域文本提示实现像素级控制,但受到精确空间规范要求限制),instruction-guided形式(instructpix2pix、HIVE通过接受编辑命令,来消除以往详细文本描述和精确的mask依赖,提高用户可访问性)
参考项目:GitHub - showlab/PhotoDoodle: Code Implementation of "PhotoDoodle: Learning Artistic Image Editing from Few-Shot Pairwise Data"