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

Tinder

Как клеить по 13 девушек в час используя VPS Tinder ML

26.06.2020 12:10:59 | Автор: admin
*Исключительно ради изучения Machine Learning, разумеется. Под немного недовольным взглядом любимой жены.

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

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


В чем проблема сетей для знакомств


Был такой ресурс Ashley Madison. Специфичный, с лозунгом Life is short. Have an affair. Основная аудитория женатые мужчины, ищущие себе интрижку на стороне. Монетизация тоже веселая помимо стандартных потрать баллы, чтобы лайкнуть и написать они просили $19, чтобы удалить аккаунт пользователя без следов.

В 2015 году сайт закономерно протек и 60 ГБ персональных данных утекли в открытый доступ. Помимо множества разрушенных семей, эта утечка дала очень много интересной информации аналитикам. Я всегда подозревал, что мужчин на сайтах знакомств намного больше, но в этом случае оказалось совсем интересно. Журналистка Annalee Newitz, анализируя утекшие данные обнаружила, что из 5 миллионов пользователей только 12 000 были похожи на настоящие аккаунты девушек и использовались регулярно. Остальные были просто ботами, которые общались с мужчинами посетителями.

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

Особенность Tinder


image
Идеальный брутфорсер в гендерных отношениях

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

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

Собираем данные


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

За основу возьмем репозиторий TinderAutomation. Фотографии у Tinder всегда общедоступны, но функция лайков уже лимитирована. Поэтому надо извлечь все живое в радиусе и тщательно промаркировать. Для начала надо воспользоваться довольно простым скриптом:

from skimage.io import imread, imsave, imshow, showimport matplotlib.pyplot as pltimport pynderfrom helpers import get_access_token, get_login_credentialsfrom io_helper import save_imageemail, password, FBID = get_login_credentials()FBTOKEN = get_access_token(email, password)session = pynder.Session(facebook_token=FBTOKEN)while True:    users = session.nearby_users()    for user in users:        photos = user.get_photos()        print("Fetched user photos..")        for photo in photos:            print(photo)            image = imread(photo)            imshow(image)            show()            input_string = "Write 1 to like. Write 2 to dislike."            ans = str(input(input_string)).lower()            if ans == "1":                save_image(image, photo, True)            else:                save_image(image, photo, False)

Он позволит вам максимально быстро разметить датасет всего двумя кнопками. Ключевой подводный камень кроется в том, что библиотека werkzeug сломала обратную совместимость и придется делать ее принудительный даунгрейд. Иначе вываливает вот такую ошибку.

Traceback (most recent call last):  File "img_scrape.py", line 4, in <module>    from helpers import get_access_token, get_login_credentials  File "/home/someone/tmp/TinderAutomation/helpers.py", line 1, in <module>    import robobrowser  File "/home/someone/tmp/TinderAutomation/venv/lib/python3.6/site-packages/robobrowser/__init__.py", line 3, in <module>    from .browser import RoboBrowser  File "/home/someone/tmp/TinderAutomation/venv/lib/python3.6/site-packages/robobrowser/browser.py", line 8, in <module>    from werkzeug import cached_propertyImportError: cannot import name 'cached_property'

Поэтому, в requirements.txt надо прописать Werkzeug==0.16.1. Тогда взлетит.
Вторая проблема добыть этот самый токен. Стандартный способ из репозитория у меня не взлетел, но получилось добыть его из консоли разработчика. Для этого переходим по ссылке и выдергиваем ответ на POST-запрос в www.facebook.com/v2.6/dialog/oauth/confirm?dpr=1. Внутри ищем 'access_token'. С первого раза почему-то не получилось, но потом я его нашел и захардкодил в скрипт.

Требования к датасету


Есть несколько ключевых требований к датасетам машинного обучения:

  1. Достаточность
  2. Равномерность
  3. Разнообразие

Достаточность в данном случае требует не меньше 10000 фотографий для построения адекватной модели. Да, это очень много. Собственно поэтому и существуют сервисы вроде Amazon Mechanical Turk, где за определенную плату вы можете перепоручить разметку вашего датасета другим людям. С другой стороны, вы точно хотите, чтобы ваш бот лайкал чудесных луноликих азиаток или не менее прекрасных девушек с индийскими корнями? Все-таки модель должна отражать именно ваш вкус.

