强烈推荐,原文地址: Model入门学习 更舒服的排版和阅读
如果你不是很了解计算机和数学,但想要了解AI合成图片的工作流程和大致原理,这篇文章也许可以帮到你。 如果你已经熟悉概念,更多的内容可以参考: 数学层面的解读可参考深入学习: Model 原理解析 完整的代码复现可参考 Model代码实践概要
本文主要科普一个问题,一张噪声的图片,如何在DM模型下,合成人类理解的图片?
2022年10月,以 , 为代表的DM原理模型的产品从专业领域走进公众视野,此前的等DM模型产品更多的停留在大实验室和大型公司里。
随着SD,的大火,其背后的DM模型也受到人们的关注。对比人类绘画的过程,AI学会图片合成需要几个关键的条件,一是理解需求,二是根据需求掌握在不同需求下”图片合成”这一动作。
理解需求的关键就是把人类绘画过程中用到的知识,条件,绘画的主题等用数据化来表示。
掌握”图片合成”这一动作的关键则是我们要用函数化来表式动作的步骤和原理,因为计算机只有明确的步骤,才能下达命令,从而完成我们预期它执行的事。
因此文章会先铺垫”数据化”和”函数化”这两个概念和原理,接着以DM模型为例,拆解其原理,试图为读者展示一个清晰的图片合成全流程。
不足或问题之处,还请指正。
是什么?
扩散模型(DM),从字面上理解就是,像分子运动一样,一点点改变(放到图像里就是,最开始噪声一般的图像,它的像素值一点点改变,或者说叫”运动”,直到最后改变成了有意义的图像)。它能够通过噪音合成图画,就像下面这样:
输入随机生成了噪声图片如下
(图1) – 实际的噪声更加随机,看不出来什么模式
扩散模型通过对噪声做运动得到下面的图片
(图2)
DM模型有两部分构成:学习过程,推理过程,上面的去噪过程就是推理过程。
人类绘画过程
要了解DM合成图像是什么,怎么做到的,我们先看一下人类通常的作画流程,当人类开始进行艺术创作时,拿绘画举例,我们通常大致可以归类为以下几个步骤:
1. 确立主题:这一阶段,我们会收集绘画想表达的内容,中心思想,或者说绘画本身的目的,再具体的说,这一阶段我们会去和绘画的需求方确认创作的这幅画的尺寸,主题,背景,需求方的目的,应用的场景等等。
2. 细化需求:确立了主题,接下来就是细化需求,这一阶段我们可能需要收集内容的相关的素材,相关的文化知识,背景信息,画风等等来辅助我们创作。
3. 绘制草稿:细化了需求,接下来可能会找一些参考图,进行一些草图,线稿,素描等创作。
4. 完成绘图:最后,在草稿的基础上根据需求完成整体的构图,绘画,最终交付。
如果用类似公式用来表达人类的绘画,我们大概可以写出如下表达式:
y_{img}=f(B, D, E)
B:代表-背景,即这幅画的主题,表达的东西,背后蕴含的故事等等。
D:代表-需求,即我们在”确立主题”阶段裁定的这副画的应用场景,需求方相关要求等等。
E:代表-表达,即绘画者自我对于做画的理解和表达,包括技法上的输出,如油画,水彩,素描,厚涂等。
绘画者根据自己掌握的创作背景B,需求D以及表达E,再通过绘画技巧 f 完成绘画。
DM合成图片过程
如果想用机器模拟人类绘画过程,我们可以把机器合成图片流程也分为 1. 确立主题+需求 2. 绘制草稿 3. 完成绘画
类似的三个阶段;
但是,现阶段的机器不是人类,无法理解人类的思考过程,人类的语言,机器应该怎么表达”确立主题+需求”、”绘制草稿”和”完成绘画”这几个事呢? 解决这几个问题,我们需要先搞清”数据化”和”函数化”。 确立主题+需求本质上对计算机来说就是把人类的东西用数据表达出来,即”数据化”。 而绘画这一行为,对应到计算机,就是一个函数,只要我们能用一个清晰明确流程的函数表达出来,计算机就能执行,即”函数化”。
数据化
如果想用计算机的模型计算上面的东西,最重要的一个步骤就是”数据化”,将一切东西数据化表达,只有数据,计算机才认识,才能计算。
首先是输出 y_{img} 图像,在计算机,图像是以矩阵的形式存储
img = [[[123,123,123], [123,123,123], ..., [123,123,123]],
..................................................
[[123,123,123], [123,123,123], ..., [123,123,123]]]
其次,如何将各种输入即B,D,E用数据表达?
一个最简单的桥梁就是,文字。
在我们进行绘画时,我们想要对绘画内容的一切表达其实都可以用文字来进行替代,不管是绘画的技巧还是绘画的内容,又或者想要表达的内涵。
在计算机的底层,文字是以诸如”0001 0000″类似的二进制编码的形式存储;往上抽象,我们可以用向量/张量的形式来表达文字,这种技术有很多,如,等,其本质都是用一个向量空间,存储,表达文字。
至此,我们可以将B,D,E和文字建立关系,而文字可以通过文本向量化的技术转化成可以计算的数字(通常是张量)。
另一个直观能想到的数据化表达方式就是,图像。
一个原因是图像本身在计算机就是矩阵存储,而这种数据结构,其潜在的向量空间包含了图像上的结构信息,比较利于模型的学习。
其次是因为文字能表达的内涵固然丰富,但是,在大多数时候,我们需要表达准确,范围小,在专业上,我们说希望这个信息熵足够小,即给绘画者传递的信息足够准确,确保双方的理解一致。
因此,我们在需求沟通或者自己画图时,也常常用图像来做沟通和参考,图片本身包含的信息丰富,在传递时,给画师的信息熵相对文字也足够小。
比如我们需要一个戴着棒球帽的狗狗,我们可以用以下图片传递信息
对于文字来说,我们可能需要许多文字来减少双发理解的误差,比如要告诉对方“狗狗是萨摩耶,白白的,戴着蓝色的棒球帽”等等,即便如此,但看文字,双方依然可能产生许多理解偏差。
函数化
光有了数据化还不够,我们还需要找到一个函数,让它能把我们的输入,转化成我们想要的输出 y_{img} 图片。
这个函数背后它其实象征的是一种模式,一种变化,即把我们输入的数据 x 转化成输出 y ,我们想要的其实就是这种变化,我们需要把这种变化用数学公式表达出来,这样,在下次输入类似的 x ,他就可以按照这种变化,给到我们想要的 y 。
那么,我们应该怎么找到这个模式呢?
这里就引入大名鼎鼎的神经网络和深度学习。
所谓的神经网络,其实都是由简单的神经单元构成,而简单的神经单元,用数学公式表示的话,最基础,最简单的就是这样一个线性函数公式 y=ax+b 有了这个简单的神经单元我们可以拟合一些数据的表现,比如下面这个图。
图3
我们用了y=ax+b 这样一个公式,表达了上面x和y的关系。
尽管公式给到的 hat y (图中红色线段上的值)和实际分布点的 y (图中蓝色的点)有差距,但是差距不大,所以我们认为这个公式(这条红色线)表达了这组数据的模式(学会了这组数据的模式)。
那么又怎么通过这组数据(图中蓝色的点)找到这个函数(红色的直线)呢?
这就是深度学习(后文图片用”参数迭代器”称呼这个过程)做的事情了。
专业上,不断做一个反向传播/梯度下降的过程,计算机就可以找到最符合 x , y 关系的这么一个表达式;通俗简单的说,计算机通过不断的调整a,b参数,然后计算实际的 y 和预测的 hat{y} 之间的差值越小,当找到一组a,b使得两个y之间差值最小,计算机就完成这个深度学习。
这里举的例子是一个最基础最简单的神经单元的数学表达,这样一个 y=ax+b 的简单的公式只能拟合一些简单的数据。
实际上,一个真正的大型的神经网络里面有千万到百亿级这样的基础神经单元,当基础神经单元的规模达到这个级别,计算机可以通过不断调整这些单元的参数,也就是刚刚公式里面的 a 和 b ,模拟出复杂的数据分布。
数据化+函数化
有了把抽象的B,D,E变成数据化的能力,以及”深度学习”数据模式的能力,那绘画理论上我们可以用算法学习了!
我们只需要把大量的输入B,D,E变成数据和我们对应想要的数据化后的图片关联起来,在搭建一个很大很大的神经网络,让他通过”深度学习”,不就能学到我们想要的”绘画”能力了吗?
没错,不管是在2016年大火的GAN模型和至今文章所讨论的DM模型,他们本质上都是:
给定输出y(数据化的图片、文本等)
给定输入x(数据化的图片、文本等)
然后通过神经网络和深度学习建立两者的模式。
但是,不同的神经网络结构和学习方式会导致不同的效果
这里面会涉及许多技术上和数学上的问题,这些问题可能导致神经网络不能学习到你想要的效果,大致分为以下:
– 问题无解: x 和 y 之间根本无法找到解,即它们映射空间过于复杂,或者现有计算机的神经网络不足以支持它的模式。
– 找到的模式不符合预期:有的可能是尽管它学习到了一些模式,但这些模式并不是我们期望的。
– 找到的模式过于死板:有的则是它过于死板,不会变通,就比如学习图片生成,有的模型可能到最后成了”复制图片”,完全没有学习到”过程”本身。
我很想告诉各位读者,图像合成这一目标正确高效的模型结构和思路,怎么利用深度学习找到 y_{img}=f(B,D,E) 我们想要的函数映射,或者说数据模式。
但很遗憾截至到目前人类还没办法总结出,什么样的神经网络结构才是真正正确的,高效的,最符合预期的模型。
不过DM模型,无疑是截至2022年,在图像合成最棒的模型结构和解法。 它通过几个巧妙的想法,解决了上面说的三个问题:
Q1:用噪声图片生成图片?但是SD等产品不是输入文本或图片吗?图解DM两大过程
知道了神经网络和深度学习的大概原理,以及DM学习的目标。让我们来绘制一张DM模型真实的运作图来理解。
学习过程
上传图片
1. 首先我们拿到要学习的图片 I
2. 然后用固定的方法添加一个噪声 N ,并把这个噪声 N 保存下来
3. 把噪声 N 扔给我们的神经网络,神经网络会返回一个同尺寸的噪声 PN
4. 比较神经网络预测的噪声 PN 和 N 数学尺度上的”差距” ,这个差距我们记为 D
5. 把这个差距 D 扔给一个迭代器,它会告诉神经网络应该怎么调整它里面众多的神经参数来缩小 N 和 PN的差距。
6. 最后重复不断这个过程,直到 D 的值足够小,我们就认为神经网络学会我们期望的运动方式啦!
Q2:噪声的生成应该用什么方法(公式)生成呢?
Q3: 为什么不直接预测图片,要预测噪声呢?推理过程(运动过程)
首先我们随机生成一个噪声 RD把 RD 喂给已经学习好的 神经网络得到神经网络给出的噪声 PD用原始的噪声 RD根据 PD做运动,得到预测的图片I。这里的运动可以简单理解为原始噪声RD的数值减去预测噪声PD。实际上是做了一些数学变化,而非简单的加减。判断做完运动得到的预测I是否符合我们的预期,如果符合,那就完成预测啦!否则继续6如果运动还不够,则刚刚得到的预测图片I成为新的噪声RD,进行下一轮运动,直到得到我们需要的图片。 Q4:为什么要一点一点运动,神经网络不能学会一次运动到位吗?
这便是DM模型的所有概念啦。
Q&AQ1:用噪声图片生成图片?但是SD等产品不是输入文本或图片吗?
我们在主流产品如 上输入的文字,其实是因为他们做了一层”加工”,把你的文本通过clip等语言处理模块(函数)转化成了噪声图片x,然后再拿着这个噪声输入到dm”函数”。这个具体的转化过程可以搜索clip,等关键词去了解具体的工作原理。
Q2:噪声的生成应该用什么方法(公式)生成呢?
这里先直接给出 q(X_t|X_0)=N(X_t; sqrt{bar a}x_0, (1-bar a)I)
N : 表示这是一个正态分布,其他的符合是正态分布的参数
x_0 :我们输入的原始图片。
bar alpha :我们设定的参数,控制运动的节奏。
I :单位矩阵,这里表示正态分布的方差为1. 噪声的生成,实际是用到了正态分布,更详细的解读请参考另一篇文章深入学习: Model 原理解析
Q3:为什么不直接预测图片,要预测噪声呢?
因为比起学习图片的分布,图片的”运动”更好归纳总结和学习。直接学习图片的分布,要么现有的网络结构和计算能力不能支撑,要不就是学习过头,成了复制图片。
Q4:为什么要一点一点运动,神经网络不能学会一次运动到位吗?
数学层面的原因请参考深入学习: Model 原理解析这篇文章中的逆向过程推导,因为我们的加噪过程是一个马尔科夫链的正态分布,直接一步到位在数学上目前做不到。 通俗的理解有原因可以同Q3,可以理解为我们没有这么强大的计算能力一步到位。另外我们也提到过,把计算机绘制图片的过程转换成运动实际上具有连续性,我们每一个当前状态的噪音都是一点一点改变,我们学习的是在不同状态下,像素”运动的模式”。
Q其他:待补充附录加噪代码
注意这个不是DM真实加噪代码,只是近似。这是一个正态分布的加噪算法,用于生成本文的图片1,2
import numpy as np
import os
import cv2
def gaussian_noise(img, mean, sigma):
'''
此函数用将产生的高斯噪声加到图片上
传入:
img : 原图
mean : 均值
sigma : 标准差
返回:
gaussian_out : 噪声处理后的图片
noise : 对应的噪声
'''
# 将图片灰度标准化
img = img / 255
# 产生高斯 noise
noise = np.random.normal(mean, sigma, img.shape)
# 将噪声和图片叠加
gaussian_out = img + noise
# 将超过 1 的置 1,低于 0 的置 0
gaussian_out = np.clip(gaussian_out, 0, 1)
# 将图片灰度范围的恢复为 0-255
gaussian_out = np.uint8(gaussian_out*255)
# 将噪声范围搞为 0-255
# noise = np.uint8(noise*255)
return gaussian_out, noise # 这里也会返回噪声,注意返回值
img = cv2.imread("./2021122-95416.jpeg")
img, noise = gaussian_noise(img, 0, 1)
cv2.imshow('img', img)
cv2.waitKey(0)
线性拟合
用于文章生成图片用到的代码
# 参考: https://blog.csdn.net/zbp_12138/article/details/113939744
def linear_plot():
import numpy as np
import matplotlib.pyplot as plt
"""
参数:无
返回:
w -- 自变量系数, 保留两位小数
b -- 截距项, 保留两位小数
fig -- matplotlib 绘图对象
"""
data = [[5.06, 5.79], [4.92, 6.61], [4.67, 5.48], [4.54, 6.11], [4.26, 6.39],
[4.07, 4.81], [4.01, 4.16], [4.01, 5.55], [3.66, 5.05], [3.43, 4.34],
[3.12, 3.24], [3.02, 4.80], [2.87, 4.01], [2.64, 3.17], [2.48, 1.61],
[2.48, 2.62], [2.02, 2.50], [1.95, 3.59], [1.79, 1.49], [1.54, 2.10], ]
### TODO: 线性拟合计算参数 ###
SumXiYi = 0
SumXi = 0
SumYi = 0
SumXi2 = 0
PointX = []
PointY = []
for item in range(len(data)):
XiYi = data[item][0] * data[item][1]
SumXiYi += XiYi
SumXi += data[item][0]
SumYi += data[item][1]
SumXi2 += data[item][0] * data[item][0]
PointX.append(data[item][0])
PointY.append(data[item][1])
w = (len(data) * SumXiYi - SumXi * SumYi) / (len(data) * SumXi2 - SumXi * SumXi)
b = (SumXi2 * SumYi - SumXiYi * SumXi) / (len(data) * SumXi2 - SumXi * SumXi)
w = round(w,2)
b = round(b,2)
X = np.arange(0.5, 6, 0.01)
Y = w * X + b
plt.plot(X, Y, color='red')
plt.scatter(PointX, PointY, color='blue') # 散点图
plt.show()
return w, b, fig # 务必按此顺序返回
if __name__=="__main__":
linear_plot()