从去年 Tony Beltramelli 发表 pix2code 论文和 Airbnb 推出 sketch2code 后,这一领域才开始崭露头角。
更多干货内容请关注微信公众号“AI 前线”,(ID:ai-front)
目前,自动化前端开发的最大障碍是计算能力。不过,我们可以使用当前的深度学习算法和人造的训练数据来探索人工前端自动化。
在这篇文章中,我们将教会一个神经网络如何基于一张设计原型图片来编写基本的 HTML 和 CSS 代码。以下是该过程的简要概述:
1)为神经网络提供的设计图
2)神经网络将图片转化成 HTML 代码
3)渲染输出
我们将通过三次迭代来构建神经网络。
第一次迭代得到的是最基础的版本,先了解图样中的活动部件。第二次迭代得到的是 HTML 代码,将着重于自动化所有步骤,并解释神经网络层。最后一次迭代得到的是 bootstrap 版本,我们将创建一个模型可用于泛化和探索 LSTM 层。
所有的代码都放在了 Github 和 FloydHub 上。
这些模型是基于 Beltramelli 的 pix2code 论文和 Jason Brownlee 的图像自然语言描述教程而构建的,代码使用 Python 和 Keras(一个基于 Tensorflow 的框架)编写。
如果你是刚接触深度学习的新手,建议你先大概了解下 Python,反向传播和卷积神经网络。
让我们再简明扼要地复述下我们的目标。我们想构建一个神经网络,生成与屏幕截图对应的 HTML/CSS 代码。
在训练神经网络时,你可以给它几个截图和与之对应的 HTML 代码。
在学习过程中,它会逐一预测所有匹配的 HTML 标签。在预测下一个标签时,它会接收到屏幕截图以及在那一个点上所有匹配的标签。
这个 Google Sheet 包含了一个简单的训练数据样本。
创建一个能够逐字预测的模型是现在最常见的方式,也是我们在这个教程中将会使用的方式,虽然还有其他的方式。
请注意,对于每次预测,神经网络得到的都是相同的截图。也就是说,如果要预测 20 个单词的话,它将会收到相同的图样 20 次。在现在这个阶段,请不要担心神经网络的工作原理,而是着重关注神经网络的输入输出。
让我们看下之前的标签。假设我们要训练网络来预测“I can code”这句话。当它收到“I”的时候,它能预测到“can”。下一次,它将收到“I can”,并且预测到“code”。每次它会收到之前所有的单词,并且只需预测接下来的一个单词。
神经网络从数据中创建出不同的特征,用于连接输入和输出数据,创建模型,以便理解每张截图中所包含的内容和 HTML 语法,这就得到了预测下一个标签所需要的知识。
在将训练过的模型用在现实当中时,情况跟训练模型时差不多。每次使用相同的屏幕截图逐一生成文本。不同的是,现在它不会直接收到正确的 HTML 标签,而是会收到它迄今为止已经生成的标签,然后去预测下一个标签。整个预测的过程会从“起始标签”开始,在预测到“结束标签”或达到最大限度时终止。
让我们开始构建一个 Hello World 的版本。我们将为神经网络提供一个显示有“Hello World!”的网页截图,并且训练它生成对应的标签。
首先,神经网络将图样映射成一组像素值列表。每个像素点有 RGB 三个通道,每个通道的值都在 0-255 之间。
为了让神经网络理解这些标记,我使用了独热编码(one hot encoding)。因此“I can code”这一句就可以映射成:
上图中包含了开始和结束标签。这些标签控制着神经网络预测的开始和结束时间。
对于输入数据,我们将用不同的句子,从第一个单词开始,然后逐步添加每个单词。输出的数据总是一个单词。
句子遵循与单词相同的逻辑。它们也需要相同的输入长度,但是在这里我们限制的是句子最大的长度,而不是单词的数量。如果句子比最大长度短,则用空词填充它,空词完全由零组成。
正如所看到的,单词都是从右向左打印出来的。这样的话,每次训练都会迫使每个单词改变自己的位置,这样模型就可以记住单词顺序而不是每个单词的位置。
在下图中有四次预测,每一行表示一次预测。从左边起,是以 RGB 通道表示的图像:红色、绿色和蓝色,还有之前提到过的单词。括号之外,是一个接一个的预测,最后以红色方块结束。
在 Hello World 版本中,我们用了三个记号:“start”、“Hello World!”和“end”。记号可以是任何东西,它可以是一个字符、单词或句子。虽然使用字符记号需要更少的词汇量,但会限制神经网络。单词记号往往有最好的表现。
这里我们做出了预测:
10 epochs:start start start
100 epochs: start <HTML><center><H1>Hello World!</H1></center></HTML> <HTML><center><H1>Hello World!</H1></center></HTML>
300 epochs: start <HTML><center><H1>Hello World!</H1></center></HTML> end
在收集数据之前构建了第一个版本。 在这个项目的早期,我想办法从 Geocities 网站获得了一个旧的存档副本,其中包含 3800 万多个网站。不过,当时我被数据冲昏了头脑,却忽视了缩减 100K 词汇所需要的巨大工作量。
处理 TB 级的数据需要很好的硬件或者极大的耐心。 在用我的 Mac 电脑运行出现了几次问题以后,最后选择了功能强大的远程服务器。为了保持工作的顺畅,预计我将会租用包含 8 核 CPU 和一个 1GPS 网络连接的远程测试机组。
在我明白输入和输出之前,很多东西都说不通。 输入 X 是屏幕截图和之前已经预测的标签,输出 Y 是下一个要预测标签。当我明白这个以后,理解它们之间的关系就更容易了,而且构建不同的构架也更容易了。
小心兔子洞陷阱。 因为这个项目跟深度学习的很多领域都有交叉,在研究的过程中我好多次都陷入对其他领域的研究中。我花了一周的时间从头开始编写 RNN,过度着迷于向量空间,又被其他的实现所迷惑。
图片到编码网络其实就是 Image Caption 模型。 但即使我已经意识到了这点,我仍然忽略了很多有关 Image Caption 的论文,只是因为觉得它们没有那么酷。当我发现到这点后,我加快了对问题的了解。
Floydhub 是一个深度学习训练平台。在我刚开始接触深度学习时,才知道有这个平台。从那以后,我都在用它来训练和管理深度学习试验。你可以在 10 分钟内安装它并且运行你的第一个模型。这是在云端 GPU 上训练模型的最佳选择。
如果你是刚接触 Floydhub,推荐你去看下他们的 2 分钟安装教程和我的 5 分钟概览教程。
复制仓库:
登录并启动 FloydHub 命令行工具:
在 FloydHub 的云 GPU 机器上运行 Jupyter notebook:
所有的 notebook 都在 FloydHub 目录中准备好了。运行起来之后,你可以在这里找到第一个 notebook:floydhub/Helloworld/helloworld.ipynb。
如果想要更多详细的指导和标记说明,请参阅我早期写的文章。
在这个版本中,我们将会自动化 Hello world 中的很多步骤。这一章节将主要关注如何创建一个可伸缩的实现和神经网络中的动态部分。
虽然这个版本还无法基于随机网站来预测 HTML,但它仍然很适合用于对问题的动态部分进行探索。
下图所示的是构架组件展开后的样子。
主要有两个部分,编码器和解码器。编码器用于创建图像特征和标签特征。特征是由网络创建用来连接图样和标签的基本构建块。在编码的最后阶段,我们将图像特征和前一个标签中的单词关联起来。
然后解码器通过图样和标签特征的组合来创建下一个标签特征,而这个特征则会通过全连接神经网络来预测下一个标签。
因为我们需要为每一个单词插入一个截图,这就成了我们训练网络时的一个瓶颈(例子)。因此,我们没有直接使用图片,而是将生成标签所需要的信息提取出来。
之后,我们用一个预先在 ImageNet 上预先训练的卷积神经网络,将这些提取出来的信息编码到图片特征中。
在最终分类之前,我们将特征从层中提取出来。
最终,我们得到了 1536 张 8×8 像素的图像作为特征图。尽管这些特征很难被人理解,但是神经网络可以从这些特征中抽取出对象和元素的位置。
在 Hello World 的版本中,我们使用了独热编码来代表标签。而在这个版本中,我们将在输入中使用词向量(word embedding),并继续使用独热编码表示输出。
在保持每个句子的构造方式不变的情况下,改变映射记号的方法。独热编码将每个单词视为一个独立的单元。但在这里,我们将输入数据中的每个单词转换为数值列表。这些数值代表了不同标签之间的关系。
词向量的维度是 8,但受词汇量大小的影响,维度经常会在 50 到 500 之间变化。
每个单词的 8 个数字类似于一般神经网络中的权重,用于映射不同单词之间的关系。
神经网络可以用这些特征来连接输入数据与输出数据。现在,先不要关心它们是什么,我们将在下一节深入探讨这个问题。
我们把词向量送到 LSTM 中运行,之后会返回一系列的标签特征。这些标签特征然后会被送到 TimeDistributed 密集层运行。
在处理词向量的同时,还会进行另一个处理。图像特征首先会被扁平化,所有的数值会被转换成一个数字列表。然后我们在这个层上应用一个密集层来抽取高级特征,随后这些图像特征会被连接到标签特征。
这可能有点难以理解,所以让我们把处理的过程拆开来看。
我们先将词向量送到 LSTM 层中运行。如下图所示,所有的句子都会被填充到三个记号的最大长度。
为了混合信号并找到更高级别的模式,我们会用 TimeDistributed 密集层应用在标签特征上。TimeDistributed 密集层与一般的密集层相同,只不过具有多个输入和输出。
与此同时,我们会准备图像。我们整理了所有迷你图像特征,然后将它们转换成一组列表。其中的信息没有变,只是组织方式变了。
跟之前提过的一样,为了混合信号和提取更高级的概念,我们应用了一个密集层。 而且由于我们只需要处理一个输入值,所以我们可以用一个常规密集层。其后,为了将图像特征连接到标签特征,我们复制了图像特征。
在这种情况下,我们就有三个标签特征。因此,我们得到了相同数量的图像特征和标签特征。
所有的句子都经过填充,以便创建三个标签特征。由于我们已经预处理过了图像特征,现在我们可以为每个标签特征添加一个图像特征。
在将每个图像特征添加到对应的标签特征之后,我们最终得到了三组图像标签特征组合。之后,我们将它们作为解码器的输入。
这里,我们使用图像标签特征组合来预测下一个标签。
在下面的例子中,我们使用三个图像标签特征组合来输出下一个标签特征。
请注意,这里 LSTM 层的 sequence 被设置为 false。由此,LSTM 层返回的是一个预测的特征,而不是输入序列的长度。在我们的例子中,这将是下一个标签的特征,包含了最终预测所需的信息。
最终的预测
密集层像传统前馈神经网络那样,将下一个标签特征中的 512 个值与 4 个最终预测连接起来。假设我们的词汇表中有四个词:start、hello、world 和 end。
词汇预测可以是 [0.1,0.1,0.1,0.7]。密集层中的 softmax 激活函数分布概率是 0 到 1,所有预测的总和等于 1。在这种情况下,它预测第 4 个单词会是下一个标签。 然后,将独热编码 [0,0,0,1] 转换为映射值,比如“end”。
这里有原始网站供参考。
对我来说,LSTM 比 CNN 更难理解。当我展开所有的 LSTM 后,它们变得更容易理解。Fast.ai 在 RNN 上的视频非常有用。另外,在尝试了解特征的原理之前,请先关注输入特征和输出特征本身。从头开始构建词汇表比缩减巨大的词汇表要容易的多。包括字体、div 标签大小、hex 颜色值、变量名称和普通的单词。大多数库被创建来解析文本文件而不是代码。在文档中,所有内容都由空格分隔,但在代码中,则需要使用自定义的解析方式。可以使用在 ImageNet 上训练的模型来提取特征。这可能看起来违反直觉,因为 ImageNet 几乎没有 Web 图像。然而,与从头开始训练的 pix2code 模型相比,这样做的损失要高出 30%。当然,我也对使用基于网页截图的预训练的 inception-resnet 类型的模型很感兴趣。
在我们的最终版本中,我们将使用 pix2code 论文中生成的 Bootstrap 网站的一个数据集。通过使用 Twitter 的 Bootstrap,我们可以将 HTML 和 CSS 相结合,并且缩减词汇表的大小。
我们将确保它能够为之前没有看过的截图生成标签,还将深入研究它是如何建立对屏幕截图和标签的认知的。
我们将使用 17 个简化过的记号,然后将这些记号转成 HTML 和 CSS,而不是在 Bootstrap 标签上进行训练。这个数据集包括 1500 个测试截图和 250 幅验证图像。每个截图平均有 65 个记号,总共将生成 96925 个训练样本。
通过对 pix2code 论文中的模型做了一些调整,该模型可以以 97%的准确度预测网页组件(BLEU 4-ngram 贪心搜索,稍后会介绍更多)。
一种端到端的方法
在 Image Caption 模型中,从预训练好的模型中提取特征的效果很好。但经过几次实验后,我发现 pix2code 的端到端方法的效果更好。预训练的模型尚未用网页数据训练过,只是用在分类上。
在这个模型中,我们用轻量级的卷积神经网络替换了预训练好的图像特征。但是我们没有使用 max-pooling 来增加信息密度,而是增加了步幅,用以维护元素的位置和颜色。
这里可以使用卷积神经网络(CNN)和递归神经网络(RNN)这两种核心模型。最常见的 RNN 是长短期记忆(LSTM)网络,也是我将要讲的。
我在之前的文章中已经介绍过很多很棒的 CNN 教程了,所以这里我就只重点介绍下 LSTM。
LSTM 的难点之一是时间步的概念。原始神经网络可以被认为有两个时间步。如果你给它“hello”,它会预测到“world”。但是,想要预测更多的时间步是很困难的。在下面的例子中,输入四个时间步,每个单词对应一个。
LSTM 适用于含有时间步的输入,是一个适合有序信息的神经网络。如果你展开我们的模型,就会看到像下图所示的那样。对于每个向下递推的步骤,你需要保持同样的权重。对于旧的输出和新的输出,你可以分别设置一套权重。
将加权后的输入和输出用激活函数连接在一起,它就是对应时间步的输出。由于我们重复使用这些权重,它们将从一些输入中提取信息,并建立起有关序列的知识。
以下是 LSTM 中每个时间步的简化版本。
为了理解这个逻辑,我建议你参考 Andrew Trask 的精彩教程,自己从头开始构建一个 RNN。
每个 LSTM 层的单元(unit)数量决定了它的记忆能力,以及每个输出特征的大小。需要再次指出的是,一个特征是用于在层与层之间传输信息的一长串数字。
LSTM 层中的每个单元会学习跟踪语法的不同方面。下面是对一个单位跟踪原始 div 信息的可视化结果,是我们用来训练 Bootstrap 模型的简化标签。
每个 LSTM 单元会维护一个细胞状态(cell state)。把细胞状态想象成记忆,而权重和激活函数用来以不同的方式修改状态。这使得 LSTM 层能够微调每个输入要保留和丢弃哪些信息。
除了每个输入传递输出特征之外,LSTM 层还会传递细胞状态,其中每个单元都分别对应一个值。为了理解 LSTM 中的组件是如何相互作用的,我推荐 Colah 的教程、Jayasiri 的 Numpy 实现以及 Karphay 的讲座和文章。
找到一个公平的方法来衡量准确性非常困难。假设你选择逐字比较,那么如果你的预测中有一个字不同步,那么你的准确性就可能会是零。如果你删掉一个符合预测的单词,最后的准确性也可能是 99%。
我用的是 BLEU 测评法,这是一种用于机器翻译和图像字幕模型的最佳实践。它按照 1 到 4 个单词序列把句子分成四个 gram。在下面的预测中的“cat”应该是“code”。
为了拿到最终的分数,你需要将得到的数字都乘以 25%,(4/5)*0.25 + (2/4)*0.25 + (1/3)*0.25 + (0/2)*0.25 = 0.2 + 0.125 + 0.083 + 0 = 0.408。求和的结果再乘以句子长度的处罚值。因为我们例子中的句子长度是正确的,所以求和的结果直接就是最终的结果。
你可以通过增加 gram 的数量让它变得更难。四个 gram 的模型是最符合人类翻译的模型。我建议使用下面的代码运行几个例子并阅读 Wiki 页,来加深对这方面的了解。
一些输出样本的链接:
生成网站 1——original 1
https://emilwallner.github.io/bootstrap/pred_1/
生成网站 2——original 2
https://emilwallner.github.io/bootstrap/real_2/
生成网站 3——original 3
https://emilwallner.github.io/bootstrap/pred_3/
生成网站 4——original 4
https://emilwallner.github.io/bootstrap/pred_4/
生成网站 5——original 5
https://emilwallner.github.io/bootstrap/pred_5/
理解不同模型的弱点,而不是测试随机模型。开始的时候,我使用了类似批次标准化和双向网络这类随机模型,并尝试实现注意力机制。然后看了测试数据,才发现这样无法准确预测颜色和位置,我意识到 CNN 有一个弱点。这就导致我采用更大的步幅来取代 maxpooling。之后验证损失就从 0.12 变为 0.02,而 BLEU 得分从 85%提高到了 97%。
如果它们相关的话,只使用预先训练好的模型。鉴于给定的数据集很小,我认为经过预训练的图像模型会提高性能。从我的实验来看,端到端的模型训练起来比较慢,需要更多的内存,但是精度要高出 30%。
在远程服务器上运行模型时,你的计划需要有所调整。在我的 Mac 上,它会按字母顺序来读取文件。但是在远程服务器上,它是随机定位的。这就造成了截图和代码之间的不匹配。虽然它仍然收敛,但验证数据比我修正之后的要差 50%。
确保你理解库函数。包括词汇表中空记号的填充空格。当我没有添加填充空格时,预测中没有包括任何一个空记号。这也是我看了好几遍输出结果之后才注意到的,而且模型从来没有预测到一个单记号。经过快速的检查,我发现到这甚至不在词汇表中。另外,词汇表要以相同的顺序进行训练和测试。
试验时使用更轻量级的模型。使用 GRU 而不是 LSTM 会将每个 epoch 周期减少 30%,并且对性能没有太大的影响。
前端开发是应用深度学习的理想领域。因为生成数据很容易,并且目前的深度学习算法可以映射绝大部分的逻辑。
其中最令人兴奋的领域之一是注意力机制在 LSTM 上的应用。这不仅会提高准确性,而且还能使我们能够直观地看到 CNN 在产生标签时将焦点放在了哪里。
注意力机制也是标签、样式表、脚本和后端之间沟通的关键。注意力层可以跟踪变量,确保神经网络能够在编程语言之间进行通信。
但在不久的将来,最大的问题在于如何找到一种可伸缩的方式来生成数据,这样就可以逐步加入字体、颜色、文字和动画。
到目前为止,大部分的进步都是发生在将草图转化为模板应用程序的过程中。在不到两年的时间里,我们将可以在纸上画一个应用程序,并在不到一秒的时间内就可以获得相应的前端代码。Airbnb 的设计团队和 Uizard 已经构建了两个可用的原型。
运行所有的模型
尝试不同的超参数
测试一个不同的 CNN 构架
添加双向 LSTM 模型
用不同的数据集来实现模型
使用相应的语法创建一个可靠的随机应用程序或网页生成器。
从草图到应用模型的数据。将应用程序或网页截图自动转换为草图,并使用 GAN 创建不同类型的草图。
应用一个注意力层,可视化每个预测在图像上的焦点,类似于这个模型。
为模块化方法创建一个框架。比如,有字体的多个编码器模型,其中一个用于颜色,另一个用于排版,之后将这些编码器整合到一个解码器中。获得稳定的固体图像特征是一个好兆头。
为神经网络提供简单的 HTML 组件,并且教它使用 CSS 生成动画。
原文链接:
https://blog.floydhub.com/turning-design-mockups-into-code-with-deep-learning/
想看更多这类文章, 请给我们点个赞吧!
有疑问加站长微信联系(非本文作者)