С разнообразием проблем особо нет, все фотографии представлены в разнообразных ракурсах и освещении. В очках, платьях, купальниках и лыжных костюмах. Проблема может возникнуть с равномерностью датасета. В идеале, когда мы разметим нашу выборку, она должны состоять из приблизительно равных частей. Если у вас получился перекошенный датасет, то его придется разбавлять фотографиями из других источников. Более симпатичных надо будет добавить или наоборот вы определите по результату разметки. У меня получилось что-то в районе 60% симпатичных. То ли я не слишком разборчив, то ли мне просто повезло и вокруг много симпатичных девушек.

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

Обработка данных


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

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

image
Более подробно это описано в мануале к OpenCV

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


Источник

У людей Hue параметр в цвете кожи не привносит значимого вклада в оценку симпатичности.
Поэтому, стоит упросить работу нейросети и оставить только grayscale.

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


Сразу хочу сказать, что без хорошей видеокарты и CUDA вы скорее всего просто не получите обученную модель в адекватные сроки. Поэтому сразу нацеливайтесь на расчеты в специализированных облаках или с использованием python-CUDA.

Я взял базовый трехслойный пример у автора репозитория и, на удивление, он показал точность в районе 72%, что вполне хороший результат.

model = Sequential()model.add(Convolution2D(32, 3, 3, activation='relu', input_shape=(img_size, img_size, 3)))model.add(MaxPooling2D(pool_size=(2,2)))model.add(Convolution2D(32, 3, 3, activation='relu'))model.add(MaxPooling2D(pool_size=(2,2)))model.add(Convolution2D(64, 3, 3, activation='relu'))model.add(MaxPooling2D(pool_size=(2,2)))          model.add(Flatten())model.add(Dense(128, activation='relu'))model.add(Dropout(0.5))model.add(Dense(2, activation='softmax'))adam = optimizers.SGD(lr=1e-4, decay=1e-6, momentum=0.9, nesterov=True)model.compile(loss='categorical_crossentropy',              optimizer= adam,              metrics=['accuracy'])

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

Запускаем бота


image

Спасибо автору репозитория за готовый вариант для быстрой проверки идеи. На самом деле вполне себе работает в базовом варианте и можно, в принципе, запустить на нашем готовом арендованном сервере. Обучать пока не получится, на данный момент мы не предоставляем виртуальные машины с GPU для расчетов, но запустить что-то на 24/7 работу можно без проблем. Бот довольно легковесный, поэтому выгоднее будет взять тариф с оплатой за использованные ресурсы.

Результаты



Наверное я очень симпатичный. И у меня богатый внутренний мир. Получил что-то в районе 13 совпадений в течение часа. Причем, несколько раз девушки писали первыми.
В итоге получались весьма милые диалоги, где я рассказывал, что зашел исключительно поиграть с машинным обучением и разметкой данных. Одна из девушек крайне заинтересовалась, так как сама разработчик. Есть стойкое ощущение, что она в итоге прочитает этот пост на Хабре. Я очень надеюсь, что Оксана сохранит мою анонимность. :-)
*машет лапой и передает привет

Немного про этическую сторону вопроса


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

Вокруг лето. Пора знакомиться.



Подробнее..

Its a match финские ученые создают идеально привлекательные лица, учитывая личные предпочтения мозга

11.03.2021 12:11:09 | Автор: admin

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

Итак, существует ли идеальная пара? На основе данных ЭЭГ ученые научили генеративно-состязательные нейронные сети (GAN) предсказывать и воссоздавать лица, которые потенциально будут казаться нам привлекательными. Только представьте, итоговая точность предсказаний составила >80%. Интересно, что будет, если сеть сможет в перспективе влиять на подборку пары в Tinder и подобных приложениях? Но разберемся во всем по порядку.

Предыстория



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

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

Как проходил эксперимент



