1.介绍AIGC与文生图
01 AIGC
AIGC(AI-Generated Content)是通过人工智能技术自动生成内容的生产方式,很早就有专家指出,AIGC将是未来人工智能的重点方向,也将改造相关行业和领域生产内容的方式。
02 了解AI生图的意义
对所有人来说,定期关注AI生图的最新能力情况都十分重要:
对于普通人来说,可以避免被常见的AI生图场景欺骗,偶尔也可以通过相关工具绘图
对于创作者来说,通过AI生图的工具可以提效,快速制作自己所需要的内容
对于技术人来说,了解AI生图的能力的玩法,可以更好地针对自己的业务进行开发和使用,甚至攻克难题开发更实用的工具
03 AI生图的历史
20世纪70年代,当时由艺术家哈罗德·科恩(Harold Cohen)发明AARON,可通过机械臂输出作画。
现代的AI生图模型大多基于深度神经网络基础上训练,最早可追溯到2012年吴恩达训练出的能生成“猫脸”的模型。
使用卷积神经网络(CNN)训练,证明了深度学习模型能够学习到图像的复杂特征。
2015年,谷歌推出了“深梦”(Deep Dream)图像生成工具,类似一个高级滤镜,可以基于给定的图片生成梦幻版图片
——2021 年 1 月 OpenAI 推出DALL-E模型(一个深度学习算法模型,是GPT-3 语言处理模型的一个衍生版本),能直接从文本提示“按需创造”风格多样的图形设计——
04 AI生图与人类作图的不同
目前大部分的模型,已经具备了去除 “AI味” 的能力,且可能存在容易误导他人的情况,这时候我们想辨别可能需要非常仔细地——
观察图片的细节。仔细检查人物的面部特征,尤其是眼睛和嘴巴
检查光线和阴影。分析图片中的光源是否一致,阴影的方向是否与光源相符,是否存在不自然的光线或阴影
分析像素。放大图片,寻找是否有模糊或像素化的部分。
注意背景。检查背景中是否有不协调的元素,比如物体边缘是否平滑,背景中是否有不自然的重复模式。
而这些细节上的AI特性,也许就是我们在某些特定场景下需要解决的挑战。
05 Deepfake技术
插入一段小常识,提醒大家警惕Deepfake技术
Deepfake是一种使用人工智能技术生成的伪造媒体,特别是视频和音频,它们看起来或听起来非常真实,但实际上是由计算机生成的。这种技术通常涉及到深度学习算法,特别是生成对抗网络(GANs),它们能够学习真实数据的特征,并生成新的、逼真的数据。
2.精读代码
1.安装与卸载依赖包
# 安装 Data-Juicer 和 DiffSynth-Studio
!pip install simple-aesthetics-predictor # 安装simple-aesthetics-predictor
!pip install -v -e data-juicer # 安装data-juicer
!pip uninstall pytorch-lightning -y # 卸载pytorch-lightning
!pip install peft lightning pandas torchvision # 安装 peft lightning pandas torchvision
!pip install -e DiffSynth-Studio # 安装DiffSynth-Studio
data-juicer
:这通常是一个用于数据处理和清洗的工具包,能帮助用户对数据进行各种操作以提高数据质量和可用性。
pytorch-lightning
:是一个基于 PyTorch 的轻量级高级接口,旨在简化深度学习模型的训练过程,提高代码的可复用性和可读性。
2.数据集下载和处理
# 从魔搭数据集中下载数据集AI-ModelScope/lowres_anime
from modelscope.msdatasets import MsDataset #引入数据集模块msdatasets
ds = MsDataset.load(
'AI-ModelScope/lowres_anime',
subset_name='default',
split='train',
cache_dir="/mnt/workspace/kolors/data" # 指定缓存目录
) # 从魔搭数据集中下载数据集AI-ModelScope/lowres_anime,赋值给参数ds
1.subset_name='default'
表示加载默认的子集
default就是默认的意思。
2.split='train'
在这段代码中,split='train'
的意思是指定要加载的数据集的分割部分为 “训练集”(train
)。通常在数据集的组织中,会将数据划分为不同的部分,比如训练集(train
)用于模型的训练,验证集(validation
)用于调整模型超参数,测试集(test
)用于评估模型的最终性能。这里通过指定 split='train'
,表明只加载这个数据集中用于训练的那一部分数据。
3.“cache_dir”
“cache directory” 的缩写,意思是 “缓存目录”
# 生成数据集
import json, os # 导入json和os模块
from data_juicer.utils.mm_utils import SpecialTokens # 导入SpecialTokens
from tqdm import tqdm # 导入tqdm进度条管理
os.makedirs("./data/lora_dataset/train", exist_ok=True) # 创建文件夹./data/lora_dataset/train
os.makedirs("./data/data-juicer/input", exist_ok=True) # 创建文件夹./data/data-juicer/input
with open("./data/data-juicer/input/metadata.jsonl", "w") as f:
for data_id, data in enumerate(tqdm(ds)): # 遍历数据集ds
image = data["image"].convert("RGB") # 将数据集的图片转换为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)) # 将索引数据写入文件./data/data-juicer/input/metadata.jsonl
f.write("\n")
1. for data_id, data in enumerate(tqdm(ds)):
ds是上一步我们加载的数据集最后被赋予的参数名。enumerate
函数用于将一个可遍历的数据对象(如列表、元组、字符串等)组合为一个索引序列,同时列出数据和数据的索引。在这个例子中,当遍历 ds
时,data_id
会依次表示每个元素的索引,而 data
则表示 ds
中的每个实际元素。
2.f.write(json.dumps(metadata))
:
将 metadata
这个字典对象转换为 JSON 格式的字符串,并将其写入文件 f
中。json.dumps()
函数用于将 Python 对象序列化为 JSON 格式的字符串。
3.f.write("\n")
:
在写入 JSON 字符串之后,再写入一个换行符 \n
。这样做的目的是为了在文件中区分每一行的元数据,使得文件中的每一行都是一个独立的 JSON 数据,便于后续的读取和处理。
# 配置data-juicer,并进行数据筛选过滤
# 配置过滤的规则
data_juicer_config = """
# global parameters
project_name: 'data-process' # 名称
dataset_path: './data/data-juicer/input/metadata.jsonl' # 你前面生成的数据的索引文件
np: 4 # 线程数
text_keys: 'text' # 文件./data/data-juicer/input/metadata.jsonl的描述的字段名
image_key: 'image' # 文件./data/data-juicer/input/metadata.jsonl的图片字段名
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 # 最小宽度1024
min_height: 1024 # 最小高度1024
any_or_all: any # 符合前面条件的图片才会被保留
- image_aspect_ratio_filter: # 图片长宽比过滤
min_ratio: 0.5 # 最小长宽比0.5
max_ratio: 2.0 # 最大长宽比2.0
any_or_all: any # 符合前面条件的图片才会被保留
"""
上述代码是一个 data_juicer
的配置文件(准确来说是定义配置内容,文件是在下面一步创建的)。 “configuration” (配置、布局、结构)
1.配置文件有什么用?与函数定义有什么区别?
这些参数通常是在程序中被定义和使用的,配置文件的作用是将这些可能会根据不同情况需要调整的参数集中起来,以一种方便修改和管理的方式呈现。在程序内部,会有相应的代码来读取配置文件中的这些参数值,并将其应用到函数或程序的运行逻辑中。这样做的好处是,不必每次需要更改参数时都去修改程序代码,只需要调整配置文件即可,提高了程序的灵活性和可维护性。
2.生成文件
注意export_path: './data/data-juicer/output/result.jsonl这行配置的意思是指定处理后的结果数据将被导出到 './data/data-juicer/output/result.jsonl'
这个路径的文件中。
# 保存data-juicer配置到data/data-juicer/data_juicer_config.yaml
with open("data/data-juicer/data_juicer_config.yaml", "w") as file:
file.write(data_juicer_config.strip())
# data-juicer开始执行数据筛选
!dj-process --config data/data-juicer/data_juicer_config.yaml
1.
前面定义了配置的内容,现在这段代码将其写入到指定路径的文件中,从而得到了 data/data-juicer/data_juicer_config.yaml
这个配置文件
2.
!dj-process --config data/data-juicer/data_juicer_config.yaml 这段代码的意思是执行 `dj-process` 这个命令或程序,并通过 `--config` 参数指定了配置文件的路径为 `data/data-juicer/data_juicer_config.yaml` 。 这意味着 `dj-process` 在运行时会读取这个指定的配置文件,按照其中设定的参数和规则来进行相应的数据处理操作。
3.数据处理和模型相关部分:
import pandas as pd
import os, json
from PIL import Image
from tqdm import tqdm
texts, file_names = [], []
os.makedirs("./data/data-juicer/output/images", exist_ok=True)
with open("./data/data-juicer/output/result.jsonl", "r") as f:
for line in tqdm(f):
metadata = json.loads(line)
texts.append(metadata["text"])
file_names.append(metadata["image"][0])
df = pd.DataFrame({"text": texts, "file_name": file_names})
df.to_csv("./data/data-juicer/output/result.csv", index=False)
df
1.df = pd.DataFrame({"text": texts, "file_name": file_names})
使用 pandas
创建一个 DataFrame
对象 df
,其中包含两列:text
列和 file_name
列,数据分别来自 texts
和 file_names
列表。
2.df.to_csv("./data/data-juicer/output/result.csv", index=False)
:将创建的 DataFrame
对象 df
保存为一个 CSV 文件,文件路径为 ./data/data-juicer/output/result.csv
。index=False
参数表示在保存的 CSV 文件中不包含索引列。
3.CSV文件是什么?
CSV(Comma-Separated Values,逗号分隔值)文件是一种常见的、用于存储表格数据的纯文本文件格式。在 CSV 文件中,每行代表一条记录,列与列之间的值通常用逗号分隔。但有时也会使用其他分隔符,如制表符(\t
)等。例如,以下是个简单的 CSV 文件示例:
name,age,city
Alice,25,New York
Bob,30,London
Charlie,28,Paris
4.在这段代码中,最后显示 df
对象的操作是直接在代码末尾写 df
这一行。
在 Python 的交互式环境(如 Jupyter Notebook 或 IPython )中,单独写一个变量名,会自动输出该变量的值。所以在这段代码的最后,直接写 df
就实现了显示这个 DataFrame
对象的效果。
from transformers import CLIPProcessor, CLIPModel
import torch
model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
images = [Image.open(img_path) for img_path in df["file_name"]]
inputs = processor(text=df["text"].tolist(), images=images, return_tensors="pt", padding=True)
outputs = model(**inputs)
logits_per_image = outputs.logits_per_image # this is the image-text similarity score
probs = logits_per_image.softmax(dim=1) # we can take the softmax to get the probabilities
probs
1.images = [Image.open(img_path) for img_path in df["file_name"]]
这行代码使用列表推导式创建了一个名为 `images` 的列表。 列表中的元素是通过遍历 `df` 数据框中 `file_name` 列的每个路径,并使用 `Image.open()` 函数打开对应的图像文件得到的 `Image` 对象。 简单来说,它将数据框中存储的图像文件路径转换为实际的图像对象,并将这些对象存储在 `images` 列表中,以便后续对图像进行处理和操作。
2.img_path在文件df["file_name"]中,溯源。
df["file_name"]的信息来自metadata["image"][0],这个又来自/data/data-juicer/output/result.jsonl,这个又来自/data/data-juicer/input/metadata.jsonl,这个来自metadata = {"text": "二次元", "image": [f"/mnt/workspace/kolors/data/lora_dataset/train/{data_id}.jpg"]} f.write(json.dumps(metadata))这一块代码。
3.
outputs = model(**inputs)
:将之前准备好的输入数据 inputs
传递给模型 model
进行计算,并将得到的输出结果存储在 outputs
中。
logits_per_image = outputs.logits_per_image
:从 outputs
中提取出与图像 - 文本相似度相关的得分,并将其存储在 logits_per_image
中。
probs = logits_per_image.softmax(dim=1)
:对 logits_per_image
沿着维度 1 应用 softmax
函数,将得分转换为概率,并将结果存储在 probs
中。
from torch.utils.data import Dataset, DataLoader
class CustomDataset(Dataset):
def __init__(self, df, processor):
self.texts = df["text"].tolist()
self.images = [Image.open(img_path) for img_path in df["file_name"]]
self.processor = processor
def __len__(self):
return len(self.texts)
def __getitem__(self, idx):
inputs = self.processor(text=self.texts[idx], images=self.images[idx], return_tensors="pt", padding=True)
return inputs
dataset = CustomDataset(df, processor)
dataloader = DataLoader(dataset, batch_size=8)
1.继承
这段代码定义了一个名为 CustomDataset
的自定义数据集类,继承自 Dataset
类。
2.`__getitem__` 方法
用于根据给定的索引 `idx` 从数据集中获取单个样本。 在这个方法中: - 首先使用传入的 `processor` 对当前索引对应的文本 `self.texts[idx]` 和图像 `self.images[idx]` 进行处理。处理时指定了返回的张量类型为 `pt`(可能表示 PyTorch 张量),并且设置了填充 `padding=True` 。 - 然后将处理得到的结果 `inputs` 直接返回。 这样,当在后续的代码中通过索引访问这个数据集时,就能够得到经过处理的、对应索引位置的输入数据,以便用于模型的训练、验证或测试等操作。
dataset = CustomDataset(df, processor)
dataloader = DataLoader(dataset, batch_size=8)
1.首先,创建了一个 CustomDataset
类的对象 dataset
,将 df
(包含数据的 DataFrame
)和 processor
(用于处理数据的对象)作为参数传递给它,从而初始化了这个自定义的数据集。
2.然后,使用 DataLoader
类来创建一个数据加载器 dataloader
。将前面创建的 dataset
作为数据来源,并设置了 batch_size=8
,这意味着在每次迭代中,数据加载器将从数据集中取出 8 个样本作为一个批次返回。
for batch in dataloader:
outputs = model(**batch)
logits_per_image = outputs.logits_per_image
probs = logits_per_image.softmax(dim=1)
print(probs)
这段代码使用前面创建的数据加载器 dataloader
进行迭代。在每次迭代中,获取一个批次的数据 batch
,然后将这个批次的数据传递给模型 model
进行计算,得到输出 outputs
。接着,从输出中提取与图像 - 文本相似度相关的得分 logits_per_image
。再对得分应用 softmax
函数沿维度 1 进行计算,得到概率 probs
。最后,打印出计算得到的概率 probs
。通过这样的循环,可以对数据集中的数据按批次进行处理和计算,并查看每一批次计算得到的概率结果。
4.图像生成部分:
import torch
from diffusers import StableDiffusionPipeline
torch.manual_seed(1)
pipe = StableDiffusionPipeline.from_pretrained("CompVis/stable-diffusion-v-1-4", torch_dtype=torch.float16)
pipe = pipe.to("cuda")
prompt = "二次元,一个紫色长发小女孩穿着粉色吊带漏肩连衣裙,在练习室练习唱歌,手持话筒"
negative_prompt = "丑陋、变形、嘈杂、模糊、低对比度"
guidance_scale = 4
num_inference_steps = 50
image = pipe(
prompt=prompt,
negative_prompt=negative_prompt,
guidance_scale=guidance_scale,
num_inference_steps=num_inference_steps,
height=1024,
width=1024,
).images[0]
image.save("example_image.png")
image
1 `torch.manual_seed(1)`
设置了随机数种子为 1,以确保结果的可重复性。
2.
从预训练的模型 `CompVis/stable-diffusion-v-1-4` 加载了 `StableDiffusionPipeline` ,并指定数据类型为 `torch.float16` ,然后将模型移动到 `cuda` 设备上,以便利用 GPU 进行加速计算。
3.
之后,定义了生成图像的提示 `prompt` 为“二次元,一个紫色长发小女孩穿着粉色吊带漏肩连衣裙,在练习室练习唱歌,手持话筒”,以及负面提示 `negative_prompt` 为“丑陋、变形、嘈杂、模糊、低对比度”。还设置了指导缩放 `guidance_scale` 为 4 和推理步骤数 `num_inference_steps` 为 50 ,以及生成图像的高度和宽度分别为 1024 。 通过调用 `pipe` 函数,并传入上述参数,进行图像生成。生成的图像结果存储在一个列表中,通过 `.images[0]` 取出第一个图像,并将其赋值给 `image` 变量。 最后,使用 `image.save("example_image.png")` 将生成的图像保存为 `example_image.png` 文件,并在最后显示了 `image` 对象。
from PIL import Image
torch.manual_seed(1)
image = pipe(
prompt="二次元,日系动漫,演唱会的观众席,人山人海,一个紫色短发小女孩穿着粉色吊带漏肩连衣裙坐在演唱会的观众席,舞台上衣着华丽的歌星们在唱歌",
negative_prompt="丑陋、变形、嘈杂、模糊、低对比度",
cfg_scale=4,
num_inference_steps=50, height=1024, width=1024,
)
image.save("1.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
这段代码的主要作用是读取多个图像,将它们组合成一个大的图像,并进行尺寸调整。
1.images = [np.array(Image.open(f"{i}.jpg")) for i in range(1, 9)]
这行代码使用列表推导式读取 8 个名为 1.jpg
到 8.jpg
的图像,并将每个图像转换为 numpy
数组,然后将这些数组存储在 images
列表中。
2.image = np.concatenate(...)
这里将 images
列表中的图像数组进行组合。首先在水平方向(axis=1
)上两两组合,然后在垂直方向(axis=0
)上依次拼接,形成一个大的图像数组。
3.
最后,将组合后的 numpy
数组转换回 PIL
的 Image
对象,并将其尺寸调整为 (1024, 2048)
,并返回这个调整后的 Image
对象。
3.卷积知识的一点补充
阶层型神经网络
深度学习,顾名思义,是叠加了很多层的神经网络。叠加层有各种各样的方法,其中著名的是卷积神经网络。