Углубленный анализ

Использование машинного обучения для анализа целевых страниц

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

Автор: Майкл Макдермотт (Майк Макдермотт, https://twitter.com/mbmcdermott), старший научный сотрудник, Томми Леви (Томми Леви, https: // twitter. com / tslevi ) Директор по науке о данных, Джордан Доу (Jordan Dawe, https://twitter.com/freedryk ), старший разработчик науки о данных, Йосем Свит (Йосем Свит , https://twitter.com/yosemsweet) Старший директор по технологиям, Тэвис Радд (https://twitter.com/tavisrudd), Главный инженер, Macarena Poo (Macarena Poo ) Разработчик пользовательского интерфейса

В Unbounce у нас есть команда R&D, которая занимается исследованием новых функций, чтобы помочь нашим клиентам. Главный вопрос, который нас задает: как сделать целевую страницу с высокой конверсией? Есть много факторов, которые могут повлиять на коэффициент конверсии целевой страницы, например, копия страницы, графический дизайн или качество. трафика, направляемого на страницу. Мы использовали машинное обучение, чтобы изучить, как макет, типографика и цветовая палитра целевой страницы влияют на ее коэффициент конверсии. В интересах сообщества мы хотели открыть исходный код оптимизированной реализации PyTorch Tree-LSTM, поделиться некоторыми из наших работ по машинному обучению в Unbounce и описать работу, связанную с машинным обучением в промышленных условиях.

Наш репозиторий PyTorch Tree-LSTM можно найти здесь: https://github.com/unbounce/pytorch-tree-lstm

Целевые страницы обычно имеют стандартный набор элементов, составляющих целевую страницу - заголовок, резюмирующий предложение страницы, призыв к действию, детализирующий действия, которые дизайнер страницы хочет, чтобы посетитель совершил, среди прочего. (Более полное описание элементов целевой страницы можно найти здесь.) В области маркетинга существует довольно много стандартных знаний о лучших практиках в отношении того, как элементы дизайна должны использоваться на странице. К ним относятся такие правила, как следите за своим брендом, сделайте заметное предложение в заголовке, четко отображайте кнопку / форму для действий пользователя и логотип должен располагаться в левом верхнем углу. У нас есть уникальная возможность исследовать эти вопросы с точки зрения данных. Например: должно ли у вас быть заметное предложение в заголовке? Это вообще имеет значение?

Имея набор данных этих элементов вместе с коэффициентом конверсии целевой страницы, мы сможем анализировать дизайн целевой страницы и получать информацию для наших клиентов о том, как они могут улучшить свои страницы. К сожалению, на целевых страницах эти элементы обычно не помечены машиночитаемым способом. Чтобы решить эту проблему, мы хотим создать семантический классификатор элементов дизайна, систему машинного обучения, которая с учетом HTML и CSS целевой страницы будет определять, какие части объектной модели документа HTML (DOM) представляют какой тип элемента дизайна.

Наша команда R&D верит в быструю итерацию и минимально жизнеспособные продукты, поэтому мы решили посмотреть, сможем ли мы обучить модель машинного обучения определять для нас заголовки целевых страниц. Заголовки легко распознаются людьми, поэтому наши успехи или неудачи в создании такого классификатора многое расскажут о том, насколько сложно будет классифицировать другие элементы страницы. В этом посте мы хотели поделиться с сообществом тем, как мы разрабатывали эту систему.

Набор данных

Unbounce в любой момент времени размещает на своих серверах около 500 000 целевых страниц. Для каждой из этих страниц у нас есть два типа данных, которые мы потенциально можем использовать для прогнозирования заголовков:

  • Скриншоты отображаемой страницы
  • HTML DOM, CSS и файлы изображений, которые используются веб-браузером для отображения целевой страницы.

В принципе, любой из них должен дать нам достаточно информации, чтобы идентифицировать элемент страницы (поскольку обученный человек мог бы это сделать). Как команда, мы обсудили использование обоих вариантов. Мы решили начать с данных HTML и CSS по следующим причинам:

  1. Данные DOM обычно требуют меньше вычислительных ресурсов для работы, чем данные изображений.
  2. Модель, которая работала с данными изображения, должна была бы сегментировать изображение на элементы перед классификацией элементов, в то время как данные DOM уже сегментированы на элементы HTML.
  3. Текст, отображаемый элементом HTML, вероятно, полезен для классификации типа элемента, а извлечение текста из DOM намного проще, чем из изображения.
  4. Конвертировать DOM в изображение с помощью веб-браузера легко, но сделать наоборот - сложно.

Unbounce хранит данные целевой страницы своих клиентов в хранилище данных, которое легко запрашивать, но есть проблемы с простым запуском моделей с HTML и CSS, потому что браузер может изменять эти данные во время рендеринга страницы. Многие целевые страницы встраивают в свою структуру другие страницы как фреймы, загруженный JavaScript может изменять DOM страницы произвольным образом, а CSS имеет сложные правила, регулирующие каскадирование стилей для каждого элемента страницы. На самом деле мы хотим тренироваться на окончательной визуализированной структуре DOM и стилях CSS для страницы, а для этого требуется запускать HTML и CSS через механизм DOM веб-браузера.

Чтобы справиться с этим, мы обратились к модулю Puppeteer в Node.js, который предоставляет интерфейс для управления автономным экземпляром веб-браузера Chromium. Используя экспериментальный API в Протоколе разработчика Chrome, мы сделали снимок DOM, вычисленные свойства CSS для каждого узла DOM и снимок экрана страницы, отрендеренной в окне просмотра браузера шириной 1280 пикселей для 10 000 целевых страниц Unbounce.

Маркировка набора данных

Amazon Mechanical Turk позволяет отправлять людей на веб-страницу для выполнения своих задач, поэтому мы создали приложение Typescript, которое может отображать дерево HTML DOM и снимок экрана для веб-страницы и позволять пользователям щелкать компоненты страницы, чтобы определить заголовок страницы. После этого мы провели недельный конкурс для Unbouncers; три ведущих этикетировщика получат подарочные карты на 25 долларов, а команда, которая обозначит больше всего как группа, получит торт. Это сработало на удивление хорошо, и в конце конкурса у нас было более 5000 страниц с помеченными заголовками. Более подробную информацию о нашем конкурсе этикетирования можно найти здесь.

Этикетка согласованности

Мы обнаружили, что 4714 страниц были помечены более одного раза. Из них 41% были маркированы последовательно. Что еще более интересно, 2072 страницы были помечены несколько раз одним и тем же пользователем, и 18% этих не согласились. Прежде чем мы сможем использовать этот набор данных для какого-либо машинного обучения, нам необходимо устранить эти несоответствия, поскольку загрузка модели одним и тем же примером, помеченным по-разному, сделает очень трудным или невозможным сходимость обучения модели.

Подход

После того, как мы собрали данные, нам нужно было выбрать модель для обучения, и чтобы выбрать подходящую модель, нам нужно было немного подумать о задаче, которую мы хотим выполнить. Наша задача - проблема контролируемой классификации - мы хотим пометить компоненты страницы как заголовки, и у нас есть обучающий набор с доступными помеченными заголовками. Самый простой подход к этой задаче - запустить классификатор свойств и атрибутов CSS для каждого элемента HTML в дереве DOM. Такой вид поэлементной классификации обеспечивает точность тестирования 60–90% (Lim et al. 2012) - приемлемая производительность, но мы бы предпочли, чтобы классификатор был немного более точным, поэтому мы начали рассматривать, как включить информация о структуре DOM в модели.

Семантическое значение элементов DOM основано не только на свойствах отдельного элемента HTML: свойства дизайна элемента также содержатся в его отношениях с элементами, вложенными внутри этого элемента и окружающими его. Например, заголовки часто группируются с изображением в структуре DOM или разбиваются на несколько родственных элементов, которые вместе образуют заголовок. Мы рассмотрели несколько моделей, которым можно научиться из структуры дерева DOM:

  • Модели рекурсивной нейронной сети (Socher et al. 2011; не путать с рекуррентными нейронными сетями) способны обрабатывать древовидную структуру, но сформулированы так, чтобы пытаться извлекать структуру из изображений или предложения. Модель запускается на смежных элементах изображения или предложения, а древовидная структура выводится из сетевых выходных данных. Это не совсем то, что мы хотим; у нас есть древовидная структура, и мы хотим пометить узлы, в то время как Рекурсивные сети имеют данные о смежности и пытаются вывести древовидную структуру.
  • Модели Child-Sum Tree-LSTM расширяют модели LSTM для прогнозирования значений родительских узлов с использованием входных данных от переменного числа дочерних узлов, что позволяет модели обрабатывать любую древовидную структуру. Таким образом, они способны кодировать отношения по целым ветвям дерева, а также суммировать данные дерева в выходных данных корневого узла. В итоге мы решили попробовать эту модель.
  • Модели Seq2seq с вниманием работают, преобразуя последовательность функций в последовательность выходных данных, но вместо того, чтобы обрабатывать каждый узел в последовательности в установленном порядке, они используют механизм внимания для одновременного изучения всех узлов входной последовательности. Это чрезвычайно эффективный подход, но, поскольку эти модели обычно применяются к последовательностям, он не идеально подходит для наших данных. Однако, что наиболее важно, в то время, когда мы выполняли эту работу, наша команда не была знакома с этими типами моделей. Было бы чрезвычайно интересно попробовать этот подход, даже если бы мы не смогли придумать метод кодирования древовидной структуры для модели.

Решив использовать Tree-LSTM, мы нашли реализацию Treelstm PyTorch Риддхимана Дасгупты и решили использовать этот код в качестве отправной точки для построения модели.

Извлечение функций

Наш набор данных содержит большое количество необработанных и потенциальных характеристик каждого узла. Каждый элемент или узел дерева содержит:

  1. CSS-свойства узла (например, свойства цвета и шрифта, ограничивающие рамки)
  2. Свойства DOM узла (например, отношения родитель-потомок / брат, тип узла)
  3. Является ли каждый узел заголовком

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

Для нашей первоначальной попытки мы хотели выбрать минимальный набор функций, которые были бы просты для кодирования и были (надеюсь) достаточными для предсказания заголовков. В частности, мы не включали такие свойства, как полный текст, кодировки / URL-адреса изображений, относительное позиционирование узлов и функции компьютерного зрения (такие как SIFT или HOG).

Набор функций, который мы использовали:

Числовой: размер шрифта, толщина шрифта, цвет, цвет фона, координаты ограничивающей рамки.

Категориальный: семейство шрифтов, тип элемента.

Ярлыки: заголовок / не заголовок.

Обеспечение единообразия этикеток

Когда мы начинали этот проект, мы надеялись, что с таким простым семантическим элементом, как заголовок, эксперты-люди будут склонны соглашаться в том, что составляет заголовок. Как мы видели выше, это не так. Мы увидели два основных типа разногласий в названиях заголовков:

  1. Люди не согласны с тем, что является заголовком
  2. Люди согласны с тем, что такое заголовок, но не согласны с тем, как его обозначить.

Поговорим о каждом из них. В тех случаях, когда люди не согласны с тем, какой элемент страницы пометить заголовок, это представляет собой настоящую неоднозначность в нашем наборе данных. То есть у нас есть страницы либо с несколькими потенциальными заголовками, либо с ненастоящим. В этом случае эти страницы ближе к «шуму» в том смысле, что, если бы мы включили их изначально, мы бы попросили машину изучить одни и те же данные примера, но сделать разные прогнозы. Это, вероятно, приведет к замедлению или отсутствию сближения в обучении, и будет трудно интерпретировать результаты. Мы изначально решили эту проблему, просто удалив эти страницы из набора данных. Эти страницы составляли менее 10% от общего набора. Обратите внимание, что мы удаляем их как из обучающего, так и из тестового наборов.

Во втором случае все иначе. В этом случае люди принципиально согласны с тем, что представляет собой заголовок целевой страницы, но расходятся во мнениях по поводу мелких деталей. Например, некоторые люди выбирают текстовое поле в качестве заголовка, в то время как другие выбирают родительский узел, который также содержит информацию о стиле и ограничениях. В этих случаях люди оба указывают на одно и то же, когда их спрашивают, что такое заголовок, мы просто немного теряем перевод на необработанные элементы DOM. Мы хотим включить эти страницы в наш набор данных для машинного обучения, но нам нужно устранить неоднозначность меток.

Для этого мы использовали детерминированный алгоритм. Он берет человеческие метки и пытается сопоставить разные схемы маркировки с одним и тем же. Мы переназначаем набор данных по двум схемам:

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

Эта схема перемаркировки позволяет устранить всю неоднозначность примеров в нашем наборе данных.

Выполнив эти подготовительные шаги, мы, наконец, были готовы обучить модель классификации узлов заголовков. Мы используем Amazon SageMaker для обучения моделей, что позволяет нам объединять код в контейнеры Docker и легко переносить обучение в облачные системы. Мы настроили модель с оптимизатором Adam, настроенным на параметры по умолчанию, и функцию двоичной кросс-энтропийной потери, а также установили скрытый слой Tree-LSTM на 40 единиц. Мы написали код интерфейса для загрузки данных целевой страницы и начали обучение модели.

После нескольких обучающих прогонов у нас появились первые признаки того, что модель учится определять заголовки: наши потери неуклонно сокращались, наша точность и отзывчивость были на удивление высокими из-за того, что гиперпараметры еще не были настроены, а наши прогнозы были очень близки к истинному заголовку. локации. Однако мы столкнулись с общей проблемой машинного обучения: все длилось чертовски долго. Тренировка каждой эпохи занимала около 10 минут, а полный тренировочный цикл занимал около трех дней, что не поддерживало быстрые циклы итераций. Таким образом, мы начали искать оптимизацию, которую можно было бы выполнить, чтобы ускорить вычисления.

Самый простой способ оптимизации, который мы нашли, - это уменьшение размера набора данных. Заголовки почти всегда находятся в верхней части целевой страницы, поэтому мы удалили все узлы с каждой целевой страницы, которые находились более чем на 900 пикселей от верха страницы. Целевые страницы обычно содержат несколько вертикальных страниц контента, поэтому одно это изменение ускорило обучение в 2 раза; сейчас эпохи длились 5 минут.

Мы были не уверены, приведет ли оптимизация кода Tree-LSTM к значительному увеличению скорости, чтобы оправдать затраты времени и увеличение сложности кода. Чтобы оценить, насколько прирост скорости нам даст оптимизация кода, мы решили реализовать линейную модель LSTM из библиотеки PyTorch и применить ее к данным Tree-LSTM. PyTorch LSTM реализован на C ++ и поэтому должен представлять верхнюю границу достижимого увеличения скорости. Мы запускали модель LSTM на последовательностях, состоящих из узлов на путях между каждым листом и корневым узлом дерева DOM. Это привело к тому, что эпоха заняла около 40 секунд, но, к сожалению, модель не смогла хорошо классифицировать заголовки, что свидетельствует о том, что древовидная структура действительно важна для нашей проблемы.

Мы решили, что увеличение скорости, которое мы наблюдали в ванильном LSTM, было достаточно большим, чтобы оправдать оптимизацию нашего кода Tree-LSTM. Наш исходный код провел простой рекурсивный обход деревьев DOM и последовательно оценил каждый узел. Мы переписали оценку модели, чтобы оценивать деревья поэтапно, в течение которых все узлы, для которых были удовлетворены их дочерние зависимости, оценивались в одной параллельной операции PyTorch. С этим оптимизированным кодом эпохи обучения теперь занимали около 10 секунд. Мы выпустили эту оптимизированную модель PyTorch под лицензией MIT с открытым исходным кодом здесь. (После реализации этих оптимизаций мы обнаружили реализацию той же стратегии TensorFlow здесь - если бы мы обнаружили это раньше, мы бы, вероятно, просто перешли на использование TensorFlow).

Одна оптимизация, которая не сработала, - это тренировка на графическом процессоре. На самом деле мы обнаружили, что работа на GPU была медленнее, чем на CPU, хотя мы не знаем почему. Мы предполагаем, что модель просто недостаточно велика, чтобы извлечь выгоду из параллелизма графического процессора. Наш Tree-LSTM имеет только два слоя нейронов, у нас есть только 125 функций на узел, и весь наш набор данных в совокупности достаточно мал, чтобы поместиться в ОЗУ графического процессора. Мы ожидаем, что по мере увеличения размера нашей модели и набора функций ускорение графического процессора станет более важным.

Полученные результаты

После завершения этих оптимизаций мы смогли завершить тренировочные прогоны всего за один час, и мы начали настройку гиперпараметров, которая включала опробование различных алгоритмов оптимизатора, изменение размера скрытого слоя, добавление дополнительных слоев и изменение скорости обучения. Через день или два настройки мы смогли достичь точности и вспомнить значения 90%.

Ниже мы взяли конкретную страницу и нанесли на график вероятности классификации заголовков в процессе обучения. К концу обучения заголовки имеют очень высокую вероятность, связанную с ними, а не заголовки - очень низкую, поэтому модель хорошо справляется с изучением этой пошаговой функции. Обратите внимание, что даже несмотря на то, что мы в конечном итоге находимся в хорошем состоянии в том, что касается обучения, вероятности не приближаются к функции шага каким-либо монотонным образом. Это может указывать на очень сложную поверхность потерь с множеством локальных минимумов.

Выводы и выводы

Благодарим вас за то, что вы продолжили наше путешествие, поскольку мы начинаем пытаться понять, как можно использовать машинное обучение для маркировки и извлечения семантического веб-контента. Хотя мы приложили много усилий для этого, мы понимаем, что это всего лишь небольшой, начальный шаг. Мы показали, что машинное обучение может обнаруживать заголовки в разнообразном наборе целевых страниц, и это дает нам надежду на то, что в конечном итоге мы сможем извлечь большую часть или всю семантическую структуру страницы. По ходу дела мы также узнали кое-что:

  • Сама концепция заголовка непроста даже для экспертов-людей. Это влияет на нашу абсолютную верхнюю границу, а также требует стратегии смягчения последствий при построении набора данных. Для некоторых других элементов это, вероятно, будет еще сложнее.
  • Сам текст и абсолютное позиционирование не нужны для правильного прогнозирования заголовков.
  • Машинное обучение для данных с древовидной структурой требует больших вычислительных ресурсов, но имеет значительный потенциал.
  • Tree-LSTM способны изучать очень прерывистые функции по многим элементам (у нас есть страницы из ~ 400 элементов).
  • Правильное пакетирование и структуры данных могут повысить производительность древовидных LSTM примерно в 30 раз.
  • По нашему набору данных процессоры конкурируют с графическими процессорами для этих моделей. Требуются дальнейшие исследования, чтобы понять, почему это так, и есть ли дальнейшие потенциальные оптимизации.