Русский
Русский
English
Статистика
Реклама

Блог компании dentsu aegis network

Новый tech новая этика. Исследование отношения людей к технологиям и приватности

30.07.2020 12:15:48 | Автор: admin
Мы в коммуникационной группе Dentsu Aegis Network ежегодно проводим исследование Digital Society Index (DSI). Это наш глобальный ресерч в 22 странах мира, включая Россию, о цифровой экономики и ее влиянии на общество.

В этом году мы, конечно, не могли обойти стороной COVID-19 и решили посмотреть на то, как пандемия повлияла на цифровизацию. В итоге DSI 2020 вышел в двух частях: первая посвящена тому, как люди стали использовать и воспринимать технологии на фоне коронавирусных событий, вторая как они теперь относятся к приватности и оценивают уровень своей уязвимости. Делимся результатами нашего исследования и прогнозами.

image

Предыстория


Как один из крупнейших игроков digital и проводник технологий для брендов, группа Dentsu Aegis Network верит в важность развития цифровой экономики для всех (наш девиз digital economy for all). Для того, чтобы оценить ее текущее состояние с точки зрения удовлетворения социальных потребностей, в 2017 году на глобальном уровне мы инициировали проведение исследования Digital Society Index (DSI).

В 2018 году было опубликовано первое исследование. В нем мы впервые оценили цифровые экономики (тогда было 10 исследуемых стран и 20 тысяч респондентов) с точки зрения того, насколько обычные люди вовлечены в цифровые сервисы и позитивно настроены по отношению к digital-среде.

Тогда Россия к удивлению многих обывателей заняла второе место по этому показателю! Хотя и была в конце десятки по остальным параметрам: динамичность (как сильно цифровая экономика влияет на благополучие населения), уровень доступа к digital и доверие. Один из любопытных выводов первого исследования заключается в том, что в развивающихся экономиках люди гораздо больше вовлечены в digital, нежели в развитых.

В 2019 году в связи с расширением выборки до 24 стран Россия опустилась на предпоследнее место в рэнкинге. А само исследование вышло под девизом Люди на первом месте (Human Needs in a Digital World), фокус сместился в сторону изучения удовлетворенности людей технологиями и цифрового доверия.

В рамках DSI 2019 мы выявили большую глобальную тенденцию люди стремятся вернуть цифровой контроль. Вот несколько триггерных в этом отношении цифр:
44% людей предприняли шаги по сокращению объема данных, которыми они делятся в интернете
27% установили программное обеспечение для блокировки рекламы
21% активно ограничивают количество времени, которое они проводят в интернете или у экрана смартфона,
а 14% удалили учетную запись в соцсети.

2020: техлэш или техлав?


Опрос DSI 2020 проводился в марте-апреле 2020 года, на которые пришелся пик пандемии и ограничительных мер по всему миру, среди 32 тысяч человек в 22 странах, включая Россию.

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

Техлав (techlove):
  • По сравнению с прошлым годом люди стали чаще использовать цифровые сервисы: почти три четверти опрошенных во всех странах (более 50% в России) заявили, что теперь активнее прибегают к банковским услугам и покупкам онлайн.
  • 29% респондентов (и в мире, и в России) признались, что именно технологии позволили им не потерять связь с семьей, друзьями и окружающим миром во время карантина. Еще столько же (среди россиян таких больше около 35%) отметили, что цифровые сервисы помогали расслабиться и отдохнуть, а также приобрести новые навыки и знания.
  • Сотрудники стали чаще использовать цифровые навыки в своей работе (это характерно почти для половины опрошенных в 2020 году против одной трети в 2018 году). На данный показатель мог повлиять массовый переход на дистанционный формат работы.
  • Люди стали больше верить в способность технологий решать социальные проблемы, такие как вызовы COVID-19 для здравоохранения и других сфер. Доля оптимистов в отношении значимости технологий для общества выросла до 54% против 45% в 2019 году (в России аналогичная динамика).

Техлэш (techlash):
  • 57% людей на глобальном уровне (53% в России) по-прежнему считают, что темпы технологических изменений слишком быстрые (показатель практически не изменился с 2018 года). Как следствие, они стремятся к цифровому балансу: почти половина респондентов (и в мире, и в нашей стране) намерены выделять время на отдых от гаджетов.
  • 35% людей, как и в прошлом году, отмечают негативное влияние цифровых технологий на здоровье и благополучие. Заметен разрыв между странами в этом вопросе: наибольшую обеспокоенность высказывают в Китае (64%), более оптимистично настроены в России (только 22%) и Венгрии (20%). Помимо прочего респонденты указывают, что технологии заставляют их чувствовать себя более напряженными, им становится труднее отключиться от digital (13% в мире и 9% в России).
  • Только 36% в мире верят, что новые технологии, такие как искусственный интеллект и робототехника, будут создавать рабочие места в будущем. Россияне более пессимистичны в этом вопросе (среди них таких 23%).
  • Около половины опрошенных, как и годом ранее, уверены в том, что цифровые технологии увеличивают неравенство между богатыми и бедными. Отношение россиян к данной проблеме также остается неизменным, но в нашей стране аналогичного мнения придерживаются только 30%. В качестве примера можно привести использование мобильного интернета и цифровых сервисов. Покрытие и качество интернет-услуг респонденты оценивают гораздо выше, чем их доступность для всего населения (см. график в начале статьи).

Прайваси дизрапшн


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

  • Меньше половины опрошенных в мире (и только 19% в России самый низкий показатель на опрошенных рынках) верят, что компании защищают конфиденциальность их персональных данных.
  • 8 из 10 потребителей как на глобальном уровне, так и в нашей стране, готовы отказаться от услуг компании, если узнают, что их персональные данные были использованы неэтично.

Далеко не все считают, что бизнесу приемлемо использовать все разнообразие персональных данных для улучшения своих продуктов и сервисов. На использование даже самой базовой информации, такой как адрес электронной почты, согласны 45% в мире и 44% в России.
Данными о просмотренных интернет-страницах готовы делиться 21% потребителей на глобальном уровне, информацией из профилей социальных сетей 17%. Интересно, что россияне более открыты в предоставлении доступа к истории браузера (25%). В то же время социальные сети воспринимаются ими как более приватное пространство отдавать эти данные сторонним организациям хотят только 13%.

image

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

image

image

Негативное отношение людей к вопросам сохранения приватности идет вразрез с их реальным поведением в сети. И это более чем парадоксально:
  • Люди не уверены в добросовестности использования их персональных данных, но все чаще делятся ими, больше и активнее используя цифровые сервисы.
  • Большинство пользователей не хотят делиться персональными данными, но все равно делают это (часто не отдавая себе отчета).
  • Люди требуют, чтобы компании в явном виде запрашивали у них разрешение на использование персональных данных, однако практически не читают пользовательские соглашения.
  • Потребители ожидают персонализацию в продуктах и услугах, но к персонализированной рекламе настроены более осторожно.
  • Пользователи стремятся вернуть себе цифровой контроль, но верят, что в перспективе преимущества digital-сервисов, вероятнее всего, перевесят возможные риски.
  • Технологии во благо обществу главный запрос потребителей на перспективу.

О будущем


По мере роста использования цифровых продуктов, например, для работы и диагностики здоровья, объем персональных данных продолжит увеличиваться, вызывая опасения относительно прав и возможностей по их защите.
Мы видим несколько сценариев развития ситуации от создания этических регуляторов и специальных надзорных корпоративных политик (central control) до партнерства компаний и пользователей в монетизации персональных данных (free for all).

image

Заглядывая на 2-3 года вперед, почти половина опрошенных нами потребителей хочет получать финансовую выгоду в обмен на свои персональные данные. Пока это, пожалуй, футурология: за последний год только 1 из 10 пользователей на глобальном уровне продал свои персональные данные. Хотя в Австрии о таких кейсах заявила четверть респондентов.
Что еще важно для тех, кто создает цифровые продукты и сервисы:
  • 66% людей в мире (49% в России) ожидают, что компании будут использовать технологии во благо обществу в ближайшие 5-10 лет.
  • Прежде всего, это касается развития продуктов и услуг, которые улучшают здоровье и благополучие такие ожидания разделяют 63% потребителей на глобальном уровне (52% в России).
  • Несмотря на то, что потребители обеспокоены этической стороной использования новых технологий (например, распознавание лиц), почти половина респондентов в мире (52% в России) готовы оплачивать продукты и услуги, используя системы Face-ID или Touch-ID.


image

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

Как бесплатно перенести свои любимые треки в Spotify, используя Javascript

18.08.2020 16:06:31 | Автор: admin

Все любители музыки в России с нетерпением ждали выхода Spotify на наш рынок. Когда наконец это случилось, перед пользователями встала проблема переноса музыки из других сервисов. Лично у меня за годы накопилась огромная коллекция музыки на разных платформах: Яндекс.Музыка, ВКонтакте, личная коллекция на жёстком диске и облако на Яндекс.Диске.


Сначала я думал воспользоваться сервисами вроде Soundiiz и TuneMyMusic. Но вдохновившись статьёй об использовании Python в тех же целях, мне стало интересно решить эту задачку с помощью Javascript. Далее я опишу ряд простых шагов с исходным кодом, которые помогут перенести вашу музыку из разных источников в Spotify.



Итак, для начала нам потребуется три вещи:
Node.js, браузер Google Chrome и исходники скриптов.


И установить зависимости, конечно же.


Идём в корень проекта и выполняем команду:


npm i

Готово?


Отлично, идём дальше.


В первую очередь, нам необходимо получить список треков.


Дисклеймер:

Код и подходы, представленные в скриптах, не являются эталонными.
Уверен, есть масса более лаконичных способов.
Мне просто было интересно реализовать это именно так.


ВКонтакте


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


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


Открываем свой профиль, переходим в музыку и прокручиваем страницу вниз. После этого открываем консоль браузера (F12) и копируем туда то, что находится в файле src/grabTracksVk.js. Нажимаем Enter.


В консоли появится список ваших треков.


Яндекс.Диск


Долгое время я хранил свою коллекцию на жёстком диске, пока не осознал, что слишком уж много она стала занимать полезного места. Поэтому, собравшись с духом, я перенёс всё на Яндекс.Диск. И лишь самое любимое оставил на винте.


Здесь нам потребуется получить токен для работы с API. Парсить DOM-дерево в этом случае уже не получится.


Идём по ссылке:
https://yandex.ru/dev/disk/poligon


Нажимаем кнопку "Получить OAuth токен".


Далее нужно пройти в файл src/grabTracksYandexDisk.js.


Там найти строчку


const token = ''

И вставить полученный токен.


Например:


const token = 'AgAAAAACbokuAADL45FDSww2d3SDSffddwAAAAA';

Если вы, как и я, храните огромную коллекцию дискографий, в которой более тысячи групп, возможно, вам будет достаточно получить список только из названий исполнителей.
Для этого рядом стоит переменная onlyArtist, которой следует установить значение true.


После этого нужно открыть консоль в корне проекта и запустить выполнение скрипта командой:


