抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

DataWhaleX魔搭_AI夏令营_四期AIGC方向_task01 学习笔记

AI生图

学习主题介绍

从零入门AI生图原理&实践 是 Datawhale 2024 年 AI 夏令营第四期的学习活动(“AIGC”方向),基于魔搭社区“可图Kolors-LoRA风格故事挑战赛”开展的实践学习——

适合想 入门并实践 AIGC文生图、工作流搭建、LoRA微调 的学习者参与

学习内容提要:从文生图实现方案逐渐进阶,教程偏重图像工作流、微调、图像优化等思路,最后会简单介绍AIGC应用方向、数字人技术(选学)

比赛介绍

本次学习任务基于可图Kolors-LoRA风格故事挑战赛,该比赛同样由阿里云天池平台联合魔搭社区举办。进行学习任务,需要报名参加该比赛来更好地检验自己的学习成果。

赛题内容

  1. 参赛者需在可图Kolors 模型的基础上训练LoRA 模型,生成无限风格,如水墨画风格、水彩风格、赛博朋克风格、日漫风格……

  2. 基于LoRA模型生成 8 张图片组成连贯故事,故事内容可自定义;基于8图故事,评估 LoRA 风格的美感度及连贯性

样例:偶像少女养成日记

对样例的想法与解说

由八图故事表现形式,联想到早期四格漫画

八图故事,顾名思义,由八张图片组成连贯的故事。其实我最初看到这个要求的时候,脑中立马闪过小时候很流行的“四格漫画”。

啥是四格漫画?

四格漫画,是以四个画面分格来完成一个小故事或一个创意点子的表现形式。

四格漫画短短几格就涵盖了一个事件的发生、情节转折及幽默的结局。让人看完不觉莞尔,会心一笑或捧腹大笑。

四格漫画着重点子创意,画面不需很复杂,角色也不需要太多,对白精简,让人容易轻松阅读。

四格漫画是一种古老的艺术表现形式,四格漫画在表现上的特点主要强调叙事。

早期的漫画除了传统的单幅外,就是四格讽刺漫画:开头,发展,高潮,结尾,所以是四格。六格和八格是后来才出现的,情节展开会比较多。其实四格漫画对格数已经没有限制。

当时的四格漫画大多画风简单而突出重点,富有表现力,故事情节简单而富有转折、诙谐幽默冷幽默

早期四格漫画示例

赛尔号打钱!

早期四格漫画形式的借鉴意义

而现在,我们用AI生成故事,也就不需要拘泥于漫画的表现风格了,赛题要求中也写道:

如水墨画风格、水彩风格、赛博朋克风格、日漫风格……

而我们要讲的故事所适合的风格,我想这是我们需要思考的。

如在交流群中,有一位同学想讲“指环王里一开头弗罗多的故事”,但生成的图片风格却是比较写实的,头发画的比较细腻,光线也打的较为柔和,整体全是人物近景,有背景的图片中,背景又虚化比较严重……导致该同学表示“生成的好像和我想的一点儿关系也没有,除了衣服配上了”,这就说明该同学存在的问题是描述细节多,而对整体风格要求可能较少。

风格上,我们不必拘泥于早期四格漫画形式,但在如何讲好一个故事上,我想四格漫画有很重要的参考意义。

  1. 在早期四格漫画中,故事大多具有转折或幽默特点,四格中一般即起、承、转、结,前三格铺陈蓄势,第四格让人意想不到;

  2. 使用的对白尽量精简,不用对白只靠表情跟动作是最好也是最难的演出;但是,由于AI绘图很难绘制出正确的文字,所以这一点我们可以不予考虑(记得要在反向提示词中加入”text”以防万一),取而代之的是,我们可以在故事的描述文字上(示例中每张图片上方的文字)多做注意,做到文字简明、概括。

  3. 动作、表情、对白、情节安排必须符合各个角色设定的性格特征与一贯性。如果在仅仅八格的内容中就出现了角色前后形象大不一致(不是由于剧情发展而引起的正常转变)、甚至直接人设崩塌,这是绝对不能接受的。

