Как провести анализ рекламных креативов с помощью генеративных сетей

814d75b6bde2aae74dac8ba93db7089e.png

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

На связи Сергей и Григорий — Data Scientist’ы.

Сегодня расскажем, как заняли 2 место в общем зачете AI Generative Product Hackathon, инициированного Napoleon IT,   и 1 место в кейсе по анализу рекламных креативов для крупной российской фармацевтической компании.

Проблематика

Компания, занимающаяся промышленным производством и продажей медицинских препаратов, представила на хакатон кейс «Медиа корреляции», в котором требовалось проводить анализ рекламных креативов.


Даже самые лучшие креативы перестают приносить конверсии, если крутятся слишком долго. Отслеживание «выгорания» креатива позволяет маркетологу грамотно распределить бюджет и заблаговременно подготовить вариант на смену.


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

Постановка задачи

Кейс включал в себя две разные задачи:

  • Проанализировать ход рекламной кампании и предоставить рекомендации для маркетолога.

  • Предложить, основываясь на прошлом опыте, реализацию определенного способа коммуникации. К примеру, текст сообщения для рассылки.

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

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

Исходные данные

Исходные данные состояли из csv-файла с данными о предыдущих рекламных кампаниях различных препаратов. Данные были разделены по дням, и для каждого было известно общее количество показа данного типа рекламы («impressions») и количество кликов («clicks»).

997d85a8a35158638fb90635dad85c3a.png

Дополнительную информацию о препарате, ЦА и типе рекламы мы получали из столбца «campaign_name». 

В качестве целевой метрики использовался CTR, как единственно возможный. Для эффективного отслеживания изменений CTR было испробовано несколько кастомных оценок основанных на скользящих средних, среди которых наиболее эффективным и устойчивым к выбросам оказалось значение скользящего среднего по 3 последним значениям скользящего среднего CTR по последним пяти дням. Вот так выглядел код для преобразования данных:

def pretty_dataframe (dataframe):

    dataframe.columns = dataframe.iloc[0]

    dataframe.drop (dataframe.index[0], inplace=True)

    dataframe.drop ('campaign_id', axis=1, inplace=True)

    dataframe.drop ('platform', axis=1, inplace=True)

    dataframe = type_dataframe (dataframe)

    dataframe = clear_zeros (dataframe)

    dataframe = add_click_rate_to_dataframe (dataframe)

    dataframe = add_drug_name_to_dataframe (dataframe)

    dataframe = add_medic_group_to_dataframe (dataframe)

    dataframe = add_adv_format_to_dataframe (dataframe)

    dataframe = add_campaign_numbers_to_dataframe (dataframe)

    dataframe = add_rolling_mean_to_dataframe (dataframe, window=3)

    dataframe = add_rolling_mean_to_dataframe (dataframe, window=5)

    dataframe = add_rolling_mean_to_dataframe (dataframe, window=7)

    dataframe = add_trend_flag_to_dataframe (dataframe)

    dataframe = add_moving_average_change_to_dataframe (dataframe)

    dataframe = add_day_of_campaign (dataframe)

    return dataframe

Финальный набор новых признаков выглядел примерно так:

67e04168bb86797820925f31f99f761a.png

Техническое решение

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

b49b78e7d555f00d7ce86c817bce6973.png

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

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