В проведенном исследовании приняли участие 30 сотрудников и студентов из Хельсинского университета. Для обучения GAN они использовали 30 тыс. фотографий знаменитостей. Так сеть научили создавать синтетические портреты. Всего их смоделировали 240.

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

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

Что потом?


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

Спустя 2 месяца, ученые вновь собрали участников. Они поместили в подборки изображений как новые привлекательные, так и другие нейтральные и/или непривлекательные. Добровольцы получили матрицу из 24 картинок. Оценка привлекательности проводили по шкале от 1 до 5. Нажатием клавиш участники проставили оценки изображениям.

В итоге выяснилось, что 86,7% изображений, созданных GAN, участники эксперимента выделили как привлекательные. Что интересно еще 20% изображений, которые потенциально создавались как непривлекательные, оказались все же симпатичными для добровольцев. То есть результат работы сети был ложноотрицательный.

Идеальная пара



Из доводов в пользу того, что система работает большая часть именно привлекательно созданных изображений получили оценку больше 1 балла по сравнению с созданными, как нейтральные. Ученые сделали итоговый вывод, что GAN действительно научилась отделять реакции мозга на привлекательные и непривлекательные, причем делает она это с точностью в 83,3%.

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

Shazam vs ЭЭГ?


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

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

Исследователи из Индии и Нидерландов смогли научиться воссоздавать из активности мозга, фиксируемой ЭЭГ, конкретные песни. Точность определения мелодии составила 85%.

Обучение сети проводили на 20 добровольцах, которые слушали 12 мелодий. Когда сеть работала на данных одного конкретного испытуемого, точность идентификации мелодии составила почти 85%. Когда процесс распознавания запустили без привязки к личности, то точность снизилась почти на 77%.

Подробнее..

Перевод Vespa лучше Elasticsearch для поиска пар среди миллионов мужчин и женщин

30.09.2020 18:23:32 | Автор: admin


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

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

Мы должны выдавать наилучшие результаты и практически бесконечный список рекомендаций. В других приложениях, где контент меняется не так часто, это можно сделать периодически обновляя рекомендации. Например, при использовании функции Spotify Discover Weekly вы наслаждаетесь набором рекомендуемых треков, этот набор не меняется до следующей недели. На OkCupid пользователи бесконечно просматривают свои рекомендации в режиме реального времени. Рекомендуемый контент очень динамичен по своей природе (например, пользователь может изменить свои предпочтения, данные профиля, местоположение, деактивироваться в любое время и т.д.). Пользователь может изменить, кому и как его можно рекомендовать, поэтому мы хотим убедиться, что потенциальные совпадения лучшие на данный момент времени.

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

Какие проблемы у существующей системы поиска совпадений


OkCupid уже много лет использует собственную внутреннюю систему поиска совпадений. Не будем вдаваться в детали, но на высоком уровне абстракции она представляет собой фреймворк map-reduce над шардами пользовательского пространства, где каждый шард содержит в памяти некоторую часть релевантных пользовательских данных, которые используются при включении различных фильтров и сортировок на лету. Поисковые запросы расходятся на все шарды, и в конечном счете результаты объединяются, чтобы вернуть k лучших кандидатов. Эта написанная нами система поиска пар работала хорошо, так почему сейчас мы решили изменить её?

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

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

Сегодня в OkCupid многие из этих подсистем обслуживаются более надёжными и дружественными к облакам опциями OSS, и команда за последние два года с большим успехом внедрила различные технологии. Не будем здесь рассказывать об этих проектах, а вместо этого сосредоточимся на действиях, которые мы предприняли для решения вышеуказанных проблем, перейдя к более удобной для разработчиков и масштабируемой поисковой системе для наших рекомендаций: Vespa.

Это совпадение! Почему OkCupid подружился с Vespa


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

Elasticsearch


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

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

Vespa