样例是如何生成的?

你可能使用过AI生成图片,那么对于我们要做的事情应该很清楚:给AI提示词。

什么是 prompt ?

在使用大语言模型时,使用 prompt 往往可以获得更好、更精确的结果。prompt直译为“提示语”。现在已经有专门研究大语言模型prompt的工作(prompt engineer)。

我们也可以简单地理解为在这次任务中,我们对于AI的要求。试想:我们在请人类作画时,尚且需要提出要求呢。

我们可以打开baseline.ipynb查看样例所给的prompt。

由于现在还没有按照教程打开魔搭社区notebook,且我们只是要先下载baseline.ipynb文件看一眼,所以我在本地新建一个临时文件夹进行了clone:

git lfs install
git clone https://www.modelscope.cn/datasets/maochase/kolors.git

打开文件夹,找到baseline.ipynb文件并用notepad–等类似软件打开,在稍后面一点的位置,找到了八组prompt。去掉格式化字符”\n”等后内容如下:

8组prompt内容

图片1

二次元,一个紫色短发小女孩,在家中沙发上坐着,双手托着腮,很无聊,全身,粉色连衣裙

丑陋、变形、嘈杂、模糊、低对比度

图片2

二次元,日系动漫,演唱会的观众席,人山人海,一个紫色短发小女孩穿着粉色吊带漏肩连衣裙坐在演唱会的观众席,舞台上衣着华丽的歌星们在唱歌

丑陋、变形、嘈杂、模糊、低对比度

图片3

二次元,一个紫色短发小女孩穿着粉色吊带漏肩连衣裙坐在演唱会的观众席,露出憧憬的神情

丑陋、变形、嘈杂、模糊、低对比度,色情擦边

图片4

二次元,一个紫色短发小女孩穿着粉色吊带漏肩连衣裙,对着流星许愿,闭着眼睛,十指交叉,侧面

丑陋、变形、嘈杂、模糊、低对比度,扭曲的手指,多余的手指

图片5

二次元,一个紫色中等长度头发小女孩穿着粉色吊带漏肩连衣裙,在练习室练习唱歌

丑陋、变形、嘈杂、模糊、低对比度

图片6

二次元,一个紫色长发小女孩穿着粉色吊带漏肩连衣裙,在练习室练习唱歌,手持话筒

丑陋、变形、嘈杂、模糊、低对比度

图片7

二次元,紫色长发少女,穿着黑色连衣裙,试衣间,心情忐忑

丑陋、变形、嘈杂、模糊、低对比度

图片8

二次元,紫色长发少女,穿着黑色礼服,连衣裙,在台上唱歌

丑陋、变形、嘈杂、模糊、低对比度

我们再将大赛所给样例拿出来进行对照:

样例:偶像少女养成日记

这样一来,你是否理解了prompt、negative_prompt的作用呢?其中,negative_prompt为排除项,即negative_prompt中写上“多余的手指”,则AI生成的图片中就不会有多余的手指。

我们在写自己的prompt时,可以借助于一些prompt工具,如我使用的AI绘画提示词生成器,预览如下:

样例存在哪些问题?

大赛所给样例只是最基本的完成情况,我们可以挑一挑其中存在的问题,在我们自己作画时避免。

首先是故事讲述的问题,这是一个极其平淡的故事。

其次,整个画面风格有些奇怪。这应该也是由于prompt对风格要求较少导致的。

注意到图片4中negative_prompt多次出现手指

首先我们看到:图片4的prompt中有 十指交叉 的要求。而即使图片4negative_prompt中排除了扭曲的手指,多余的手指,AI绘制出来的手指也稍显奇怪。

为什么AI总是画不好人的手呢?这是一个非常经典的问题,了解这个问题可以让我们对AI绘画的原理理解更深。