node ./src/grabTracksYandexDisk.js

Результат появится в файле collection-yandex-disk.txt.


У Яндекс.Диска в API есть очень удобный для этих целей запрос (получить все файлы сразу):


https://cloud-api.yandex.net/v1/disk/resources/files

В параметрах можно указать media type, чем я и воспользовался.


Файловая система


Поскольку на Яндекс.Диске лежит не всё, то нужно ещё перенести список из файловой системы. Плюс, я знаю людей, которые облачным сервисам не доверяют, а попробовать Spotify (или аналоги) хочется.


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


Итак, идём в файл src/grabTracksFileSystem.js.


Ищем строку


const rootPath = ''

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


Например:


const rootPath = 'D:/Music/Metal';

Рядом находятся ещё две переменные.


mask нужна для фильтрации файлов по расширению. В массив следует передать необходимые значения.


Например:


const mask = ['.mp3', '.flac'];

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


Затем нужно открыть консоль в корне проекта и запустить выполнение скрипта командой:


node ./src/grabTracksFileSystem.js

Результат появится в файле collection-fs.txt.


Яндекс.Музыка


Здесь дела обстояли сложнее. У Яндекс.Музыки нет открытого API, поэтому я сперва решил, как и в случае с ВКонтакте, просто открыть страницу с плейлистом и забрать из него треки через парсинг DOM-а. Но не учёл того, что единовременно в DOM-е могут находиться максимум 150 элементов, а остальные начинают выгружаться. И при прокрутке появляются следующие элементы, которые заменяют собой предыдущие.


В итоге я придумал другое решение (впечатлительным лучше не смотреть):


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


Чтобы это сделать, мне пришлось немного поменять прототип метода send у XMLHttpRequest.


Из минусов можно отметить низкую скорость работы. Если треков очень много это займёт время. Тем не менее результат достижим!


Итак, алгоритм действий такой:


Открываем Яндекс.Музыку, далее "Моя коллекция" -> плейлисты, плейлист "Мне нравится" -> нажимаем на название (теоретически, любой другой плейлист тоже можно). Далее должен открыться плейлист на всё окно.


Открываем консоль браузера, вставляем туда код из файла src/grabTracksYandexMusic и нажимаем Enter.


Ждём.


После завершения выполнения скрипта в консоли распечатается список треков.


P.S.:
Во время работы скрипта лучше ничего не делать.


Прервать выполнение всегда можно, перезагрузив страницу.


Apple Music


Сам я этим сервисом не пользуюсь, но просьб было очень много, поэтому этот сервис мы тоже разберём.
Здесь я просто парсю DOM, как и в случае с ВКонтакте. Проблема лишь в том, что ни у кого не нашлось очень длинного плейлиста. И может возникнуть ситуация, как в Яндекс.Музыке.


Итак, алгоритм действий такой же, как и с ВКонтакте.
Открываем наш плейлист, прокручиваем страницу вниз и выполняем в консоли бразуера код из файла src/grabTracksAppleMusic.js.


В консоли появится список ваших треков.


Spotify


Наконец, мы получили список треков, которые хотим добавить в Spotify.


У Spotify с API всё очень даже хорошо.
Но для работы нам необходимо получить авторизационный токен.
Для этого нужно зарегистрировать своё приложение.


Идём сюда:
https://developer.spotify.com/dashboard


Нажимаем на кнопку "Create an app", даём имя нашему приложению.
Заходим внутрь.
Здесь нам нужно забрать Client ID и Client Secret.


Ищем в файле src/getSpotifyToken.js переменные client_id и client_secret. Вставляем значения соответственно.


Далее нам нужно задать redirect_uri.


На странице нашего приложения нажимаем кнопку "Edit settings".
Находим там "Redirect URIs", пишем http://localhost:8888/callback.
Нажимаем кнопку "Save" внизу.


Теперь мы готовы перенести наши треки!


Создаём файлик в корне проекта с именем tracks.txt, вставляем в него список треков, полученный ранее.
Треки должны разделяться вертикальным пробелом между собой.


Например:


Slaughter To Prevail - Misery SermonGrim Christmas - Jingle BellsNovembers Doom - Rain

Далее нам понадобится скрипт для получения токена: ./src/getSpotifyToken.js
В нём я минимально изменил код из официального примера Spotify под наши нужды.


Запускаем скрипт:


node ./src/getSpotifyToken.js

Откроется браузер с окном авторизации Spotify.
Нам нужно сделать логин и дать наше согласие на доступ.


После чего появятся две строчки:
access token: <значение токена> и refresh token: <значение токена>.


Нас интересует первый.
Копируем его значение, кликнув на него.


Далее идём в файлик src/addTracksToSpotify.js.
Находим там строку


const accessToken = ''

И вставляем туда наш токен.


После этого запускаем скрипт командой:


node ./src/addTracksToSpotify.js

И ждём.


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


Также появится файлик spotify-tracks-no-found.txt, в котором будет список не найденных в Spotify треков.


Если вы хотите добавить новых исполнителей, то алгоритм точно такой же, только скрипт другой (в коде тоже отличий мало).


Создаём файлик в корне проекта с именем artists.txt, вставляем в него список исполнителей.


Они должны разделяться вертикальным пробелом между собой.


Например:


Bob DylanSlaughter To PrevailNovembers Doom

Далее всё то же самое, что и с треками, только файл нам нужен src/addArtistsToSpotify.js.
После вставки токена, просто запускаем скрипт командой:


node ./src/addTracksToSpotify.js

В вашем аккаунте появятся новые исполнители.
Но если вдруг кого-то не нашлось, он будет помещён в файл spotify-artists-no-found.txt.


Всё!


P.S.:
Если хочется добавить все треки сразу в избранное, это удобнее всего сделать через десктопную версию приложения.


Переходим в плейлист, кликаем на первый трек и нажимаем Ctrl + A (Cmd + A).
Будут выбраны все треки в плейлисте. И нажимаем лайк.
Приятного прослушивания! :)

Подробнее..

Поиск автовладельцев в Instagram от хвостов китов до автомобилей

06.08.2020 08:23:05 | Автор: admin

image


К нам в рекламную группу Dentsu Aegis Network часто приходят компании-рекламодатели с запросом изучить и проанализировать их целевую аудиторию. И сделать это необходимо быстро и точно. Предположим, у нас есть клиент из автопрома, который хочет найти владельцев авто, а потом узнать их интересы, пол, возраст в общем, раскрасить аудиторию. Логично было бы сделать социологическое исследование, но это займет несколько недель. А если у клиента очень дорогие авто стоимостью выше 2,5 млн рублей? Много ли таких владельцев наберется для исследования? А для фокус-группы?


Хорошим способом найти нужного человека остается социальная сеть. Это место, где пользователь оставляет о себе много полезной информации, а если даже информации нет, то можно попробовать собрать её с помощью черной магии. Да, все верно, тут на помощь приходит data science.


Будем решать задачи поступательно. Давайте подумаем, как найти автовладельца в социальной сети.


  1. Провести текстовый анализ постов
    Казалось бы, здесь все просто. Написал помыл свою зайку/ласточку или вечерние покатушки, и мы нашли нужного человека. Но потом выясняется, что часть примеров нерелевантна, а чистить приходится руками.
  2. Поискать в хэштегах
    Тоже вариант, но не соответствующих запросу аккаунтов еще больше: попадается и коммерция, и ребята, которые тюнингуют старые машины, и дрифтеры. А мы ищем владельцев авто за 2,5 млн рублей здесь и сейчас.
  3. Найти пост с фотографией того авто, которое нам нужно и определить модель
    Затем нужно придумать эвристику, которая с большой вероятностью бы говорила, что владелец этого аккаунта в соцсети также является владельцем нужной нам машины.

Мы пробовали все варианты, но остановились на последнем.


Первый подход к снаряду


Итак, нам необходима модель, которая бы определяла марку и производителя авто. Но сколько марок и производителей мы хотим охватить? Здравый смысл подсказывает, что можем взять наших клиентов, их основных конкурентов и на этом остановиться. Около 100 различных марок автомобилей более чем достаточно. Каждая марка автомобиля будет являться отдельным классом в модели.


Кажется, что 100 это не так уж и много. Возможно, нам даже подойдет что-то из методов обучения с учителем. Это значит, что мы будем проводить обучение модели через примеры, по принципу стимул-реакция.


А что если у нас появится новый клиент из нового сегмента? Будем добирать еще 50-100 марок? Да, есть компании, которые идут именно таким путем, новая проблема это новая модель. В итоге получается зоопарк различных моделей. Мы решили, что на обучение новой модели у нас просто нет времени, поэтому сделаем все сразу.


Небольшая, но важная подготовка датасета


Если мы хотим что-то более универсальное, то в таком случае напрашивается подход обучения без учителя: система должна обучаться спонтанно, без привлечения экспериментатора. Для такого подхода собирать данные проще берем из интернета все картинки по релевантному запросу. Благодаря тому, что многие поисковые инструменты позволяют фильтровать контент по лицензии, можно быть спокойными за соблюдение авторских прав.
Сначала данные:
Оказалось, что многие поисковые инструменты под запросом автомобиль могут понимать вот это:
image


Технически это, конечно, автомобили, но не совсем те, которые нужны нам.
Гораздо хуже, когда результат такой:
image


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


Существующие подходы в компьютерном зрении

image
Источник
Object detection это технология, связанная с компьютерным зрением и обработкой изображений, которая позволяет находить объекты определенного класса на изображениях и видео.


В качестве архитектуры взяли retinanet, так как уже был готов весь пайплайн, нужно только подложить разметку. Для разметки воспользовались инструментом CVAT (подробнее мы рассказывали на pycon19) и всей командой потратили несколько часов на это веселое занятие. За это время удалось разметить несколько тысяч картинок, что позволило обучить модель с mAP ~ 0.97.


С какими сложностями мы столкнулись при подготовке набора данных? Первое, что хочется отметить это отсутствие автолюбителей в нашей команде, из-за чего иногда возникали споры по поводу сложных случаев, например, когда кузов авто визуально едва ли отличим на разных моделях. Хорошим примером могут послужить Lexus RX и Lexus NX.
image


Гораздо сложнее, когда кузов один и тот же, а названия автомобилей разные. Такое случается, когда бренд по разному себя позиционирует на разных рынках. Примеры Chevrolet Spark и Ravon R2:
image


Autoencoder


Приступаем к выбору модели. Первое что пришло нам на ум, это автоэнкодеры.


Автоэнкодер

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


  • Энкодер сжимает входные данные в скрытое пространство (latent space).
  • Декодер восстанавливает входные данные из скрытого пространства.

Прелесть автоэнкодера в том, что он обучается без учителя. А скрытое пространство может помочь кластеризовать данные.


С помощью автоэнкодеров кластеризуют даже Trading Card Game карточки, например, Magic the Gathering, так что появилось желание сделать кластеризацию автомобилей именно через этот инструмент. К сожалению, получилось неудачно: время потратили, а результат не оправдал ожиданий. Стали думать дальше.


Semantic Embeddings


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


Идея подробно:

Иерархия классов представляет собой направленный ацикличный граф $G = (V, E)$ с множеством вершин $V$ и множеством ребер $EVV$, что определяет гипонимические связи между семантическими понятиями. Другими словами, ребро $(u,v)E$ означает что $v$ является подклассом $u$. Тогда классы являются вершинами такого графа $C={c_{1},...,c_{n}}V$. Пример графа представлен ниже:



Авторы использовали меру непохожести $d_{G}:CC R$, рассчитываемую по формуле $d_{G}(u,v) =\frac{height(lcs(u,v))}{max_{wV}height(w)}$, где под высотой имеется в виду самый длинный путь от текущей вершины до листа. $lcs$ двух вершин это ближайший предок к этим двум вершинам. Так как $d_{G}$ ограничено между 0 и 1, авторы определили меру семантической близости между двумя семантическими понятиями как $s_{G}(u,v) = 1 d_{G}(u,v)$.
Рассмотрим граф из рисунка выше, его высота равняется 3, $inline$lcs("dog","cat")="mammal"$inline$, а $inline$lcs("dog","trout")="animal"$inline$, тогда $inline$s_{G}("dog","cat") = 1 d_{G}("dog","cat")=1-1/3=2/3$inline$, а $inline$s_{G}("dog","trout")=1-2/3=1/3$inline$. Таким образом, кошка и собака в представленной модели более близки семантически друг к другу, чем собака и форель.
Цель авторов посчитать вектора единичной длины $(c_{i})^n$ для всех классов $c_{i}, i=1,...,n$, так, чтобы скалярное произведение векторов соответствующих классов было равно мере их похожести:

$$display$$_{1 i,j n}:(c_{i})^T(c_{j}) =s_{G}(c_{i},c_{j})$$display$$


$$display$$_{1 i n}:(c_{i})= 1$$display$$



Собирать такой датасет показалось слишком долгим и дорогим процессом, ведь помимо сбора релевантных фотографий необходимо думать над грамотной иерархией классов по текстовым запросам. Но авторы предлагают вариант без иерархии, с поддержкой датасета Stanford Cars, который имеет 196 различных классов автомобилей и по 80 фотографий на каждый (почти то, что нам нужно, да?). Результат на stanford cars оказался лучшим, чем все то, что было до этого. Но на наших данных повторить успех не удалось. Понять, что на это повлияло плохая разметка, шум в данных или что-то еще не удалось, так как время на эксперименты закончилось, а проект был отложен на неопределенный срок.


Примерно так чувствовала себя наша нейросеть на тот момент:

Siamese Networks или от китов к машинам


Спустя 9 месяцев снова родилась необходимость в определении модели авто по фото. На этот раз у нас была возможность привлечь команду асессоров, чтобы собрать более качественный датасет. А самое главное, появилось понимание, какие марки авто нам нужны точно и какие необходимо добавить, чтобы иметь задел на будущее. Вместе с более качественной разметкой пришла идея использовать metric learning подход, например, сиамские сети c triplet loss.


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



Было привлекательно, что решение представляло из себя всего одну модель, а не целый зоопарк, как это бывает на kaggle. Из интересных особенностей, которые могли быть использованы у нас: на вход к 3 стандартным RGB каналам подаются маски. Поэтому размер входа составляет 512х256х4.


В первом подходе мы решили обучить модель без использования масок и получили на валидации mAP = 0.89. Такой результат с самого старта нас очень обрадовал. Но прогнав модель на отложенной выборке из живой среды обитания результат оказался плачевен.


И снова похожие ошибки:

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



Что поступало на вход, и это ещё очень удачный пример:



Очевидно, что нужно было что-то сделать с обучающим набором данных. Мы решили оставить всё как есть, но использовать более сильную аугментацию. Для этого использовали пакет albumentations. Он поддерживает bounding boxы и maskи, имеет множество готовых преобразований: помимо стандартных flip-crop-rotate еще и различные distortionы.


К более сильной аугментации решили сразу добавить маски. Для предсказания масок использовали фейсбуковский detectron. Мотивацией послужило наличие модели для сегментации изображений, обученной на датасете COCO, в котором присутствует класс авто. И наличие пайплайна под детектрон, потому что он уже был использован в команде. А еще мы любим копаться в гите facebookresearch.


Обучив модель на дополненных данных с новой аугментацией, мы смогли получить mAP ~ 0.81. Это сильно хуже предыдущего результата, но зато модель получилась более жизнеспособная.


Эвристики


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


  • Пользователь сфотографировался с автомобилем на фоне.
  • Авто могло оказаться на фоне случайно.
  • Автолюбитель выкладывает фотографии его любимых марок. Тут может быть случай и многих различных марок, и одной единственной.
  • Мы попали в профиль автодилера, автомастерской или автоблогера.
  • ...
  • И десятки других вариантов.

Как понять, что две фотографии одного и того же автомобиля являются одним и тем же автомобилем? Можно определить цвет автомобиля. Но с цветом имеются свои проблемы, так как даже зеленый цвет при определенном освещении выглядит серым. Кажется, что если мы научились отличать несколько сотен моделей авто друг от друга, то с цветом проблем возникнуть не должно. Поэтому мы взяли пайплайн для retinanet и обучили еще одну нейронную сеть на определение 16 цветов.


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


Заключение и применение модели


Мы научились находить владельцев определенных авто по их постам. Что нам для этого понадобилось: нейронная сеть для первичной чистки данных; 5 человек, помогающих с разметкой; нейронная сеть для создания масок; нейронная сеть для определения марки автомобиля; нейронная сеть для определения цвета автомобиля. Почему бы теперь не применить результат в бою?


Попробуем найти владельцев BMW с большим количеством подписчиков:



Фотография BMW M5. Источник.



Ещё один пример BMW M5 от того же автора. Источник.



BMW 3 серии. Источник.



BMW M3. Источник.


Зачем нам искать владельцев BMW с большим количеством подписчиков, которые часто постят свой автомобиль? Например, они могут стать амбассадорами бренда.


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



Источник.



Источник.



Источник.


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


Распределение по полу:
image


Распределение по возрасту:
image


Топ-10 верхнеуровневых интересов:
image


Спустимся на два уровня ниже в категорию Спорт и активных отдых:
image


Кажется, что Audi преимущественно не женский автомобиль. Разберемся почему же у нас выходит 57% женщин? Согласно исследованию brand analytics, распределение мужчин и женщин в инстаграме соотносится как 25:75. Учитывая этот факт, можно сделать перевзвес наших данных и получить более натуральное распределение по полу среди автовладельцев Audi. По этой причине при анализе социальной сети необходимо учитывать её специфику.


Что ещё мы можем узнать про автовладельцев?


Например, откуда они:
image


И куда они путешествуют:
image


Что можно сделать еще?


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


  • В первую очередь хотелось бы ещё раз пройтись по датасету. Как известно, есть прямая взаимосвязь между качеством моделей машинного обучения и данных, которые они используют.
  • Mixup аугментация. Смысл её в том, чтобы с разными весами смешать две картинки в одну. Веса при этом должны давать в сумме единицу. Сложность заключается в том, что для такой аугментации нужны несколько картинок. В то время как пакет albumentation работает с одной картинкой на вход. Делать самописное решение или добавлять стороннее для проверки гипотезы на тот момент показалось нецелесообразным.
    Пример:
    image
    Источник
  • Попробовать другие архитектуры нейронных сетей. Тут всё просто мы взяли решение первого места. Но ведь можно попробовать более простые архитектуры, потерять немного в качестве, но сильно выиграть в скорости. Ведь мы не на kaggle, и нам не так важны сотые и тысячные значения в метрике качества.
  • Добавить задачу определения цвета автомобиля в нейронную сеть определяющую марку и модель автомобиля. Иногда оказывается, что добавление дополнительного выхода в нейронную сеть для решения ещё одной проблемы повышает и качество метрики, и обобщающую способность.

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


image
Источник


Благодарим за внимание и надеемся что этот материал будет полезен и интересен читателям Хабра!


Статья написана при поддержке моих коллег Артёма Королёва, Алексея Маркитантова и Арины Решетниковой.


R&D Dentsu Aegis Network Russia.

Подробнее..

Tableau Hyper API BI-команда скажет вам спасибо

28.08.2020 12:23:00 | Автор: admin
Мы хотим рассказать вам о том, как мы помогли нашей BI-команде организовать автоматический процесс доставления данных на Tableau-сервер из MongoDB, используя таблошный формат хранения данных hyper, а сам процесс настройки формирования данных осуществляется через простой веб-интерфейс.

В начале коротко расскажем, как выглядел процесс до и после того, как мы научили наш внутренний продукт А1 программно собирать датасорсы и публиковать их на Tableau Server. Затем подробнее разберем проблему BI-команды и найденное решение, а также заглянем под капот (здесь о создании .hyper файла, публикации файла на tableau-сервере и обновлении хайпера). Добро пожаловать под кат!

Tableau Hyper API BI-команда скажет вам спасибо


Мы в рекламной группе DAN много работаем с данными мониторинга рекламы от Mediascope, индустриального измерителя на медиарынке. Сценарии бывают разные: некоторые сотрудники выгружают сырые данные, другие пользуются готовыми предобработанными базами, а кто-то заказывает разработку автоматизированных дашбордов на этих данных. О последнем сценарии расскажем подробнее дашборды наши BI-разработчики собирают в Tableau, но прежде чем начать рисовать, им тоже необходимо привести данные к нужному удобному для разработки формату.

Жизненный путь данных от сырья до красивых автоматизированных графиков можно условно разбить на 4 шага:
  1. Получение сырых данных
  2. Чистка и доработка данных
  3. Создание источников данных для Tableau
  4. Разработка визуализаций


Было


До того, как мы научились программно генерировать источники данных для Tableau, процесс выглядел так:
Старый жизненный процесс

1. Получение сырых данных
Пользователи формируют табличные-отчеты через внутренний инструмент А1. О нем мы подробнее расскажем далее.

2. Чистка и доработка данных
Возможность трансформации данных также заложена в инструмент А1, после чего очищенные данные можно выгрузить в xslx/csv и продолжить с ними работу вне инструмента. Тут стоит отметить, что некоторые пользователи ограничиваются 1ым пунктом и после выгрузки отчетов дорабатывают данные своими силами.

3. Создание источников данных для Tableau
Раньше заказчики дашбордов приходили с набором экселей, которые они сгенерировали на предыдущих пунктах. А BI-разработчики сводили эти эксели в единый датасорс (таблошный сленг) своими силами. Не всегда удавалось ограничиться только инструментами Tableau, часто писали скрипты на Python.

4. Разработка визуализаций
Наконец, вершина айсберга создание дашборда и публикация его на Tableau Server, где его увидит заказчик. На практике визуализация часто занимает меньше времени, чем сбор данных и настройка обновлений.

Боль копилась на третьем шаге, так как росло количество кастомных решений, которые было затратно поддерживать и реализовывать. Также регулярно просачивались ошибки в данных со второго шага промежуточный эксель между двух систем (А1 и Tableau) прямо-таки подталкивал пользователя: давай поправь что-нибудь ручками, никто не заметит.

