Не только нейронные сети верстают сайты


Использование генетического программирования для верстки HTML

Введение

Было много шумихи после выхода pix2code(https://github.com/tonybeltramelli/pix2code), проекта по генерации HTML при помощи нейронной сети по картинке. Сразу пошли слухи, что “всё, верстка скоро умрет”, “глубокое обучение убьет frontend” и т.п.

Почти все издания так или иначе связанные с IT писали про этот проект. И за неделю github репозиторий pix2code вошел в TOP-10, хотя ни одной строчки кода там не было %).

Новая волна хайпа поднялась после публикации поста из floydhub (https://blog.floydhub.com/turning-design-mockups-into-code-with-deep-learning/), где уже было больше примеров, в том числе и с генерацией реального HTML, а не просто DSL как было в pix2code. И снова все стали “ванговать” смерть верстке.

Однако, если посмотреть глубже, то в данной работе был ряд допущений, которые нужно преодолеть прежде, чем можно говорить о каком-то варианте автоматической верстки.

Архитектура, которую использовала команда это уже стандартное решение для задач типа image captioning, поэтому это не был какой-то новых подход, скорее новое применение. К тому же, ребята немного схитрили и вместо генерации сырого HTML генерировали DSL из небольшого числа элементов(https://github.com/tonybeltramelli/pix2code/blob/master/compiler/assets/web-dsl-mapping.json). А далее этот DSL компилировался отдельным скриптом в разные варианты верстки (ios, android, web/bootstrap).

Рассматривать HTML чисто как текст не совсем верно, но результат команда получила.

Однако HTML это всё-таки язык разметки, а значит предполагает наличие AST. Из наиболее подходящих инструментов ML для работы с AST является генетическое программирование. DSL возьмем тот же, что и у pix2code.

Генетическое программирование

Для незнакомых с генетическим программированием(GP) стоит обратиться к данной статье https://habrahabr.ru/post/163195/. Если кратко, то это создание программ при помощи генетических алгоритмов.

DEAP

DEAP — это framework для работы с генетическими алгоритмами, содержащий много готовых инструментов, главное знать как их использовать.

Для иллюстрации решим классическую задачу символьной регрессии (в нашем случае будем восстанавливать формулу x ** 4 — x ** 3 — x ** 2 — x).

Импортируем необходимые модули.

Далее описываем из каких примитивов будет состоять наше будущее дерево (здесь это просто арифметические функции, второй параметр это арность функции).

Описываем наш генотип.

И fitness-функцию.

Настраиваем основные операции (отбор, скрещивание, мутация) и добавляем ограничения по размеру деревьев для мутации и скрещивания.

И наконец основной код.

Процесс обучения.

С основами разобрались — переходим к основной задаче.

Задача

Предположим, что мы хотим получить код верстки следующего блока.

кнопка справа имеет класс btn-orange но из-за функции рендеринга получаем не совсем orange :)

Это достаточно простой пример, но дает возможность показать основные элементы генерации верстки при помощи GP.

Необходимые функции

Чтобы собирать нужное нам AST, нам нужно создать несколько функций, которые будут нашими строительными блоками.

Mapping это словарь с HTML разметкой. Его можно найти в репозитории проекта.

Так как DEAP работает с бинарным деревом, то нам придется сделать финт ушами, представив массив, как результат нескольких функций, собирающих массив с конца к началу concat(block1, concat(block2, block3)).

Fitness-функция

В качестве fitness-функции возьмем функцию попиксельного сравнения evalByColorProportion, с указанием весов для больших блоков (чем больше цвет по площади, тем нам интереснее чтобы пиксели за него отвечающие совпадали на втором изображении).

Размер изображения фиксируем. Если не подходит — получаем штраф в 10000.

Запускаем

$ python gp_by_img.py
               fitness              size   
------------------------ -----------
gen nevals avg min avg min
0 300 47 464,8 9 945,72 6,88 2
1 186 21 913,6 9 945,72 5,39333 1
2 189 22 880,3 9 945,72 6,06333 1
3 176 20 074,9 9 945,72 5,38333 1
4 166 23 838,3 9 945,72 4,66667 1
5 183 22 182 9 945,72 4,31667 1
6 184 22 484,4 9 945,72 4,11667 1
7 198 24 173,1 9 945,72 4,01333 1
8 186 23 723,3 4 099,15 4,14 1
9 195 22 026,5 2 049,98 4,27667 1
10 160 19 021,4 0,393263 4,29 1
11 166 20 558,1 0,393263 4,93333 1
12 178 20 804,6 0,393263 6,23667 1
13 166 17 868,5 0 7,40667 1
14 175 20 839,2 0 7,94 1
15 177 16 587,1 0 7,98 1
16 192 22 399,2 0 7,87 1
17 193 19 394,3 0 7,92333 1
18 173 16 216,4 0 8,01333 1
19 180 18 485,9 0 7,99667 1
20 184 19 850 0 8,04333 1
LOSS: (0.0,)
CODE: row(concat(double(btn_green(x)), double(btn_orange(x))))
HTML:
<div class="row"><div class="col-md-6">
<a class="btn btn-success" href="#" role="button">text</a>
</div>
<div class="col-md-6">
<a class="btn btn-warning" href="#" role="button">text</a>
</div>
</div>

И в итоге мы получаем ту же картинку, что и хотели + код верстки.

Посмотрим на историю обучения.

Вот и нужный код.

И его AST.

На пути к веб-компонентам

Но таким образом мы сможем получить результат только для одного примера, а хотелось бы, чтобы данный механизм можно было применять и для других.

Тут может помочь автоматическая группировка тегов в более высокие структурные единицы, которыми обычно манипулируют при frontend разработке (меню, слайдеры, тарифные сетки и т.п.)

Для автоматической группировки берем библиотеку asttokens, которая дает возможность для каждого узла получить код его поддерева. Далее просто считаем сколько было повторений у поддеревьев и группируем их в новые блоки (если это не всё дерево или не элементарные блоки).

Именовать новые блоки мы можем автоматически или же задавать вручную дабы отразить логический смысл (форма регистрации или подписки).

И таким образом получаем широко известные во frontend разработке веб-компоненты(конечно не совсем те самые, ибо логики там нет).

Заключение

Нейронная сеть все-таки универсальный аппроксиматор, который обучается, чтобы работать на всех примерах, а GP проводит процесс поиска на каждом новом примере. Однако, если встретился новый пример, он будет более стабильным, ибо нет необходимости переучиваться.

Добавляя поощрение при использовании группированных блоков, мы можем стимулировать GP создавать более компактные деревья, что сокращает время поиска. А применяя более хитрую fitness-функцию, основанную на пространственной информации о блоках, можно сделать поиск более интеллектуальным.

Но это уже совсем другая история.

Исходный код использованный в статье: https://github.com/alifanov/deap4pix/

Source: Deep Learning on Medium