def generate_report_text (self):   # Текст отчёта

        if (self.user_timeseries.trend.tail (1).values[0] == 'Increase') & (

                self.user_timeseries.moving_average_change.tail (1).values[0] > 0):

            trend = f'''\nПоказатель CTR за последние дни вырос на {self.user_timeseries.moving_average_change.tail (1).values[0]:.2f}%!\nДля получения дополнительно информации, можете ознакомиться с графиками.  

            '''

        elif (self.user_timeseries.trend.tail (1).values[0] == 'Decrease') & (

                self.user_timeseries.moving_average_change.tail (1).values[0] < 0):

            trend = f'''\nПоказатель CTR за последние дни упал на {self.user_timeseries.moving_average_change.tail (1).values[0]:.2f}%.\nРекомендуется ознакомиться с графиком общей тенденции CTR, чтобы принять решение.\n

            '''

        else:

            trend = f'''\nПоказатель CTR колеблется, либо остаётся на одном уровне.\nОзнакомьтесь с другими графиками, чтобы принять взвешенное решение.

            '''

        impressions = self.user_timeseries.impressions.sum ()

        ctr = self.user_timeseries.clicks.sum () / self.user_timeseries.impressions.sum ()

        return (

            f«Отчёт по креативу:\n»

            f«Название препарата: {self.user_timeseries.drug_name[1]}\n»

            f«Целевая группа: {self.user_timeseries.medic_group[1]}\n»

            f«Тип креатива: {self.user_timeseries.adv_format[1]}\n»

            f»{trend}\n»

            f«Общее количество показов: {impressions}, это больше чем у »

            f»{(self.history_timeseries_for_this.groupby (['campaign_name']).impressions.sum () < impressions).sum() * 100 / (self.history_timeseries_for_this.groupby(['campaign_name']).impressions.sum() < impressions).count():.1f}% "

            f«кампаний для данных препарата/ца/формата креатива, а так же больше чем у »

            f»{(self.history_timeseries.groupby (['campaign_name']).impressions.sum () < impressions).sum() * 100 / (self.history_timeseries.groupby(['campaign_name']).impressions.sum() < impressions).count():.1f}% "

            f«всех предыдущих рекламных кампаний.\n\n»

            f«СTR кампании за всё время составил {ctr * 100:.3f}%. Для данных препарата/ца/формата креатива средний показатель CTR составляет »

            f»{(self.history_timeseries_for_this.groupby (['campaign_name']).clicks.sum () * 100 / self.history_timeseries_for_this.groupby (['campaign_name']).impressions.sum ()).median ():.3f}% »

            f«Средний показатель CTR для всех предыдущих рекламных кампаний составляет »

            f»{(self.history_timeseries.groupby (['campaign_name']).clicks.sum () * 100 / self.history_timeseries.groupby (['campaign_name']).impressions.sum ()).median ():.3f}%\n\n»

            f«На данный момент кампания длится {self.user_timeseries.day.iloc[-1]} дней.»

            f«Обычно кампания для данных препарата/ца/формата креатива длится »

            f»{self.history_timeseries_for_this.groupby (['campaign_name', 'campaign_number'])['day'].max ().median ():.0f} дня/дней, а »

            f«средняя длительность всех рекламных кампаний составляет {self.history_timeseries.groupby (['campaign_name', 'campaign_number'])['day'].max ().median ():.0f} дня/дней.\n\n»

            f«В ходе этой кампании значение CTR росло {(self.user_timeseries.trend == 'Increase').sum ()} дня/дней, падало {(self.user_timeseries.trend == 'Decrease').sum ()} дня/дней и стабилизровалось, либо было стабильно {(self.user_timeseries.trend == 'Plateau').sum ()} дня/дней.»

        )

    def generate_report_images (self):

        report_images = []

        buffer = BytesIO ()

        fig2, ax = plt.subplots (figsize=(15, 9))

        image2 = sns.lineplot (data=self.user_timeseries[self.user_timeseries['day'] > 5], x='day', y='rolled_5', ax=ax,

                              label=«Текущая кампания»)

        history = select_campaign_metrics (drug_name=None, medic_group=self.user_timeseries.medic_group.iloc[0],

                                          adv_format=None)

        history = history[history['click_rate'] < 0.015]

        history = history[(history['day'] > 5) & (history['day'] < self.user_timeseries.day.max() + 5)]

        image2 = sns.lineplot (data=history, x='day', y='rolled_5', ax=ax, label=«Усредненная история кампаний»)

        image2.set_ylabel («Среднее CTR»)

        image2.set_xlabel («День кампании»)

        image2.set_title («Сравнение среднего CTR для данной ГРУППЫ ВРАЧЕЙ в ходе прошлых кампаниях и нынешней»)

        ax.legend ()

        ax.grid (True)

        sns.set_style («whitegrid»)

        image2.get_figure ().savefig (buffer, format='png')

        buffer.seek (0)

        report_images.append (buffer)

        buffer.flush ()

        buffer2 = BytesIO ()

        fig2, ax2 = plt.subplots (figsize=(15, 9))

        image2 = sns.lineplot (data=self.user_timeseries[self.user_timeseries['day'] > 5], x='day', y='rolled_5', ax=ax2,

                              label=«Текущая кампания»)

        history = select_campaign_metrics (drug_name=None, medic_group=None, adv_format=None)

        history = history[history['click_rate'] < 0.015]

        history = history[(history['day'] > 5) & (history['day'] < self.user_timeseries.day.max() + 5)]

        image2 = sns.lineplot (data=history, x='day', y='rolled_5', ax=ax2, label=«Усредненная история кампаний»)

        image2.set_ylabel («Среднее CTR»)

        image2.set_xlabel («День кампании»)

        image2.set_title («Сравнение среднего CTR для данного ТИПА КРЕАТИВА в ходе прошлых кампаниях и нынешней»)

        ax2.legend ()

        ax2.grid (True)

        sns.set_style («whitegrid»)

        image2.get_figure ().savefig (buffer2, format='png')

        buffer2.seek (0)

        report_images.append (buffer2)

        buffer2.flush ()

        buffer3 = BytesIO ()

        fig3, ax3 = plt.subplots (figsize=(15, 9))

        image3 = sns.lineplot (data=self.user_timeseries[self.user_timeseries['day'] > 5], x='day', y='rolled_5', ax=ax3,

                              label=«Текущая кампания»)

        history = select_campaign_metrics (drug_name=self.user_timeseries.drug_name.iloc[0], medic_group=None,

                                          adv_format=None)

        history = history[history['click_rate'] < 0.015]

        history = history[(history['day'] > 5) & (history['day'] < self.user_timeseries.day.max() + 5)]

        image3 = sns.lineplot (data=history, x='day', y='rolled_5', ax=ax3, label=«Усредненная история кампаний»)

        image3.set_ylabel («Среднее CTR»)

        image3.set_ylabel («День кампании»)

        image2.set_title («Информация об изменении CTR в ходе кампании»)

        image3.set_title («Сравнение среднего CTR данного ПРЕПАРАТА в ходе прошлых кампаниях и нынешней»)

        ax3.legend ()

        ax3.grid (True)

        sns.set_style («whitegrid»)

        image3.get_figure ().savefig (buffer3, format='png')

        buffer3.seek (0)

        report_images.append (buffer3)

        buffer3.flush ()

        return report_images