Стало


Основной задачей было исключить эксели между 2 и 3 шагом. В итоге мы научили А1 собирать датасорсы и публиковать их на Tableau Server. Вот что получилось:
Новый жизненный процесс

Сейчас шаги с 1 по 3 происходят в А1, на выходе BI-команда получает опубликованный на Tableau Server датасорс для разработки визуализаций. Связующим звеном стал Hyper API, о котором дальше и пойдет речь.

Результаты


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

Освободили время BI команды. Раньше было мало шаблонных решений и много кастомизаций. Чаще всего под каждый проект дописывали обработку на Python. В редких случаях, где обработка не нужна была, работали сразу в Tableau Desktop (основной инструмент разработки).
Сейчас подготовка датасорса это: накликать нужные поля в интерфейсе А1, отметить, какие из них разворачиваем в строки (если это необходимо) и опционально заранее определить тип полей.

Не нагружаем Tableau Server обновлением громоздких датасорсов обновление происходит силами А1, а на сервер закачивается уже готовый hyper.

*Бонус стимулируем пользователей работать внутри А1. Если раньше некоторые пользователи после выгрузки сырых отчетов, дорабатывали их вручную вне инструмента, то теперь, так как весь процесс с 1 по 3 шаг происходит в А1, пользователям проще настраивать процесс чистки там же.

Проблема и решение


Немного о А1


Прежде чем мы начнем рассказывать про наше решение, нужно рассказать о нашем внутреннем продукте А1, к которому прикрутили генерацию датасорсов.

А1 это внутренний продукт компании, который призван упростить рабочий процесс сотрудникам, у которых основная работа заключается в следующем:
  • Забирать данные из программных продуктов компании MediaScope
  • Приводить эти данные (чистить) в удобный для аналитиков-предметников вид
  • По необходимости подготавливать данные для создания дашбордов (об этом мы сегодня и поговорим)

После того, как пользователь завершает чистку данных, они хранятся в системе А1. В нашей терминологии это называется Контейнером. Контейнер это обычный документ в MongoDB, который нам и надо передавать на Tableau-сервер.

Проблема BI-команды


Нашей команде BI-разработчиков нужно было как-то получать данные из А1, которые хранились в MongoDB, и на основе полученных данных строить дашборды. В первую очередь мы попробовали забирать данные из MongoDB штатными средствами табло, но проблему это не решало:
  • Поскольку данные хранятся в MongoDB, то на вход в табло поступают данные с произвольной структурой, а это значит, что постоянно пришлось бы заниматься поддержкой данной логики.
  • Для агрегации данных из MongoDB нужно было тащить определенные записи из коллекции, а не коллекцию целиком драйвер Tableau делать это не умеет.
  • Кроме всего прочего, мало было получить данные: иногда их нужно было разворачивать делать unpivot некоторых столбцов в строки. Что тоже не так просто было сделать, от слова совсем.

Что мы придумали


Было принято решение попробовать решить данную задачу своим велосипедом, используя библиотеку Tableau Hyper API. Данная библиотека позволяет создавать файл в формате .hyper, в который легко складывать данные, а потом использовать как источник данных для создания дашборда на табло сервере.

Как описывают хайпер сами разработчики табло:
Hyper это высокопроизводительный in-memory механизм обработки данных, который помогает клиентам быстро анализировать большие или комплексные датасеты, эффективно оценивая запросы в базу данных. Основанная на платформе Tableau, Hyper использует собственные методы динамической генерации кода и передовые технологии параллелизма, позволяющие достигать высокой производительности при создании экстрактов и выполнение запросов.

Примерный процесс работы в нашей программе выглядит следующим образом:
  • Пользователь выбирает контейнеры и нужные колонки
  • Система выдергивает из контейнеров данные
  • По полученным данным система определяет типы колонок
  • Инициализируется создание хайпера и вставка в него данных
  • Загружается хайпер на табло-сервер
  • BI-разработчики видят хайпер на сервере и создает на его основе дашборд

Когда в контейнеры зальют новые данные системе будет подан сигнал, что нужно обновить хайпер:
  • Система скачает с табло-сервера хайпер
  • Заберет из MongoDB свежие данные и обновит хайпер
  • После система загружает на сервер новый хайпер с перезаписью существующего
  • Пользователю достаточно просто нажать на кнопку Refresh, чтобы в дашборде отобразилась актуальная информация


Что видит пользователь


Как уже говорилось ранее, А1 является веб-приложением. Для создания сервиса генерации хайпера на фронте мы использовали Vue.js и Vuetify.

Интерфейс приложения разделен на три экрана.
Экран с выбором контейнеров

На первом экране пользователь выбирает нужные контейнеры и колонки.

Если включена опция Unpivot, то в хайпере будут созданы две дополнительные колонки: variable- наименования колонок, которые выбираются столбцом Metrics и values- значения из этих колонок.

Столбец Dimension добавляет в хайпер колонку с одноименной выбранной колонкой. Количество выбранных колонок Dimensions и их названия должны быть одинаковые во всех контейнерах, чтобы не нарушилась целостность таблицы в хайпере, поэтому присутствует столбец Имя hyper, который позволяет указать имя выбранной колонке, если в контейнерах они называются по-разному.
Логи процесса создания хайпера
На этом процесс настройки хайпера заканчивается. Пользователю достаточно перейти на второй экран, нажать Создать hyper и наблюдать за ходом событий в логах.

Дополнительные настройки

На третьем экране находятся дополнительные настройки:
  • Можно включить игнорирование обновлений, если нам не нужно, чтобы система автоматически обновляла хайпер
  • Можно указать email, на который отправлять отчеты об обновлениях
  • Можно руками указать тип данных для колонки values (используется только при unpivot режиме): float, string или автоматически определять системой (про типы поговорим дальше)
  • Также можно указать типы данных для выбранных колонок у контейнеров.


Что под капотом


А1 написан на Python. Для работы с данными мы используем Pandas, а сами данные мы сериализуем из pandas в pickle и храним в MongoDB GridFS.

Когда поступает команда на создание хайпера, система выполняет следующие операции:
  • Выгружает из MongoDB все необходимые контейнеры и десиреализует данные в датайфремы pandas
  • Производит подготовку данных: оставляет в датафреймах только нужные колонки, дает им новые имена, при необходимости разворачивает таблицы через pandas.melt
  • Если пользователь выставил тип данных у колонок, то произвести конвертацию данных либо во float32, либо в string
  • После всех подготовительных работ с данными система через hyper api создает файл и через tabcmd отправляет файл на табло-сервер.

Стоит немного поговорить о типах данных у колонок. Одной из особенностей хранения данных в контейнерах А1 является то, что пользователи не заморачиваются над тем, какие типы назначать колонкам, за них это прекрасно делает pandas: система спокойно справляется с ситуациями, когда в колонке присутствуют числа и строковые значения. Однако хайперу это не нравится: если сказать ему, что колонка должна иметь тип int, то система ругнется при попытке вставить что угодно кроме целого числа. Поэтому было принято решение использовать в хайперах только два типа данных: string и float.

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

Создание .hyper файла


Для работы с Hyper API понадобится установить библиотеку, скачать ее можно с официально сайта тут. Там же есть неплохие примеры, как работать с этим инструментом. Мы же кратко укажем основные моменты.

Сам файл хайпера из себя представляет эдакую базу данных, чем-то напоминает SQLite.Через api можно обращаться к данным, используя like SQL синтаксис:
f"SELECT {escape_name('Customer ID')} FROM {escape_name('Customer')}"

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

with HyperProcess(Telemetry.SEND_USAGE_DATA_TO_TABLEAU) as hyper:    with Connection(        hyper.endpoint, self.fullpath_hyper, CreateMode.CREATE_AND_REPLACE    ) as connection:        connection.catalog.create_schema("Extract")        main_table = TableName("Extract", "Extract")        example_table = TableDefinition(main_table)

После создания таблицы нам нужно создать колонки и задать типы. Как мы уже говорили ранее, данные у нас имеют только два типа (float или string), поэтому отталкиваясь от того, какой тип стоит у колонок в датафрейме, такой мы и выставляем для колонок:

for column in dataframe.columns:    if dataframe[column].dtype.name in ("category", "object"):        example_table.add_column(TableDefinition.Column(column, SqlType.text()))    elif dataframe[column].dtype.name in ("float32"):        example_table.add_column(            TableDefinition.Column(column, SqlType.double())        )    connection.catalog.create_table(example_table)

После создания таблицы можно и вставить данные:

with Inserter(connection, example_table) as inserter:    for val in dataframe.values:        inserter.add_row(val.tolist())    inserter.execute()

Здесь мы построчно бежим по датафрейму и накапливаем список значениями через inserter.add_row(). На самом деле в апи хайпера есть функция add_rows(), которая принимает список списков и вставляет уже значения. Почему не было так сделано? Ради экономии оперативной памяти: для того чтобы предоставить список списков значений из датафрейма, нужно попросить pandas сделать values.tolist(). А когда у тебя 150 млн строк данных, получается очень дорогая операция для оперативки, при этом на производительности это никак не сказывается (во всяком случае, не было замечено, что из-за итерационного перебора строк как-то просела скорость создания хайпера). Плюс ко всему, add_rows() работает как синтаксический сахар: на деле он принимает список списков и так же итерационно добавляет данные.

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

Публикация файла на tableau-сервере


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

Запускать команду tabcmd будем через питоновский subprocess.Popen:

popen = subprocess.Popen(    f'/opt/tableau/tabcmd/bin/tabcmd publish "{fullpath_hyper}" -n "{filename}" -o -r "A1_test" '    '-s http://tableau.domain.com -u "username" -p "password" --no-certcheck',    shell=True,    stderr=subprocess.PIPE,    stdout=subprocess.PIPE,)return_code = popen.wait()if return_code:    error = str(popen.communicate()[1])    return f"Ошибка сервера во время публикации файла. {error}"

Мы передаем tabcmd следующую команду и ключи:
  • publish: залить файл на сервер
  • -n (--name): какое имя файла будет на сервере
  • -o (--overwrite): если присутствует файл с таким именем, то перезаписать
  • -r A1_test (--project): положить файл в папку (он же проект)
  • -s (--server): адрес tableau-сервера
  • -u -p: логин и пароль для авторизации на сервере
  • --no-certcheck: игнорировать проверку SSL-сертификата

Обновление хайпера


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

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

Чтобы понимать, какие данные из какого контейнера лежат в хайпере, система при создании хайпера также создает дополнительную колонку container_id. При таком подходе обновление становится очень простым:
  • Забираем файл с сервера
  • Удаляем все строки в хайпере, где container_id равняется обновляемому контейнеру
  • Вставляем новые строчки
  • Загружаем обратно на сервер файл с перезаписью.

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

Для того, чтобы забрать файл, мы используем tabcmd:

