(5)、将文本转换为tokens
使用CLIP模型的tokenize
方法将文本转换为tokens
text_tokens = openai.clip.tokenize(text_description)
这里,text_tokens
是一个PyTorch张量,形状为(1, N)
,其中N是文本描述中的token数量。
在这个例子中,N=3,因为"苹果"被分成了3个tokens。
(6)、 查看tokens
print(f"Tokens: {text_tokens}")
输出结果可能类似于:
Tokens: tensor([[49406, 3782, 49407]])
这里,49406
表示开始符号(start-of-sentence),3782
表示“苹果”,49407
表示结束符号(end-of-sentence)。
通过以上步骤,我们将文本“苹果”转换为了tokens。
PS:
- Stable Diffusion v1使用了CLIP模型的tokenizer
- Tokenizer只能将其在训练过程中见到过的单词进行分词
例如:假设CLIP模型里有“dream”与“beach”单词,但是没有“dreambeach”单词。
Tokenizer会将“dreambeach”分成2个单词“dream”与“beach”。 - 1个单词并非代表1个token,而是有可能进一步进行拆分
- 空格也是token的一部分
例如:短语 “dream beach” 产生了两个token “dream” 和 “[space]beach”。
这些标记与 “dreambeach” 产生的标记不同,后者是 “dream” 和 “beach”(beach 前没有空格)。
2、词嵌入(Embedding)
(1)、为什么需要词嵌入(Embedding)?
因为有些单词相互之间是非常相似,我们希望利用到这些语义信息。
例如:
man、gentleman、guy的词嵌入是非常相近的,因此它们可以相互替换。
Monet、Manet以及Degas都以印象派的风格绘画,但是方式各不相同。
这些名字看起来是非常相似,但是在词嵌入(Embedding)里是不一样的。
(2)、词嵌入(Embedding) 是如何工作的?
Embedding 将输入的tokens转换为一个连续的向量来表示,这个向量可以捕捉文本中的语义信息。在我们的例子中,"苹果"的tokens经过CLIP模型的encode_text
方法后,会得到一个特征向量。
这个特征向量是一个高维空间中的点,通常具有固定的维度(在CLIP模型中,维度为512)。请注意,由于模型权重和随机性的原因,每次运行时生成的特征向量可能略有不同。以下是一个示例输出:
print(f"Text features: {text_features}")
输出结果可能类似于:
Text features: tensor([[-0.0123, 0.0345, -0.0678, ..., 0.0219, -0.0456, 0.0789]])
这里,text_features
是一个形状为(1, 512)
的PyTorch张量,其中包含了“苹果”这个词的向量表示。神经网络可以利用这个向量表示进行训练和预测任务。
Stable diffusion v1使用Open AI的ViT-L/14模型,词嵌入为768维的向量。
3、文本转换器(text transformer)
(1)、为什么需要text transformer
?
既然通过embedding
后可以直接输入到模型中进行训练,为何在stable diffusion中还需要将embedding
通过text transformer
转换后再作为模型的输入呢?
这是因为Stable Diffusion模型是一个图像生成模型,它需要理解输入文本的语义信息以生成与之相关的图像。直接使用基本的文本embedding可能无法充分捕捉到文本中的复杂语义关系。通过使用text transformer,可以获得一个更丰富、更具表现力的文本表示,这有助于提高生成图像的质量和与输入文本的相关性。
使用text transformer 在捕捉文本语义信息时,能够考虑到更多上下文关系和抽象概念。
这个转换器就像是一个通用的条件(conditioning)适配器。
(2)、text transformer转换示例
下面以"苹果"为例进行说明。
假设我们已经获得了"苹果"的基本embedding(一个形状为(1, 512)
的PyTorch张量):
text_features = tensor([[-0.0123, 0.0345, -0.0678, ..., 0.0219, -0.0456, 0.0789]])
接下来,我们将这个张量输入到text transformer中:
transformed_text_features = text_transformer(text_features)
经过text transformer处理后,我们可能会得到一个新的张量,如:
print(f"Transformed text features: {transformed_text_features}")
输出结果可能类似于:
Transformed text features: tensor([[ 0.0234, -0.0567, 0.0890, ..., -0.0321, 0.0672, -0.0813]])
这个新的张量(形状仍为(1, 512)
)包含了更丰富的语义信息,例如上下文关系和抽象概念。
这有助于Stable Diffusion模型更好地理解输入文本,并生成与之相关的图像。
请注意:
由于模型权重和随机性的原因,每次运行时生成的特征向量可能略有不同。
此外,具体的变化过程取决于所使用的text transformer结构和参数。
六、Stable Diffusion Cross-attention技术
Cross-attention 是通过提示词产生图片的核心技术。
文本转换器的输出,会被noise predictor在U-Net中使用到多次。
U-Net以一个叫做cross-attention机制的方式来使用它,cross-attention机制允许模型在不同的特征层次上关注相关的区域,从而提高生成结果的质量,这即是prompt适配图片的地方。
下面代码是stable diffusion所使用的transformers块,实现了cross-attention:
class SpatialTransformer(nn.Module): """ Transformer block for image-like data. First, project the input (aka embedding) and reshape to b, t, d. Then apply standard transformer action. Finally, reshape to image """ def __init__(self, in_channels, n_heads, d_head, depth=1, dropout=0., context_dim=None): super().__init__() self.in_channels = in_channels inner_dim = n_heads * d_head self.norm = Normalize(in_channels) self.proj_in = nn.Conv2d(in_channels, inner_dim, kernel_size=1, stride=1, padding=0) self.transformer_blocks = nn.ModuleList( [BasicTransformerBlock(inner_dim, n_heads, d_head, dropout=dropout, context_dim=context_dim) for d in range(depth)] ) self.proj_out = zero_module(nn.Conv2d(inner_dim, in_channels, kernel_size=1, stride=1, padding=0)) def forward(self, x, context=None): # note: if no context is given, cross-attention defaults to self-attention b, c, h, w = x.shape x_in = x x = self.norm(x) x = self.proj_in(x) x = rearrange(x, 'b c h w -> b (h w) c') for block in self.transformer_blocks: x = block(x, context=context) x = rearrange(x, 'b (h w) c -> b c h w', h=h, w=w) x = self.proj_out(x) return x + x_in
七、Stable Diffusion noise schedule 技术
1、什么是 noise schedule ?
噪声通过多次U-Net的处理,最终会输出我们想要的图片。
在这多次处理中,每一次的降噪的幅度是不同的,所以我们就要通过schedulers来控制每次降噪的幅度(幅度一般是递减的)。这个技术就叫做 noise schedule
。
如图:
那么为什么要使用 noise schedule
技术呢?
在 Stable Diffusion 这种生成模型中,U-Net 是一个核心组件,用于从噪声图像中逐步恢复出原始图像。在多次迭代过程中,降噪幅度逐渐减小的原因是为了更精细地恢复图像的细节和结构。
Stable Diffusion 的过程可以看作是一个逆向扩散过程,它从一个高度噪声的图像开始,然后通过多个步骤逐渐去除噪声以重建原始图像。在这个过程中,U-Net 被用来预测每一步的降噪操作。
在前几轮迭代中,图像中的噪声较大,因此需要较大的降噪幅度来消除这些噪声。随着迭代次数的增加,图像中的噪声逐渐减小,因此降噪幅度也应相应减小。这样做的目的是避免过度平滑或损坏已经恢复的图像细节。
通过逐渐减小降噪幅度,U-Net 可以更好地控制去噪过程,使其在保留图像细节的同时有效地去除噪声。这有助于生成更清晰、更真实的图像。
这里举一个文生图的代码,用于说明noise schedule
技术:
def txt2img(): #unet unet = load_unet() #调度器 scheduler = lms_scheduler() scheduler.set_timesteps(100) #文本编码 prompts = ["a photograph of an astronaut riding a horse"] text_embeddings = prompts_embedding(prompts) text_embeddings = text_embeddings.cuda() #(1, 77, 768) uncond_prompts = [""] uncond_embeddings = prompts_embedding(uncond_prompts) uncond_embeddings = uncond_embeddings.cuda() #(1, 77, 768) #初始隐变量 latents = torch.randn( (1, 4, 64, 64)) #(1, 4, 64, 64) latents = latents * scheduler.sigmas[0] #sigmas[0]=157.40723 latents = latents.cuda() #循环步骤 for i, t in enumerate(scheduler.timesteps): #timesteps=[999. 988.90909091 978.81818182 ...100个 latent_model_input = latents #(1, 4, 64, 64) sigma = scheduler.sigmas[i] latent_model_input = latent_model_input / ((sigma**2 + 1) ** 0.5) timestamp = torch.tensor([t]).cuda() with torch.no_grad(): noise_pred_text = unet(latent_model_input, timestamp, text_embeddings) noise_pred_uncond = unet(latent_model_input, timestamp, uncond_embeddings) guidance_scale = 7.5 noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond) latents = scheduler.step(noise_pred, i, latents) vae = load_vae() latents = 1 / 0.18215 * latents image = vae.decode(latents.cpu()) #(1, 3, 512, 512) save_image(image,"txt2img.png") txt2img()
八、Stable Diffusion 文生图底层运行演示
在文本生成图的场景下,我们给SD模型输入一组文本提示词,它可以返回一张图片。
第一步、 Stable Diffusion在潜空间里生成一个随机张量。
我们通过设置随机种子seed来控制这个张量的生成。如果我们设置这个随机种子为一个特定的值,则会得到相同的随机张量。这就是我们在潜空间里的图片。但是当前还全是噪点。
第二步、 Noise predictor U-Net将潜噪点图已经文本提示词作为输入,并预测噪点
此噪点同样也在潜空间内(一个4 x 64 x 64的张量)
第三步、从潜图片中抽取潜噪点,并生成了新的潜图片
第二步 与 第三步重复特定采样次数,例如20次。
第四步、VAE 的decoder将潜图片转回像素空间
这便是我们通过SD模型最终得到的图片。
参考资料: