Как научить ИИ превращать дизайн-макеты в HTML и CSS. Часть 1
Перевод статьи Emil Wallner: How you can train an AI to convert your design mockups into HTML and CSS
За ближайшие три года глубокое обучение в корне преобразит фронтенд-разработку. Благодаря нему возрастет скорость прототипирования и упростится процесс создания приложений.
Толчком к развитию направления послужила статья pix2code Тони Белтрамелли и запуск sketch2code от Airbnb.
В настоящее время главной трудностью при автоматизации фронтенд разработки является ограниченная вычислительная мощность. Тем не менее, уже сейчас можно использовать алгоритмы обучения и синтезированные наборы данных для внедрения ИИ во фронтенд-автоматизацию.
В данной статье мы научим нейAjnjронную сеть писать код для базового сайта на HTML/CSS по представленному макету страницы. Общая схема приведена ниже:
1) Показываем картинку обученной нейронной сети
2) Нейронная сеть преобразует картинку в HTML-разметку
3) Получаем значения на выходе
Нейронную сеть будем выстраивать в три этапа.
Для начала возьмем самую минимальную версию, чтобы закрепить на ней подвижные части. На втором этапе (с HTML) начнем автоматизировать все шаги и знакомить нейронную сеть с последовательностями. В финальной итерации (Bootstrap) создадим модель, которая будет обобщать и исследовать LSTM-слой (долгая краткосрочная память).
Весь код готовится на GitHub и FloydHub с помощью Jupyter Notebooks. Все блокноты FloydHub расположены внутри директории floydhub, их локальный эквивалент хранится в local.
Для создания моделей использовалась статья Белтрамелли о pix2code и уроки Джейсона Браунли о захвате изображений. Код написан на Python и Keras и аккуратно завернут во фреймворк TensorFlow.
Если вы — новичок в глубоком обучении, то советую для начала познакомиться с Python, методом обратного распространения ошибки и сверточными нейронными сетями. Разобраться в этом помогут следующие посты на FloydHub:
· My First Weekend Of Deep Learning
· Coding The History Of Deep Learning
· Colorizing B&W Photos with Neural Networks
Базовая логика
Давайте вспомним основную цель: мы хотим создать нейронную сеть, которая сможет генерировать HTML/CSS разметку по определенному скриншоту.
При обучении нейронной сети вы даете ей несколько изображений с идентичным HTML.
Так система учится последовательно предугадывать все используемые теги разметки. При предсказании следующих тегов, алгоритму выдается скриншот с «правильными» тегами разметки.
Вот простой пример обучающих данных в Google-таблице.
На сегодняшний день активно используются модели, предсказывающие очередность слов. Существуют и другие подходы, но нам понадобится именно этот пример.
Обратите внимание, что для каждого предсказания алгоритму выдается одинаковый скриншот. Так что если вы хотите, чтобы нейронная сеть предугадывала 20 слов, то каждое из этих слов должно сопровождаться одним и тем же графическим представлением. Пока что даже не вникайте в механизм работы сети. Сконцентрируйтесь на входных и выходных значениях алгоритма.
Рассмотрим предыдущую разметку. Допустим, мы научили сеть предугадывать предложение «Я могу писать код». При получении «я» алгоритм предсказывает продолжение — «могу». В следующий раз, когда системе представляется «я могу», она достраивает предложение до «писать код». Она уже получила все предыдущие слова и должна всего лишь предугадать следующее.
Нейронная сеть создает признаки данных. Эти признаки формируют связи между входными и выходными значениями. Для определения содержания рисунка сеть создает представления данных, на основании которых генерируется HTML синтаксис предсказания. Так у алгоритма появляются необходимые знания для предсказания следующего тега.
Использование уже тренированных сетей для решения практических задач мало отличается от обучения модели с нуля. Текст генерируется пошагово, сопровождается одинаковыми картинками. Вместо скармливания алгоритму правильных HTML тегов, она получается уже сгенерированную разметку. Далее — предсказывается следующий тег. Предсказание инициируется «начальным тегом» и заканчивается «конечным тегом», либо достижением максимального предела. Вот еще один пример в Google таблице.
Версия “Hello World”
Давайте создадим традиционный пример с “hello world”. Скармливаем нейронной сети скриншот сайта с надписью “Hello World!” и учим генерировать разметку.
Первым делом, нейронная сеть разбивает разметку на список пиксельных значений от 0 до 255 в RGB-спектре: красный, зеленый, синий.
Для представления разметки в понятном алгоритму виде я пользуюсь прямым кодированием (one-hot). Таким образом, предложение «я могу писать код» можно привести к следующему виду:
В рисунке выше мы добавили начальный и конечный теги. Данные теги — это некие сигналы, объясняющие алгоритму, когда необходимо начать и закончить предсказание.
Для входных значений возьмем предложения, начинающиеся с одного слова, а затем последовательно добавим остальные слова. Выходное значение — это всегда одно слово.
Предложения подчиняются той же логике, что и слова. Для них также требуется определенная длина входного значения. Вместо ограничений по набору слов (словарю) мы делаем привязку по максимальной длине предложения. Если предложение короче максимальной длины, то оно дополняется «пустыми» словами, т.е. словами с одними нулями.
Как вы видите, слова печатаются справа налево. Таким образом, в каждом цикле обучения все слова меняют свое положение. Данный способ позволяет алгоритму запоминать целую последовательность, а не отдельное расположение каждого слова.
На картинке ниже приведены четыре прогноза. Каждая строка — одно предсказание алгоритма. Слева расположены картинки в трех цветовых каналах: красном, зеленом, синем и предыдущие слова. За скобками расположены последовательные предсказания; красными квадратами отменено окончание.
Зеленые блоки = начальные токены; красные блоки = конечный токен
#Длина самого длинного предложения
max_caption_len = 3
#Объем словаря
vocab_size = 3
# Загрузка скриншота для каждого слова, преобразование его к числовому значению
images = []
for i in range(2):
images.append(img_to_array(load_img('screenshot.jpg', target_size=(224, 224))))
images = np.array(images, dtype=float)
# Предварительная обработка входного значения для модели VGG16
images = preprocess_input(images)
#Перенос начальных токенов в прямое кодирование
html_input = np.array(
[[[0., 0., 0.], #start
[0., 0., 0.],
[1., 0., 0.]],
[[0., 0., 0.], #start <HTML>Hello World!</HTML>
[1., 0., 0.],
[0., 1., 0.]]])
#Перенос следующего слова в прямое кодирование
next_words = np.array(
[[0., 1., 0.], # <HTML>Hello World!</HTML>
[0., 0., 1.]]) # end
# Загрузка предобученной на Imagenet модели VGG16 и размещение на выходе признаков классификации
VGG = VGG16(weights='imagenet', include_top=True)
# Извлечение признаков из изображения
features = VGG.predict(images)
# Загрузка признака сети, наложение Dense-слоя и повторение вектора
vgg_feature = Input(shape=(1000,))
vgg_feature_dense = Dense(5)(vgg_feature)
vgg_feature_repeat = RepeatVector(max_caption_len)(vgg_feature_dense)
# Извлечение информации из входящей последовательности
language_input = Input(shape=(vocab_size, vocab_size))
language_model = LSTM(5, return_sequences=True)(language_input)
# Обобщение информации из изображения и входного значения
decoder = concatenate([vgg_feature_repeat, language_model])
# Извлечение информации из обобщенного выхода
decoder = LSTM(5, return_sequences=False)(decoder)
# Предсказание следующего слова
decoder_output = Dense(vocab_size, activation='softmax')(decoder)
# Компиляция и запуск нейронной сети
model = Model(inputs=[vgg_feature, language_input], outputs=decoder_output)
model.compile(loss='categorical_crossentropy', optimizer='rmsprop')
# Обучение нейронной сети
model.fit([features, html_input], next_words, batch_size=2, shuffle=False, epochs=1000)
В версии с Hello World имеется три токена: start
, <HTML><center><H1>Hello World!</H1></center></HTML>
и end
. Токеном может быть что угодно: символ, слово или предложение. Версия с символами требует меньшего набора слов (словаря), однако нейронная сеть в таком случае будет более ограниченной. Лучше всего выбирать токены на уровне слов.
Вот как мы создаем предсказание:
# Создание пустого предложения и добавление начального токена
sentence = np.zeros((1, 3, 3)) # [[0,0,0], [0,0,0], [0,0,0]]
start_token = [1., 0., 0.] # start
sentence[0][2] = start_token # place start in empty sentence
# Создание первого предсказания с начальным токеном
second_word = model.predict([np.array([features[1]]), sentence])
# Включение второго слова в предложение и составление итогового предсказания
sentence[0][1] = start_token
sentence[0][2] = np.round(second_word)
third_word = model.predict([np.array([features[1]]), sentence])
# Добавление начального токена и двух наших предсказаний в предложение
sentence[0][0] = start_token
sentence[0][1] = np.round(second_word)
sentence[0][2] = np.round(third_word)
# Преобразование предсказаний из one-hot прямого кодирования в итоговые токены
vocabulary = ["start", "<HTML><center><H1>Hello World!</H1></center></HTML>", "end"]
for i in sentence[0]:
print(vocabulary[np.argmax(i)], end=' ')
Значение на выходе
- 10 эпох:
start start start
- 100 эпох:
start <HTML><center><H1>Hello World!</H1></center></HTML> <HTML><center><H1>Hello World!</H1></center></HTML>
- 300 эпох:
start <HTML><center><H1>Hello World!</H1></center></HTML> end
Что я делал не так:
· Создавал рабочую версию, не собрав все данные. В начале проекта я умудрился отыскать копию старого архива сайта Geocities. Там хранилось 38 миллионов сайтов. Ослепленный радужными перспективами, я напрочь забыл о том, какая масштабная работа потребуется для сокращения 100к словаря.
· Работа с терабайтами данных требует безграничного терпения и мощного компьютера. После того, как мой Мак столкнулся с рядом проблем по части производительности, я перешел на мощный удаленный сервер. Для более-менее приличного качества работы будьте готовы арендовать серверную стойку с 8 современными и многоядерными процессорами и 1 GPS подключением.
· Пока не разберетесь во входных и выходных значениях, остальное можете даже не открывать. Вход (Х) — это один скриншот и предыдущие теги разметки. Выход (У) — это следующий тег разметки. Стоит понять это, и остальное дастся вам намного проще. А еще я научился экспериментировать с различными архитектурами.
· Помните о возможных проблемах. Глубокое обучение (в т.ч. и наш проект) состоит из множества полей, из-за чего в процессе работы возникают всякого рода трудности. Целую неделю я программировал рекуррентную нейронную сеть с нуля — слишком увлекся добавлением векторных полей и повелся на нестандартные реализации.
· Сети со схемой «картинка-в-код» — это завуалированная модель захвата изображений. И даже поняв это, я все равно не хотел читать профильные статьи по захвату изображений — мне они казались чем-то совершенно не клевыми. Как только я заинтересовался темой, то начал активно изучать проблемную область.
На этом первая часть подходит к концу. Во-второй части мы будем автоматизировать модель Hello World и переводить ее в HTML-версию.