popen = subprocess.Popen(    f'/opt/tableau/tabcmd/bin/tabcmd get "datasources/{filename_tdsx}" '    f'-s http://tableau.domain.com -u "username" -p "password" '    f'--no-certcheck -f "{fullpath_tdsx}"',    shell=True,    stderr=subprocess.PIPE,    stdout=subprocess.PIPE,)return_code = popen.wait()if return_code:    error = str(popen.communicate()[1])    return f"Ошибка. {error}"

Тут используем следующую команду и ключи:
  • get: забрать с сервера файл. Если на сервере лежит файл test.hyper, то обращаться надо к файлу test.tdsx, а лежат они все в директории datasource (я не смог нагуглить, почему такая особенность работы в табло, если знаете, поделитесь в комментариях )
  • -f (--filename): полный путь, включая имя файла и его расширение, куда надо сохранить файл

После того, как файл будет скачен, его надо разархивировать через zipfile:

with zipfile.ZipFile(fullpath_tdsx, "r") as zip_ref:    zip_ref.extractall(path)

После разархивации хайпер будет лежать в директории ./Data/Extracts.

Теперь, когда у нас есть актуальная версия файла, мы можем удалить из него ненужные строки:

table_name = TableName("Extract", "Extract")with HyperProcess(Telemetry.SEND_USAGE_DATA_TO_TABLEAU) as hyper:    with Connection(hyper.endpoint, self.fullpath_hyper) as connection:        connection.execute_query(            f"DELETE FROM {table_name} WHERE "            f'{escape_name("container_id")}={container_id}'        ).close()

Ну а вставка и публикация файла были уже описаны выше.

Заключение


Что в итоге? Проделав работу по внедрению генерации hyper-файлов и автоматической доставки их на tableau-сервер, мы в разы снизили нагрузку на BI-команду, данные в дашборде обновлять стало проще и, самое главное, быстрее. Само знакомство с hyper api не было болезненным, документация неплохо написана, а сама интеграция технологии в нашу систему прошла легко.

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

Статья написана совместно с Василием Лавровым (VasilyFromOpenSpace) Старшим разработчиком бизнес-аналитики
Подробнее..

FAISS Быстрый поиск лиц и клонов на многомиллионных данных

02.07.2020 12:23:15 | Автор: admin


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

Велосипед мы не придумывали, взяли всем известную библиотеку dlib и получили эмбеддинги (векторные представления) каждого лица.

Добавили Telegram-бота для удобства, и всё было отлично. С точки зрения алгоритмов распознавания лиц всё работало на ура, но конференция завершилась, а расставаться с опробованными технологиями не хотелось. От нескольких тысяч лиц хотелось перейти к сотням миллионов, но конкретной бизнес-задачи у нас не было. Через некоторое время у наших коллег появилась задача, которая требовала работы с такими большими объемами данных.

Вопрос был в том, чтобы написать умную систему мониторинга ботов внутри сети Instagram. Тут наша мысль породила простой и сложный подходы:

Простой путь: Рассматриваем все аккаунты, у которых подписок гораздо больше, чем подписчиков, нет аватарки, не заполнено full name и т.д. В итоге получаем непонятную толпу полумертвых аккаунтов.

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

Что дальше?


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

  1. Скорость и точность поиска
  2. Размер занимаемого данными места на диске
  3. Размер используемой RAM памяти.

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

Существуют известные и зарекомендовавшие себя технологии, такие как Annoy, FAISS, HNSW. Быстрый алгоритм поиска соседей HNSW , доступный в библиотеках nmslib и hnswlib, показывает state-of-the-art результаты на CPU, что видно по тем же бенчмаркам. Но его мы отсекли сразу, так как нас не устраивает количество используемой памяти при работе с действительно большими объемами данных. Мы стали выбирать между Annoy и FAISS и в итоге выбрали FAISS из-за удобства, меньшего использования памяти, потенциальной возможности использования на GPU и бенчмарков по результативности (посмотреть можно, например, здесь). К слову, в FAISS алгоритм HNSW реализован как опция.

Что такое FAISS?


Facebook AI Research Similarity Search разработка команды Facebook AI Research для быстрого поиска ближайших соседей и кластеризации в векторном пространстве. Высокая скорость поиска позволяет работать с очень большими данными до нескольких миллиардов векторов.

Основное преимущество FAISS state-of-the-art результаты на GPU, при этом его реализация на CPU незначительно проигрывает hnsw (nmslib). Нам хотелось иметь возможность вести поиск как на CPU, так и на GPU. Кроме того, FAISS оптимизирован в части использования памяти и поиска на больших батчах.

Source

FAISS позволяет быстро осуществлять операцию поиска k ближайших векторов для данного вектора x. Но как же этот поиск устроен под капотом?

Индексы


Главное понятие в FAISS это index, и, по сути, это просто набор параметров и векторов. Наборы параметров бывают совершенно разные и зависят от нужд пользователя. Векторы могут оставаться неизменными, а могут перестраиваться. Некоторые индексы доступны для работы сразу после добавления в них векторов, а некоторые требуют предварительного обучения. Имена векторов хранятся в индексе: либо в нумерации от 0 до n, либо в виде числа, влезающего в тип Int64.

Первый индекс, и самый простой, который мы использовали ещё на конференции, это Flat. Он лишь хранит в себе все вектора, а поиск по заданному вектору осуществляется полным перебором, поэтому обучать его не нужно (но об обучении ниже). На маленьком объеме данных такой простой индекс может вполне покрыть нужды поиска.

Пример:

import numpy as npdim = 512  # рассмотрим произвольные векторы размерности 512nb = 10000  # количество векторов в индексеnq = 5 # количество векторов в выборке для поискаnp.random.seed(228)vectors = np.random.random((nb, dim)).astype('float32')query = np.random.random((nq, dim)).astype('float32')

Создаем Flat индекс и добавляем векторы без обучения:

import faissindex = faiss.IndexFlatL2(dim)print(index.ntotal)  # пока индекс пустойindex.add(vectors)print(index.ntotal)  # теперь в нем 10 000 векторов

Теперь найдем 7 ближайших соседей для первых пяти векторов из vectors:

topn = 7D, I = index.search(vectors[:5], topn)  # Возвращает результат: Distances, Indicesprint(I)print(D)

Output
[[0 5662 6778 7738 6931 7809 7184] [1 5831 8039 2150 5426 4569 6325] [2 7348 2476 2048 5091 6322 3617] [3  791 3173 6323 8374 7273 5842] [4 6236 7548  746 6144 3906 5455]][[ 0.  71.53578  72.18823  72.74326  73.2243   73.333244 73.73317 ] [ 0.  67.604805 68.494774 68.84221  71.839905 72.084335 72.10817 ] [ 0.  66.717865 67.72709  69.63666  70.35903  70.933304 71.03237 ] [ 0.  68.26415  68.320595 68.82381  68.86328  69.12087  69.55179 ] [ 0.  72.03398  72.32417  73.00308  73.13054  73.76181  73.81281 ]]


Видим, что самые близкие соседи с расстоянием 0 это сами векторы, остальные отранжированы по увеличению расстояния. Проведем поиск по нашим векторам из query:

D, I = index.search(query, topn) print(I)print(D)

Output
[[2467 2479 7260 6199 8640 2676 1767] [2623 8313 1500 7840 5031   52 6455] [1756 2405 1251 4136  812 6536  307] [3409 2930  539 8354 9573 6901 5692] [8032 4271 7761 6305 8929 4137 6480]][[73.14189  73.654526 73.89804  74.05615  74.11058  74.13567  74.443436] [71.830215 72.33813  72.973885 73.08897  73.27939  73.56996  73.72397 ] [67.49588  69.95635  70.88528  71.08078  71.715965 71.76285  72.1091  ] [69.11357  69.30089  70.83269  71.05977  71.3577   71.62457  71.72549 ] [69.46417  69.66577  70.47629  70.54611  70.57645  70.95326  71.032005]]


Теперь расстояния в первом столбце результатов не нулевые, так как векторов из query нет в индексе.

Индекс можно сохранить на диск и затем загрузить с диска:

faiss.write_index(index, "flat.index")index = faiss.read_index("flat.index")

Казалось бы, всё элементарно! Несколько строчек кода и мы уже получили структуру для поиска по векторам высокой размерности. Но такой индекс всего с десятком миллионов векторов размерности 512 будет весить около 20Гб и занимать при использовании столько же RAM.

В проекте для конференции мы использовали именно такой базовый подход с flat index, всё было замечательно благодаря относительно маленькому объему данных, однако сейчас речь идет о десятках и сотнях миллионов векторов высокой размерности!

Ускоряем поиск с помощью Inverted lists



Source

Основная и наикрутейшая особенность FAISS IVF index, или Inverted File index. Идея Inverted files лаконична, и красиво объясняется на пальцах:

Давайте представим себе гигантскую армию, состоящую из самых разношерстных воинов, численностью, скажем, в 1 000 000 человек. Командовать всей армией сразу будет невозможно. Как и принято в военном деле, нужно разделить нашу армию на подразделения. Давайте разделим на $\sqrt{1 000 000} = 1000$ примерно равных частей, выбрав на роли командиров по представителю из каждого подразделения. И постараемся отправить максимально похожих по характеру, происхождению, физическим данным и т.д. воинов в одно подразделение, а командира выберем таким, чтобы он максимально точно представлял свое подразделение был кем-то средним. В итоге наша задача свелась от командования миллионом воинов к командованию 1000-ю подразделениями через их командиров, и мы имеем отличное представление о составе нашей армии, так как знаем, что из себя представляют командиры.

В этом и состоит идея IVF индекса: сгруппируем большой набор векторов по частям с помощью алгоритма k-means, каждой части поставив в соответствие центроиду, вектор, являющийся выбранным центром для данного кластера. Поиск будем осуществлять через минимальное расстояние до центроид, и только потом искать минимальные расстояния среди векторов в том кластере, что соответствует данной центроиде. Взяв k равным $\sqrt{n}$, где $n$ количество векторов в индексе, мы получим оптимальный поиск на двух уровнях сначала среди $\sqrt{n}$ центроид, затем среди $\sqrt{n}$ векторов в каждом кластере. Поиск по сравнению с полным перебором ускоряется в разы, что решает одну из наших проблем при работе с множеством миллионов векторов.


Пространство векторов разбивается методом k-means на k кластеров. Каждому кластеру в соответствие ставится центроида

Пример кода:

dim = 512k = 1000  # количество командировquantiser = faiss.IndexFlatL2(dim) index = faiss.IndexIVFFlat(quantiser, dim, k)vectors = np.random.random((1000000, dim)).astype('float32')  # 1 000 000 воинов

А можно это записать куда более элегантно, воспользовавшись удобной штукой FAISS для построения индекса:

index = faiss.index_factory(dim, IVF1000,Flat)Запускаем обучение:print(index.is_trained)   # False.index.train(vectors)  # Train на нашем наборе векторов # Обучение завершено, но векторов в индексе пока нет, так что добавляем их в индекс:print(index.is_trained)  # Trueprint(index.ntotal)   # 0index.add(vectors)print(index.ntotal)   # 1000000

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

D, I = index.search(query, topn) print(I)print(D)

Output
[[19898 533106 641838 681301 602835 439794 331951] [654803 472683 538572 126357 288292 835974 308846] [588393 979151 708282 829598  50812 721369 944102] [796762 121483 432837 679921 691038 169755 701540] [980500 435793 906182 893115 439104 298988 676091]][[69.88127  71.64444  72.4655   72.54283  72.66737  72.71834  72.83057] [72.17552  72.28832  72.315926 72.43405  72.53974  72.664055 72.69495] [67.262115 69.46998  70.08826  70.41119  70.57278  70.62283  71.42067] [71.293045 71.6647   71.686615 71.915405 72.219505 72.28943  72.29849] [73.27072  73.96091  74.034706 74.062515 74.24464  74.51218  74.609695]]


Но есть одно но точность поиска, как и скорость, будет зависеть от количества посещаемых кластеров, которое можно задать с помощью параметра nprobe:

print(index.nprobe)  # 1  заходим только в один кластер и ведем поиск только в нёмindex.nprobe = 16  # Проходим по топ-16 центроид для поиска top-n ближайших соседейD, I = index.search(query, topn) print(I)print(D)

Output
[[ 28707 811973  12310 391153 574413  19898 552495] [540075 339549 884060 117178 878374 605968 201291] [588393 235712 123724 104489 277182 656948 662450] [983754 604268  54894 625338 199198  70698  73403] [862753 523459 766586 379550 324411 654206 871241]][[67.365585 67.38003  68.17187  68.4904   68.63618  69.88127  70.3822] [65.63759  67.67015  68.18429  68.45782  68.68973  68.82755  69.05] [67.262115 68.735535 68.83473  68.88733  68.95465  69.11365  69.33717] [67.32007  68.544685 68.60204  68.60275  68.68633  68.933334 69.17106] [70.573326 70.730286 70.78615  70.85502  71.467674 71.59512  71.909836]]


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

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

Ведем поиск по диску On Disk Inverted Lists


Отлично, первую проблему мы решили, теперь на десятках миллионов векторов мы получаем приемлемую скорость поиска! Но всё это бесполезно до тех пор, пока наш огромный индекс не умещается в RAM.

Конкретно для нашей задачи основное преимущество FAISS в возможности хранить Inverted Lists IVF индекса на диске, загружая в RAM только метаданные.

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

index = faiss.index_factory(512, ,IVF65536, Flat, faiss.METRIC_L2)

Обучение индекса на GPU осуществляем таким образом:

res = faiss.StandardGpuResources()index_ivf = faiss.extract_index_ivf(index)index_flat = faiss.IndexFlatL2(512)clustering_index = faiss.index_cpu_to_gpu(res, 0, index_flat)  #  0  номер GPUindex_ivf.clustering_index = clustering_index

faiss.index_cpu_to_gpu(res, 0, index_flat) можно заменить на faiss.index_cpu_to_all_gpus(index_flat), чтобы использовать все GPU вместе.

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

train_vectors = ...  # предварительно сформированный датасет для обученияindex.train(train_vectors)# Сохраняем пустой обученный индекс, содержащий только параметры:faiss.write_index(index, "trained_block.index") # Поочередно создаем новые индексы на основе обученного# Блоками добавляем в них части датасета:for bno in range(first_block, last_block+ 1):    block_vectors = vectors_parts[bno]    block_vectors_ids = vectors_parts_ids[bno]  # id векторов, если необходимо    index = faiss.read_index("trained_block.index")    index.add_with_ids(block_vectors, block_vectors_ids)    faiss.write_index(index, "block_{}.index".format(bno))

После этого объединяем все Inverted Lists воедино. Это возможно, так как каждый из блоков, по сути, является одним и тем же обученным индексом, просто с разными векторами внутри.

ivfs = []for bno in range(first_block, last_block+ 1):    index = faiss.read_index("block_{}.index".format(bno), faiss.IO_FLAG_MMAP)    ivfs.append(index.invlists)    # считать index и его inv_lists независимыми    # чтобы не потерять данные во время следующей итерации:    index.own_invlists = False# создаем финальный индекс:index = faiss.read_index("trained_block.index")# готовим финальные invlists# все invlists из блоков будут объединены в файл merged_index.ivfdatainvlists = faiss.OnDiskInvertedLists(index.nlist, index.code_size, "merged_index.ivfdata")ivf_vector = faiss.InvertedListsPtrVector() for ivf in ivfs:     ivf_vector.push_back(ivf)ntotal = invlists.merge_from(ivf_vector.data(), ivf_vector.size())index.ntotal = ntotal  # заменяем листы индекса на объединенныеindex.replace_invlists(invlists)  faiss.write_index(index, data_path + "populated.index")  # сохраняем всё на диск

Итог: теперь наш индекс это файлы populated.index и merged_blocks.ivfdata.

В populated.index записан первоначальный полный путь к файлу с Inverted Lists, поэтому, если путь к файлу ivfdata по какой-то причине изменится, при чтении индекса потребуется использовать флаг faiss.IO_FLAG_ONDISK_SAME_DIR, который позволяет искать ivfdata файл в той же директории, что и populated.index:

index = faiss.read_index('populated.index', faiss.IO_FLAG_ONDISK_SAME_DIR)

За основу был взят demo пример из Github проекта FAISS.

Мини-гайд по выбору индекса можно посмотреть в FAISS Wiki. Например, мы смогли поместить в RAM тренировочный датасет из 12 миллионов векторов, поэтому выбрали IVFFlat индекс на 262144 центроидах, чтобы затем масштабироваться до сотен миллионов. Также в гайде предлагается использовать индекс IVF262144_HNSW32, в котором принадлежность вектора к кластеру определяется по алгоритму HNSW с 32 ближайшими соседями (иными словами, используется quantizer IndexHNSWFlat), но, как нам показалось при дальнейших тестах, поиск по такому индексу менее точен. Кроме того, следует учитывать, что такой quantizer исключает возможность использования на GPU.

Спойлер:
Даже при использовании on disk inverted lists FAISS по возможности загружает данные в оперативную память. Так как RAM памяти на этапах тестов нам хватало, пусть и с трудом, а для масштабных тестов было необходимо иметь значительно больший запас данных здесь и сейчас, тесты на объемах свыше объема RAM не проводились. Но FAISS wiki и обсуждения данного подхода на Github говорят, что всё должно работать корректно.


Значительно уменьшаем использование дискового пространства с Product Quantization


Благодаря методу поиска с диска удалось снять нагрузку с RAM, но индекс с миллионом векторов всё равно занимал около 2 ГБ дискового пространства, а мы рассуждаем о возможности работы с миллиардами векторов, что потребовало бы более двух ТБ! Безусловно, объем не такой большой, если задаться целью и выделить дополнительное дисковое пространство, но нас это немного напрягало.

И тут приходит на помощь кодирование векторов, а именно Scalar Quantization (SQ) и Product Quantization (PQ). SQ кодирование каждой компоненты вектора n битами (обычно 8, 6 или 4 бит). Мы рассмотрим вариант PQ, ведь идея кодирования одной компоненты типа float32 восемью битами выглядит уж слишком удручающе с точки зрения потерь в точности. Хотя в некоторых случаях сжатие SQfp16 до типа float16 будет почти без потерь в точности.

Суть Product Quantization состоит в следующем: векторы размерности 512 разбиваются на n частей, каждая из которых кластеризуется по 256 возможным кластерам (1 байт), т.е. мы представляем вектор с помощью n байт, где n обычно не превосходит 64 в реализации FAISS. Но применяется такая квантизация не к самим векторам из датасета, а к разностям этих векторов и соответствующих им центроид, полученным на этапе генерации Inverted Lists! Выходит, что Inverted Lists будут представлять из себя кодированные наборы расстояний между векторами и их центроидами.

index = faiss.index_factory(dim, "IVF262144,PQ64", faiss.METRIC_L2)

Выходит, что теперь нам не обязательно хранить все векторы достаточно выделять n байт на вектор и 2048 байт на каждый вектор центроиды. В нашем случаем мы взяли $n = 64$, то есть $\frac{512}{64} = 8$ длина одного субвектора, который определяется в один из 256 кластеров.



При поиске по вектору x сначала обычным Flat квантайзером будут определяться ближайшие центроиды, а затем x так же разделяется на суб-векторы, каждый из которых кодируется номером одной из 256 соответствующей центроиды. И расстояние до вектора определяется как сумма из 64 расстояний между суб-векторами.

Что в итоге?


Мы остановили свои эксперименты на индексе IVF262144, PQ64, так как он полностью удовлетворил все наши нужды по скорости и точности поиска, а также обеспечил разумное использование дискового пространства при дальнейшем масштабировании индекса. Если говорить конкретнее, на данный момент при 315 миллионах векторов индекс занимает 22 Гб дискового пространства и около 3 Гб RAM при использовании.

Еще одна интересная деталь, которую мы не упоминали ранее, метрика, используемая индексом. По умолчанию расстояния между любыми двумя векторами считаются в евклидовой метрике L2, или более понятным языком, расстояния считаются как квадратный корень из суммы квадратов покоординатных разностей. Но задать метрику можно и другую, в частности, мы тестировали метрику METRIC_INNER_PRODUCT, или метрику косинусных расстояний между векторами. Косинусная она потому, что косинус угла между двумя векторами в Евклидовой системе координат выражается как отношение скалярного (покоординатного) произведения векторов к произведению их длин, а если все векторы в нашем пространстве имеют длину 1, то косинус угла будет в точности равен покоординатному произведению. В таком случае чем ближе расположены векторы в пространстве, тем ближе к единице будет их скалярное произведение.

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

пару слов о GPU
Обучение и использование индексов FAISS на GPU весьма ограничено в выборе параметров индекса, а при работе с гигантскими объемами данных высокой размерности использование если и будет возможным, то вызовет трудности, несопоставимые с полученным результатом. К тому же на GPU реализована только метрика L2.

Однако, стоит заметить, что для использования индекса с PQ квантилизацией на GPU требуется ограничить размер кода 56-ю байтами, либо в случае большего размера сменить float32 на float16, связано это с ограничениями на используемую память.

В итоге о FAISS на GPU мы думать перестали, потому что CPU реализация нас полностью устроила по скорости, и хотелось сохранить работу с метрикой скалярных произведений, что на GPU недоступно. Чтобы регулировать нагрузку на процессоры при работе с индексами, можно задать максимальное число используемых процессоров:

faiss.omp_set_num_threads(N)


Заключение и любопытные примеры


Итак, вернемся же к тому, с чего всё начиналось. А начиналось, напомним, с мотивации решить задачу поиска ботов в сети Instagram, а конкретнее искать дубликаты постов с людьми или аватарок в определенных множествах юзеров. В процессе написания материала стало понятно, что подробное описание нашей методологии поиска ботов тянет на отдельную статью, о чем мы расскажем в следующих публикациях, а пока ограничимся примерами наших экспериментов с FAISS.

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

Например, заручившись разрешением коллег и знакомых, мы загрузили в поиск их лица и быстро нашли фотографии, на которых они присутствуют:


Наш коллега попал на фотографию посетительницы Comic-Con, оказавшись на заднем фоне в толпе. Источник


Пикник в многочисленной компании друзей, фотография из аккаунта подруги. Источник


Просто проходили мимо. Неизвестный фотограф запечатлел ребят для своего тематического профиля. Они не знали, куда попала их фотография, а спустя 5 лет и вовсе забыли, как их фотографировали. Источник


В этом случае и фотограф неизвестен, и сфотографировали тайно!
Сразу вспомнилась подозрительная девушка с зеркальным фотоаппаратом, сидевшая в тот момент напротив:) Источник


Таким образом, путём нехитрых действий FAISS позволяет собрать на коленке аналог всем известного FindFace.

Другая интересная особенность: в индексе FAISS чем больше лица похожи друг на друга, тем ближе друг к другу расположены соответствующие им векторы в пространстве. Я решил повнимательнее изучить чуть менее точные результаты поиска по своему лицу и обнаружил до ужаса похожих на себя клонов:)


Некоторые из клонов автора.
Источники фото: 1, 2,3


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


Source

Благодарим за внимание и надеемся что этот материал будет полезен читателям Хабра!

Статья написана при поддержке моих коллег Артёма Королёва (korolevart), Тимура Кадырова и Арины Решетниковой.

R&D Dentsu Aegis Network Russia.
Подробнее..

Прогноз нестационарногоряда,или как жить дата-сайентисту в 2020 году

01.10.2020 12:15:50 | Автор: admin

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

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

Задача

Специфика размещениярекламы заключается в том, чтоу каждого бренда есть своя целевая аудитория (ЦА) потенциальных покупателей, до которой нужно донести рекламное сообщение. При этом на ТВу каждогоканала иэфирногособытия (передачи, кино, рекламы) есть своя аудиториязрителей, которая не всегда совпадает с целевой аудиториейбренда.

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

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

Давайте рассмотрим нашу модель прогнозирования более подробно.

Для начала нам придётся ввести несколько специфических терминов из области медиа и рекламного рынка:

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

Баинговаяаудиторияэтоаудитория,определяемаятелевизионнымканалом какее целеваяаудитория.То естьканал определяет свою ЦАи показывает контент, соответствующий этой ЦА.

TVRрекламного блокарейтинг рекламного блока, который вычисляется как отношениесуммыпосмотревшихтелезрителейиз определеннойЦАксумметелезрителейвсейЦА.

Affinityрекламного блокаотношения рейтинга рекламного блока,рассчитанногодля ЦА бренда, к рейтингублока,рассчитанногодлябаинговойаудитории канала(TVRЦА /TVR баинговойаудитории).

Данные

Для построения прогностической модели на входе мы имели следующие данные.

Исторические данные о телепросмотре. Мы покупаем данные компании Mediascope, которая измеряет аудиторию ТВ по всей России. Основа измерений панельное исследование. Данные о телепросмотре собираются среди респондентов, у которых установлены пиплметры (устройства, которые фиксируют информацию о просмотре ТВ) в специальный софт компании. В этих данных содержится поминутная информация о том, какой респондент, что и когда смотрел. Также есть описание самого респондента: пол, возраст, семейное положение, уровень дохода и т.д.

Сетка вещания стелевизионнымипередачами и рекламными блоками.Компания Национальный Рекламный Альянс (НРА) как основной продавец телерекламы предоставляет информацию об исторической и будущейтелевизионных сетках вещания. Основные данные, используемые в модели:дата и время начала и окончания передачи\рекламного блока,номер рекламного блока в передаче,название исловесное описание и категория передачи (новости, сериал ит.д.). Раз в неделю приходит уточнение сетки на следующую неделю.

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

COVID-19. Тут все ясно: люди сидели дома во время самоизоляции, телесмотрение резко увеличилось, потом так же резко снизилось. Телесмотрение в апреле-июле 2020 выросло относительно 2019 года, в августе-сентябре 2020 оно вернулось на прошлогодний уровень, то есть мы видим всплеск ТВ-потребления в карантинные месяца, с последующей возвращением к историческим значениям.

В 2020 году измениласьгеография измерения телесмотрения в России. Если раньшемониторилосьтелесмотрение только в городах с населением от 100 тысяч человек(100+), то начиная с 2020 годав исследование былидобавлены и малые города, и населённые пункты с населением менее 100 тысяч человек(100-). Сложность заключается в том, что с точки зрения генеральной совокупностив небольших населенных пунктах (население менее 100 тысяч человек) проживает почти столько же россиян, что и в крупных городах (население более 100 тысяч человек). При этомпанелистовв небольших населенных пунктах в силу молодости панели пока меньше, чем в крупных городах.Этоприводитк тому, что веса респондентов из малых городов в среднембольше, чем у респондентов из крупных городов, что в свою очередь приводитк изменению распределенияTVR.

Сильное отклонение аффинитина телеканалах с небольшой аудиторией. На таких малых каналах среднийбаинговыйTVRблока составляет 0.10. С точки зрения бизнес-смысла нет особой разницы между целевымTVR0.05 и 0.15 (всё равно очень мало), ноаффинитиполучится 0.05 / 0.10 = 50% или 0.15 / 0.10 = 150%.То естьразница как будто бы огромная.

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

Корректировка сетки вещания в течение дня.

Много данных

Описание модели:

Так как мы за простоту, то самого начала было решено не использовать громоздкие решения, поэтому выбрали следующий стэк: MS SQL+Python+LightGBM(CPU) и всё, нет причин усложнять.

Данныемы решилипредставить в следующем виде: берем толькорекламныеблоки и считаем по нимTVR, отбрасывая все остальное,нерекламное, время. Сделано это по той причине, чтотелесмотрениево время передачи сильноотличается оттелесмотренияво время рекламы. Поэтому в использованиителесмотрениявне рекламного блока для нашей задачи нет необходимости (без дополнительных расчетов).

Поведение телезрителей постоянно меняется, также панель измерения телесмотрения компанииMediascope изменяется два раза в год.После проведения отдельных исследований решили ограничить горизонт обучения последними 6 месяцами. Конечно, для расчета годовых сезонок используются данные за 4 года, но для сезонки достаточно использовать месячныеагрегаты, которые рассчитываются быстро,занимают мало места (12*4*[количество аудиторий=130]*[количествоТВ-каналов=24]= 150тыс. точек) и требуют пересчета только раз вмесяц.

Витоге используемые данные здорово уменьшились. Так как в день в среднем 40 блоков на канал, вместо поминутноготелесмотрения(60мин*24ч=1440),то естьв 36 раз меньше.Плюс, горизонт обучения всего 6 месяцев (не год и не два).

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

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

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

Мынагенерилимножество идей. Что-то сработало, что-то было мимо. Вот основные фичи, которые так и не оправдали наших надежд:

Избавление от корреляциифичей:PCAи ручноеизбавление.

Города 100-.Использовать имеющуюся модельпрогноза телесмотрения каждого респондента,чтобы выделить только респондентов 100-. Расчет оказался слишком медленным при весьма небольшом улучшении качества прогноза.

Anomalydetection. Удаление аномально высоких\низких TVR во временном ряде.Также пробовалиisolationforest-тоже не помог.

Сезонкидля каждогоТВ-канала.

Финальный наборфичей:
Общиефичи: длительность передачи, длительность блока, описание передачи,ТВ-канал, день недели, день месяца, время с точностьюдо30 минут, как давно существует передача (например, Поле чудес), праздничные дни. Здесь много категориальных переменных, поэтому использовалидля нихобычные техники обработки.

Сезонки и тренды с учетом изменения весов. Производимдекомпозицию временного рядадля каждой аудитории, реализованнуювручную:

1. Получение тренда

a. Сначаластроим линейную регрессию сdummyмесяцев+ фича с количеством уникальныхреспондентов100-, которые были заявлены в базе в течение соответствующего месяца, помноженная на dummy месяца.

b. Из полученной модели выделяем все остатки получаем ряд, в которомнет сезонки.

c. Итоговый ряд остатков аппроксимируем кубическим сплайном. Получаем искомый тренд.

2. Используя полученный в п.1 тренд +dummyмесяцев + фича с количеством уникальныхреспондентов100- (которые были заявлены в базе в течение соответствующего месяца), помноженная наdummy месяца, строим линейную регрессию.

3. Коэффициенты приdummyмесяцев используем в качестве значений сезонки: собираем в единый ряд. Получаем чистую сезонку, без100-, и используем как единую фичу.

4. Используя тренд из п.3 + чистую сезонку + количествореспондентов100-, помноженное на чистую сезонку, строим линейную регрессию. Получаем веса регрессоров.

5. Конечный ряд сезонки (прогнозный!) равен сумме: чистая сезонка + количествореспондентов 100-, помноженное на чистую сезонку и взятое с весами из линейной регрессии.

COVID-19.

1. Беремпонедельное отношениесуммывременителепросмотра блоков респондентамик количеству блоков.

2. Разбиваемполученныйвременной ряд на стадии самоизоляции:

a. 2020-03-05 - первое заражение России

b. 2020-03-25 - обращение президента

c. 2020-03-28 как будто выходные

d. 2020-04-06 - строго сидим дома

e. 2020-06-01 - снятие самоизоляции для некоторых категорий граждан

f. 2020-06-14 - полное снятие ограничений

3. Прогнозируемвременной ряд из п.1линейной регрессией от одногорегрессора- стадиисамоизоляции.Этот прогноз используем как конечную фичу.

Такой подходдал нам следующее:

Здорово описал уже произошедшие изменения,

Но ине позволил модели сильнооверфититьсяна этих знаниях,

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

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

Клиппированиепоаффинити.

1. Берем 95 квантильисторическогофактическогоаффинити (целевой аудитории к баинговой)для каждого каналав каждой аудитории. Считаем, чтоэто максимальныезначенияаффинити.

2. Производим обратное преобразование:фиксируяпрогнозныйTVRблокадлябаинговойаудиториии максимальныйаффинитиканала (из п.1),рассчитываем максимально возможныйTVR блока целевой аудитории.

3. Если прогнозныйTVRблокавыше максимально возможного в этой аудитории, то приравнять максимально возможному.

ЗаменаTVRнаlog(TVR+1). В качестве целевой прогнозируем неTVR, а его логарифм.Послепостроения модели иполучения прогнозныхзначенийобратно переводимвабсолютное значение.

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

https://download.dentsuaegis.ru/index.php/s/emtvlKKdPkUCdvn

Техническиесложностии их решение

Для нас как MachineLearningEngineerотдельнымчеледжембыло написать код на уровнеdeveloper. БудучиMLEngineerот насизначальноожидается знание основ ООП иCleanCodeParadigm, однако даже на уровнеразработчикабывает качественный код и не очень.Мы решили отнестись серьезно к написанию кода и часто консультировались с нашими архитекторами иData Engineerами. Хотелось бы отдельно поделиться сдвумярешениями, которыездорово ускорили работу кода:кастомнаязапись данных наMS SQL serverи непрерывная работа скрипта.

