前面小L给大家讲了不少多模态模型,尤其是Stable Diffusion的理论知识。这时候,E县的同事们有点儿坐不住了,他们已经迫不及待地想赶快用上Stable Diffusion,为家乡的宣传推广制作素材了。
既然这几位同事如此热切,那咱们就让小L教他们怎样使用代码调用Stable Diffusion绘制图像吧!为了免去环境部署的麻烦,小L还是先教他们如何使用Kaggle提供的免费GPU来进行实验。
一些准备工作
在Kaggle上创建好Notebook之后,首先我们需要安装(或更新)一些必要的库,使用下面的代码即可完成这项工作:
# 使用pip安装diffusers库,指定版本为0.3.0
# 并且减少输出信息(--q是--quiet的简写,用于抑制输出)
!pip install diffusers==0.3.0 --q
# 使用pip安装transformers、scipy和ftfy库,同样抑制输出信息
# transformers库也可以用于加载和微调生成式AI模型
# scipy是用于数学、科学和工程的开源Python库
# ftfy库用于修复Unicode文本中的错误和乱码
!pip install transformers scipy ftfy --q
# 使用pip安装ipywidgets库,指定版本范围在7.x.x到8.0.0之前(不包含8.0.0)
# ipywidgets库用于在Jupyter Notebook中创建交互式小部件(widgets)
!pip install "ipywidgets>=7, --q
# 导入IPython.display模块,该模块提供了在Jupyter Notebook中显示对象的方法
# 例如,可以显示图像、视频、HTML内容等
import IPython.display
运行上面的代码之后,我们就准备好了需要用到的库。接下来,我们要去Hugging Face上获取一个Token。首先登录你的Hugging Face账号(如果没有就先注册一个),然后点击右上角自己的头像图标,就会看到一个下拉菜单,再点击菜单中的“Access Tokens(访问令牌)”。如图所示。
点击“Access Tokens”之后,会进入新的页面。接下来我们在新的页面中点击“Create new token”来创建新的Token(如果你已经有token,可以不做这一步),如图所示。
点击“Create new token”按钮之后,就会看到创建新的Token的界面。在这个界面中,我们选择Token类型为“Read”即可,再随便给它起个名字,最后点击下方的“Create Token”按钮。如图所示。
在点击“Create token”之后,就会看到新创建的Token了。这时我们要做的是把这个Token复制下来,点击“Copy”按钮即可,如图所示
现在我们要做的事情是,回到Kaggle平台上我们创建的Notebook中,把Token保存到Notebook的密钥当中。方法很简单,在菜单栏中点击“Add-ons”,会看到下拉菜单。然后在菜单中选择“Secrets”,如图所示。
点击“Secrets”选项之后,我们就会在Notebook的右侧看到添加密钥的界面。这时我们需要点击“Add secret”按钮,如图所示。
到这里,我们前期的准备工作完成了一半,下面我们要导入必要的库,并设置一些超参数。使用的代码如下:
# 导入Python的垃圾回收模块
import gc
# 导入PyTorch库
import torch
# 从PIL库导入Image模块,用于图像处理
from PIL import Image
# 导入IPython的display模块,用于在Jupyter Notebook中显示图像等
import IPython.display
# 从torch库中导入autocast上下文管理器
# 用于在支持的设备上自动选择最优的数据类型
from torch import autocast
# 从tqdm库中导入tqdm,它是一个快速、可扩展的Python进度条库
from tqdm.auto import tqdm
# 从kaggle_secrets库中导入UserSecretsClient
# 用于安全地管理和访问Kaggle上的秘密(如API密钥)
from kaggle_secrets import UserSecretsClient
# 从transformers库中导入CLIPTextModel和CLIPTokenizer
# 它们分别用于文本模型的加载和文本的编码
from transformers import CLIPTextModel, CLIPTokenizer
# 从diffusers库中导入StableDiffusionPipeline
# 它是用于生成图像的管道
from diffusers import StableDiffusionPipeline
# 从diffusers库中导入AutoencoderKL和UNet2DConditionModel
# 它们是扩散模型中的组件
from diffusers import AutoencoderKL, UNet2DConditionModel
# 从diffusers库中导入LMSDiscreteScheduler和PNDMScheduler
# 它们是用于控制扩散过程中噪声调度的类
from diffusers import LMSDiscreteScheduler, PNDMScheduler
# 创建一个UserSecretsClient的实例,用于访问Kaggle上的Secret
user_secrets = UserSecretsClient()
# 从Kaggle的秘密存储中获取名为"try_sd"的密钥或令牌
Hugging_face = user_secrets.get_secret("try_sd")
完成库的导入工作之后,我们再来设定一些超参数。使用的代码如下:
class config:
# 根据系统是否支持CUDA来设置设备为"cuda"或"cpu"
# 如果CUDA可用则使用"cuda"
# 否则,使用"cpu"作为计算设备
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
# 设置生成图像的高度为512像素
HEIGHT = 512
# 设置生成图像的宽度为512像素
WIDTH = 512
# 设置推理步骤的数量为500
# 推理步骤的数量会影响图像生成的质量和所需的计算时间
# 更多的步骤通常意味着更高的质量和更长的生成时间
NUM_INFERENCE_STEPS = 500
# 设置引导尺度为7.5
# 引导尺度是一个控制图像生成过程中文本提示影响力的参数
# 较高的值会使生成的图像更紧密地遵循给定的条件信息
GUIDANCE_SCALE = 7.5
# 设置随机数生成器的种子为48
GENERATOR = torch.manual_seed(48)
# 设置批处理大小为1
# 这意味着在每次推理过程中,将只处理一个图像
BATCH_SIZE = 1
到这里为止,全部的准备工作就都已经完成了,可以开始我们的艺术创作之旅了。
创建Pipeline
前面我们已经让小L给E县的同事们介绍过,Stable Diffusion是把一个扩散模型“塞进”自编码器当中,并且还要有一个文本编码器将提示词转换为嵌入向量。这些组件如果我们都从头训练肯定是不现实的,所以这里干脆都用预训练好的模型就可以了。要载入这些预训练模型,使用的代码如下:
# 从diffusers库中加载预训练的AutoencoderKL模型
# 这是稳定扩散模型中的一个组件
# "CompVis/stable-diffusion-v1-4"是模型的标识符
# subfolder="vae"指定了模型权重所在的子文件夹
# use_auth_token=Hugging_face提供了访问私有模型所需的身份验证令牌
vae = AutoencoderKL.from_pretrained("CompVis/stable-diffusion-v1-4",
subfolder="vae",
use_auth_token=Hugging_face)
# 从transformers库中加载预训练的CLIPTokenizer,用于文本编码
# "openai/clip-vit-large-patch14"是tokenizer的标识符
tokenizer = CLIPTokenizer.from_pretrained(
"openai/clip-vit-large-patch14"
)
# 从transformers库中加载预训练的CLIPTextModel,用于文本特征提取
# 同样使用"openai/clip-vit-large-patch14"作为模型的标识符
text_encoder = CLIPTextModel.from_pretrained(
"openai/clip-vit-large-patch14"
)
# 从diffusers库中加载预训练的UNet2DConditionModel
# 这是稳定扩散模型中的另一个关键组件
# 同样使用"CompVis/stable-diffusion-v1-4"作为模型的标识符
# subfolder="unet"指定了模型权重所在的子文件夹
# use_auth_token=Hugging_face提供了访问私有模型所需的身份验证令牌
unet = UNet2DConditionModel.from_pretrained(
"CompVis/stable-diffusion-v1-4",
subfolder="unet",
use_auth_token=Hugging_face)
# 将AutoencoderKL模型移动到配置中指定的设备上(GPU或CPU)
vae = vae.to(config.DEVICE)
# 将CLIPTextModel模型移动到配置中指定的设备上(GPU或CPU)
text_encoder = text_encoder.to(config.DEVICE)
# 将UNet2DConditionModel模型移动到配置中指定的设备上(GPU或CPU)
unet = unet.to(config.DEVICE)
运行这一段代码之后,我们就将所需的预训练模型加载完毕了。眼下还需要做一件事——不知道大家还记不记得我们在第9章中学习过,扩散模型还需要指定一个扩散计划(Diffusion Scheduler,也有翻译成“扩散调度”的)。这次我们尝试使用一个新的扩散计划——LMSDiscreteScheduler,使用的代码如下:
# 创建一个LMSDiscreteScheduler对象,用于在训练过程中调整beta值
scheduler = LMSDiscreteScheduler(
beta_start=0.00085, # 初始beta值,训练开始时使用的值
beta_end=0.012, # 最终beta值,训练结束时希望达到的值
beta_schedule="scaled_linear", # beta值调整策略,按比例线性调整
num_train_timesteps=1000 # 训练的总时间步长,用于计算beta值的变化
)
这里稍微介绍一下LMSDiscreteScheduler,它也是一种在扩散模型中使用的扩散计划。其通过设置初始噪声水平(beta_start)、最终噪声水平(beta_end)、噪声水平的调整策略(beta_schedule)以及训练的总时间步长(num_train_timesteps)等参数,来定义噪声在整个训练过程中的变化情况。这些参数共同决定了模型在何时添加多少噪声,以及在何时开始减少噪声以生成图像。
现在,就到了激动人心的时刻了。我们创建的这个Pipeline能不能根据给定的提示词“画”出图像呢?接下来让我们见证“奇迹”!
根据提示词生成图像
既然咱们是要帮助E县的同事们生成宣传素材,那提示词就可以好好夸一下E县的风景。正好有一位同事文笔很不错,于是他写了一段文字——“E县的自然风光美不胜收,天空晴朗万里无云,湛蓝的云朵与郁郁葱葱的山峦形成鲜明对比,清澈见底的溪流蜿蜒其间。图像的背景展现了E县标志性的景观,无论是如画的乡村田野,还是宁静的湖畔,都散发着生机与和谐的气息。”文字很美,不过考虑到Stable Diffusion需要我们给出英文的提示词,于是小L把它翻成了英语,并赋值给一个名叫prompt的变量。代码如下:
prompt = ['''
The natural scenery of Yixian County, with sunny skies, azure blue clouds contrasting against verdant mountains and crystal-clear streams.
The backdrop of the image showcases Yixian's iconic landscapes, be it picturesque countryside or serene lakeside, exuding vitality and harmony.
8k
''']
这里的英文提示词,除了表达E县同事写出的意境,还增加了一个“8k”。意思是希望这幅画面能以8K的超高清分辨率来呈现,以捕捉并展现所有细节的美丽。
有了提示词之后,我们就要按照步骤,先对其进行预处理,然后输入到文本编码器当中,使用的代码如下:
# 导入文本并对其进行预处理,准备输入到文本编码器中
text_input = tokenizer(
prompt, # 输入的文本提示
padding="max_length", # 对不足最大长度的文本进行填充
max_length=tokenizer.model_max_length, # 分词器支持的最大长度
truncation=True, # 对超出最大长度的文本进行截断
return_tensors="pt" # 返回PyTorch张量格式的输入
)
# 获取处理后的文本输入的最大长度
max_length = text_input.input_ids.shape[-1]
# 在不计算梯度的情况下,通过文本编码器获取文本嵌入
with torch.no_grad():
text_embeddings = text_encoder(text_input.input_ids.to(config.DEVICE))[0]
# 准备一批空的文本输入,用于生成无条件嵌入
uncond_input = tokenizer(
[""] * config.BATCH_SIZE, # 创建一个空字符串列表,长度为批量大小
padding="max_length", # 对不足最大长度的文本进行填充
max_length=max_length, # 使用与之前相同的最大长度
return_tensors="pt" # 返回PyTorch张量格式的输入
)
# 在不计算梯度的情况下,通过文本编码器获取无条件嵌入
with torch.no_grad():
uncond_embeddings = text_encoder(
uncond_input.input_ids.to(config.DEVICE)
)[0]
# 将无条件嵌入和文本嵌入拼接在一起,形成最终的嵌入表示
text_embeddings = torch.cat([uncond_embeddings, text_embeddings])
运行完上面的代码,我们就得到了提示词的文本嵌入。下面我们再准备一些随机的潜在向量,作为U-Net的输入。使用的代码如下:
# 生成一批随机的潜在向量(latents),这些向量将作为U-Net的输入
# 潜在向量的维度由批量大小、U-Net的输入通道数、以及高度和宽度决定
latents = torch.randn(
(config.BATCH_SIZE, unet.in_channels, # 批量大小和U-Net的输入通道数
config.HEIGHT // 8, config.WIDTH // 8), # 缩小后的高度和宽度
generator=config.GENERATOR, # 指定随机数生成器
)
# 将生成的潜在向量移动到GPU上
latents = latents.to(config.DEVICE)
运行上面的代码之后,我们就有了输入给U-Net的潜在向量了。接下来,我们就可以执行扩散的过程,使用的代码如下:
# 设置扩散计划的当前时间步长
scheduler.set_timesteps(config.NUM_INFERENCE_STEPS)
# 将潜在向量(latents)乘以扩散计划在初始时间步长下的sigma值
latents = latents * scheduler.sigmas[0]
# 使用自动混合精度(autocast)根据配置的设备(CPU或GPU)来执行以下代码块
with autocast(config.DEVICE):
# 遍历调度器中的时间步长
for i, t in tqdm(enumerate(scheduler.timesteps)):
# 将潜在向量(latents)复制两份并拼接起来
latent_model_input = torch.cat([latents] * 2)
# 获取当前时间步长的噪声标准差
sigma = scheduler.sigmas[i]
# 根据噪声标准差调整潜在模型输入的尺度
latent_model_input = latent_model_input / ((sigma**2 + 1) ** 0.5)
# 在不计算梯度的情况下,通过U-Net模型预测噪声
# 这里使用了文本嵌入作为条件信息的一部分
with torch.no_grad():
# 这里.sample是U-Net模型返回的噪声预测
noise_pred = unet(latent_model_input, t,
encoder_hidden_states=text_embeddings).sample
# 将噪声预测分割成无条件部分和条件部分
noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
# 根据引导尺度结合无条件噪声预测和条件噪声预测
noise_pred = noise_pred_uncond +
config.GUIDANCE_SCALE * (noise_pred_text - noise_pred_uncond)
# 使用调度器的step方法更新潜在向量
# 这里返回的.prev_sample用于下一个时间步长的迭代
latents = scheduler.step(noise_pred, i, latents).prev_sample
上面的代码需要较长的时间才能运行完毕,此后我们就得到了U-net的预测结果。接下来就可以使用自编码器把U-Net的预测结果解码成图像了,使用的代码如下:
# 将latents进行缩放
latents = 1 / 0.18215 * latents
# 禁用梯度计算,生成新图像时不需要进行反向传播
with torch.no_grad():
# 使用变分自编码器(vae)的解码器部分将latents解码成图像
image = vae.decode(latents).sample
# 将图像数据从[-1, 1]范围线性变换到[0, 1]范围,
# 使用.clamp(0, 1)确保值不会超出这个范围。
image = (image / 2 + 0.5).clamp(0, 1)
# .detach()从计算图中分离出image
# .cpu()将图像数据从GPU内存移动到CPU内存,以便后续处理。
# .permute(0, 2, 3, 1)重新排列图像的维度
# .numpy()将图像数据从PyTorch张量转换为NumPy数组。
image = image.detach().cpu().permute(0, 2, 3, 1).numpy()
# 将图像数据从[0, 1]范围缩放到[0, 255]范围,并转换为无符号8位整数类型,
# 这是保存为JPEG格式图像时的标准格式。
images = (image * 255).round().astype("uint8")
# 使用PIL库的Image.fromarray()函数将每个NumPy数组图像转换为PIL图像对象。
pil_images = [Image.fromarray(image) for image in images]
# 保存PIL图像列表中的第一个图像为"img1.jpg"文件。
pil_images[0].save("img1.jpg")
# 显示PIL图像列表中的第一个图像对象
pil_images[0]
运行上面的代码之后,我们就会得到第一幅作品,如图所示。
看到模型生成的图像,E县的同事们都很激动——这就是他们家乡的样子啊!那碧蓝的天空,壮丽的山峦,还有清澈的溪流,无不勾起了他们的思乡之情。
参考图书:北京大学出版社《人工智能大模型:机器学习基础》

文章来源于互联网:【生成式AI】如何在kaggle上调用Stable Diffusion创作图像
相关推荐: 如何把论文AIGC率降到最低?三招搞定,过审无忧!
最近好多同学都在吐槽,学校突然开始检测论文的AIGC率了。这玩意儿要是太高,论文就直接被打回来,和当年的查重率一样让人头疼。更气人的是,有些同学明明是自己写的,系统却判定AI率超高,简直冤到家了!别急,今天我就来教你怎么搞定这件事,保证你顺利过审。 一、为啥要…
5bei.cn大模型教程网