Исходные коды открыты всего несколько лет назад. Разработчики заявили поддержку хранения, поиска, ранжирования и организации Big Data в реальном времени. Функции, которые поддерживает Vespa:

  • высокая производительность выдачи благодаря реальным частичным обновлениям в памяти без необходимости переиндексировать весь документ (как сообщается, до 40-50тыс. обновлений в секунду на узел)
  • обеспечивает гибкую структуру ранжирования, позволяющую обрабатывать данные во время запроса
  • непосредственно поддерживает в ранжировании интеграцию с моделями машинного обучения (например, TensorFlow)
  • запросы можно выполнять с помощью выразительного YQL (Yahoo Query Language) в вызовах REST
  • возможность настройки логики с помощью Java-компонентов

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

В целом Vespa, видимо, лучше всего подходила для наших вариантов использования. OkCupid включает в себя множество различной информации о пользователях, чтобы помочь им найти лучшие пары с точки зрения просто фильтров и сортировок там более сотни параметров! Мы всегда будем добавлять фильтры и сортировки, поэтому очень важно поддерживать этот рабочий процесс. Что касается записей и запросов, Vespa больше всего похожа на нашу существующую систему; то есть наша система также требовала обработки быстрых частичных обновлений в памяти и обработки в реальном времени во время запроса на поиск совпадений. У Vespa также гораздо более гибкая и простая структура ранжирования. Ещё одним приятным бонусом стала возможность выражать запросы в YQL, в отличие от неудобной структуры для запросов в Elasticsearch. Что касается масштабирования и обслуживания, то возможности автоматического распределения данных в Vespa оказались очень привлекательны для нашей относительно небольшой команды. В целом выяснилось, что Vespa лучшие поддерживает наши варианты использования и требования к производительности, будучи при этом проще в обслуживании по сравнению с Elasticsearch.

Elasticsearch более известный движок, и мы могли бы воспользоваться опытом его использования в Tinder, но любой вариант потребует тонны предварительных исследований. В то же время Vespa обслуживает множество систем в продакшне, таких как Zedge, Flickr с миллиардами картинок, рекламная платформа Yahoo Gemini Ads с более чем ста тысячами запросов в секунду для выдачи рекламы миллиарду активных пользователей в месяц. Это дало нам уверенность в том, что это проверенный в боях, эффективный и надёжный вариант на самом деле Vespa появилась даже раньше, чем Elasticsearch.

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

Как работает Vespa и как выглядит поиск в OkCupid




Прежде чем погрузиться в наш пример использования Vespa, вот краткий обзор того, как она работает. Vespa это набор многочисленных служб, но каждый контейнер Docker можно сконфигурировать на роль узла admin/config, узла контейнера Java, не зависящего от состояния (stateless) и/или узла контента C++, зависящего от состояния (stateful). Пакет приложения с конфигурацией, компонентами, моделью ML и т.д. может быть развернут через State API в конфигурационном кластере, который обрабатывает применение изменений к контейнеру и кластеру содержимого. Запросы фида и остальные запросы проходят через stateless-контейнер Java (который позволяет настроить обработку) по HTTP, прежде чем обновления фида поступают в кластер контента или запросы разветвляются на уровень контента, где происходит распределённое выполнение запросов. По большей части развёртывание нового пакета приложений занимает всего несколько секунд, и Vespa обрабатывает эти изменения в реальном времени в контейнере и кластере контента, так что вам редко приходится что-либо перезапускать.

Как выглядит поиск?


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