其实原因很简单:

  1. 人手动作极多。人的手看似结构简单,只有五根指头,仿佛画好五根指头就可以了。然而,对于AI来说,人手做出的每个动作都是完全不同的。举个例子:我们先虚握拳头,让手围成一个圈,这是第一个动作;然后紧握拳头,这是第二个动作。对于人类来说,我们知道这两个动作是类似的,可能只是幅度的不同。然而对于AI来说,给它两张这样的动作图片,它将感到十分困惑:为什么手有时候是这样的,有时候是那样的?而这只是一个浅显的例子,在这个例子中,我们尚且可以告诉AI:第一个动作叫虚握,第二个动作叫实握,从而让AI理解。但是人手的动作实在太多了,我们既不可能给每个有细微差别的动作都起名,也不大可能准备如此海量的训练数据喂给AI。
  2. 人手空间变化多。人的手十分灵巧,关节很多、自由度很大,而我们训练生成图片的AI,使用的自然也是图片,这就导致一个问题:在照片中,当人手做出某个动作时,就可能有部分手指看不到,这就导致AI不清楚人类到底应该有几根手指。另一个严重的问题是使AI分不清楚各个手指的粗细关系。比如,同样是竖两根手指,比个正常的耶(✌),AI发现这两根手指差不多长。接着我们竖起来食指和小拇指,小拇指比食指短很多,AI就要变得困惑了。

那么,如果我们一直只喂给AI手完全打开的、正面的图片,AI是不是就不会画错了呢?

是的,但AI也就只能画出手完全打开的、正面的图片了。

  1. 人手的细节非常复杂,包括指甲、指纹、皮肤褶皱等,这些需要很高的算力才能画出。

同样的问题有时也会出现在AI试图绘画更小的特征时,比如耳朵,看起来像没有复杂软骨结构的肉质漩涡;或者牙齿,不正确地放在嘴里;或者瞳孔,变成了小山羊的斑点。AI可以掌握视觉模式,但无法掌握潜在的生物逻辑。

暂时地,AI还有很多东西都画不好。

文生图的历史

(点击展开介绍)

早期探索(20世纪60年代-20世纪90年代)

文生图的概念最早出现于计算机视觉和图像处理的早期研究中。

早期的图像生成技术主要依赖于规则和模板匹配,通过预定义的规则将文本转换为简单的图形。

然而,由于计算能力和算法的限制,这一阶段的技术能力非常有限,生成的图像质量较低,应用场景也非常有限。

基于统计模型的方法(2000年代)

进入2000年代,随着统计模型和机器学习技术的发展,文生图技术开始得到更多关注。

研究者们开始利用概率图模型和统计语言模型来生成图像。尽管这一阶段的技术在生成图像的多样性和质量上有了一定提升,但由于模型的复杂性和计算资源的限制,生成的图像仍然较为粗糙,不够逼真。

深度学习的崛起(2010年代)

2010年代是文生图技术发展的一个重要转折点。

随着深度学习,尤其是卷积神经网络(CNN)和生成对抗网络(GAN)的发展,文生图技术取得了突破性进展。2014年,Goodfellow等人提出的GAN模型通过生成器和判别器的对抗训练,极大地提升了图像生成的质量。随后,各类变种GAN模型被提出,如DCGAN、Pix2Pix等,使得文生图技术在生成逼真图像方面达到了前所未有的高度。

大规模预训练模型(2020年代)

进入2020年代,大规模预训练模型如OpenAI的CLIP、DALL-E以及Stable Diffusion等的出现,标志着文生图技术进入了一个新的时代。

CLIP通过大规模的文本和图像配对数据训练,能够理解和生成高度一致的文本和图像;DALL-E和Stable Diffusion进一步提升了生成图像的创意和细节表现能力,使得通过简单的文本描述生成高质量、复杂图像成为可能。