Во-первых, запись данныхнаMS SQL. Методы записиреализованы в большом количествебиблиотек. Один лишьSQLAlchemy имеет три варианта записи. Однаков результате тестирования,оказалось, что все они работают очень медленнои здорово тормозят весь код(возможно, это только сMS SQL так?).Но главное:заливка падала сdeadlockом, когда пыталисьлить данные в конечную таблицуасинхроннымитрэдами.Как следствие, пришлось по крупицам узнавать хитрости и в итоге написатьсобственныйметод записи, который использует классическуюлибуpyodbc.Метод состоит из треххитростей:

1. Создаемвременнуютаблицубез индексов (это важно) ильемтудаданные параллельнымитрэдами.Так как нет индексов иconstraints, параллельное записывание не падаетс ошибкойraceconditionилиdeadlock.

2. Запись реализовать не построчно, абакетами. Оказалось, что есть существенная разница в скорости между этими двумя конструкциями:

a. Insert into table(col1,col2) values (1,1);

Insert into table(col1,col2) values (1,2);

b. Insert into table(col1,col2) values

(1,1),

(1,2);

Оказывается,варианта) пишет построчнокак2 отдельныетранзакции, тогда как вариант б) выполняется как одна транзакция,бакетами. Но таких строк может быть до 1000 вбакете.

3. Для заливки\обновленияданных из временной таблицы в конечную использоватьmergetablesссоответствующимииндексами вtargettable (ускорение за счет использования индексов вtargettable).

В итогескорость записиувеличивается во много раз.От 2 до 10 раз - смотря с каким методом записи сравнивать.Можетепотеститьсами, но чувствую, у вас здесь будет много сомнений - готов обсудить вкомментах.И повторюсь, что, скорее всего, это работает эффективнее только сMS SQL.

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

  1. Обучаем модель раз в день, пересчитав всефичи, пишем\перезаписываемпрогноз на два месяца вперед. Далее храним (точнее, не выгружаем) модель и ее окружение в памяти.

  2. Раз в час проверяем наличие новых блоков и строим по ним прогнозпообученной моделиизп.1.

Контролькачества модели

Телесмотрение меняется ежедневно и зависит от множества факторов, в том числе и ненаблюдаемых в данных. Поэтому требуется постоянно держать руку на пульсе. То есть нужна отчетность на ежедневной основе. Для этого мы раз в неделю сохраняемпрогнозына два месяца впередв отдельную таблицу. Далее ежедневно подгружаем фактическоетелесмотрениеи считаем ошибку.Отчетность смотрим вtableau.Данные и отчет, конечно, собираются автоматически, но на результатысмотримпокаглазами, до автоматизации анализа ошибок руки не дошли.В качестве метрики ошибки выбрали формулуavg[(factpredict) / (fact+predict+epsilon)].Затем смотримboxplotи просто почасовое среднееэтой ошибки.Причин такогоподходамножество. Вотосновные:

РаспределенияTVRсильно отличаются в каждом канале.Поэтому метрики типаR_squaredне подходят там используется единая дисперсия.

Внутриканала выходные\рабочиеи день\ночьимеют сильноразное распределение

Гетероскедастичностьпрогноза- увеличение дисперсии ошибки с увеличением значенияTVR.

Желание отличатьперепрогнозотнедопрогнозабезопаснеенедопрогнозить, чемперепрогнозить.

Эволюция развития качества прогноза.

Прогноз, составленный 22 июля дает много ошибок. Далее почти в каждом следующем прогнозе происходит улучшение качестваза счет добавления новых фичей. Особенноэтовидно при сравнении 2 и 4 сентября: даты почти одинаковые, но разница ошибок очевидна.

Почасовая проверка качества:

Направления развитиямодели

Особые выпуски (финалы,особыечемпионатыит.д.).

КонкуренцияТВ-каналов: если на одном из ТВ-каналов транслируетсяпопулярная программа, то остальные ТВ-каналы проседают (в разной степени).

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

Ночное время- см.предыдущийпункт.

Подробнее..

Новая медиавалюта. Исследование внимания аудитории к интернет-рекламе с применением технологии айтрекинга (часть 2)

30.09.2020 10:08:50 | Автор: admin

Как оценивать эффективность рекламы в условиях информационного шума и меняющихся паттернов поведения потребителя? Классические метрики засчитывают просмотр, даже если он не был совершен: пользователь мог смотреть на экран, но не видеть рекламу. Специфика digital-форматов такова, что далеко не все рекламные ролики занимают 100% экрана, а значит, у аудитории есть возможность избежать просмотра. Мы решили протестировать технологичный подход, чтобы изучить внимание пользователей к рекламе на разных медиа сервисах и экранах.


В предыдущей статье мы уже немного рассказали о нашем пилотном исследовании Attention Economy Russia целях, предыстории (глобальный проект и локализация) и ТВ-результатах. На этот раз хочется поделиться подробностями оценки внимания в digital, которое мы провели в партнерстве с UX-лабораторией Mail.ru Group. В рамках данного этапа исследования команда использовала айтрекеры, с помощью которых отслеживали взгляд человека при просмотре видеорекламы на десктопных и мобильных устройствах.



Наш подход


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


Для digital-исследования мы выделили три типа внимания:


  • полное (зрительный контакт от 3 секунд без отвлечения на другой контент)
  • фоновое (контакт менее 3 секунд или при наличии отвлекающих факторов, таких как чтение текста)
  • отсутствующее (сквозное пролистывание, полное отсутствие взгляда или зрительный контакт менее 1 секунды).

В ходе анализа и экспериментов изучали четыре ключевых рекламных видеоформата:


  • in-stream (внутри видеоконтента, например, сериала или ролика)
  • out-stream видеореклама (в окружающем контенте), которая была дополнительно разделена на in-content (главным образом, в тексте статьи) и in-feed (в новостной ленте соцсети)
  • истории

Основные вопросы, на которые мы хотели ответить в рамках исследования, определили так:


  1. Есть ли разница во внимании к одному и тому же контенту на разных экранах?
  2. Важна ли последовательность роликов при интернет-просмотре? На какой платформе это играет более значимую роль?
  3. Как отличается внимание в зависимости от формата рекламы?
  4. Влияет ли звук на эффективность?
  5. Отличается ли эффективность внимания на разных экранах и форматах?

Мы выдвинули следующие гипотезы:


  1. На разных экранах и в разных форматах реклама получает разную интенсивность внимания.
  2. Чем интенсивнее внимание к рекламе, тем выше ее эффект.

Для проверки гипотез, как и для ТВ исследования, остановились на Москве и провели замеры среди самой востребованной у рекламодателей аудитории в возрасте 25-44 лет. Рекрутинг среднего москвича делали по аудиторному профилю исследовательской панели компании Mediascope (крупнейший медиаизмеритель в России) соцдем параметрам и особенностям медиапотребления. Это было необходимо для дальнейшей интерпретации результатов в привязке к общепринятым на медиарынке показателям. Все респонденты должны были быть пользователями тестируемых digital-сервисов.
Ключевым параметром оценки эффективности стала запоминаемость рекламы. В ходе исследования мы решили проверить, как внимание к рекламному сообщению конвертируется в запоминание (с подсказкой и без).


Технология и механика


Если для определения внимания человека к ТВ-рекламе было достаточно установленных в домашних условиях камер, то в digital этот метод не помог бы понять реальное направление взгляда. На телевидении реклама занимает 100% экрана, тогда как в интернете все зависит от устройства, с которого пользователь заходит в сеть и от платформы, которую он потребляет в момент времени.
На десктопе ролики могут занимать от 10 до 100% экрана, на смартфонах от 10-15 до 100%, но видео легко пролистнуть. Для качественной оценки мы анализировали не только внимание, но и взаимодействие пользователя с рекламой (закрыл, пролистал и тд.).
Чтобы точно определить внимание к рекламе на мобильных устройствах и на десктопе мы использовали технологию айтрекинга (eye-tracking). Нам важно было повысить точность результатов, поэтому в анкете респондента мы указывали пункты про наличие/ отсутствие наращенных ресниц или контактных линз/ очков. Иногда эти факторы могут мешать результатам оценки, поэтому таким образом мы максимально повышали качество анализа.
В лабораторных условиях трекеры автоматически фиксировали взгляд человека. Мы просили пользователей смотреть контент на тех площадках, которыми они постоянно пользуются под своими аккаунтами, а в случае мобильных устройств на своих гаджетах. Мы анализировали, как пользователи реагируют на разные рекламные форматы, которые изучали в рамках исследования.
После айтрекинг-замера мы предлагали пользователям пройти опрос о просмотренной рекламе. Сначала задавали открытый вопрос: Какую рекламу вы видели за прошедшую сессию?, так проверяли спонтанное вспоминание. Затем показывали кадры рекламных роликов без указания бренда и просили вспомнить, видели ли они их во время потребления контента вспоминание с подсказкой.


Результаты


Говоря о результатах хочется отметить, что исследование внимания в диджитал среде оказалось сложнее, чем на ТВ. Это связано с тем, что необходимо оценивать не только направленность взгляда в экран, но и внимание к контенту внутри экрана. Плюс, мы не всегда знаем, какую рекламу увидят наши респонденты. В части каналов нам удалось настроить таргетирование на необходимых респондентов (их аккаунты / данные мобильных устройств) или оценить распознавание рекламных материалов по ходу замера.


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


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


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


Для различных рекламных форматов очевидно наблюдается разница в распределении интенсивности внимания. При этом внимание аудитории динамично: оно переключается между разными уровнями интенсивности в процессе контакта.


Несмотря на то, что в in-stream форматах значительно выше уровень полного внимания, оно нестабильно доля полного внимания снижается после просмотра двух рекламных роликов (что также характерно для in-content видеорекламы). То же характерно и для in-content форматов. В in-feed контенте уровень полного внимания практически не меняется даже при просмотре пяти рекламных роликов. Таким образом, можно сделать вывод, что в ленте соцсети видеоролики привлекают одинаковое внимание, в каком бы порядке их не просматривали. Среди out-stream форматов в in-feed роликах выше доля полного внимания и ниже доля отсутствующего внимания.



По нашим предположениям, это может быть связано с размером рекламы на экране и ее значимостью. In-stream формат показывается вместо основного контента. In-feed и in-content нативно встраиваются в интересный для пользователя контент.


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


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


В in-feed роликах значительно более высокий уровень запоминаемости, чем в других форматах. Таким образом, если удалось поймать взгляд аудитории в социальных сетях, то вероятность успеха высока. Яркие, нестандартные и встроенные в формат соцсети ролики более эффективны в построении знания.



Интересны и другие наблюдения, выявленные в ходе исследования.


Например, на внимание к рекламе влияет предыдущий опыт потребления бренда. Если респонденты ранее пользовались товарами или услугами бренда, то доля полного внимания к его рекламе в два раза выше. Именно потребление бренда самый устойчивый показатель с точки зрения влияния на внимание: ни отношение к бренду, ни лояльность не показывают такой корреляции.



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


Планы


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


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


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

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru