【Datawhale X 魔搭 AI夏令营】从零入门AI生图原理&实践

222 阅读10分钟

01 相关链接

可图Kolors-LoRA风格故事挑战赛_创新应用大赛_天池大赛-阿里云天池的赛制 (aliyun.com)

阿里云免费试用 - 阿里云 (aliyun.com)

Datawhale (linklearner.com)

02 背景知识

一、文生图技术

文生图技术(Text-to-Image Generation)是一种利用人工智能技术将文本描述转化为图像的技术。其核心在于利用GAN等深度学习模型,根据用户输入的文本描述来生成相应的图像。

  1. 将文本描述进行编码处理,得到文本特征向量;
  2. 将该特征向量作为生成器的输入,通过生成器生成对应的图像;
  3. 利用判别器对生成的图像进行评估,以确保其符合文本描述的要求。

二、生成模型

image.png

(一)生成对抗网络(GAN)

生成对抗网络(Generative Adversarial Networ)是一类基于深度学习的无监督机器学习模型,它通过两个神经网络——生成器(Generator)和判别器(Discriminator)——的相互对抗训练,来提升生成数据的质量和真实性。

  • 生成器(G) :负责接收随机噪声作为输入,并生成尽可能接近真实数据的假数据。
  • 判别器(D) :负责区分输入的数据是真实的还是由生成器生成的假数据,并输出一个概率值来表示其判断结果。

在训练过程中,生成器试图欺骗判别器,使其无法区分生成的数据和真实数据;而判别器则努力提高自己的判别能力,以准确区分真实数据和假数据。这种对抗训练的过程不断迭代,直到生成器能够生成足以“欺骗”判别器的逼真数据,而判别器也无法再准确区分数据的真伪。

image.png

(二)变分自编码器(VAE)

变分自编码器(Variational Autoencoder)是一种基于概率生成模型的深度学习架构,它通过将输入数据编码为潜在空间的分布参数,然后从这些参数中解码出新的数据样本,生成与训练数据相似的新数据点。

  • 编码器:负责将输入数据映射到潜在空间,并输出该空间的分布参数(通常是均值和方差)。这些参数定义了潜在空间中的一个分布,该分布描述了输入数据的潜在表示。
  • 解码器:根据编码器输出的分布参数,从中采样得到潜在变量,并将这些潜在变量解码为原始数据的高品质复制。

在训练过程中,主要分为三个步骤:

  1. 编码:输入数据通过编码器被映射到潜在空间,并输出该空间的分布参数
  2. 解码:从潜在空间的分布中采样得到潜在变量,并通过解码器将其解码为原始数据的高品质复制
  3. 重构:计算重构数据与原始数据之间的误差,并通过反向传播算法更新编码器和解码器的参数。

image.png

(三)流模型(Flow-based models)

流模型(Flow-based models)是一类基于概率密度函数的深度学习模型,它通过构建可逆的神经网络变换,实现从一种分布到另一种分布的直接且可逆的映射。这类模型的主要特点是能够直接对数据的概率密度函数进行建模,从而在数据生成和推断方面具有显著优势。

(四)Stable Diffusion

Stable Diffusion基于Latent Diffusion Models(LDMs)实现,其工作原理主要基于扩散过程(Diffusion Process)和反向扩散过程(Reverse Diffusion Process)。

  • 扩散过程:模型将随机噪声逐渐地向原始数据集的中心值靠近,学习数据的分布特性。
  • 反向扩散过程:与扩散过程相反,该过程将噪声逐渐变换为用户所需的图像。通过参数化的马尔可夫链(Markov Chain)实现,具有较高的灵活性和可定制性。

image.png

使用VAE压缩图像,在压缩后的latents域上建模扩散过程 image.png

03 代码实践

第一步:安装

安装 Data-Juicer 和 DiffSynth-Studio

Data-Juicer 数据处理和转换工具

将复杂的数据原料转化为易于“吸收”的精华。该系统强大之处在于它针对多模态数据的处理,覆盖了文本、图像、音频甚至视频,为当今和未来多模态模型的发展提供了强有力的支持。

DiffSynth-Studio 高效微调训练大模型工具

Diffutoon渲染技术通过结合深度学习和计算机图形学,实现了高质量的图像和视频渲染。Diffutoon技术主要利用了生成对抗网络(GANs)和扩散模型(Diffusion Models),能够生成具有高度真实感和艺术效果的图像和视频。

!pip install simple-aesthetics-predictor

!pip install -v -e data-juicer

!pip uninstall pytorch-lightning -y
!pip install peft lightning pandas torchvision

!pip install -e DiffSynth-Studio

重启 Notebook kernel

第二步:下载数据集

from modelscope.msdatasets import MsDataset

ds = MsDataset.load(
    'AI-ModelScope/lowres_anime',
    subset_name='default',
    split='train',
    cache_dir="/mnt/workspace/kolors/data"
)

保存数据集中的图片及元数据

import json, os
from data_juicer.utils.mm_utils import SpecialTokens
from tqdm import tqdm


os.makedirs("./data/lora_dataset/train", exist_ok=True)
os.makedirs("./data/data-juicer/input", exist_ok=True)
with open("./data/data-juicer/input/metadata.jsonl", "w") as f:
    for data_id, data in enumerate(tqdm(ds)):
        image = data["image"].convert("RGB")
        image.save(f"/mnt/workspace/kolors/data/lora_dataset/train/{data_id}.jpg")
        metadata = {"text": "二次元", "image": [f"/mnt/workspace/kolors/data/lora_dataset/train/{data_id}.jpg"]}
        f.write(json.dumps(metadata))
        f.write("\n")

第三步:数据处理

使用 data-juicer 处理数据

data_juicer_config = """
# global parameters
project_name: 'data-process'
dataset_path: './data/data-juicer/input/metadata.jsonl'  # path to your dataset directory or file
np: 4  # number of subprocess to process your dataset

text_keys: 'text'
image_key: 'image'
image_special_token: '<__dj__image>'

export_path: './data/data-juicer/output/result.jsonl'

# process schedule
# a list of several process operators with their arguments
process:
    - image_shape_filter:
        min_width: 1024
        min_height: 1024
        any_or_all: any
    - image_aspect_ratio_filter:
        min_ratio: 0.5
        max_ratio: 2.0
        any_or_all: any
"""
with open("data/data-juicer/data_juicer_config.yaml", "w") as file:
    file.write(data_juicer_config.strip())

!dj-process --config data/data-juicer/data_juicer_config.yaml

保存处理好的数据

import pandas as pd
import os, json
from PIL import Image
from tqdm import tqdm


texts, file_names = [], []
os.makedirs("./data/lora_dataset_processed/train", exist_ok=True)
with open("./data/data-juicer/output/result.jsonl", "r") as file:
    for data_id, data in enumerate(tqdm(file.readlines())):
        data = json.loads(data)
        text = data["text"]
        texts.append(text)
        image = Image.open(data["image"][0])
        image_path = f"./data/lora_dataset_processed/train/{data_id}.jpg"
        image.save(image_path)
        file_names.append(f"{data_id}.jpg")
data_frame = pd.DataFrame()
data_frame["file_name"] = file_names
data_frame["text"] = texts
data_frame.to_csv("./data/lora_dataset_processed/train/metadata.csv", index=False, encoding="utf-8-sig")
data_frame

第四步:训练模型

下载模型

from diffsynth import download_models

download_models(["Kolors", "SDXL-vae-fp16-fix"])

查看训练脚本的输入参数

!python DiffSynth-Studio/examples/train/kolors/train_kolors_lora.py -h

开始训练

import os

cmd = """
python DiffSynth-Studio/examples/train/kolors/train_kolors_lora.py \
  --pretrained_unet_path models/kolors/Kolors/unet/diffusion_pytorch_model.safetensors \
  --pretrained_text_encoder_path models/kolors/Kolors/text_encoder \
  --pretrained_fp16_vae_path models/sdxl-vae-fp16-fix/diffusion_pytorch_model.safetensors \
  --lora_rank 16 \
  --lora_alpha 4.0 \
  --dataset_path data/lora_dataset_processed \
  --output_path ./models \
  --max_epochs 1 \
  --center_crop \
  --use_gradient_checkpointing \
  --precision "16-mixed"
""".strip()

os.system(cmd)

参数解释

  • --pretrained_unet_path:指定预训练的U-Net模型路径。U-Net是一种常用于图像分割和生成的神经网络结构。这里的模型用于图像生成过程的一部分。
  • --pretrained_text_encoder_path:指定预训练的文本编码器路径。这个编码器用于将文本描述转换为模型可以理解的嵌入向量,以指导图像的生成。
  • --pretrained_fp16_vae_path:指定预训练的FP16 VAE(变分自编码器)模型路径。这里的FP16指的是模型使用了半精度浮点数,这有助于减少内存占用和加速训练。
  • --lora_rank 16:设置LORA的秩为16。LORA是一种低秩适应技术,用于微调大型预训练模型,而不需要修改原始模型的全部参数。
  • --lora_alpha 4.0:设置LORA的alpha值为4.0。
  • --dataset_path:指定训练数据集的路径。
  • --output_path:指定训练后模型输出的路径。
  • --max_epochs 1:设置训练的最大轮次为1。
  • --center_crop:在训练过程中使用中心裁剪作为数据增强的一部分。
  • --use_gradient_checkpointing:启用梯度检查点。
  • --precision "16-mixed":设置训练过程中的精度为混合精度16位。

加载模型

from diffsynth import ModelManager, SDXLImagePipeline
from peft import LoraConfig, inject_adapter_in_model
import torch


def load_lora(model, lora_rank, lora_alpha, lora_path):
    lora_config = LoraConfig(
        r=lora_rank,
        lora_alpha=lora_alpha,
        init_lora_weights="gaussian",
        target_modules=["to_q", "to_k", "to_v", "to_out"],
    )
    model = inject_adapter_in_model(lora_config, model)
    state_dict = torch.load(lora_path, map_location="cpu")
    model.load_state_dict(state_dict, strict=False)
    return model


# Load models
model_manager = ModelManager(torch_dtype=torch.float16, device="cuda",
                             file_path_list=[
                                 "models/kolors/Kolors/text_encoder",
                                 "models/kolors/Kolors/unet/diffusion_pytorch_model.safetensors",
                                 "models/kolors/Kolors/vae/diffusion_pytorch_model.safetensors"
                             ])
pipe = SDXLImagePipeline.from_model_manager(model_manager)

# Load LoRA
pipe.unet = load_lora(
    pipe.unet,
    lora_rank=16, # This parameter should be consistent with that in your training script.
    lora_alpha=2.0, # lora_alpha can control the weight of LoRA.
    lora_path="models/lightning_logs/version_0/checkpoints/epoch=0-step=500.ckpt"
)

生成图像

torch.manual_seed(0)
image = pipe(
    prompt="二次元,一个紫色短发小女孩,在家中沙发上坐着,双手托着腮,很无聊,全身,粉色连衣裙",
    negative_prompt="丑陋、变形、嘈杂、模糊、低对比度",
    cfg_scale=4,
    num_inference_steps=50, height=1024, width=1024,
)
image.save("1.jpg")
torch.manual_seed(1)
image = pipe(
    prompt="二次元,日系动漫,演唱会的观众席,人山人海,一个紫色短发小女孩穿着粉色吊带漏肩连衣裙坐在演唱会的观众席,舞台上衣着华丽的歌星们在唱歌",
    negative_prompt="丑陋、变形、嘈杂、模糊、低对比度",
    cfg_scale=4,
    num_inference_steps=50, height=1024, width=1024,
)
image.save("2.jpg")
torch.manual_seed(2)
image = pipe(
    prompt="二次元,一个紫色短发小女孩穿着粉色吊带漏肩连衣裙坐在演唱会的观众席,露出憧憬的神情",
    negative_prompt="丑陋、变形、嘈杂、模糊、低对比度,色情擦边",
    cfg_scale=4,
    num_inference_steps=50, height=1024, width=1024,
)
image.save("3.jpg")
torch.manual_seed(5)
image = pipe(
    prompt="二次元,一个紫色短发小女孩穿着粉色吊带漏肩连衣裙,对着流星许愿,闭着眼睛,十指交叉,侧面",
    negative_prompt="丑陋、变形、嘈杂、模糊、低对比度,扭曲的手指,多余的手指",
    cfg_scale=4,
    num_inference_steps=50, height=1024, width=1024,
)
image.save("4.jpg")
torch.manual_seed(0)
image = pipe(
    prompt="二次元,一个紫色中等长度头发小女孩穿着粉色吊带漏肩连衣裙,在练习室练习唱歌",
    negative_prompt="丑陋、变形、嘈杂、模糊、低对比度",
    cfg_scale=4,
    num_inference_steps=50, height=1024, width=1024,
)
image.save("5.jpg")
torch.manual_seed(1)
image = pipe(
    prompt="二次元,一个紫色长发小女孩穿着粉色吊带漏肩连衣裙,在练习室练习唱歌,手持话筒",
    negative_prompt="丑陋、变形、嘈杂、模糊、低对比度",
    cfg_scale=4,
    num_inference_steps=50, height=1024, width=1024,
)
image.save("6.jpg")
torch.manual_seed(7)
image = pipe(
    prompt="二次元,紫色长发少女,穿着黑色连衣裙,试衣间,心情忐忑",
    negative_prompt="丑陋、变形、嘈杂、模糊、低对比度",
    cfg_scale=4,
    num_inference_steps=50, height=1024, width=1024,
)
image.save("7.jpg")
torch.manual_seed(0)
image = pipe(
    prompt="二次元,紫色长发少女,穿着黑色礼服,连衣裙,在台上唱歌",
    negative_prompt="丑陋、变形、嘈杂、模糊、低对比度",
    cfg_scale=4,
    num_inference_steps=50, height=1024, width=1024,
)
image.save("8.jpg")
import numpy as np
from PIL import Image


images = [np.array(Image.open(f"{i}.jpg")) for i in range(1, 9)]
image = np.concatenate([
    np.concatenate(images[0:2], axis=1),
    np.concatenate(images[2:4], axis=1),
    np.concatenate(images[4:6], axis=1),
    np.concatenate(images[6:8], axis=1),
], axis=0)
image = Image.fromarray(image).resize((1024, 2048))
image

04 Ending

修改了一下图片元素描述:

1723376088483.png