这些技术的应用范围从艺术创作、广告设计到辅助医疗诊断,展现了广泛的商业价值和社会影响力。

Task1 流程回顾

该部分内容详见Task1学习手册,以下仅作简要回顾。

开通阿里云PAI-DSW试用

报名赛事

在魔搭社区创建实例

如果 在魔搭无法授权 或 点击【打开】无法打开,可到阿里云控制台创建 & 打开实例

如果之前试用的额度已经过期,可使用魔搭的免费Notebook实例

跑代码!

下载baseline文件

git lfs install
git clone https://www.modelscope.cn/datasets/maochase/kolors.git

安装环境,然后重启kernel

打开baseline文件,运行第一块儿代码。

安装 Data-Juicer 和 DiffSynth-Studio

Data-Juicer:数据处理和转换工具,旨在简化数据的提取、转换和加载过程

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

然后重启kernel。最上方点击这个按钮。

认准鼠标悬停时显示 restart 的那个。

调整prompt

但不要运行这部分代码块儿。

依次顺序运行剩余的代码块

需要一个一个去点代码框左上角执行按钮。

为节省时间,以上两步可同时进行,即在运行前面的代码块儿同时修改prompt。

但要注意代码块儿执行需要按顺序执行。

代码块按照功能主要分成这几类:

  1. 使用Data-Juicer处理数据,整理训练数据文件
  2. 使用DiffSynth-Studio在基础模型上,使用前面整理好的数据文件进行训练微调
  3. 加载训练微调后的模型
  4. 使用微调后的模型,生成用户指定的prompt提示词的图片

上传结果

关闭实例

代码一个小提示

images = [np.array(Image.open("{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

这是最后一段代码,运行完这段代码后,将在主界面显示我们八张图片组成的故事。这是因为在python中,输入变量的名称回车,会返回变量的值。

而这段代码中,变量名称就是image。我们还可以在下面补上一句:

image.save("all.jpg")

这样它还会将我们的八图组成的图片保存为”all.jpg”。

部分代码解读

!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

我们上文已经提到,这部分是安装环境。

#下载数据集
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_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"])

#模型训练
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)

lora微调。

Stable Diffusion中的Lora(LoRA)模型是一种轻量级的微调方法,它代表了“Low-Rank Adaptation”,即低秩适应。Lora不是指单一的具体模型,而是指一类通过特定微调技术应用于基础模型的扩展应用。在Stable Diffusion这一文本到图像合成模型的框架下,Lora被用来对预训练好的大模型进行针对性优化,以实现对特定主题、风格或任务的精细化控制。

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")

图片生成(改变种子值重复8次)。

最后是我们上一节中提到的将8张图片合为一张。

思考

这个图片的风格能不能修改,能不能微调出来一个其他风格类型的图片?

点击展开

尽管比赛要求要不同的风格、我们也在注意不同风格的尝试,但经过不断的调整和观察对比大家的结果,我认为如果不对代码部分进行其它调整的话,这个图片的风格是不会改变的,即一直倾向于二次元风格。我认为这可能与训练数据有关。

而在上文代码解读中, 下载数据集 部分里也写明了我们训练的数据只有“二次元”。

结果展示

自己写了prompt

组成故事

无明显bug

效果满意

我的第一张图片是测试上文中提到的prompt生产工具,按照自己喜好选了几个prompt生成的。接下来7张图都是调整prompt试图生成一张《孤独摇滚》中的波奇。没有进行连贯故事的尝试,因为我还没想好故事。

画风崩坏还是挺严重的。

从效果上看,第6张与波奇最为接近,但同时第6张也是作画bug最大的一张:手臂不协调、手指数量不正确、吉他那儿有不明意义的大幅色块儿。

关于发卡:从一开始我就没指望AI能正确理解波奇的发卡长什么样子,但是画成这样还是让我很惊喜了。整体的氛围也很像《孤独摇滚》。

评论

评论区不是无人区喵