search user {    document user {        field userId type long {            indexing: summary | attribute            attribute: fast-search            rank: filter        }        field latLong type position {            indexing: attribute        }        # UNIX timestamp        field lastOnline type long {            indexing: attribute            attribute: fast-search        }        # Contains the users that this user document has liked        # and the corresponding weights are UNIX timestamps when that like happened         field likedUserSet type weightedset<long> {            indexing: attribute            attribute: fast-search        }           }    rank-profile myRankProfile inherits default {        rank-properties {            query(lastOnlineWeight): 0            query(incomingLikeWeight): 0        }        function lastOnlineScore() {            expression: query(lastOnlineWeight) * freshness(lastOnline)        }        function incomingLikeTimestamp() {            expression: rawScore(likedUserSet)        }        function hasLikedMe() {            expression:  if (incomingLikeTimestamp > 0, 1, 0)        }         function incomingLikeScore() {            expression: query(incomingLikeWeight) * hasLikedMe        }        first-phase {            expression {                lastOnlineScore + incomingLikeScore            }        }        summary-features {            lastOnlineScore incomingLikeScore        }    }    }

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

Предположим, мы заполнили кластер такими пользовательскими документами. Затем мы могли бы выполнить фильтрацию и ранжирование по любому из вышеперечисленных полей. Например, сделать POST-запрос к обработчику поиска по умолчанию http://localhost:8080/search/, чтобы найти пользователей, за исключением нашего собственного пользователя 777, в пределах 50 миль от нашего местоположения, которые были онлайн с момента отметки времени 1592486978, с ранжированием по последней активности и сохраняя двух лучших кандидатов. Давайте также выберем summaryfeatures, чтобы увидеть вклад каждого выражения ранжирования в нашем профиле ранжирования:

{    "yql": "select userId, summaryfeatures from user where lastOnline > 1592486978 and !(userId contains \"777\") limit 2;",    "ranking": {        "profile": "myRankProfile",        "features": {            "query(lastOnlineWeight)": "50"        }    },    "pos": {        "radius": "50mi",        "ll": "N40o44'22;W74o0'2",        "attribute": "latLong"    },    "presentation": {        "summary": "default"    }}

Мы могли бы получить такой результат:

{    "root": {        "id": "toplevel",        "relevance": 1.0,        "fields": {            "totalCount": 317        },        "coverage": {            "coverage": 100,            "documents": 958,            "full": true,            "nodes": 1,            "results": 1,            "resultsFull": 1        },        "children": [            {                "id": "index:user/0/bde9bd654f1d5ae17fd9abc3",                "relevance": 48.99315843621399,                "source": "user",                "fields": {                    "userId": -5800469520557156329,                    "summaryfeatures": {                        "rankingExpression(incomingLikeScore)": 0.0,                        "rankingExpression(lastOnlineScore)": 48.99315843621399,                        "vespa.summaryFeatures.cached": 0.0                    }                }            },            {                "id": "index:user/0/e8aa37df0832905c3fa1dbbd",                "relevance": 48.99041280864198,                "source": "user",                "fields": {                    "userId": 6888497210242094612,                    "summaryfeatures": {                        "rankingExpression(incomingLikeScore)": 0.0,                        "rankingExpression(lastOnlineScore)": 48.99041280864198,                        "vespa.summaryFeatures.cached": 0.0                    }                }            }        ]    }}

После фильтрации по совпадающим попаданиям вычисляются выражения ранжирования первой фазы (first-phase) для ранжирования попаданий. Возвращаемая релевантность (relevance) это общая оценка как результат выполнения всех функций ранжирования первой фазы в профиле ранжирования (rank-profile), который мы указали в нашем запросе, то есть ranking.profile myRankProfile. В списке ranking.features мы определили query(lastOnlineWeight) как 50, на неё затем ссылается единственное используемое нами выражение ранжирования lastOnlineScore. Оно использует встроенную функцию ранжирования freshness, которая представляет собой число, близкое к 1, если временная метка в атрибуте является недавней по сравнению с текущей временной меткой. Пока всё идет хорошо, здесь ничего сложного.

В отличие от статического контента, этот контент может влиять на то, показывать его пользователю или нет. Например, они могут вас лайкнуть! Мы могли бы индексировать взвешенное поле likedUserSet для каждого пользовательского документа, который содержит в качестве ключей идентификаторы пользователей, которых они лайкнули, и в качестве значений метку времени, когда подобное произошло. Тогда было бы просто отфильтровать тех, кто вас лайкнул (например, добавлениес выражения likedUserSet contains \777\ в YQL), но как включить эту информацию во время ранжирования? Как повысить в результатах тогр пользователя, который лайкнул нашего человека?

В предыдущих результатах выражение ранжирования incomingLikeScore было равно 0 для обоих этих попаданий. Пользователь 6888497210242094612 на самом деле лайкнул пользователя 777, но в настоящее время он недоступен в рейтинге, даже если бы мы поставили "query(incomingLikeWeight)": 50. Мы можем использовать функцию rank в YQL (первый и только первый аргумент функции rank() определяет, является ли документ совпадением, но все аргументы используются для вычисления оценки ранжирования), а затем использовать dotProduct в нашем выражении ранжирования YQL для хранения и извлечения необработанных оценок (в данном случае метки времени, когда пользователь нас лайкнул), например, таким образом:

{    "yql": "select userId,summaryfeatures from user where !(userId contains \"777\") and rank(lastOnline > 1592486978, dotProduct(likedUserSet, {\"777\":1})) limit 2;",    "ranking": {        "profile": "myRankProfile",        "features": {            "query(lastOnlineWeight)": "50",            "query(incomingLikeWeight)": "50"        }    },    "pos": {        "radius": "50mi",        "ll": "N40o44'22;W74o0'2",        "attribute": "latLong"    },    "presentation": {        "summary": "default"    }}

{    "root": {        "id": "toplevel",        "relevance": 1.0,        "fields": {            "totalCount": 317        },        "coverage": {            "coverage": 100,            "documents": 958,            "full": true,            "nodes": 1,            "results": 1,            "resultsFull": 1        },        "children": [            {                "id": "index:user/0/e8aa37df0832905c3fa1dbbd",                "relevance": 98.97595807613169,                "source": "user",                "fields": {                    "userId": 6888497210242094612,                    "summaryfeatures": {                        "rankingExpression(incomingLikeScore)": 50.0,                        "rankingExpression(lastOnlineScore)": 48.97595807613169,                        "vespa.summaryFeatures.cached": 0.0                    }                }            },            {                "id": "index:user/0/bde9bd654f1d5ae17fd9abc3",                "relevance": 48.9787037037037,                "source": "user",                "fields": {                    "userId": -5800469520557156329,                    "summaryfeatures": {                        "rankingExpression(incomingLikeScore)": 0.0,                        "rankingExpression(lastOnlineScore)": 48.9787037037037,                        "vespa.summaryFeatures.cached": 0.0                    }                }            }        ]    }}

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

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

Настройка промежуточного уровня middleware в Java


Что, если бы мы хотели пойти другим путём и сделать это выражение dotProduct неявно частью каждого запроса? Вот где появляется настраиваемый уровень контейнера Java мы можем написать пользовательский компонент Searcher. Это позволяет обрабатывать произвольные параметры, переписывать запрос и обрабатывать результаты определённым образом. Вот пример на Kotlin:

@After(PhaseNames.TRANSFORMED_QUERY)class MatchSearcher : Searcher() {    companion object {        // HTTP query parameter        val USERID_QUERY_PARAM = "userid"        val ATTRIBUTE_FIELD_LIKED_USER_SET = likedUserSet    }    override fun search(query: Query, execution: Execution): Result {        val userId = query.properties().getString(USERID_QUERY_PARAM)?.toLong()        // Add the dotProduct clause        If (userId != null) {            val rankItem = query.model.queryTree.getRankItem()            val likedUserSetClause = DotProductItem(ATTRIBUTE_FIELD_LIKED_USER_SET)            likedUserSetClause.addToken(userId, 1)            rankItem.addItem(likedUserSetClause)               }        // Execute the query        query.trace("YQL after is: ${query.yqlRepresentation()}", 2)        return  execution.search(query)    }}

Потом в нашем файле services.xml мы можем настроить этот компонент следующим образом:

...                <search>            <chain id="default" inherits="vespa">                <searcher id="com.okcupid.match.MatchSearcher" bundle="match-searcher"/>            </chain>        </search>        <handler id="default" bundle="match-searcher">            <binding>http://*:8080/match</binding>        </handler>...

Затем мы просто создаём и развёртываем пакет приложения и делаем запрос к пользовательскому обработчику http://localhost:8080/match-что?userid=777:

{    "yql": "select userId,summaryfeatures from user where !(userId contains \"777\") and rank(lastOnline > 1592486978) limit 2;",    "ranking": {        "profile": "myRankProfile",        "features": {            "query(lastOnlineWeight)": "50",            "query(incomingLikeWeight)": "50"        }    },    "pos": {        "radius": "50mi",        "ll": "N40o44'22;W74o0'2",        "attribute": "latLong"    },    "presentation": {        "summary": "default"    }}

Мы получаем те же результаты, что и раньше! Обратите внимание, что в коде Kotlin мы добавили трассировку для выдачи представления YQL после изменения, поэтому, если установить tracelevel=2 в параметрах URL, ответ также будет показан:

...                    {                        "message": "YQL after is: select userId, summaryfeatures from user where ((rank(lastOnline > 1592486978, dotProduct(likedUserSet, {\"777\": 1})) AND !(userId contains \"777\") limit 2;"                    },...

Контейнер промежуточного слоя Java является мощным средством, чтобы добавить пользовательскую логику обработки через Searcher или собственную генерацию результатов с помощью Renderer. Мы настраиваем наши компоненты Searcher для обработки случаев, подобных приведённым выше, и других аспектов, которые мы хотим сделать неявными в наших поисках. Например, одной из концепций продукта, которую мы поддерживаем, является идея взаимной подгонки вы можете искать пользователей с определёнными критериями (например, возрастной диапазон и расстояние), но вы также должны соответствовать критериям поиска кандидатов. Чтобы поддержать такой вариант в нашем компоненте Searcher, мы могли бы извлечь документ пользователя, который выполняет поиск, чтобы предоставить некоторые из его атрибутов в последующем разветвлённом запросе для фильтрации и ранжирования. Структура ранжирования и кастомный промежуточный слой вместе обеспечивают гибкий способ поддержки многочисленных вариантов использования. В этих примерах мы рассмотрели только несколько аспектов, но здесь вы можете найти подробную документацию.

Как мы построили кластер Vespa и запустили его в продакшн


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

Первые этапы прототипирования


Системы бэкенда OkCupid написаны на Golang и C++. Чтобы написать кастомные логические компоненты Vespa, а также обеспечить высокую скорость подачи фида с помощью Java Vespa HTTP feed client API, нам пришлось немного познакомиться со средой JVM мы в конечном итоге использовали Kotlin при настройке компонентов Vespa и в наших конвейерах подачи.

Несколько лет занял портирование прикладной логики и раскрытие функций Vespa, консультации с командой Vespa по мере необходимости. Большая часть системной логики движка поиска соответствий написано в C++, поэтому мы также добавили логику для перевода нашей текущей модели данных фильтров и сортировок в эквивалентные запросы YQL, которые мы выдаем через REST кластеру Vespa. На раннем этапе мы также позаботились о создании хорошего конвейера для повторного заполнения кластера полной пользовательской базой документов; прототипирование должно включать в себя множество изменений для определения правильных типов полей для использования, а также непреднамеренно требует повторной подачи фида с документами.

Мониторинг и нагрузочное тестирование


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

Перед нагрузочными тестами мы везде добавили метрики Prometheus. Vespa-exporter предоставляет массу статистических данных, а сама Vespa также предоставляет небольшой набор дополнительных метрик. Исходя из этого, мы создали различные информационные панели Grafana по запросам в секунду, задержкам, использованию ресурсов процессами Vespa и т.д. Мы также запустили vespa-fbench для тестирования производительности запросов. С помощью разработчиков Vespa мы определили, что из-за относительно высокой стоимости статических запросов наш сгруппированный готовый макет обеспечит более скоростную выдачу. В плоском макете добавление большего количества узлов в основном только сокращает стоимость динамического запроса (то есть той части запроса, которая зависит от количества проиндексированных документов). Сгруппированный макет означает, что каждая настроенная группа узлов будет содержать полный набор документов, и поэтому одна группа может обслужить запрос. Из-за высокой стоимости статических запросов, сохраняя количество узлов одинаковым, мы значительно увеличили пропускную способность, увеличив количество с одной группы с плоской компоновкой до трёх. Наконец, мы также провели тестирование неучтённого теневого трафика в реальном времени, когда стали уверены в надёжности статических бенчмарков.

Оптимизация производительности


Производительность выдачи стала одним из самых больших препятствий, с которым мы столкнулись на ранней стадии. В самом начале у нас появились проблемы с обработкой обновлений даже на 1000 QPS (запросов в секунду). Мы активно использовали поля из взвешенного множества (weighted set fields), но поначалу они не были эффективными. К счастью, разработчики Vespa быстро помогли решить эти проблемы, а также другие, связанные с распространением данных. Позже они также добавили обширную документацию по калибровке фидов, которую мы в какой-то степени используем: целочисленные поля в больших взвешенных множествах, когда это возможно, позволяют дозировать, устанавливая visibility-delay, используя несколько условных обновлений и полагаясь на поля атрибутов (то есть в памяти), а также сокращая количество пакетов туда-обратно от клиентов за счёт уплотнения и слияния операций в наших конвейерах фмдов. Теперь конвейеры спокойно обрабатывают 3000 QPS в устойчивом состоянии, и наш скромный кластер обрабатывает обновления на 11тыс.QPS, когда такой всплеск возникает по какой-то причине.

Качество рекомендаций


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

Схема системы


В упрощённом виде итоговая схема архитектуры новой системы выглядит так:



Как Vespa работает сейчас и что будет дальше


Давайте сравним состояние системы поиска пар Vespa, с прошлой системой:

  • Обновления схемы
    • Раньше: неделя с сотнями новых строк кода, тщательно скоординированное развёртывание с несколькими подсистемами
    • Теперь: за пару часов добавляете простое поле в определение схемы и развёртываете пакет приложения
  • Добавление новой сортировки/ранжирования
    • Раньше: полдня на развёртывание
    • Теперь: выражения ранжирования сами по себе являются обновлением определения схемы и могут быть развёрнуты в рабочей системе. Таким образом, их включение занимает всего несколько секунд!
  • Масштабирование и поддержка
    • Раньше: многонедельные усилия по ручному распределению шардов и размещению файлов обслуживания продакшна, чтобы добиться высокой доступности
    • Теперь: просто добавляем новый узел в конфигурационный файл, и Vespa автоматически распределит данные для желаемых уровней избыточности. Основная часть операций не требует ручного вмешательства или перезапуска каких-либо узлов с отслеживанием состояния

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

Что дальше?


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

Кроме того, Vespa недавно объявила о поддержке многомерных приближённых индексов ближайших соседей (nearest neighbor index), которые работают полностью в реальном времени, одновременно доступны для поиска и динамически обновляются. Нам очень интересно изучить другие варианты использования поиска с индексом ближайших соседей в режиме реального времени.

OkCupid и Vespa. Поехали!


Многие слышали или работали с Elasticsearch, но вокруг Vespa нет такого большого сообщества. Мы считаем, что много других приложений на Elasticsearch лучше бы работали на Vespa. Она отлично подходит для OkCupid, и мы рады, что перешли на неё. Эта новая архитектура позволила нам гораздо быстрее развиваться и разрабатывать новые функции. Мы относительно небольшая компания, так что это здорово особо не беспокоиться о сложностях обслуживания. Теперь мы гораздо лучше готовы к горизонтальному масштабированию нашего поисковика. Без Vespa мы, конечно, не смогли бы добиться того прогресса, которого достигли за последний год. Для получения дополнительной информации о технических возможностях Vespa обязательно ознакомьтесь с рекомендациями по Vespa AI в электронной коммерции от @jobergum.

Мы сделали первый шаг и лайкнули разработчиков Vespa. Они послали нам ответное сообщение, и это оказалось совпадение! Мы не смогли бы сделать это без помощи команды Vespa. Особая благодарность @jobergum и @geirst за рекомендации по ранжированию и обработке запросов, а также @kkraune и @vekterli за их поддержку. Уровень поддержки и усилий, которые оказала нам команда Vespa, был поистине потрясающим от глубокого изучения нашего варианта использования до диагностики проблем производительности и мгновенного внесения улучшений в движок Vespa. Товарищ @vekterli даже прилетел в наш офис в Нью-Йорке и в течение недели работал непосредственно с нами, чтобы помочь в интеграции движка. Большое спасибо команде Vespa!

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

Категории

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

  • Имя: Макс
    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