В окончательный отчет входят:

  • Шаблонная часть с текущими показателями рекламной кампании.

  • Гипотезы и рекомендации, сгенерированные языковой моделью.

  • Визуализация динамики показателей на фоне средней динамики по соответствующей группе.

Ниже приведен пример отчета, полученного от бота в автоматическом режиме:


Отчет по креативу:

Название препарата: -

Целевая группа: pharmacy

Тип креатива: banner

Показатель CTR за последние дни упал на -3.45%.

Рекомендуется ознакомиться с графиком общей тенденции CTR, чтобы принять решение.

Общее количество показов: 355187, это больше чем у 84.6% кампаний для данных препарата/ца/формата креатива, а также больше чем у 84.9% всех предыдущих рекламных кампаний.

СTR кампании за всё время составил 0.365%. Для данных препарата/ца/формата креатива средний показатель CTR составляет 0.441% Средний показатель CTR для всех предыдущих рекламных кампаний составляет 0.372%

На данный момент кампания длится 38 дней.Обычно кампания для данных препарата/ца/формата креатива длится 126 дня/дней, а средняя длительность всех рекламных кампаний составляет 47 дня/дней.

В ходе этой кампании значение CTR росло 11 дня/дней, падало 19 дня/дней и стабилизировалось, либо было стабильно 8 дня/дней.

Гипотеза 1: Кампания находится на этапе спада

За: CTR кампании в данный момент находится ниже среднего показателя как для данного препарата/формата, так и для всех проведенных кампаний. Также за последние дни наблюдается падение этого показателя, что свидетельствует о том, что кампания может быть менее эффективной.

Против: Длительность кампании для данного препарата/формата обычно составляет около 126 дней, кампания длится только 38 дней, что говорит о том, что она еще не достигла своего среднего срока жизни и есть потенциал для роста.

Гипотеза 2: Кампания имеет перспективы на улучшение показателей

За: CTR кампании уже превышает средний показатель по всем проведенным кампаниям. Большое количество показов говорит о хорошей достигаемости и масштабируемости кампании.

Против: В течение длительного периода (19 дней) значение CTR падало и текущий показатель является относительно низким. Это может говорить о неэффективности кампании в текущих рамках.

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

c99a18a21c27d4a044b2a1e9eaac2eb3.png1cb08629119d9f93e3ca82c70fd50ab9.png7c2343359d176fa9b0b320c7aa122aa7.png

Полный код решения посмотреть здесь  

Итог

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

Наша команда постаралась представить максимально подходящее кейсодержателю решение, поэтому в конечном продукте для хакатона не было обучено какой-либо предсказательной модели, весь упор делался на расчёт понятных и хорошо интерпретируемых метрик, которые будут полезны в анализе любому маркетологу. Думаем, что именно длительное и основательное выявление желаний и требований кейсодателя и принесло нам в результате 1 место по кейсу и 2 по всему хакатону) 

© Habrahabr.ru