Радар тенденций новостных статей по мнению ruDALL-E Kandinsky
Радар тенденций новостных статей по мнению ruDALL-E Kandinsky

Привет, Хабр!

Это одна из трех статей, в которых я (автор канала Зайцем по ХаХатонам) рассказываю о задачах Всеросийского чемпионата Цифрового Прорыва, объясняю базовые решения (baseline) и даю советы, которые помогут подняться выше по рейтингу. В данной статье будет рассмотрен кейс от РБК по предсказанию численных характеристик, которые в полной мере показывают популярность статьи.

Спойлер: в конце статьи есть советы для улучшения базового решения.

Цифровой Прорыв

Думаю, все и так знают, что такое Цифровой Прорыв. Однако, напомню, что в этом году основной тематикой стал искусственный интеллект. И сезон этого года в самом разгаре!

Хоть часть мероприятий уже прошла, впереди участников ждет ещё 19 региональных чемпионатов, 5 окружных хакатонов и 3 всероссийских чемпионата. Советую присоединиться ко мне и другим участникам, чтобы не упустить возможность выиграть денежные призы и крутые путешествия, а также набраться опыта на самых разных задачах.

Введение

Фраза “в нужный момент в нужном месте” хорошо описывает положение авторских текстов. Иногда качественно написанная статья проходит мимо своей потенциальной аудитории из-за более актуальных тем дня или неудачного заголовка.

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

Разумеется, что есть такие общемировые темы, которые невозможно предсказать, как, например, пандемия “коронавируса” или застрявший контейнеровоз, тем не менее исследования специалистов показывают, что в обществе есть тенденции, которые приходят и уходят в фиксированный временной период.

Условие задачи

У компании РБК довольно взрослая аудитория, которую она хочет расширить за счет добавления статей на актуальные темы. Для этого вам нужно проанализировать лучшие новости российских СМИ и научиться предсказывать их популярность. Ожидается, что для этого будут использованы NLP модели.

Данные

  • train.csv — файл для обучения, содержит 7000 строчек, каждая из которых представляет из себя одну новостную статью

  • test.csv — файл, содержащий 3000 строк, для предсказания

  • sample_solution.csv — пример файла для отправки

    В наборе данных присутствует уникальных 11 строк:

  • document id - идентификатор

  • title - заголовок статьи

  • publish_date - время публикации

  • session - номер сессии

  • authors - код автора

  • views - количество просмотров

  • depth - объем прочитанного материала

  • full_reads percent - процент читателей полностью прочитавших статью

  • ctr - показатель кликабельности

  • category - категория статьи

  • tags - ключевые слова в статье

Метрика

Цель модели участников — предсказать 3 численные характеристики, которые в полной мере показывают популярность статьи: views, full reads percent, depth.

Для оценки качества решения используется метрика R2.

???????????????????????? = 0. 4 * ????2???????????????????? + 0. 3 * ????2???????????????? ???????????????????? ???????????????????????????? + 0. 3 * ????2????????????????h

Подробно о решении

Методология решения

Решение данной задачи включает в себя два этапа:

  1. Подготовка и предобработка данных

  2. Обучение регрессионной модели

Какие библиотеки нам нужны

Для данного решение нам понадобится всего две библиотеки pandas и sklearn. Соответственно, импортируем их и нужные нам функции

import pandas as pd
from sklearn.metrics import r2_score
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.datasets import make_regression

Загрузка данных

Далее загрузим тренировочный и тестовый наборы данных. И выведем информацию о датасете.

df_train = pd.read_csv("/content/train.csv", index_col= 0)
df_test = pd.read_csv("/content/test.csv", index_col= 0)

df_train.info()

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

Data columns (total 10 columns):
 #   Column              Non-Null Count  Dtype  
---  ------              --------------  -----  
 0   title               7000 non-null   object 
 1   publish_date        7000 non-null   object 
 2   session             7000 non-null   object 
 3   authors             7000 non-null   int64  
 4   ctr                 7000 non-null   float64
 5   category            7000 non-null   int64  
 6   tags                7000 non-null   object 
 7   views               7000 non-null   int64  
 8   depth               7000 non-null   float64
 9   full_reads_percent  7000 non-null   float64
dtypes: float64(3), int64(3), object(4)

Преобразования данных

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

df_train["category"] = df_train["category"].astype('category')
df_train["category"] = df_train["category"].cat.codes
df_train["category"] = df_train["category"].astype('int')

df_train["authors"] = df_train["authors"].astype('category')
df_train["authors"] = df_train["authors"].cat.codes
df_train["authors"] = df_train["authors"].astype('int')

df_train['day'] = pd.to_datetime(df_train['publish_date']).dt.strftime("%d").astype(int)
df_train['month'] = pd.to_datetime(df_train['publish_date']).dt.strftime("%m").astype(int)

df_train.head(3)
Как выглядят данные после преобразования
Как выглядят данные после преобразования

Проделаем те же самые операции с тестовым набором данных.

df_test["category"] = df_test["category"].astype('category')
df_test["category"] = df_test["category"].cat.codes
df_test["category"] = df_test["category"].astype('int')

df_test["authors"] = df_test["authors"].astype('category')
df_test["authors"] = df_test["authors"].cat.codes
df_test["authors"] = df_test["authors"].astype('int')

df_test['day'] = pd.to_datetime(df_test['publish_date']).dt.strftime("%d").astype(int)
df_test['month'] = pd.to_datetime(df_test['publish_date']).dt.strftime("%m").astype(int)

df_test.head(3)
Тестовый набор данных
Можно заметить отсутствие таргетных колонок
Тестовый набор данных Можно заметить отсутствие таргетных колонок

Визуализация данных

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

features = list(set(df_train.columns) - set(['publish_date']))

_ = df_train[features].hist(figsize=(20,12))
Гистограммы распределения признаков
Гистограммы распределения признаков

Подготовка данных для обучения модели

Для начала разделим датасет по столбцам, в X пойдут столбцы с признаками, в y таргетные столбцы.

X = df_train.drop(["views","depth","full_reads_percent","title","publish_date", "session", "tags"], axis = 1)
y = df_train[["views","depth","full_reads_percent"]]

X.head()
Так выглядит датасет для обучения (X)
Так выглядит датасет для обучения (X)

Следующим шагом будет разделение X и y на тренировочную и валидационную части при помощи функции train_test_split из библиотеки sklearn.

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

Обучение и тестирование модели

Перейдем к долгожданному этапу обучения модели. Для базового решения будет использован RandomForestRegressor из sklearn.

regr = RandomForestRegressor(random_state=0)
regr.fit(X_train, y_train)

Создадим предсказания на валидационных данных и посчитаем метрику.

pred = regr.predict(X_test)

score_views = r2_score(y_test["views"], pred[:,0])
score_depth = r2_score(y_test["depth"], pred[:,1])
score_frp = r2_score(y_test["full_reads_percent"], pred[:,2])

Формулу для метрики можно найти выше. На валидации получаем скор равный 0.549725, что уже неплохо.

score = 0.4 * score_views + 0.3 * score_depth + 0.3 * score_frp

score

0.5497252178188364

Создание предсказаний на тестовых данных

Для отправки решения на платформу нам нужно создать предсказания на тестовых данных и сохранить в csv-формат.

pred = regr.predict(df_test[X.columns])

df_test["views"] = pred[:,0]
df_test["depth"] = pred[:,1]
df_test["full_reads_percent"] = pred[:,2]

df_test.reset_index()[['document_id', 'views', 'depth', 'full_reads_percent']] \
			 .to_csv('base_solution.csv', index=False)

При отправке данного решения на лидерборде увидим скор 0.485415. Лучший же результат на момент написания статьи равен 0.788117. Однако, не стоит отчаиваться, ведь есть огромное количество возможностей по улучшению решения.

Рекомендации для улучшения результата

Как улучшить данные

  • В чате чемпионата уже не один раз участники отмечали важность данных, которые можно спарсить из открытых источников. Так что советую посмотреть, что полезного можно извлечь с сайта РБК.

  • Мы никак не использовали такие признаки, как title и tags, которые потенциально несут в себе крайне полезную информацию. Тем более, что даже авторы задачи ставили её как NLP-задачу, а значит признак title крайне важен. С тем, что получится спарсить также нужно будет провести тщательную работу по генерации полезных признаков.

Варианты улучшения модели

  • В базовом решение используется самая простая модель. Сразу на ум приходит замена данной модели на градиентный бустинг, например, Catboost.

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

  • Также стоит задуматься об использовании языковых моделей (NLP).

Итоги

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

Все интересующие вас вопросе вы можете задать в канале Зайцем по ХаХатонам.

Всем удачи на хакатонах и чемпионатах!

Комментарии (2)


  1. sergeyns
    13.07.2022 10:13

    Да ладно, что так сложно? Побольше чернухи-порнухи, и популярность обеспечена. Даже на рбк. *сарказм*


    1. sweetlhare Автор
      13.07.2022 10:14

      Вполне адекватная задача на мой взгляд.