
Облако слов для кликбейта
TL;DR: Я добился точности распознавания кликбейта 99,2% на тестовых данных по особенностям заголовка и контента. Код доступен в репозитории GitHub.
Когда-то в прошлом я написал статью о выявлении кликбейта. Та статья получила хорошие отклики, а также много критики. Некоторые сказали, что нужно учитывать содержимое сайта, другие просили больше примеров из разных источников, а некоторые предложили попробовать методы глубинного обучения.
В этой статье я постараюсь решить эти вопросы и вывести выявление кликбейта на новый уровень.
Не бывает бесплатной еды информации
Просматривая собственную ленту Facebook, я обнаружил, что кликбейт нельзя классифицировать просто по заголовку. Он зависит ещё от контента. Если контент сайта хорошо соответствует заголовку, его не следует классифицировать как кликбейт. Однако очень сложно определить, что тако�� настоящий кликбейт.
Посмотрим некоторые реальные примеры на Facebook.
| 1. | ![]() |
| 2. | ![]() |
| 3. | ![]() |
| 4. | ![]() |
Что вы думаете теперь? Какие из них вы бы классифицировали как кликбейт, а какие нет? Стало сложнее после удаления информации об источнике?
Мои предыдущие модели на базе TF-IDF и Word2Vec классифицируют первые три как кликбейт, а может и четвёртую тоже. Однако среди этих примеров только два кликбейта: второй и третий. Первый пример — это статья, которую распространяет CNN, а четвёртая из The New York Times. Это два авторитетных источника новостей, независимо от того, что говорит Трамп! :)
Если Facebook/Google будут учитывать только заголовки, то они заблокируют все вышеперечисленные примеры в ленте новостей или результатах поиска.
Так что я решил классифицировать не только по заголовку, но ещё и по содержимому сайта, который открывается по ссылке, и по некоторым основным особенностям, заметным на этом сайте.
Начнём со сбора данных.
Сбор данных
В этот раз я извлекал данные из публично доступных страниц Facebook. Макс Вульф написал отличную статью, как это делается. Питоновские скрипты доступны здесь. Этот скрапер позволяет извлекать данные с публичных страниц Facebook, которые доступны для просмотра без авторизации на сайте.
Я извлёк данные со следующих страниц:
- Buzzfeed
- CNN
- The New York Times
- Clickhole
- StopClickBaitOfficial
- Upworthy
- Wikinews
Посмотрим, какие данные скрапер Макса Вульфа извлёк со страницы Clickhole.

Здесь интересны следующие поля:
- link_name (заголовок опубликованного URL)
- status_type (есть ли ссылка, фотография или видео)
- status_link (реальный URL)
Я отфильтровал статусы status_type==link, потому что интересуют только опубликованные URL, где есть какой-то текстовый контент.
Затем объединил собранные данные в два файла CSV: Buzzfeed, Clickhole, Upworthy и Stopclickbaitofficial попали в clickbaits.csv, а остальное — в non_clickbaits.csv.
Обработка данных и генерация признаков
Когда данные собраны и сохранены в два разных файла, пришло время сбора документов html по всем ссылкам и сохранения всех данных как файлов pickle. Для этого я создал очень простой скрипт Python:

Я очень строго подошёл к извлечению html, и в случае любого сбоя возвращался ответ “no html”. Для экономии своего времени и ускорения сбора данных использовал Parallel в Joblib. Обратите внимание, что нужно включить поддержку куков для краулинга сайтов вроде The New York Times.
Поскольку на моём компьютере 64 ГБ ОЗУ, я делал всё в памяти. Вы можете легко модифицировать ��од, чтобы сохранять результаты построчно в CSV и освободить много памяти.
Следующим шагом было сгенерировать признаки из этих данных HTML.
Для генерации признаков использовал BeautifulSoup4 и goose-extractor.
Сгенерированные признаки включали в себя:
- Размер HTML (в байтах)
- Длина HTML
- Общее количество ссылок
- Общее количество кнопок
- Общее количество полей ввода (inputs)
- Общее количество ненумерованных списков
- Общее количество нумерованных списков
- Общее число тегов H1
- Общее число тегов H2
- Длина текста во всех найденных тегах H1
- Длина текста во всех найденных тегах H2
- Общее количество изображений
- Общее количество тегов html
- Количество уникальных тегов html
Посмотрим на некоторые из этих признаков:

Похоже, что среднее количество списков больше на сайтах без кликбейта, чем на сайтах с кликбейтом. Я думал, что будет наоборот, но данные убеждают в обратном. Ещё выходит, что на сайтах с кликбейтом больше ссылок:

Поначалу я предположил, что на сайтах с кликбейтом будет больше изображений. И да, я был прав:

Вот как они привлекают людей. Картинки в статьях — это как червяки для рыб :)
Количество интерактивных элементов, например, кнопок, почти не отличается:

При работе с текстовыми данными принято составлять облака слов :), так что вот они.
| Облако слов заголовков для кликбейта | ![]() |
| Облако слов заголовков без кликбейта | ![]() |
В конце концов, текстовые признаки включают в себя:
- Весь текст H1
- Весь текст H2
- Мета-описание
Конечно, вы можете извлечь с веб-страниц больше текстовых и нетекстовых признаков, но я ограничил себя вышеприведённым списком. На этом этапе извлечения текстовых данных с веб-страниц я также удалил доменные имена из текстовых данных соответствующих веб-страниц, чтобы избежать какого-либо переобучения.
Я заметил, что текст с Buzzfeed часто содержит слова «Сообщите о проблеме спасибо», так что удалил их тоже. Также создал специальный список стоп-слов для веб-страниц:

В опрелелённый момент данные стали меня удовлетворять и появилась уверенность, что модель не переобучится на этих данных. Тогда я начал конструктировать некоторые крутые модели глубинного обучения. Но сначала посмотрим на окончательные данные:


В итоге я закончил примерно с 50 000 образцами. Примерно 25 000 для кликбейтерских сайтов, примерно столько же для остальных.
Начнём строить модели!
Модели глубинного обучения
Перед началом построения моделей глубинного обучения я разделил данные на две части, используя расслоение по метке (1=кликбейт, 0=не_кликбейт). Назовём это тестовыми данными, они состоят примерно из 2500 примеров из каждой выборки. Тестовые данные были оставлены в неприкосновенности, их использовали только для оценки модели. Настоящая модель была построена и проверена на остальных 90% данных — данных для обучения.
Сначала я попробовал простую модель LSTM с встроенным слоем, который преобразует положительные индексы в векторы плотности фиксированного размера. За ним следовали два плотных слоя (Dense) с отсевом (Dropout) и пакетной нормализацией (Batch Normalization):

Простая сеть показывает точность 0,904 на наборе валидации после нескольких периодов, а также точность 0,884 на тестовом наборе. Я расценил это как бенчмарк и постарался улучшить показатели. У нас всё ещё есть признаки контента, которые можно добавить! Возможно, это улучшит точность и на наборе валидации, и на тестовом наборе.
В следующей модели без изменения плотных слоёв, за которыми следуют слои LSTM, я добавил признаки текстового контента и осуществил их слияние перед проходом через плотные слои:

Такая модель сразу же улучшила показатели на 7% по бенчмаркам! Точность валидации выросла до 0,975, а точность на тестовом наборе — до 0,963. Недостаток этих моделей в том, что требуется много времени для обучения, поскольку необходимо усвоить встраивание. Чтобы преодолеть это, следующие модели я делал со встраиванием GloVe как инициализацией для встроенных слоёв. Было использовано 840 млрд 300-мерных вставок GloVe, обученных на данных Common Crawl. Эти вставки можно скачать здесь.
Предыдущие модели с распределённой плотностью по времени (time distributed dense), как рассказывалось в моей статье о дублирующихся постах в Quora. Вставки были инициализированы GloVe:

Модель показывает точность валидации 0,977, а точность на тестовом наборе — 0,971. Преимущество использования этой модели в том, что время обучения на период выходит менее 10 секунд, в то время как предыдущей модели требовалось 120-150 секунд на период.
Так что если вам нужна модель быстрой обучаемой нейросети для определения кликбейта, выбирайте одну из вышеупомянутых. Если нужна более точная модель, см. ниже :)

Эта сеть даёт хорошую прибавку к точности валидации: 0,983, но тестовая точность увеличилась очень незначительно до 0,975.
Если помните, я создал также некоторые численные признаки на основе того, что мы не видим, когда переходим по URL. Моя следующая и окончательная модель включает в себя и эти признаки тоже, вместе с LSTM для данных заголовка и текста контента:

Как оказалось, это лучшая модель с точностью валидации 0,996 и 0,991 на тестовом наборе. Каждый период занимает примерно 60 секунд на этой конкретной модели.
В таблице сведены воедино показатели точности, полученные разными моделями:

Подводя итоги, я смог построить модель для определения кликбейта, которая учитывает заголовок, текстовое содержимое и некоторые функции веб-сайта и демонстрирует точность выше 99% как во время валидации, так и на тестовом наборе.
Код всего, о чём я говорил, доступен на GitHub.
Все модели обучались на NVIDIA TitanX, система Ubuntu 16.04 c 64 ГБ памяти.
Объединим усилия и остановим кликбейт #StopClickBaits!
Не стесняйтесь комментировать или писать по электронной почте на abhishek4 [at] gmail [dot] com, если у вас есть какие-то вопросы.






