22

如何用一句话凭空生成一幅画

 2 years ago
source link: https://mp.weixin.qq.com/s?__biz=MjM5ODkzMzMwMQ%3D%3D&%3Bmid=2650429918&%3Bidx=2&%3Bsn=c8d851627dd0a7e313e7d74817f2d7e7
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

如何用一句话凭空生成一幅画

AINLP 2022-04-11 10:10

The following article is from Social Listening与文本挖掘 Author Scottish Fold Cats

640?wx_fmt=jpeg

这两天被OpenAI的DALL.E 2刷屏了,它的文本到图像生成程序,因为根据该模型生成的图像的颗粒度非常细致、逼真,完全达到了商业可用的级别。

下面看看一些由它生成的示例,效果十分惊艳!

一位宇航员骑着一匹白马:

640?wx_fmt=png

戴着贝雷帽和黑色高领毛衣的柴犬:

640?wx_fmt=png

一碗看起来像怪物的汤,它用毛线织成的:

640?wx_fmt=png

泰迪熊作为疯狂的科学家在混合闪光的化学品,蒸汽朋克风格:

640?wx_fmt=png

看了上面栩栩如生的“画作”,是不是也想动手来一幅?网上类似的开源代码很多,但是都太复杂,安装环境也比较麻烦。鉴于此,我们来点简单的,几十行代码来操作。

这个操作很简单:

我们要做的是使用CLIP对输入的提示文本进行语义表示提取,生成一个embedding,然后持续迭代地一个512x512像素的网格化图像,然后使该网格化图像无限逼近匹配提示文本的embedding。

CLIP 是openAI 在去年年初发布的一个多模态模型,能够从自然语言标注数据中学到有价值的视觉概念,并且和GPT-2/3一样拥有zero-shot的能力。它旨在将相似的图像和文本对嵌入到相似的向量空间中(例如,一个苹果的照片将与文本 "苹果 "相似)。通过在原始像素数据和它的嵌入上使用梯度下降,我们可以使它与任何指定的文本嵌入相匹配。

简而言之,笔者接下来要做的是:

  1. 对一些文本提示(也就是我们想要生成的图像的文字描述)进行编码,作为我们的目标向量

  2. 创建一个随机初始化的图像,然后将该图像的编码向量与我们的目标进行比较

  3. 计算误差,并使用torch的optimizer进行反向传播计算,减少提示文本嵌入和图像嵌入之间的相似度误差

好了,让我进行实战coding环节!

首先安装clip官方库:

pip install git+https://github.com/openai/CLIP

加载必要的库:

from tqdm.notebook import trange   import torch   from torchvision import transforms   import clip

参数设定:

device='cuda'       # 使用显卡cutn =  16          # 图像增强的数量shape = (256, 256)  # 图像的大小,即图像的高度和宽度lr = 0.03           # 学习率steps = 600          # 模型运行的步数,即我们将改进多少次图像clip_model = "Chinese_ViT-B_32"   # 中文版模型prompt = "奇幻森林"   # 提示语

值得注意的是上面的cutn参数,它表示我们要向clip模型展示多少个输入图像的增强部分,最好大于2,它将创造一些看起来像噪音但对嵌入相似性有好处的东西。

模型部分:

image=torch.rand((1, 3, shape[0], shape[1]), device=device, requires_grad=True)opt=torch.optim.Adam((image,),lr)f=transforms.Compose([lambda x:torch.clamp((x+1)/2,min=0,max=1),transforms.RandomAffine(degrees=60, translate=(0.1, 0.1)),transforms.RandomGrayscale(p=0.2),                      transforms.Lambda(lambda x: x + torch.randn_like(x) * 0.01),transforms.Resize(224)])m=clip.load(clip_model, jit=False)[0].eval().requires_grad_(False).to(device)embedding=m.encode_text(clip.tokenize(prompt).to(device))def total_variation_loss(img):    yv = torch.pow(img[:,:,1:,:]-img[:,:,:-1,:], 2).sum()    xv = torch.pow(img[:,:,:,1:]-img[:,:,:,:-1], 2).sum()    return (yv+xv)/(1*3*shape[0]*shape[1])def spherical_distance_loss(x, y):    return (torch.nn.functional.normalize(x, dim=-1) - torch.nn.functional.normalize(y, dim=-1)).norm(dim=-1).div(2).arcsin().pow(2).mul(2).mean()

前两行定义了我们的image图像和我们的优化器optimizer.。

下一行创建了将增强我们的图像的东西,使用Torchvision进行转换。

接下来我们下载/加载CLIP模型,并创建提升文本的嵌入。

最后两个方法用来计算损失:第一个方法告诉我们我们的图像有多 "丝滑(Smooth)",下一个函数告诉我们图像表示离文本嵌入到底有多远。平滑度(Smooth)是必要的,因为否则图像可能看起来太像噪音了,它将更有可能试图搞乱CLIP的生成结果,以创建一个嘈杂但高分的图像,而不是一个“丝滑”的自然高分辨率图像。

开始实际的图像生成:

for i in trange(steps):    opt.zero_grad()    clip_in = m.encode_image(torch.cat([f(image.add(1).div(2)) for _ in range(cutn)]))    loss = spherical_distance_loss(clip_in, embedding.unsqueeze(0)) + (image - image.clamp(-1, 1)).pow(2).mean()/2 + total_variation_loss(image)    loss.backward()    opt.step()

第1行创建了一个循环,重复后面的每一段代码,无论我们提供多少步(使用 trange 而不是 range,控制台中会显示一个进度条,以便我们实时看到模型生成进度~)。

第2行 为优化器在训练循环中积累和应用梯度的方式提供了更多的灵活度。

第3行将图像表示到与clip生成提示文本表示投射到同一向量空间中,因此我们可以在下一行中对它们进行比较......在这里对它们进行相似度比较。另外,在下一行,我们添加了一个 "范围损失(Range Loss)"(确保图像处于我们需要的数值中)和总变化损失,这与我们的 "平滑度 "相反,任何高的数值都会受到这个惩罚。

最后两行使用 Adam optimizer来改变图像的方式,使其损失下降。

图像可视化呈现:

transforms.ToPILImage()(image.squeeze(0).clamp(-1,1)/2+.5)
640?wx_fmt=png

看起来,这个效果太拉胯!

我们需要换个高级点的操作~

基于Pyramid的Text2image 2.0版本

Image Pyramid(图像金字塔)实际上将图片表示为一系列不同频率的图像,这些图像保留了不同的细节信息,因为这个特性,又叫多分辨率或者多尺度。它同时利用低层特征高分辨率和高层特征的高语义信息,通过融合这些不同层的特征达到较好的预测效果。

import torchimport torchvision.transforms as transformsfrom tqdm.notebook import trangeimport clipimport sysfrom pytorch_lamb import Lamb, log_lamb_rsdevice = 'cuda'cutn = 96prompt = "奇幻森林"class Pyramid:    def __init__(self, sizes, device='cpu', interpolation=transforms.InterpolationMode.BILINEAR):        self.layers = []        self.weights = []        self.activation = torch.nn.Tanh()        self.sizes = sizes        for i in sizes:            self.layers.append(torch.randn((1,3,i,i), device=device, requires_grad=True))            self.weights.append(torch.rand((1,), device=device, requires_grad=True))        self.fix = transforms.Resize(sizes[-1], interpolation)    def flatten(self):        return self.activation(torch.cat([self.fix(self.layers[i]).unsqueeze(0)*self.weights[i] for i in range(len(self.layers))], dim=0).sum(0)/len(self.layers))    def to_pil(self):        return transforms.ToPILImage()(self.flatten().squeeze(0).clamp(-1,1)/2+0.5)class RandomRandomCrop(torch.nn.Module):  def __init__(self, min_size, max_size):    super().__init__()    self.min_size = min_size    self.max_size = max_size  def forward(self, x):    import random    return transforms.RandomCrop(random.randint(self.min_size, self.max_size))(x)model = clip.load("Chinese_ViT-B_32", jit=False)[0].float().to(device)image = Pyramid((1,2,4,8,12,16,20,24,28,32,36,40,48,64,68,72,96,100,128,132,136,140,144,148,152,157,192,224,256,320,384,448,512,784,1024), device=device)augment = transforms.Compose([RandomRandomCrop(int(image.sizes[-1]*0.3),image.sizes[-1]),transforms.RandomRotation(30),transforms.Resize(model.visual.input_resolution),lambda x: x+torch.randn_like(x)*0.01])optimizer = Lamb(image.layers, lr=0.05, weight_decay= 0.01, betas=(.9, .999), adam= 'lamb')encoding = model.encode_text(clip.tokenize(prompt).to(device))def total_variation_loss(img):  return (torch.pow(img[:,:,1:,:]-img[:,:,:-1,:], 2).sum()+torch.pow(img[:,:,:,1:]-img[:,:,:,:-1], 2).sum())/(1*3*image.sizes[-1]*image.sizes[-1])def spherical_distance_loss(x, y):  return (torch.nn.functional.normalize(x, dim=-1)-torch.nn.functional.normalize(y, dim=-1)).norm(dim=-1).div(2).arcsin().pow(2).mul(2).mean()def run(encoding, steps):  for i in trange(steps):    optimizer.zero_grad()    render = image.flatten()    img = model.encode_image(torch.cat([augment(render) for cut in range(cutn)], dim=0))    loss = spherical_distance_loss(img, encoding) + total_variation_loss(render) + (render-render.clamp(-1,1)).pow(2).mean()    loss.backward(retain_graph=True)     torch.nn.utils.clip_grad_norm_(image.layers, 1.0)    optimizer.step()    if i%10==0:      print(loss.detach().item())      image.to_pil().save("output.png")  image2 = image.to_pil()  image2.save("output.png")  return image2painting = run(model.encode_text(clip.tokenize( prompt).to(device))*0.5+encoding*0.5, 250)

最终生成的“奇幻森林”:

640?wx_fmt=png

是不是效果好太多了!

下面是其他提示语生成的图像,大家来欣赏欣赏~

“深海中绽放的玫瑰花”: 

640?wx_fmt=png

“绢画风格-沙漠中的绿洲”:

640?wx_fmt=png

“油画风格-沙漠中的绿洲”:

640?wx_fmt=png

“吉卜力画风-沙漠中的绿洲”:

640?wx_fmt=png

写在最后

这些生成的效果当然不是最好的,使用其他更复杂的算法,比如VGGAN+dual CLIP、基于Latent Diffusion的图像合成等,加上海量的图文数据对进行训练,效果会十分逼真,比如这些:

“国画风格-深山藏古寺”

640?wx_fmt=png

“深夜,一大群人在抢菜”

640?wx_fmt=png

“梵高画风 - 一大群人在晚上疯狂抢菜”

640?wx_fmt=png

“盛夏午后,一缕微光透过茂密的原始森林,照射到地上”

640?wx_fmt=png

“月光如流水一般,静静地泻在这一片叶子和花上。薄薄的青雾浮起在荷塘里。叶子和花仿佛在牛乳中洗过一样;又像笼着轻纱的梦...”

640?wx_fmt=png

“国画风格 --- 巴黎圣母院”

640?wx_fmt=png
0?wx_fmt=png
AINLP
一个有趣有AI的自然语言处理公众号:关注AI、NLP、机器学习、推荐系统、计算广告等相关技术。公众号可直接对话双语聊天机器人,尝试自动对联、作诗机、藏头诗生成器,调戏夸夸机器人、彩虹屁生成器,使用中英翻译,查询相似词,测试NLP相关工具包。
343篇原创内容
Official Account
进技术交流群请添加AINLP小助手微信(id: ainlper)
请备注具体方向+所用到的相关技术点
640?wx_fmt=jpeg

关于AINLP

AINLP 是一个有趣有AI的自然语言处理社区,专注于 AI、NLP、机器学习、深度学习、推荐算法等相关技术的分享,主题包括文本摘要、智能问答、聊天机器人、机器翻译、自动生成、知识图谱、预训练模型、推荐系统、计算广告、招聘信息、求职经验分享等,欢迎关注!加技术交流群请添加AINLPer(id:ainlper),备注工作/研究方向+加群目的。

640?wx_fmt=jpeg

阅读至此了,分享、点赞、在看三选一吧🙏


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK