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

Блог компании вконтакте

Прокачиваем разметку мультимодальных данных меньше асессоров, больше слоёв

18.08.2020 18:15:11 | Автор: admin

Всем привет! Мы учёные лаборатории Машинное обучение ИТМО и команда Core ML ВКонтакте проводим совместные исследования. Одна из важных задач VK заключается в автоматической классификации постов: она необходима не только чтобы формировать тематические ленты, но и определять нежелательный контент. Для такой обработки записей привлекаются асессоры. При этом стоимость их работы можно значительно снизить с помощью такой парадигмы machine learning, как активное обучение.


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


image


Введение


Активное обучение это раздел machine learning, где модель взаимодействует с учителем. Она запрашивает у него для тренировки лишь те данные, которые позволят обучиться лучше и, следовательно, быстрее.


Это направление интересно компаниям, которые привлекают асессоров для разметки данных (например, с помощью сервисов Amazon Mechanical Turk, Яндекс.Толока) и хотят удешевить этот процесс. Один из вариантов использовать reCAPTCHA, где пользователь должен отмечать снимки, скажем, со светофорами, и заодно получать бесплатную разметку для Google Street View. Другой способ применять активное обучение.


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


Компания Amazon в своей работе описывает фреймворк DALC (Deep Active Learning from targeted Crowds). Он раскрывает концепцию активного обучения с точки зрения нейронных сетей, байесовского подхода и краудсорсинга. В исследовании в том числе используется техника Monte Carlo Dropout (о ней мы тоже поговорим в этой статье). Ещё авторы вводят любопытное понятие noisy annotation. Если в большинстве работ по активному обучению предполагается, что асессор говорит правду и ничего, кроме правды, то здесь допускается вероятность ошибки в силу человеческого фактора.


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


Но хватит говорить про использование парадигмы пора рассмотреть активное обучение в деталях! У него есть несколько основных подходов, или сценариев. В нашем исследовании модель взаимодействовала с учителем по сценарию pool-based sampling.


Рис. 1. Общая схема pool-based сценария активного обучения
Рис. 1. Общая схема pool-based сценария активного обучения


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


Наиболее полезные данные отправляются на разметку эксперту, чтобы затем вернуться для дообучения модели уже в активной фазе. Отправленные на разметку данные называют запросом (англ. query). Запрос может состоять как из одного объекта, так и из нескольких. Способы выбора объектов пула для запроса к эксперту (иными словами, способы оценить полезность данных) называются стратегиями активного обучения.


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


Набор данных и задача


Напомним, общая задача классификация постов ВКонтакте. Они представляют собой мультимодальные данные (изображение и текст). Предоставленный набор данных включает 250 тыс. готовых эмбеддингов постов. Здесь каждый объект (пост) размечен одним из 50 классов тематик публикаций и опционально содержит:


  1. векторное представление, или эмбеддинг (англ. embedding), картинки поста;
  2. векторное представление текста.

Стоит отметить, что набор данных сильно несбалансирован (см. рис. 2).


Рис. 2 гистограмма распределения классов
Рис. 2 гистограмма распределения классов


Модель для классификации


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


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


В проекте мы экспериментально изучили различные конфигурации глубоких нейронных сетей. Проводились эксперименты с добавлением residual соединений, highway блоков, использованием энкодеров (англ. encoder). Рассмотрели разные варианты, учитывающие мультимодальность на основе слияния (англ. fusion): метод внимания для мультимодальных данных, матричное слияние.
Но некоторые способы учёта мультимодальности данных невозможно было применить к нашей задаче например, выравнивание и обучение различным представлениям. Это связано с готовым представлением данных в виде предобученных векторов-эмбеддингов.


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


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


Рис. 3. Подобранная архитектура для классификации
Рис. 3. Подобранная архитектура для классификации


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


Блоки, обозначенные красным и синим на рис. 3, имеют следующий вид:


Рис. 4. Описание основных блоков модели нейронной сети для классификации
Рис. 4. Описание основных блоков модели нейронной сети для классификации


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


Один из закономерных и важных вопросов, которые возникли при построении этой архитектуры: как считать функцию потерь? Варианты:


  1. простое покомпонентное суммирование элементов функции потерь с разных голов;
  2. взвешенная функция потерь с ручным перебором весов;
  3. взвешенная функция потерь с обученными весами голов.

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


$L = \frac{1}{\sigma_1 ^ 2}L_1 + \frac{1}{\sigma_2 ^ 2}L_2 + \frac{1}{\sigma_3 ^ 2}L_3 + \log{\sigma_1} + \log{\sigma_2} + \log{\sigma_3}$


где $L_1, L_2, L_3$ функции потерь для разных выходов модели (в нашем случае они представляют собой категориальную кросс-энтропию), а $\sigma_1, \sigma_2, \sigma_3$ настраиваемые параметры, характеризующие дисперсию и шум в данных.


Pool-based sampling


С моделью определились теперь опишем, как будем применять и тестировать различные стратегии активного обучения. Согласно сценарию pool-based sampling мы составили пайплайн экспериментов:


  1. Берём из тренировочного набора данных какое-то количество случайных объектов.
  2. Обучаем на них модель.
  3. Выбираем новую пачку данных из оставшегося тренировочного набора, основываясь на тестируемой стратегии, и добавляем их к размеченным данным.
  4. Дообучаем модель.
  5. Считаем метрики (валидационную точность).
  6. Повторяем шаги 35 до выполнения определённого критерия (например, пока не кончится весь тренировочный набор данных).

Первые два шага соответствуют пассивной фазе обучения, шаги 36 активной.


Помимо самой стратегии, в данном пайплайне значимыми являются два параметра, а именно:


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


  2. Размер запроса к эксперту. С одной стороны, можно отправлять объекты к эксперту по одному. В этом случае первый объект в запросе будет максимизировать критерий рассматриваемой стратегии активного обучения (при сортировке объектов по убыванию соответствия критерию). И после обучения на этом объекте остальные в запросе, скорее всего, перестанут представлять интерес. Но если выбирать объекты по одному, то эксперимент затянется и всё исследование усложнится. Поэтому мы остановились на 20 объектах в запросе.
    Ещё можно варьировать количество шагов в фазе активного обучения. Очевидно, что с его увеличением точность модели может расти. Но цель нашего проекта не достичь максимальной возможной точности классификации, а исследовать эффективность активного обучения. Поэтому мы решили зафиксировать количество шагов на 100 или 200.



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


Инсайт 1: влияние выбора batch size


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


Рис. 5. График обучения baseline-модели пассивным способом. Приведён результат пяти запусков с доверительным интервалом
Рис. 5. График обучения baseline-модели пассивным способом. Приведён результат пяти запусков с доверительным интервалом


Для достоверности этот и все последующие эксперименты запускались по пять раз с разным random state. На графиках выводится средняя точность запусков с доверительным интервалом.


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


Чтобы устранить этот эффект, нужно осознать важность параметра размера батча (англ. batch size). В нашем случае он был по дефолту выбран равным 512 из-за большого количества классов (50). Получалось, что при конечном размере размеченного набора данных и фиксированном batch size последний батч мог оказаться крайне мал. Это вносило шум в значение градиента и негативно сказывалось на обучении модели в целом. Мы опробовали следующие варианты решения этой проблемы:


  1. upsample, чтобы порции данных были одной длины;
  2. увеличение числа эпох обучения, чтобы влияние маленького батча нивелировалось последующими.

Итоговым решением стало формирование адаптивного batch size: на каждом шаге активного дообучения он вычислялся согласно формуле (1).


$current\_batch\_size =b + \Big \lfloor\frac{n \mod b}{\lfloor\frac{n}{b}\rfloor}\Big\rfloor [1]$


где $b$ изначальный batch size, а $n$ текущий размер размеченного набора данных.
Адаптивный подход помог сгладить просадки точности и получить монотонно возрастающий график (рис. 6).


Рис. 6. Сравнение использования фиксированного параметра batch size (passive на графике) и адаптивного (passive + flexible на графике)
Рис. 6. Сравнение использования фиксированного параметра batch size (passive на графике) и адаптивного (passive + flexible на графике)


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


Теперь перейдём непосредственно к исследованию методов активного обучения для нашей задачи.


Uncertainty


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


В статье приводятся три варианта подсчёта неуверенности:


1. Минимальная уверенность (англ. Least confident sampling)


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


$x^{*}_{LC} = \underset{x}{\arg\max} \ 1 - P_{\theta }(\hat{y}|x) [2]$


где $\hat{y} = \underset{y}{\arg\max}\ P_{\theta}(y|x)$ класс с наибольшей вероятностью при классификации моделью, $y$ один из возможных классов, $x$ один из объектов набора данных, $x^{*}_{LC}$ объект, выбранный с помощью стратегии наименьшей уверенности объект.


Подробнее

Эту меру можно понимать так. Допустим, функция потерь на объекте выглядит как $1-\hat{y}$. В таком случае модель выбирает объект, на котором получит худшую оценку значения функции потерь. Она обучается на нём и тем самым уменьшает значение функции потерь.


Но у этого метода есть недостаток. Например, на одном объекте модель получила следующее распределение по трём классам: {0,5; 0,49; 0,01}, а на другом {0,49; 0,255; 0,255}. В таком случае алгоритм выберет второй объект, так как его наиболее вероятное предсказание (0,49) меньше, чем у первого объекта (0,5). Хотя интуитивно понятно, что бльшую информативность для обучения имеет первый объект: вероятности первого и второго класса в предсказании почти равны. Алгоритм стоит модифицировать, чтобы учитывать такие ситуации.


2. Минимальный отступ (англ. Margin sampling)


Согласно этому виду стратегии, алгоритм отправит на экспертизу те объекты, для которых наибольшую вероятность имеют два класса, причём эти вероятности близки:


$x^{*}_{M} = \underset{x}{\arg\min} \ P_{\theta }(\hat{y}_{1}|x) - P_{\theta }(\hat{y}_{2}|x)[3]$


где $\hat{y}_1$ наиболее вероятный класс для объекта $x$, $\hat{y}_2$ второй по вероятности класс.


Подробнее

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


3. Максимальная энтропия (англ. Entropy sampling)


В этом виде стратегии для измерения неуверенности модели используется мера энтропии:


$x^{*}_{H} = \underset{x}{\arg\max} -\sum \ P_{\theta }(y_{i}|x)\log{P_{\theta }(y_{i}|x)}[4]$


где $y_{i}$ вероятность $i$-го класса для объекта $x$ при классификации данной моделью.


Подробнее

Энтропия удобна тем, что она обобщает два метода, которые мы описали выше. Она выбирает объекты обоих типов:


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

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


Но практические результаты в рамках решаемой задачи показали расхождение с теорией (рис. 7).


Рис. 7. Результаты сравнения различных видов стратегии uncertainty sampling с пассивным обучением (слева с методом минимальной уверенности, по центру с методом минимального отступа, справа с методом максимальной энтропии)
Рис. 7. Результаты сравнения различных видов стратегии uncertainty sampling с пассивным обучением (слева с методом минимальной уверенности, по центру с методом минимального отступа, справа с методом максимальной энтропии)


Как можно заметить, методы least confident и entropy sampling показали себя хуже, чем пассивное обучение со случайным выбором объектов для дообучения. В то же время margin sampling оказался более эффективным.


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


Перечисленные методы просты с точки зрения реализации и обладают низкой вычислительной сложностью. Можно оценить сложность одного запроса к эксперту как $O(p\log{q})$, где $p$ размер неразмеченного набора данных, а $q$ число объектов в запросе к эксперту. Также эти методы легко применять на практике, так как они не требуют изменения используемой модели.


BALD


Следующая стратегия, о которой пойдёт речь, BALD sampling (Bayesian Active Learning by Disagreement). Это байесовский подход к измерению неуверенности комитета моделей.


Согласно классификации методов активного обучения, это представитель стратегии query-by-committee (QBC). Её основная идея в использовании предсказаний нескольких моделей с конкурирующими гипотезами. Можно брать их усреднённое предсказание за основу для uncertainty sampling. Или выбирать для разметки те объекты, в предсказаниях которых модели несогласны в большей степени. Эксперименты проводились с методом QBC на основе Monte Carlo Dropout, речь о котором пойдёт дальше.


Проблема классических байесовских методов для глубокого обучения в том, что необходимо выводить большое количество параметров, а это делает обучение моделей в два раза дороже. Поэтому авторы предложили использовать dropout в качестве способа байесовской аппроксимации. Этот подход отличается от привычного применения dropout тем, что он используется во время инференса (на стадии предсказания). Причём для каждого объекта выборки предсказание делается несколько раз одной и той же моделью, но с разными dropout-масками (рис. 8). Такой способ сэмплирования называется Monte Carlo Dropout (MC Dropout) и не требует увеличения затрат памяти при обучении модели. Так с помощью одной модели можно получить несколько предсказаний, которые могут различаться для одного и того же объекта. Несогласие моделей (где они отличаются друг от друга лишь масками dropout) считается на основе Mutual Information (MI). MI здесь представляет собой эпистемическую неопределённость, или неуверенность комитета, то есть такой вид неопределённости, которая уменьшается с добавлением новых данных. Это согласуется с концепцией активного обучения в целом.


Рис. 8. Иллюстрация MC Dropout для метода BALD
Рис. 8. Иллюстрация MC Dropout для метода BALD


Итак, для начала мы использовали усреднённое предсказание полученного QBC на основе MC Dropout комитета и применили к нему различные методы uncertainty sampling. Это не дало прироста по сравнению с соответствующими методами, использующими только одно предсказание (рис. 9).


Рис. 9. Результаты сравнения различных видов стратегии uncertainty sampling на основе QBC и без него с пассивным обучением (слева - с методом минимальной уверенности, по центру - с методом минимального отступа, справа - с методом максимальной энтропии)
Рис. 9. Результаты сравнения различных видов стратегии uncertainty sampling (на основе QBC и без него) с пассивным обучением (слева с методом минимальной уверенности, по центру с методом минимального отступа, справа с методом максимальной энтропии)


На следующем шаге мы использовали меры несогласия комитета по методу BALD. Как уже было сказано, для этого применяется Mutual Information моделей комитета:


$a_{BALD}=\mathbb{H}(y_1,...,y_n)-\mathbb{E}[\mathbb{H}(y_1,...,y_n|\omega)] [5]$


$\mathbb{E}[\mathbb{H}(y_1,...,y_n|w)]=\frac{1}{k}\sum_{i=1}^{n}\sum_{j=1}^{k}\mathbb{H}(y_i|w_j) [6]$


где $n$ число классов, $k$ число моделей в комитете.


Первое слагаемое в формуле (5) представляет собой энтропию усреднённого предсказания комитета, второе среднюю энтропию каждой модели в отдельности. Таким образом, выбираются только те объекты, в предсказании для которых комитет менее всего согласен. Результаты применения метода BALD представлены на рис. 10.


Рис. 10. Результаты применения стратегии BALD в сравнении с пассивным способом обучения
Рис. 10. Результаты применения стратегии BALD в сравнении с пассивным способом обучения

К сожалению, пока что данный метод не дал ожидаемого результата на долгом запуске эксперимента, несмотря на прирост по сравнению с пассивным методом в начале.
Сложность алгоритмов стратегии query-by-committee в целом и BALD в частности пропорциональна числу предсказаний, сделанных для каждого объекта. В свою очередь, сложность предсказания для каждого объекта аналогична методам uncertainty sampling. Таким образом, сложность одного запроса $O(kp\log(q))$, где $p$ размер неразмеченного набора данных, $q$ число объектов в запросе к эксперту, а $k$ число предсказаний, посчитанных для одного объекта.


На практике применить метод BALD может быть нелегко при использовании фреймворка tf.keras, так как он не обладает достаточной гибкостью для работы со слоями. Поэтому в рамках данного проекта мы выбрали фреймворк PyTorch, который позволил не только с легкостью включать dropout во время инференса, но и отключать batch normalization в течение активной фазы, о чем пойдет речь далее.


Инсайт 2: отключение batch normalization в активной фазе


Подобранная модель классификации в своей структуре использует слои batch normalization. Суть подхода batch normalization в обучении параметров нормализации данных во время обучения и применении найденных параметров во время инференса, или предсказания. Идея, которую мы использовали, состоит в том, чтобы рассматривать активную фазу обучения как этап инференса, и отключать на ней обучение batch normalization. К тому же интуитивно кажется, что такой подход позволит избежать смещения модели. Насколько нам известно, данный вопрос ещё не исследовался в отношении методов активного обучения. Для экспериментов мы взяли за основу метод BALD. Рассмотрим результаты (рис. 11).


Рис. 11. Результаты отключения batch normalization для метода BALD в сравнении со стандартным методом и пассивным обучением
Рис. 11. Результаты отключения batch normalization для метода BALD в сравнении со стандартным методом и пассивным обучением


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


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


Learning loss


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


Создадим вспомогательную модель, принимающую на вход выходы промежуточных и последних слоёв модели. Задача вспомогательной модели предсказывать значение функции потерь. Мы будем выбирать для разметки те объекты, для которых это значение максимально. Этот метод называется learning loss, подробнее про него можно почитать здесь. Рассмотрим результаты первичных экспериментов, где метод применялся для базовой модели (рис. 12).


Рис. 12. Результаты применения Learning loss для базовой модели в сравнении с ее пассивным обучением
Рис. 12. Результаты применения Learning loss для базовой модели в сравнении с ее пассивным обучением

Метод learning loss не дал прироста по сравнению с пассивным обучением на случайно выбранных объектах. Логично было бы использовать его для моделей других архитектур или отказаться от него как от неэффективного для нашей задачи.
Но мы вместо этого попробуем провести следующий эксперимент. В обычном сценарии активного обучения модель не знает настоящих меток классов, а в нашей задаче они известны. Это позволяет посчитать идеальный learning loss: зная настоящую метку класса объекта, будем считать на нём значение функции потерь и добавлять в размеченный набор данных те объекты, у которых оно больше. Назовём такой подход ideal learning loss (рис. 13).


Рис. 13. Результаты применения ideal learning loss для базовой модели в сравнении с ее пассивным обучением
Рис. 13. Результаты применения ideal learning loss для базовой модели в сравнении с её пассивным обучением

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


  1. Обучаем модель на начальной выборке (2000 объектов), как для активного обучения;
  2. Выбираем из всего набора неразмеченных данных 10000 объектов (чтобы ускорить подсчёт);
  3. Для выбранных объектов неразмеченной выборки считаем значения функции потерь;
  4. Сортируем объекты по полученным значениям;
  5. Разбиваем на группы по 100 объектов;
  6. Для каждой группы параллельно обучаем на ней модель, стартуя с весов, полученных на шаге 1;
  7. Фиксируем получившиеся точности.

Далее считаем корреляцию Спирмена между точностью модели, обученной на определённой выборке, и средним значением функции потерь по каждому из объектов выборки. А также вычисляем, как средняя точность модели коррелирует со средним значением параметра отступа (из метода margin sampling).


Таблица 1. Корреляции точности и метрик активного обучения для набора данных публикаций ВКонтакте


Корреляция Спирмена p-value
Точность и loss -0,2518 0,0115
Точность и margin 0,2461 0,0136

Как видим, для метода margin sampling корреляция слабая и положительная то есть, зная отступ, можно быть уверенными, что объект полезен для обучения модели. А в случае c функцией потерь корреляция слабая отрицательная.


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


Рис. 14. Результаты применения обратного ideal learning loss для базовой модели в сравнении с прямым ideal learning loss и пассивным обучением
Рис. 14. Результаты применения обратного ideal learning loss для базовой модели в сравнении с прямым ideal learning loss и пассивным обучением


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


Таблица 2. Корреляции точности и метрик активного обучения для набора данных MNIST


Корреляция Спирмена p-value
Точность и loss 0,2140 0,0326
Точность и энтропия 0,2040 0,0418

При этом сам метод ideal learning loss работает так, как ожидается (рис. 15).


Рис. 15. Активное обучение классификатора символов из набора данных MNIST стратегией ideal learning loss. Синий график ideal learning loss, оранжевый пассивное обучение
Рис. 15. Активное обучение классификатора символов из набора данных MNIST стратегией ideal learning loss. Синий график ideal learning loss, оранжевый пассивное обучение

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


Сложность метода learning loss та же, что и для методов uncertainty sampling: $O(p\log{q})$, где $p$ размер неразмеченного набора данных, а $q$ число объектов в запросе к эксперту. Но важно учесть, что при его применении обучать нужно не только основную модель, но и вспомогательную. Этот метод сложнее предыдущих в практическом применении ещё и потому, что требует проводить обучение каскада моделей.


Заключение


Не будем бесконечно раздувать статью, рассказывая обо всех применённых методах и проведённых экспериментах. Здесь мы постарались осветить основные и наиболее интересные. Любопытно, что эффективнее всех оказался самый первый и простой метод margin sampling результаты его длинного запуска можно увидеть на рис. 16.
Рис. 16. Сравнение обучения на случайно выбираемых данных (пассивное обучение) и на данных, выбираемых стратегией margin sampling
Рис. 16. Сравнение обучения на случайно выбираемых данных (пассивное обучение) и на данных, выбираемых стратегией margin sampling


График показывает: тренируя модель с помощью активного обучения (в нашем случае стратегией margin sampling), можно достичь предельной точности такой же, как у модели, обученной пассивным способом. Но при этом использовать на 25 тыс. объектов меньше. Экономия ресурсов разметки составит порядка 25% это довольно значимо.


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


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


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

Как реализовать свою идею и не сойти с ума на самоизоляции

03.07.2020 18:07:41 | Автор: admin
Во время тотальной самоизоляции разработчики стали ещё активнее интересоваться онлайн-ивентами, где можно поучиться и попробовать силы в конкурсах. На этой волне мы запустили грантово-образовательный проект для нашего комьюнити. Не мелочась, заложили призовой фонд в 15 миллионов рублей. В этой статье расскажем, почему конкурс не сбавляет оборотов даже сейчас, когда никто уже не сидит дома, что ждёт VK Fresh Code дальше и какие приложения победили в первом этапе.

image


Денис Марков, деврел VK Mini Apps:
Мини-приложения открывают большие возможности перед теми, кто интересуется веб-разработкой. Используя привычные веб-технологии и уникальные возможности ВКонтакте, вы можете быстро сделать продукт, который привлечёт внимание тысяч, а то и миллионов пользователей.



Привет от команды VK Mini Apps! Мы расскажем, как помогли независимым разработчикам с пользой скоротать время на самоизоляции и что из этого получилось.

Почему всем был нужен VK Fresh Code


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

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

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

Что мы сделали


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

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

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

За время первого этапа конкурса, с 28 апреля по 8 июня, через всё это прошли 62 полноценных мини-аппа. Но из них только 30 соответствовали заявленной тематике волны она была посвящена образованию.

Познакомим вас с десятью победителями разработчики этих сервисов поровну разделили 3 миллиона рублей.

Итог первого этапа VK Fresh Code | Тематика: образование



Решу ЕГЭ


Готовиться к ЕГЭ проще, если для этого уже подобраны материалы, есть где потренироваться и у кого спросить совета. Такой набор возможностей пригодится выпускникам в любой точке страны: 14 предметов, тестовые вопросы, режим экзамена и сохранение личных достижений. А благодаря мини-аппу теперь всё это умещается в смартфоне.

image

Вадим Трегубенко, full-stack developer и автор проекта:
В 11-м классе портал Решу ЕГЭ был моим лучшим другом вот я и предложил его руководству разработать для них мини-приложение. Мне кажется, что благодаря моему проекту подготовка к экзаменам может стать ещё более удобной и структурированной.


Brain Defense


Интеллектуальный тренажёр в жанре Tower Defense. Чтобы защищаться от монстров, нужно решать математические примеры, разбираться в правописании и быстро переводить слова с английского. Скоро добавятся ещё вопросы по географии и химии.

image

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


Мозгополия


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



Александр Белов, геймдизайнер:
Идея Мозгополии появилась спонтанно, когда мы с друзьями-программистами проводили вечер за настольной игрой. Мы увидели новость о начале первого этапа VK Fresh Code и подумали: А почему бы не перенести в онлайн то, во что мы играем здесь и сейчас, но сделать упор на образование?.


GeoPuzzle


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



Виктор Тыщенко, бэкенд-разработчик и автор проекта:
Наверное, из всех победивших разработчиков мне было проще всего. Я разрабатываю и улучшаю аналогичный сервис уже три года, но на другой площадке. Фактически мне потребовалось только понять, как перенести свои наработки на платформу VK Mini Apps. В итоге всё получилось. Этот опыт дал мне новую аудиторию, и прямо сейчас в моём приложении она влюбляется в географию
.

Эко Просто


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



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


SMM Посты


Внутри этого сервиса много интересного для SMM-специалистов: генератор тем для контента, формулы продающих текстов, галерея баннеров, трекер постинга и даже целая академия с уроками копирайтинга.

image

Анастасия Югова, дипломированный маркетолог, автор приложения и сообщества о SMM:
У меня есть экспертный опыт в продвижении сообществ ВКонтакте. Ещё полгода назад я задумала создать бесплатный сервис, который поможет админам вести коммерческие страницы. Провела анкетирование, в котором подробно опросила 566 респондентов о проблемах, с которыми они сталкиваются в этой работе. Все ответы структурировала и выявила главные боли.

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


QURAGA



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



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


Детский



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



Андрей Комаров, разработчик и автор проекта:
Я участвовал в обоих конкурсах мини-приложений от платформы VK Mini Apps. И в том и в другом занимал вторые места и брал награду за лучшее техническое решение. Потом случился Домашний разраб и очередное второе место по баллам в своей волне. Так что у меня уже четыре призовых места! Если бы у всех была такая же настойчивость и вера в себя, как у меня, думаю, мир был бы лучше.


Словоглот ридер



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



Константин Барышев, full-stack developer и автор проекта:
До сих пор скептически отношусь к монетизации на платформе, хоть в последнее время всё и становится намного лучше. Продолжаю совершенствовать своё приложение Полиглот оно давно в каталоге. Когда узнал про грант, решил создать сиквел, но с иностранными словами. Грела душу перспектива получить 300 тысяч рублей и всё получилось! Очень помогли ранние наработки и опыт, так что конкурс для меня прошёл чётко и выверено. Переживал только за то, чтобы попасть в тематику этапа. Спасибо за питчинг это мотивировало не забросить задуманное.


Грамотей



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



Александр Шарипов, full-stack developer и автор проекта:
У меня всё достаточно прозаично: идея в голове сидела давно, а делать её просто ради интереса было как-то неактуально. Можно сказать, что звёзды сошлись. Нас ограничили только общей тематикой в неё моя идея попадала идеально и пообещали приятный бонус за победу. Считаю, что не зря выжидал и вынашивал проект. Он дождался своего часа.



До конца года у нас состоятся ещё четыре этапа конкурса VK Fresh Code, включая вторую волну, которая уже идёт. В каждом из них авторы 10 лучших приложений получат грантовую поддержку 300 тысяч рублей. Половина суммы поступит на счёт в рекламном кабинете VK чтобы новый сервис получил мощное продвижение.

Участникам нужно пройти модерацию и бета-тестирование, чтобы попасть в каталог мини-приложений платформы VK Mini Apps. У каждого этапа свои сроки и тематика за ними можно следить в официальной группе платформы: vk.com/vkappsdev

Помните: не всегда побеждают большие и важные зачастую удача улыбается быстрым и дерзким. Ждём вас на VK Fresh Code поможем пройти путь от поиска идеи до релиза!
Подробнее..

ВКонтакте снова выкладывает KPHP

11.11.2020 12:15:25 | Автор: admin
Привет! Сейчас будет дежавю.
Мы снова выложили на GitHub наш PHP-компилятор KPHP. Он проделал большой путь, и чтобы рассказать о нём, сначала телепортируемся на шесть лет назад.

Поясню для тех, кто не в теме: платформа ВКонтакте изначально была написана на PHP. Со временем нас перестала устраивать производительность, и мы решили ускорить VK. Сделали компилятор KPHP, который поддерживал узкое подмножество PHP. Это было давно, и с тех пор мы о нём не рассказывали, так как KPHP почти не развивался до 2018-го года.

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

А теперь телепортация.

delorean



Из 2020-го в 2014-й


Идёт 2014 год, и ВКонтакте опенсорсит репозиторий kphp-kdb. Наверняка многие из вас помнят этот момент.

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

Что касается KPHP на тот момент он поддерживал версию PHP даже не знаю. Что-то среднее между 4 и 5. Были функции, примитивы, строки и массивы. Но не было классов и ООП, не было современных (на момент 2014-го) паттернов разработки. И весь бэкенд-код ВКонтакте был написан в процедурном стиле, на ассоциативных массивах.

В общем, инфоповод был интересным, но во что-то большее не развился.


Из 2014-го в 2020-й


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

Но наш нынешний PHP-код мало чем отличается от актуального в индустрии. Мы пишем на современном PHP: у нас есть классы, интерфейсы и наследование; есть лямбды, трейты и Composer; а ещё строгая типизация, анализ PHPDoc и интеграция с IDE. И KPHP делает всё это быстрым.

В 2014 году в техническую команду пришли новые люди, которые начали заниматься движками. (Да, ВКонтакте до сих пор десятки собственных закрытых движков, хотя точечно мы используем ClickHouse и другие известные.) Но KPHP долго никто не трогал. До 2018-го бэкенд-код действительно был на уровне PHP 4.5, а IDE плохо понимала код и почти не подсказывала при рефакторинге.

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

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


Давайте про KPHP: что это и как работает


KPHP берёт PHP-код и превращает его в С++, а уже этот С++ потом компилирует.

Эта часть техническая, она большая. Мы заглянем внутрь и увидим, что происходит с PHP-кодом: как из него получается С++ и что следует за этим. Не слишком кратко, чтобы обозначить базовые вещи, но и не чересчур детально в дебри не полезем.


KPHP выводит типы переменных


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

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

В PHP мы не пишем типы переменных (за исключением редких type hint для аргументов) поэтому KPHP сам выводит типы. То есть придумывает, как бы объявить переменную в C++, чтобы это было лучше.

Давайте посмотрим сниппеты на примерах.

Пример:
// PHP$a = 8 * 9;

// C++int64_t v$a = 0;v$a = 8 * 9;

Тут KPHP понял, что $a это целое число, то есть int64_t в C++, и сгенерил такой код.

Ещё пример:
// PHP$a = 8 / 9;

// C++double v$a = 0;v$a = divide(8, 9);

Казалось бы, просто изменили умножение на деление но уже другое. Деление в PHP работает не так, как в C++. 8/9 в С++ будет целочисленный 0, поэтому есть функция divide() с разными перегрузками. В частности, для двух интов она выполняет сначала каст к double.

Следующий пример:
// PHPfunction demo($val) { ... }// в других местах кодаdemo(1);demo(10.5);

// C++void f$demo(double v$val) {  }

KPHP проанализировал все вызовы функции demo() и увидел, что она вызывается только с целыми и дробными числами. Значит, её аргумент это double. Перегрузки нет в PHP, нет её и в KPHP (и не может быть, пока типы выводятся, а не указываются явно). Кстати, если внутри demo() будет вызов is_int($val), то на аргументе 1 это будет true в PHP, но false в KPHP, так как 1 скастится к 1.0. Ну и ладно, просто не надо так писать. Во многих случаях, если KPHP видит, что поведение может отличаться, выдаёт ошибку компиляции.

Дальше:
// PHP$a = [1];$a[] = 2;

// C++array < int64_t > v$a;v$a = v$const_array$us82309jfd;v$a.push_back(2);

Здесь KPHP понял, что $a это массив и в нём могут быть только целые числа. Значит, array<int64_t>. В данном случае array<T> это кастомная реализация PHP-массивов, которая ведёт себя идентично. В PHP массивы могут быть и векторами, и хеш-таблицами. Они передаются по значению, но для экономии используют copy-on-write. Индексация числами и числовыми строками это (почти) одно и то же. Всё это в KPHP реализовано похожим образом, чтобы работало одинаково.

Ещё пример:
// PHP$group = [  'id' => 5,  'name' => "Code mode"];

// C++array < mixed > v$group;v$group = v$const_array$usk6r3l12e;

В этом массиве (в хеш-таблице) мы смешиваем числа и строки. В KPHP есть специальный тип mixed, обозначающий какой-нибудь примитив. Это напоминает ZVAL в PHP, однако mixed это всего лишь 16 байт (enum type + char[8] storage). В mixed можно сложить числа и строки, но нельзя объекты и более сложные типы. В общем, это не ZVAL, а что-то промежуточное. Например, json_decode($arg, true) возвращает mixed, так как значение неизвестно на этапе компиляции. Или даже microtime() возвращает mixed, потому что microtime(true) это float, а microtime(false) массив (и кто это только придумал?..).

И последний пример:
// PHP$func_name = 'action_' . $_GET['act'];call_user_func($func_name);

А здесь мы получим Compilation error. Потому что нельзя вызывать функции по имени нельзя и всё. Нельзя обращаться по имени к переменным, к свойствам класса KPHP напишет ошибку, несмотря на то что это работает в PHP.


KPHP хоть и выводит типы, но позволяет их контролировать


Выше мы видели: когда разработчик типы не пишет, они выводятся автоматом.
Но их можно писать с помощью PHPDoc @var/@param/@return или через PHP 7 type hint. Тогда KPHP сначала всё выведет, а потом проверит.

Пример:
/** @param int[] $arr */function demo(int $x, array $arr) { ... }demo('234', []);  // ошибка в 1-м аргументеdemo(234, [3.5]); // ошибка во 2-м аргументе

Ещё пример:
/** @var int[] */$ids = [1,2,3];/* ... */// ошибка, если $group  это mixed[] из примера выше$ids[] = $group['id'];  // а вот так ок$ids[] = (int)$group['id'];

Ручной контроль позволяет избегать непреднамеренных ухудшений типов. Без @var переменная $ids вывелась бы как mixed[], и никто бы этого не заметил. А когда разработчик пишет PHPDoc значит, всё скомпилированное вывелось так же, как написано.


KPHP превращает PHP class в C++ struct


// PHPclass Demo {  /** @var int */  public $a = 20;  /** @var string|false */  protected $name = false;}

// C++struct C$Demo : public refcountable_php_classes<C$Demo> {  int64_t v$a{20L};  Optional < string > v$name{false};  const char *get_class() const noexcept;  int get_hash() const noexcept;};

Если в обычном PHP классы это более-менее те же хеш-таблицы, то в KPHP не так. На выходе получаются обычные плюсовые структуры, которые ведут себя ссылочно, как и в PHP (очень похоже на std::shared_ptr идеологически).

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

Наследование плюсовое (за исключением late static binding, но оно разруливается на этапе компиляции). Интерфейсы это тоже плюсовое множественное наследование, там главное refcount запрятать куда нужно. Правда, методы классов это отдельные функции, принимающие this явно, так оно логичнее с нескольких позиций.

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

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


Как конкретно происходит конвертация PHP в C++


php to cpp

Многие знакомы с этой терминологией те, кто занимался языками, или компиляторами, или статическим анализом.

Сначала PHP-файл превращается в линейный список токенов. Это такие минимальные неразрывные лексемы языка.

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

Далее выстраивается control flow graph это связывание функций и получение высокоуровневой информации о том, откуда и куда может доходить управление. Например, try/catch и if/else синтаксически похожи, но изнутри try можно добраться до внутренностей catch, а из if до тела else нет. На выходе получается информация о соответствии вершин и переменных, какие из них используются на чтение, а какие на запись, и тому подобное.

Потом происходит type inferring. Это тот магический вывод типов, который ставит в соответствие всем PHP-переменным переменные С++ с явно проставленными типами, а также определяет возвращаемые значения функций, поля классов и другое. Этот этап согласуется с тем, как код впоследствии будет исполняться на С++, какие там есть функции-хелперы, их перегрузки и прочее.

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

И наконец, кодогенерация: все PHP-функции превращаются в С++ функции, а PHP-классы в С++ структуры. Изменённые файлы и их зависимости перезаписываются, и код проекта на С++ готов.


Что дальше происходит с С++ кодом


cpp to deploy

Сгенерировать С++ из PHP этого мало. Собственно говоря, это самое простое :)

Во-первых, в PHP мы используем кучу функций стандартной библиотеки: header(), mb_strlen(), curl_init(), array_merge(). Их тысячи и все должны быть реализованы внутри KPHP с учётом типизации и работать так же, как в PHP. Реализация всего PHP stdlib (а также KPHP-дополнений), всех PHP-типов с операциями и допущениями это называется runtime, вон там квадратик сверху.

Во-вторых, PHP-сайт это веб-сервер. Следовательно, и в KPHP должна быть вся серверная часть, чтобы можно было в том же nginx подменить PHP-шный upstream на KPHP-шный и всё продолжало работать так же. KPHP поднимает свой веб-сервер, оркестрирует процессы, заполняет суперглобалы и переинициализирует состояние, как и PHP Это тоже хардкорная часть называется server, квадратик снизу.

И только имея результирующий код C++, написанные runtime и server, всё это можно объединить и отдать на откуп плюсовым компиляторам. Мы используем g++ там в диаграмме есть квадратик g++. Но не совсем так: у vk.com настолько огромная кодовая база, что этот компилятор не справляется, и поэтому мы применяем патченный distcc для параллельной компиляции на множестве агентов. В итоге всё линкуется в один огромный бинарник (это весь vk.com), он раскидывается на кучу бэкендов и синхронно перезапускается. Каждая копия запускает мастер-процесс, который порождает группу однопоточных воркеров. Вот они на самом деле и исполняют исходный PHP-код.

Многие технические проблемы остаются за кадром их не опишешь в статье на Хабре. Чего стоит один только сбор трейсов при ошибках: ведь в С++ не получить человекочитаемый стек, а хочется разработчику вообще его на PHP-код намаппить. Гигантское количество внутренних нюансов, множество подпорок и легаси но в итоге продукт хорошо работает и развивается.


KPHP vs PHP: что мы не поддерживаем


По итогам предыдущей части статьи должно было сложиться чёткое понимание: KPHP не может взять любой PHP-код и ускорить его. Так не работает.
Если код работает на PHP это не значит, что он заработает на KPHP.
KPHP это отдельный язык, со своими ограничениями и правилами.

  1. KPHP не компилирует то, что принципиально не компилируемо. Например, выше мы говорили про вызов функции по имени. Туда же eval, mocks, reflection. PHP extensions тоже не поддерживаются, так как внутренности KPHP пересекаются с Zend API примерно на 0%. Так что PHPUnit запустить на KPHP не выйдет. Но и не нужно! Потому что мы пишем на PHP, мы тестируем на PHP, а KPHP для продакшена.
  2. KPHP не компилирует то, что не вписывается в систему типов. Нельзя в массив сложить числа и объекты. Нельзя накидать рандомных интерфейсов с лямбдами и разгрести это в рантайме. В KPHP нет волшебного типа any.
  3. KPHP не поддерживает то, что нам в VK никогда не было нужно. ВКонтакте куча своих движков и мы с ними общаемся по специальному протоколу, который описан в TL-схеме. Поэтому нам никогда не нужна была человеческая поддержка MySQL, Postgres, Redis и прочего.
  4. Часть PHP-синтаксиса просто ещё не покрыта. Текущий уровень поддержки находится примерно на уровне PHP 7.2. Но отдельных синтаксических вещей нет: что-то сделать очень сложно, до другого не дошли руки, а оставшееся мы считаем ненужным. Например, KPHP не поддерживает генераторы и наследование исключений мы не любим исключения. Ссылки поддержаны только внутри foreach и в аргументах функций. Всё так, потому что мы разрабатывали KPHP как удобный инструмент для наших задач, а компилировать сторонние библиотеки в планы не входило.


KPHP vs PHP: в чём мы превосходим


В скорости. Если использовать KPHP грамотно, то код будет работать значительно быстрее, чем на PHP 7.4. А некоторых вещей нет в PHP и чтобы при разработке он не падал с ошибками, там просто заглушки.

Итак, в чём наш профит:

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

Отдельно чуть-чуть расскажу про асинхронность. Это чем-то похоже на async/await в других языках, а чем-то на горутины. KPHP-воркеры однопоточные, но умеют свитчиться между ветками исполнения: когда одна ветка ждёт ответ от движка, вторая выполняет свою работу, и когда первая дождалась управление снова переключается туда.

Например, нам нужно загрузить пользователя и одновременно посчитать какую-то подпись запроса (CPU-работа допустим, это долго). В обычном (синхронном) варианте это выглядит так:
$user = loadUser($id);$hash = calcHash($_GET);

Но эти действия независимы пока грузится пользователь, можно считать хеш, а потом дождаться загрузки. В асинхронном варианте это происходит так:
$user_future = fork(loadUser($id));$hash = calcHash($_GET);$user = wait($user_future);

То есть отличие от паттерна async/await в том, что мы никак не меняем сигнатуру функции loadUser() и всех вложенных. Просто вызываем функцию через конструкцию fork(), и она становится прерываемой. Возвращается future<T>, и потом можно подождать результат через wait(). При этом в PHP отдельно реализованы PHP-функции fork и wait, которые почти ничего не делают.

В итоге: с одной стороны, мы следим за типами. С другой, можем делать запросы к движкам параллельно. С третьей, zero-cost abstractions (плохой термин, но пусть) константы напрямую инлайнятся, всякие простые геттеры и сеттеры тоже, и оверхед от абстракций в разы меньше, чем в PHP.

Если говорить про бенчмарки, то на средних VK-страничках у нас профит от 3 до 10 раз. А на конкретных участках, где мы прицельно выжимали максимум, до 2050 раз.

Это не значит, что можно просто взять PHP-код и он будет работать в 10 раз быстрее. Нет: рандомный сниппет, даже будучи скомпилированным, может и не впечатлить, потому что чаще всего там навыводится mixed.

Это значит, что PHP-код можно превратить в быстрый, если думать о типах и использовать built-in KPHP-функции.


KPHP и IDE


Система типов в KPHP значительно шире и строже, чем в PHP. Мы уже говорили, что нельзя смешивать в массиве числа и объекты потому что какой тогда тип элементов этого массива?
function getTotalAndFirst() {  // пусть $total_count это int, $user это объект User  ...  return [$total_count, $user]; // нельзя}

Нельзя! А как можно? Например, сделать отдельный класс с двумя полями и вернуть его. Или вернуть кортеж (tuple) специальный KPHP-тип.
function getTotalAndFirst() {  ...  return tuple($total_count, $user); // ok}

К функции можно даже PHPDoc написать, KPHP его прочитает и после стрелочки (->) поймёт:
/** @return tuple(int, User) */function getTotalAndFirst() { ... }    [$n, $u] = getTotalAndFirst();$u->id;  // ok

Но вот проблема: KPHP-то понимает, а вот IDE нет. Ведь tuple это наша придумка, как и разные другие штуки внутри PHPDoc.

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

Если вы интересуетесь разработкой плагинов для IDEA загляните, все исходники открыты. KPHPStorm глубоко внедряется во внутренности IDE (через кучу недокументированного API). Многое пришлось пройти, чтобы всё заработало. Спасибо ребятам из JetBrains за помощь.


Закругляемся: вот он Open Source, что дальше?


Мы усовершенствовали KPHP и показываем его вам: можно посмотреть, покомпилировать что-то простое теперь есть все инструкции и даже Docker-образ. Но будем честны: KPHP пока остаётся инструментом, заточенным под задачи VK, и для более широкого применения в реальных сторонних проектах он ещё не адаптирован.

Почему так? Мы всегда поддерживали в первую очередь собственные движки ВКонтакте. KPHP не умеет в Redis, MongoDB и другое. Даже Memcache у нас свой, который по RPC работает. Даже перед ClickHouse, который у нас развёрнут, стоит собственная proxy, куда мы тоже ходим по TL/RPC.

Мы никогда не поддерживали стандартные базы, потому что это не было нужно. Но знаете, в чём прикол? Если мы не выйдем в Open Source, этого никогда и не произойдёт потому что это так и не потребуется. За последние два года KPHP прошёл огромный путь, возродился. Мы можем ещё пару лет продержать его у себя. Можем покрыть возможности PHP 8, сделать ещё ряд оптимизаций, освоить микросервисы и интеграцию с Kubernetes но нам не будут нужны стандартные базы. И через два года будет то же самое.

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

Теперь вся разработка KPHP будет вестись на GitHub. Правда, CI пока останется в приватной инфраструктуре. Движки по-прежнему будут закрыты но когда-нибудь команда движков, надеемся, тоже решится вынести в сообщество хотя бы часть кода.

У вас может возникнуть вопрос: а сложно ли добавить поддержку протоколов MySQL, Redis и других? И да и нет. Если пробовать интегрировать готовые модули скорее всего, будет фейл. Особенно если они порождают дополнительные потоки, ведь воркеры принципиально однопоточные. К тому же, просто поддержать протокол, может, и не проблема но сложно сделать его прерываемым, чтобы это стыковалось с корутинами. А вот к этому сейчас код совершенно не готов: там корутины тесно переплетены с сетью и TL. Непростая история, в общем :) Но выполнимая, и над этим надо работать.


Итак: где ссылки, как попробовать


GitHub
Документация
FAQ

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

VK Tech 2020 год разработки в числах Git

30.12.2020 18:06:06 | Автор: admin
Завершаем год доброй традицией рассказываем в числах Git, каким он выдался для разработки.



2020-й был непредсказуемым, но мы оперативно реагировали на все изменения. В итоге: 82151 коммитов в мастер, а ещё 5236 обновлений продакшена в среднем по 14 в день! Для чего всё это было посмотрите в специальном разделе с главными запусками года и занимательной статистикой: vk.com/2020

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

Желаем счастливого Нового года! Пусть всё задуманное осуществится.
Подробнее..

Рекомендации Друзей ВКонтакте ML на эго-графах

13.04.2021 14:10:30 | Автор: admin

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

Меня зовут Женя Замятин, я работаю в команде Core ML ВКонтакте. Хочу рассказать, как устроены рекомендации, которые делают ближе пользователей самой крупной социальной сети рунета.

Обзор

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

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

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

Ещё один важный метод рекомендаций Adamic/Adar. В его основе лежит всё тот же анализ общих друзей, но с модификацией: авторы предлагают учитывать число друзей у общего друга. Чем больше это значение, тем меньше информации о релевантности он несёт.

Кроме методов на основе анализа общих друзей, довольно распространены рекомендации на базе эмбеддингов. В Лаборатории искусственного интеллекта ВКонтакте в МФТИ мы провели исследование: сравнили эффективность разных подходов к задаче предсказания дружб в VK. Результаты совпали с нашим опытом решения на базе графовых эмбеддингов у нас работают плохо. Учитывая это, мы стали развивать систему отбора кандидатов по пути анализа общих друзей.

EGOML

Общая схема нашего метода продолжает идеи числа общих друзей и Adamic/Adar. Финальная мера релевантности E(u, v), с помощью которой мы будем отбирать кандидатов, всё так же раскладывается в сумму по общим друзьям u и v. Ключевое отличие в форме слагаемого под суммой: в нашем случае это мера ez_c(u, v).

Сначала попробуем понять физический смысл меры ez_c(u, v). Представим, что мы взяли пользователя c и спросили у него: Насколько вероятно, что два твоих друга, u и v, подружатся? Чем больше информации для оценки он учтёт, тем точнее будет его предсказание. Например, если c сможет вспомнить только число своих друзей, его рассуждения могут выглядеть следующим образом: Чем больше у меня друзей, тем менее вероятно, что случайные двое из них знакомы. Тогда оценка вероятность дружбы u и v (с точки зрения c) может выглядеть как 1/log(n), где n число друзей. Именно так устроен Adamic/Adar. Но что если c возьмёт больше контекста?

Прежде чем отвечать на этот вопрос, разберёмся, почему ez_c(u, v) важно определять через пользователя c. Дело в том, что в таком виде очень удобно решать задачу распределённо. Представим, что теперь мы разослали всем пользователям платформы анкету с просьбой оценить вероятность дружбы в каждой паре их друзей. Получив все ответы, мы можем подставить значения в формулу E(u, v). Именно так выглядит вычисление E(u, v) с помощью MapReduce:

  • Подготовка. Для каждого c выделяется тот контекст, который он будет учитывать для вынесения оценок. Например, в Adamic/Adar это будет просто список друзей.

  • Map. Спрашиваем у каждого c, что он думает про возможность дружбы в каждой паре его друзей. По сути, вычисляем ez_c(u, v) и сохраняем в виде (u, v) ez_c(u, v) для всех u, v in N(c). В случае Adamic/Adar: (u, v) 1/log|N(c)|.

  • Reduce. Для каждой пары (u, v) суммируем все соответствующие ей значения. Их будет ровно столько, сколько общих друзей у u и v.

Таким образом мы получаем все ненулевые значения E(u, v). Заметим: необходимое условие того, что E(u, v) > 0, существование хотя бы одного общего друга у u и v.

Эго-граф ХоппераЭго-граф Хоппера

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

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

  • число общих друзей u и v внутри эго-графа c;

  • число общих друзей u и c;

  • интенсивность взаимодействий между v и c;

  • время, прошедшее с последней дружбы между u и кем-либо из эго-графа c;

  • плотность эго-графа c;

  • и другие.

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

В конечном счёте мы получаем датасет следующего вида:

  • для каждой пары пользователей u и v, а также их общего друга c, посчитаны признаки по эго-графу c;

  • пара пользователей u и v встречается в датасете ровно столько раз, сколько у них общих друзей;

  • все пары пользователей в датасете не являются друзьями на момент времени T;

  • для каждой пары u и v проставлена метка подружились ли они в течение определённого промежутка времени начиная с T.

По такому датасету мы и будем обучать нашу меру ez_c. В качестве модели выбрали градиентный бустинг с pairwise функцией потерь, где идентификатором группы выступает пользователь u.
По сути, мера ez_c(u, v) определяется как предсказание описанной выше модели. Но есть один нюанс: при pairwise-обучении распределение предсказаний модели похоже на нормальное. Поэтому, если в качестве определения меры ez_c(u, v) взять сырое предсказание, может возникнуть ситуация, когда мы будем штрафовать финальную меру E(u, v) за общих друзей, так как значения предсказаний бывают отрицательными. Это выглядит не совсем логично хочется, чтобы с ростом числа общих друзей мера E(u, v) не убывала. Так что поверх предсказания модели мы решили взять экспоненту:

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

Имея такую модель, мы могли бы получить предсказания для всех пар пользователей одного эго-графа быстрее. Достаточно применить модели A и B для каждого пользователя, а затем сложить соответствующие парам предсказания. Таким образом, для эго-графа из n вершин мы могли бы сократить число применений модели с O(n^2) до O(n). Но как получить такую модель, каждое дерево которой зависит только от одного пользователя? Для этого сделаем следующее:

  1. Исключим из датасета все признаки, которые одновременно зависят и от u и от v. Например, от признака число общих друзей u и v внутри эго-графа c придётся отказаться.

  2. Обучим модель A, используя только признаки на базе u, c и эго-графа c.

  3. Для обучения модели B оставим только признаки на базе v, c и эго-графа c. Также в качестве базовых предсказаний передадим предсказания модели A.

Если объединим модели A и B, получим то что нужно: первая часть использует признаки u, вторая признаки v. Совокупность моделей осмысленна, поскольку B была обучена корректировать предсказания A. Эта оптимизация позволяет ускорить вычисления в сотни раз и делает подход применимым на практике. Финальный вид ez_c(u, v) и E(u, v) выглядит так:

Вычисление меры E в онлайне

Заметим, что E(u, v) можно представить в виде:

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

При построении рекомендаций мы уже вычислили предсказания моделей для всех существующих дружб. Поэтому для каждого пользователя мы можем собрать векторы и сложить их в доступное онлайн key-value хранилище. После этого сможем получать значение E(u, v) для любой пары пользователей в онлайне простой операцией перемножения векторов. Это даёт возможность использовать E(u, v) как лёгкую функцию релевантности в нагруженных местах либо как дополнительный признак финальной модели ранжирования.

Итог

В результате система EGOML позволяет:

  1. Распределённо отбирать кандидатов для каждого пользователя в офлайне. Асимптотическая сложность оптимизированного алгоритма составляет O(|E|) вычислений признаков и применений модели, где |E| число связей в графе. На кластере из 250 воркеров время работы алгоритма составляет около двух часов.

  2. Быстро вычислять меру релевантности E(u, v) для любой пары пользователей в онлайне. Асимптотическая сложность операции O(|N(u)| + |N(v)|).

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

В конечном счёте мы перешли со способа отбора кандидатов с использованием Adamic/Adar к системе EGOML и внедрили в модель второй уровень признаков на основе меры E(u, v). И это позволило увеличить количество подтверждённых дружб со всей платформы на несколько десятков процентов.

Благодарность

Хочу сказать спасибо руководителю команды Core ML Андрею Якушеву за помощь в разработке метода и подготовке статьи, а также всей команде Core ML за поддержку на разных этапах этой работы.

Подробнее..

Анализируем причинно-следственные связи метрик ВКонтакте

11.09.2020 14:12:59 | Автор: admin
Всем привет, меня зовут Анвер, я работаю в команде Core ML ВКонтакте. Одна из наших задач создавать и улучшать алгоритмы ранжирования для ленты новостей. В этой статье расскажу о том, как можно применять для этого причинно-следственный анализ чтобы в результате сделать сервис интереснее для пользователей. Поговорим про преимущества такого подхода по сравнению с корреляционным анализом, и я предложу модификации существующих алгоритмов.




Что такое короткие и долгие метрики?


Модели ранжирования пытаются оценить вероятность того, что пользователь повзаимодействует с новостью (постом): задержит на ней внимание, поставит отметку Нравится, напишет комментарий. Затем модель распределяет записи по убыванию этой вероятности. Поэтому, улучшая ранжирование, мы можем получить рост CTR (click-through rate) пользовательских действий: лайков, комментов и других. Эти метрики очень чувствительны к изменениям модели ранжирования. Я буду называть их короткими.

Но есть и другой тип метрик. Считается, например, что время, проведённое в приложении, или количество сессий пользователя намного лучше отражают его отношение к сервису. Будем называть такие метрики долгими.

Оптимизировать долгие метрики непосредственно через алгоритмы ранжирования нетривиальная задача. С короткими метриками это делать намного проще: CTR лайков, например, напрямую связан с тем, насколько хорошо мы оцениваем их вероятность. Но если мы знаем причинно-следственные (или каузальные) связи между короткими и долгими метриками, то можем сфокусироваться на оптимизации лишь тех коротких метрик, которые должны предсказуемо влиять на долгие. Я попытался извлечь такие каузальные связи и написал об этом в своей работе, которую выполнил в качестве диплома на бакалавриате ИТМО (КТ). Исследование мы проводили в лаборатории Машинное обучение ИТМО совместно с ВКонтакте.

Ссылки на код, датасет и песочницу


Весь код вы можете найти здесь: AnverK.

Чтобы проанализировать связи между метриками, мы использовали датасет, включающий результаты более чем 6000 реальных A/B-тестов, которые в разное время проводила команда ВКонтакте. Датасет тоже доступен в репозитории.

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

Боремся с ложными корреляциями


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


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

Даже на таком простом примере становится очевидно, что простой анализ корреляций приведёт к множеству неверных выводов. Восстановить причинно-следственные связи из данных позволяет causal inference (методы вывода связей). Чтобы применить их в задаче, мы выбрали наиболее подходящие алгоритмы causal inference, реализовали для них python-интерфейсы, а также добавили модификации известных алгоритмов, которые лучше работают в наших условиях.

Классические алгоритмы вывода связей


Мы рассматривали несколько методов вывода связей (causal inference): PC (Peter and Clark), FCI (Fast Causal Inference) и FCI+ (похож на FCI с теоретической точки зрения, но намного быстрее). Почитать о них подробно можно в этих источниках:

  • Causality (J. Pearl, 2009),
  • Causation, Prediction and Search (P. Spirtes et al., 2000),
  • Learning Sparse Causal Models is not NP-hard (T. Claassen et al., 2013).

Но важно понимать: первый метод (PC) предполагает, что мы наблюдаем все величины, влияющие на две метрики или более, такая гипотеза называется Causal Sufficiency. Другие два алгоритма учитывают, что могут существовать ненаблюдаемые факторы, которые влияют на отслеживаемые метрики. То есть во втором случае каузальное представление считается более естественным и допускает наличие ненаблюдаемых факторов $U_1, \dots U_k$:



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

Опишу контракты функции вывода связей, то есть интерфейс и результат, который можно получить на выходе. Можно передать функцию тестирования на условную независимость. Это такой тест, который возвращает $p_{value}$ при нулевой гипотезе, что величины $X$ и $Y$ условно независимы при известном множестве величин $Z$. По умолчанию используется тест, основанный на частной корреляции. Я выбрал функцию с этим тестом, потому что она используется по умолчанию в pcalg и реализована на RCPP это делает её быстрой на практике. Также можно передать $p_{value}$, начиная с которого вершины будут считаться зависимыми. Для алгоритмов PC и FCI также можно задать количество CPU-ядер, если не нужно писать лог работы библиотеки. Для FCI+ такой опции нет, но я рекомендую использовать именно этот алгоритм он выигрывает по скорости. Ещё нюанс: FCI+ на данный момент не поддерживает предложенный алгоритм ориентации рёбер дело в ограничениях библиотеки pcalg.

По итогам работы всех алгоритмов строится PAG (partial ancestral graph) в виде списка рёбер. При алгоритме PC его стоит интерпретировать как каузальный граф в классическом понимании (или байесовскую сеть): ребро, ориентированное из $A$ в $B$, означает влияние $A$ на $B$. Если ребро ненаправленное или двунаправленное, то мы не можем однозначно его ориентировать, а значит:

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

Результатом работы FCI-алгоритмов будет тоже PAG, но в нём появится новый тип рёбер с о на конце. Это означает, что стрелка там может как быть, так и отсутствовать. При этом важнейшее отличие FCI-алгоритмов от PC в том, что двунаправленное (с двумя стрелками) ребро даёт понять, что связываемые им вершины следствия некой ненаблюдаемой вершины. Соответственно, двойное ребро в PC-алгоритме теперь выглядит как ребро с двумя о на концах. Иллюстрация для такого случая есть в песочнице с синтетическими примерами.

Модифицируем алгоритм ориентации рёбер


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

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

Сравниваем модели 1: оценка правдоподобия графа


Одну из серьёзных трудностей при выводе каузальных связей представляет, как ни странно, сравнение и оценка моделей. Как так вышло? Дело в том, что обычно истинное каузальное представление реальных данных неизвестно. И тем более мы не можем знать его с точки зрения распределения настолько точно, чтобы генерировать из него реальные данные. То есть неизвестен ground truth для большинства наборов данных. Поэтому возникает дилемма: использовать (полу-) синтетические данные с известным ground truth или пытаться обходиться без ground truth, но тестировать на реальных данных. В своей работе я попробовал реализовать два подхода к тестированию.

Первый из них оценка правдоподобия графа:



Здесь $PA_G(X)$ множество родителей вершины $X$, $I(X, Y)$ совместная информация величин $X$ и $Y$, а $H(X)$ энтропия величины $X$. На самом деле второе слагаемое не зависит от структуры графа, поэтому считают, как правило, только первое. Но можно заметить, что правдоподобие не убывает от добавления новых рёбер это необходимо учитывать при сравнении.

Важно понимать, что такая оценка работает только для сравнения байесовских сетей (выхода алгоритма PC), потому что в настоящих PAG (выход алгоритмов FCI, FCI+) у двойных рёбер совсем иная семантика.

Поэтому я сравнил ориентацию рёбер моим алгоритмом и классическим PC:



Модифицированная ориентация рёбер позволила значительно увеличить правдоподобие по сравнению с классическим алгоритмом. Но теперь важно сравнить количество рёбер:



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

Сравниваем модели 2: используем подход из классификации


Перейдём ко второму способу сравнения. Будем строить PC-алгоритмом каузальный граф и выбирать из него случайный ациклический граф. После этого сгенерируем данные в каждой вершине как линейную комбинацию значений в родительских вершинах с коэффициентами $\pm[0,2, 0,8]$ с добавлением гауссова шума. Идею для такой генерации я взял из статьи Towards Robust and Versatile Causal Discovery for Business Applications (Borboudakis et al., 2016). Вершины, которые не имеют родителей, генерировались из нормального распределения с параметрами, как в наборе данных для соответствующей вершины.

Когда данные получены, применяем к ним алгоритмы, которые хотим оценить. При этом у нас уже есть истинный каузальный граф. Осталось только понять, как сравнивать полученные графы с истинным. В Robust reconstruction of causal graphical models based on conditional 2-point and 3-point information (Affeldt et al., 2015) предложили использовать терминологию классификации. Будем считать, что проведённое ребро это Positive-класс, а непроведённое Negative. Тогда True Positive ($TP$) это когда мы провели то же ребро, что и в истинном каузальном графе, а False Positive ($FP$) если провели ребро, которого в истинном каузальном графе нет. Оценивать эти величины будем с точки зрения скелета.

Чтобы учитывать направления, введём $TP_{misorient}$ для рёбер, которые выведены верно, но с неправильно выбранным направлением. После этого будем считать так:

  • $TP' = TP - TP_{misorient}$
  • $FP' = FP + TP_{misorient}$

Затем можно считать $F_1$-меру как для скелета, так и с учётом ориентации (очевидно, в этом случае она будет не выше такой меры для скелета). Однако в случае PC-алгоритма двойное ребро добавляет к $TP_{misorient}$ только $0.5$, а не $1$, потому что одно из реальных рёбер всё-таки выведено (без Causal Sufficiency это было бы неверно).

Наконец, сравним алгоритмы:



Первые два графика это сравнение скелетов PC-алгоритма: классического и с новой ориентацией рёбер. Они нужны, чтобы показывать верхнюю границу $F_1$-меры. Вторые два сравнение этих алгоритмов с учётом ориентации. Как видим, выигрыша нет.

Сравниваем модели 3: выключаем Causal Sufficiency


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

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

Сравнивать уже будем алгоритмы FCI+ (с модифицированной ориентацией рёбер и с классической):



И теперь, когда Causal Sufficiency не выполняется, результат новой ориентации становится значительно лучше.

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



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

Вывод


Сформулирую кратко, о чём мы поговорили в этой статье:

  1. Как задача вывода каузальных связей может возникнуть в крупной IT-компании.
  2. Что такое ложные корреляции и как они могут мешать Feature Selection.
  3. Какие алгоритмы вывода связей существуют и используются наиболее часто.
  4. Какие трудности могут возникать при выводе каузальных графов.
  5. Что такое сравнение каузальных графов и как с этим бороться.


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

Вездекод как перенести хакатон в онлайн и не облажаться

03.11.2020 14:09:04 | Автор: admin
2020-й не пощадил большинство офлайн-мероприятий в том числе традиционный VK Hackathon. Раньше мы проводили его в Эрмитаже и Манеже, а в этом году в паблике ВКонтакте. Рассказываем, как придумали марафон Вездекод специально для онлайн-формата, собрали больше участников, чем ожидали, набили несколько шишек и получили отличную коллекцию мемов.



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

Наш хакатон один из крупнейших в России: его призовой фонд 2 миллиона рублей. Это флагманское мероприятие ВКонтакте, которое ждут каждый год. В 2019-м VK Hackathon прошёл в Манеже историческом здании в центре Санкт-Петербурга, где проводятся крупнейшие международные форумы и выставки. В соревновании участвовали 600 человек из 150 команд как независимые разработчики, так и сотрудники крупных IT-компаний: Яндекса, Сбербанка, Mail.ru, OZON, JetBrains, Альфа-Банка и других.



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

Офлайна не будет. Что делать?


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

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

Так появилась концепция марафона кодинга и родился Вездекод.

Вездекод 1.0


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

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



Мы сделали два чата от имени сообщества Вездекод:
  • Оргвопросы здесь участники уточняли задания, давали обратную связь и спрашивали: А ГДЕ БАЛЛ?;
  • флудилку чтобы все общались на любые темы.

Чаты не умолкали 24/7: мы постоянно отвечали на вопросы, помогали понять задания и принимали обратную связь. Это был совершенно новый формат и для нас, и для участников. Так что мы старались откликаться на комментарии ребят и шли им навстречу: докручивали задания, меняли их очерёдность или критерии оценки результатов, если понимали, что что-то получилось не совсем логично.

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

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



Участники


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

Попробовать силы в марафоне могли все желающие, кому уже исполнилось 14 лет. Для регистрации участников мы разработали простое мини-приложение. Оно позволяло быстро выгружать обновлённую базу ведь к Вездекоду можно было присоединиться в любой момент. Мы сделали регистрацию максимально свободной, поэтому собрали намного больше участников, чем ожидали. Планировали, что их будет около тысячи, а в итоге приняли на марафоне 1400 команд (!) это почти 3000 участников.



Задания и их оценка


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

Мы придумали гибкую разветвлённую систему заданий. В её основе три больших проекта разного уровня: простой, средний и сложный. Каждый из них включал задачи по категориям: дизайну, мобильной и веб-разработке. Так получилось 9 заданий. Затем мы добавили блоки тестирования и задач стало 12. Они концептуально связаны друг с другом, но выполнять их можно было и по отдельности. Чем больше заданий делала команда тем больше баллов зарабатывала. Если справлялась со всеми задачами по одному проекту, получался полноценный продукт: мини-апп или мобильное приложение с веб-версией. Мы распределили задания в случайном порядке, но внимательные участники догадывались, что им предстоит реализовывать через несколько дней.



Новые задания публиковались в закрытом паблике по одному в день и выполнять их нужно было за 24 часа. Не все участники были заняты ежедневно например, если мы выкладывали задачу на веб-разработку, дизайнеры могли отдыхать. Чтобы свободные ребята не скучали, мы предлагали им дополнительные задания: приглашали на внезапные бот-викторины и онлайн-соревнования по мотивам наших любимых активностей с конференций: Code in the Dark (это вёрстка вслепую) и Kitten Contest (версия Своей игры от VK).



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

Здесь мы столкнулись с первой сложностью: даже тщательно проработанный участниками макет мог быть не полностью адаптирован под каждую из наших платформ мобильный веб, Mini App, iOS и Android. Кроме того, участникам оказалось сложно применить их гайдлайны к готовому макету.

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



Но главный челлендж онлайн-мероприятий без предварительного отбора это непредсказуемый объём проверки заданий. Составляя первое расписание, мы думали, что сможем отсматривать все решения примерно за сутки. Участники ведь справляются с задачами за 24 часа! Как же мы ошибались :) В одном из заданий по дизайну мы получили 164 решения, по мобильной разработке 100: причём у некоторых участников это были и Android-, и iOS-реализации. В итоге мы едва успевали публиковать итоговые баллы за задание только через полтора дня после того, как заканчивали принимать от участников решения. Оставлять подробную обратную связь тоже не получалось в итоге ребята обсуждали проекты друг друга в оргчате.

А ещё мы не учли огромную любовь участников Вездекода к ненативной разработке под мобильные платформы и не предусмотрели это при формулировке заданий. Многие команды стали работать с React Native и Flutter с их помощью можно легко обеспечить кросс-платформенность и даже переиспользовать код между мобильными и веб-заданиями. Но результаты не радовали нас при проверке заданий: решения не всегда собирались из исходников, основывались на не самых эффективных подходах. К сожалению, мы не сформулировали сразу список разрешённых технологий поэтому участники были справедливо недовольны тем, что правила оценки решений менялись.

Мир, дружба, мемы


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



Финал Вездекода. Питчинг вымышленных проектов


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

Чтобы было веселее, сделали приложение генератор идей. Он в случайном порядке собирает концепцию проекта из фрагментов, отвечающих на вопросы какой?, что?, для чего? и для кого?. Так что команде мог достаться Культурный агрегатор для удалённой работы диснеевских принцесс или Сезонный навигатор для саморазвития молодых родителей. Генератор идей работает и сейчас загляните, может, он предложит вам проект, который захочется реализовать ;)



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

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

Какие выводы мы сделали?


  • Позиционирование. Слово хакатон сыграло против нас, ведь участники ждали привычного состязания в этом формате, а мы сделали нечто иное. Хотя Вездекод был как раз марафоном кодинга по смыслу это очень близко к изначальному значению термина хакатон.
  • Количество участников. Не ожидали такого наплыва желающих. Всего за время Вездекода зарегистрировались 1400 команд, то есть почти 3000 человек. Активно соревновались 647 команд это 1749 участников. В общей сложности мы проверили 27 заданий, начислили 59106 баллов и отправили 1000 заказов из магазина.
  • Уровень команд. Зарегистрироваться на Вездекод мог любой человек старше 14 лет. Мы не проводили отбор по идеям и специализации участников поэтому по сравнению с классическим хакатоном порог входа ощутимо понизился. С одной стороны, это плюс попробовать силы смогли совсем юные разработчики, мы научили многих работать с Figma и решать продуктовые задачки. Но с другой получили от участников шквал базовых околотехнических вопросов, на которые отвечали почти круглосуточно.
  • Мало направлений. В формате онлайн-марафона мы решили переложить саму концепцию взаимодействия участников на хакатоне последовательное выполнение разных задач. Так в Вездекоде появились направления для заданий и их очерёдность. Но мы обожглись о полярный уровень участников. Одни, быстро разгадав логику, были заранее готовы к следующим задачам, консультировались с дизайнером и помогали друг другу на каждом этапе. А менее опытные ждали от нас чёткого ТЗ и расстраивались, что задание снова не на код (эмоциями по этому поводу делились под хештегом #агдекод). В будущем мы попробуем разнообразить специфику заданий. А ещё на берегу обозначим стек технологий: не забудем разобрать скользкие кейсы вроде мобильной разработки на Flutter. И придумаем, как прикрутить автопроверку, чтобы разгрузить жюри и авторов заданий.


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

  1. Подумайте о трудозатратах и команде. Подготовьтесь к тому, что сил и времени на онлайн уйдёт даже больше, чем на офлайн. Мы это прочувствовали в многократном объёме: Вездекод стал марафоном не только для участников, но и для нас :) На нашем событии с командами работали четыре организатора, и ещё около 20 человек были задействованы в составлении и проверке заданий.
  2. Уделите внимание деталям и максимально разжёвывайте задания. То, что на площадке можно проговорить голосом со сцены, на онлайн-соревновании быстро обрастает версиями во флудилках и провоцирует лавину нерелевантных вопросов.
  3. Заботьтесь об участниках. Придумайте механики, чтобы каждая команда могла себя проявить и получить хотя бы небольшой приз. В онлайне гораздо меньше ощущается взаимодействие с организаторами, поэтому важно оставить о мероприятии что-то на память. Один из наших участников при заказе из магазина Вездекода попросил организаторов оставить на мерче автографы и так у нас родилась идея рукописных открыток для всех.
  4. Не бойтесь ошибаться. Фиксируйте обратную связь и возвращайтесь на арену онлайн-ивентов!
Подробнее..

Бенчмарки VKUI и других ребят из UI-библиотек

26.05.2021 12:10:08 | Автор: admin

Меня зовут Григорий Горбовской, я работаю в Web-команде департамента по экосистемным продуктам ВКонтакте, занимаюсь разработкой VKUI.

Хочу вкратце рассказать, как мы написали 8 тестовых веб-приложений, подключили их к моно-репозиторию, автоматизировали аудит через Google Lighthouse с помощью GitHub Actions и как решали проблемы, с которыми столкнулись.

VKUI это полноценный UI-фреймворк, с помощью которого можно создавать интерфейсы, внешне неотличимые от тех, которые пользователь видит ВКонтакте. Он адаптивный, а это значит, что приложение на нём будет выглядеть хорошо как на смартфонах с iOS или Android, так и на больших экранах планшетах и даже десктопе. Сегодня VKUI используется практически во всех сервисах платформы VK Mini Apps и важных разделах приложения ВКонтакте, которые надо быстро обновлять независимо от магазинов.

VKUI также активно применяется для экранов универсального приложения VK для iPhone и iPad. Это крупное обновление с поддержкой планшетов на iPadOS мы представили 1 апреля.

Адаптивный экран на VKUIАдаптивный экран на VKUI

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

Какие задачи поставили

  1. Выявить главные проблемы производительности VKUI.

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

  3. Сравнить производительность VKUI и конкурирующих UI-фреймворков.

Технологический стек

Инструменты для организации процессов:

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

Бенчмарки мы проводим через Google Lighthouse официальный инструмент для измерения Web Vitals. Де-факто это стандарт индустрии для оценки производительности в вебе.

Самое важное делает GitHub Actions: связывает воедино сборку и аудит наших приложений.

Библиотеки, взятые для сравнения:

Название

Сайт или репозиторий

VKUI

github.com/VKCOM/VKUI

Material-UI

material-ui.com

Yandex UI

github.com/bem/yandex-ui

Fluent UI

github.com/microsoft/fluentui

Lightning

react.lightningdesignsystem.com

Adobe Spectrum

react-spectrum.adobe.com

Ant Design

ant.design

Framework7

framework7.io


Мы решили сравнить популярные UI-фреймворки, часть из которых основана на собственных дизайн-системах. В качестве базового шаблона на React использовали create-react-app, и на момент написания приложений брали самые актуальные версии библиотек.

Тестируемые приложения

Первым делом мы набросали 8 приложений. В каждом были такие страницы:

  1. Default страница с адаптивной вёрсткой, содержит по 23 подстраницы.

    • На главной находится лента с однотипным контентом и предложением ввести код подтверждения (абстрактный). При его вводе на несколько секунд отображается полноэкранный спиннер загрузки.

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

    • Страница с простым диалогом условно, с техподдержкой.

  2. List (Burn) страница со списком из 500 элементов. Главный аспект, который нам хотелось проверить: как вложенность кликабельных элементов влияет на показатель Performance.

  3. Modals страница с несколькими модальными окнами.

Не у всех UI-фреймворков есть аналогичные компоненты недостающие мы заменяли на равнозначные им по функциональности. Ближе всего к VKUI по компонентам и видам их отображения оказались Material-UI и Framework7.

Сделать 8 таких приложений поначалу кажется простой задачей, но спустя неделю просто упарываешься писать одно и то же, но с разными библиотеками. У каждого UI-фреймворка своя документация, API и особенности. С некоторыми я сталкивался впервые. Особенно запомнился Yandex UI кажется, совсем не предназначенный для использования сторонними разработчиками. Какие-то компоненты и описания параметров к ним удавалось найти, только копаясь в исходном коде. Ещё умилительно было обнаружить в компоненте хедера логотип Яндекса <3

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

Автоматизация

Краткая блок-схема, описывающая процессы в автоматизацииКраткая блок-схема, описывающая процессы в автоматизации

Подготовили два воркфлоу:

  • Build and Deploy здесь в первую очередь автоматизировали процессы сборки и разворачивания. Используем surge, чтобы быстро публиковать статичные приложения. Но постепенно перейдём к их запуску и аудиту внутри GitHub Actions воркеров.

  • Run Benchmarks а здесь создаётся issue-тикет в репозитории со ссылкой на активный воркфлоу, затем запускается Lighthouse CI Action по подготовленным ссылкам.

UI-фреймворк

URL на тестовое приложение

VKUI

vkui-benchmark.surge.sh

Ant Design

ant-benchmark.surge.sh

Material UI

mui-benchmark.surge.sh

Framework7

f7-benchmark.surge.sh

Fluent UI

fluent-benchmark.surge.sh

Lightning

lightning-benchmark.surge.sh

Yandex UI

yandex-benchmark.surge.sh

Adobe Spectrum

spectrum-benchmark.surge.sh


Конфигурация сейчас выглядит так:

{  "ci": {    "collect": {      "settings": {        "preset": "desktop", // Desktop-пресет        "maxWaitForFcp": 60000 // Время ожидания ответа от сервера      }    }  }}

После аудита всех приложений запускается задача finalize-report. Она форматирует полученные данные в удобную сравнительную таблицу (с помощью магии Markdown) и отправляет её комментарием к тикету. Удобненько, да?

Пример подобного репорта от 30 марта 2021 г.Пример подобного репорта от 30 марта 2021 г.

Нестабильность результатов

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

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

Предупреждения из отчётов Google LighthouseПредупреждения из отчётов Google Lighthouse

GitHub Actions по умолчанию выполняет задачи параллельно, как и в нашем воркфлоу с бенчмарками. Решение ограничить максимальное количество выполняющихся одновременно задач:

jobs.<job_id>.strategy.max-parallel: 1

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

Результаты от 30 марта 2021 г.

VKUI (4.3.0) vs ant:

app

type (app link)

report

performance

vkui (4.3.0)

default

report

0.99

ant

default

report

0.99

vkui (4.3.0)

modals

report

1

ant

modals

report

0.99

vkui (4.3.0)

list

report

0.94

ant

list

report

0.89

list - У ant нет схожего по сложности компонента для отрисовки сложных списков, но на 0,05 балла отстали.

VKUI (4.3.0) vs Framework7:

app

type (app link)

report

performance

vkui (4.3.0)

default

report

0.99

f7

default

report

0.98

vkui (4.3.0)

modals

report

1

f7

modals

report

0.99

vkui (4.3.0)

list

report

0.94

f7

list

report

0.92

list - Framework7 не позволяет вложить одновременно checkbox и radio в компонент списка (List).

VKUI (4.3.0) vs Fluent:

app

type (app link)

report

performance

vkui (4.3.0)

default

report

0.99

fluent

default

report

0.94

vkui (4.3.0)

modals

report

1

fluent

modals

report

0.99

vkui (4.3.0)

list

report

0.94

fluent

list

report

0.97

modals - Разница на уровне погрешности.

list - Fluent не имеет схожего по сложности компонента для отрисовки сложных списков.

VKUI (4.3.0) vs Lightning:

app

type (app link)

report

performance

vkui (4.3.0)

default

report

0.99

lightning

default

report

0.95

vkui (4.3.0)

modals

report

1

lightning

modals

report

1

vkui (4.3.0)

list

report

0.94

lightning

list

report

0.99

list - Lightning не имеет схожего по сложности компонента для отрисовки сложных списков.

VKUI (4.3.0) vs mui:

app

type (app link)

report

performance

vkui (4.3.0)

default

report

0.99

mui

default

report

0.93

vkui (4.3.0)

modals

report

1

mui

modals

report

0.96

vkui (4.3.0)

list

report

0.94

mui

list

report

0.77

default и modals - Расхождение незначительное, у Material-UI проседает First Contentful Paint.

list - При примерно одинаковой загруженности списков в Material-UI и VKUI выигрываем по Average Render Time почти в три раза (~1328,6 мс в Material-UI vs ~476,4 мс в VKUI).

VKUI (4.3.0) vs spectrum:

app

type (app link)

report

performance

vkui (4.3.0)

default

report

0.99

spectrum

default

report

0.99

vkui (4.3.0)

modals

report

1

spectrum

modals

report

1

vkui (4.3.0)

list

report

0.94

spectrum

list

report

1

list - Spectrum не имеет схожего по сложности компонента для отрисовки сложных списков.

VKUI (4.3.0) vs yandex:

app

type (app link)

report

performance

vkui (4.3.0)

default

report

0.99

yandex

default

report

1

vkui (4.3.0)

modals

report

1

yandex

modals

report

1

vkui (4.3.0)

list

report

0.94

yandex

list

report

1

default - Разница на уровне погрешности.

list - Yandex-UI не имеет схожего по сложности компонента для отрисовки сложных списков.

modals - Модальные страницы в Yandex UI объективно легче.

Выводы из отчёта Lighthouse о VKUI

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

  • Одно из явных проблемных мест вложенные Tappable протестированы на большом списке. Единственная библиотека, в которой полноценно реализован этот кейс, Material-UI. И VKUI уверенно обходит её по производительности.

  • Lighthouse ругается на стили после сборки много неиспользуемых. Они же замедляют First Contentful Paint. Над этим уже работают.

Два CSS-чанка, один из которых весит 27,6 кибибайт без сжатия в gzДва CSS-чанка, один из которых весит 27,6 кибибайт без сжатия в gz

Планы на будущее vkui-benchmarks

Переход с хостинга статики на локальное тестирование должен сократить погрешность: уменьшится вероятность того, что из-за внешнего фактора станет ниже балл у того или иного веб-приложения. Ещё у нас в репортах есть показатель CPU/Memory Power и он немного отличается в зависимости от воркеров, которые может дать GitHub. Из-за этого результаты в репортах могут разниться в пределах 0,010,03. Это можно решить введением перцентилей.

Также планируем сделать Lighthouse Server для сохранения статистики по бенчмаркам на каждый релиз.

Подробнее..

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

10.03.2021 14:15:34 | Автор: admin

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

Проблема и существующие решения

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

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

С проблемой угроз и оскорблений сталкиваемся не только мы, но и многие другие интернет-площадки.Разработчики вместе с представителями социальных сетей стремятсясоздаватьнадёжные модели, обнаруживающиетоксичность.Но частаяпроблематаких детекторов наличие Unintended Bias (UB) при вынесении вердикта. Иными словами, модель учится присваивать высокий скор токсичности текстам, в которыхестьспецифичные слова: оничасто встречаютсяв оскорбительном контексте, но сами по себе несодержат негативного смысла. Например:женщина, чёрный, петух, админ. Такие слова мы будем называть защищаемыми сущностями,или Protected Identities(PI).

ВКонтакте, 2020 https://vk.com/safetyВКонтакте, 2020 https://vk.com/safety

Сейчас естьмногоконтестови мастер-классов, посвящённых распознаванию токсичных выражений:например,HASOCнаFIRE-2019; TRAC-2020; HatEvalиOffensEvalнаSemEval-2019.Также на платформе Kaggle проводятся состязания по этому направлению и даже по его узкой теме Unintended Bias! В последнемсоревновании от Jigsawиспользовалась специфичная для нашей задачи метрика, на которой мы валидировали модели(подробнее в следующем разделе статьи).

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

В статье сWOAHEMNLP2020мы рассказываем о трёх подходах, которые помоглиуменьшить число false-positive предсказаний:

  1. Мыприменилиязыковую модель для генерации нетоксичных примеров с контекстом по защищаемым сущностям.

  2. Использовалидропаут на слова из спискатакихсущностей.

  3. Опробовалиmultitask-подходы, о которых расскажем далее.

Как мыразрабатывалидетектор(данные,мини-апп, проблема UnintendedBias)

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

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

Первые вопросы,на которыенам предстоялоответить на старте разработки:

  • Как понять, что перед нами токсичный текст?

  • Как разметить данные для обучения?

  • Сколько вообще их нужно?

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

Угрозы

Не угрозы

Обещанияипожелания смерти,причинения вреда здоровью

Призывы отправить кого-то в тюрьму

Угрозы с сексуальным подтекстом

Одобрение действий, которые нам неизвестны

Пожелания смерти самому себе

Эпитафии

Таблица1. Некоторые изправил разметки

Разметкой занимались модераторы. Онииспользовали нашевнутреннееприложениеи выполнялинесколько итераций, валидируярешения друг друга.

Чтобысобирать данные максимально эффективно, на финальныхэтапахразметки мы использовали подходActive Learning. Онпозволяетдокидывать примеры с максимальной энтропией для дообучения классификатора. Конечно, в перспективе было бы здорово поставить всёэто на конвейер :)

Когда завершиласьперваяитерацияразметки и тестирования классификатора,мы заметили, что существует проблема UB. Она проявляется в двух моментах:

  • классификатор считает токсичнымитекстывидатыне чучелоилиназыватьлюдей чучелами плохо этоудалось решить довольно быстро генерацией синтетики;

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

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

Дальше мы действовали так: вручную составилисписокиз 214русских слов,которые относятсяк защищаемым сущностям(тоестьтем, которые часто встречаются в оскорбительном контексте, но сами по себене несут негативного смысла). Слова распределили похарактеру токсичностина категории: сексизм, национализм, угрозы, домогательства, гомофобия и другие.Полный список защищаемыхидентичностейи относящихся к ним словможно посмотреть на GitHub:vk.cc/aAS3TQ.Воfuturework смелозаписываемавтоматизацию сбора таких сущностей.

лукизм

корова,пышка

сексизм

женщина,баба

национализм

чех,еврей

угрозы

выезжать,айпи

домогательства

киска,секси

гомофобия

гей,лгбт

другое

мамка,админ

Таблица 2. Выдержки из полного списказащищаемыхидентичностейи относящихся к ним слов

Как можно оценить Unintended Bias?

В соревновании от Jigsaw,как и на нашемчемпионатеVK Cup,качествопредсказанийоценивалосьпри помощиgeneralizedmean of Bias AUCs. Идея такой метрикив том,чторезультатыработы модели на тестеразбиваютсяна сабсетыв зависимости от выхода модели и наличия слов из списка защищаемых сущностей. Затем мы считаем AUC по каждомусабсету,берём от них обобщённое среднееикомбинируемегос варьируемыми весами с AUC по всем результатам.

Метрика качества. Взято из соревнования от JigsawМетрика качества. Взято из соревнования от Jigsaw

Метрика качества. Взято из соревнования от Jigsaw

Наши подходы

  1. Нетоксичная языковая модель

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

  2. Random dropout на Protected Indentities

    Известно,что random word dropoutможетсделатьклассификациютекстакачественнее.Мыприменилиэтот метод, чтобыво время обученияслучайным образом(свероятностью 0,5) заменять защищаемыеидентичностивовходных последовательностяхнамаркер.

  3. Multitask framework

    Мыиспользовали фреймворкmultitask-learning,расширив для негобазовую модельпри помощи предсказания классаProtected Identity(лукизм,национализм,угрозаи так далее).При обучении потери считаются как среднее взвешенное по предсказанию токсичности и классу защищаемой сущности это тожеулучшает результат.

Схема обучения модели с применением multitask-learningСхема обучения модели с применением multitask-learning

Схема обучения модели с применением nultitask-learning

Результаты

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

Попробовалидве архитектуры(self-ATTN,CNN),комбинируятриподходаиз нашей статьи.self-ATTN модель, основаннаяна self-attentive encoder.Векторыпередаютсянапрямуювattention,поэтомумодельself-attentionстановится похожей на ту,что используется втрансформерах. Преимущество этой архитектуры в том, что её отдельные веса внимания каждого входного токена поддаются интерпретации.Это позволяет визуализировать, что именно служит триггером для классификатора. Ана основе этого исследовать данные и, например,расширятьсписокзащищаемыхсущностей.

Визуализация attention по словамВизуализация attention по словам

Визуализация attention по словам

В качестве функции потерь для singletask approach мыприменилиBCE-loss. Дляmultitask approachиспользовали loss-взвешенное среднеедля двух задач:скоратоксичности и предсказанияклассаProtected Identity.Больше деталей обучения вроде числа итераций или lr внашейполнойстатье.

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

Метод

Нашдатасет

(Андрусяк и др., 2018)

(Сметанин, 2020)

GMB-AUC

F1

GMB-AUC

F1

GMB-AUC

F1

CNN

.56.005

.66.003

.51.005

.59.001

.53.003

.78.002

CNN + multitask

.58.001

.68.008

.52.002

.61.002

.53.010

.80.002

Attn

.60.002

.71.010

.54.001

.72.003

.54.005

.80.010

Attn + multitask

.60.004

.74.012

.54.009

.69.009

.54.007

.82.004

Attn + LM data

.65.003

.74.002

.58.003

.70.001

.57.006

.83.009

Attn + LM data + multitask

.67.002

.74.016

.59.003

.70.010

.58.003

.84.008

Attn + identity d/o

.61.001

.65.003

.53.004

.68.001

.54.007

.82.011

Attn + identity d/o + multitask

.61.005

.66.007

.54.004

.69.008

.58.009

.83.007

Attn + identity d/o + LM data

.67.004

.76.005

.55.003

.71.002

.59.003

.86.012

Attn + identity d/o + LM data + multitask

.68.001

.78.010

.56.004

.73.003

.60.008

.86.004

Таблица3.Generalized Mean of Bias AUCs(GMB-AUC)изначениеF-меры по наборам данных

Нашимодели достигли конкурентоспособных результатовпо F-мере на всехтрёхнаборахданных. Самая эффективнаяи хитраяиз них(Attn+ identity d/o + LM data + multitask setup)показалаF-меру0,86по тесту. А это 93% от заявленнойточностиSoTA-модели более крупнойисозданной при помощифайнтюнингаBERT-like архитектуры.

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

Авторы Надежда Зуева, Павел Калайдин и Мадина Кабирова выражают благодарность Даниилу Гаврилову и Октаю Татанову за полезные дискуссии, Даниилу Гаврилову за ревью, Виктории Логиновой и Дэвиду Принцу за редактирование текста и анонимнымрецензентамза ценные комментарии. Также мы хотим поблагодарить команду модераторов ВКонтакте(которуюкоординировала Катерина Егорушкова) за помощь в создании набора данных для обучения, Анну Мелковскую за помощь в координации проекта, Семена Полякова, Андрея Якушева, Дмитрия Сальникова за полезные советы по Active Learning и не только, Дмитрия Юткина и Александра Маркова за помощь во внедрении технологии в продакшен.

We are open for collaborations! Поэтому датасет собранных нами угроз доступен по запросунаписать можно на почту nadezhda.zueva@vk.com или ВКонтакте(vk.com/nda)

P.S.Обоснования и ссылки на подходы в нашей основной статье:arxiv.org/abs/2010.11666

Подробнее..

Особенности национальной интеграции с платёжными системами

24.02.2021 12:05:30 | Автор: admin
Электронная коммерция стала трендом 2020 года. Крупные игроки рынка начали активно развивать сервисы доставки продуктов и готовых блюд. Как грибы после дождя выросли новые маркетплейсы. Даже те, кто был далёк от интернета и технологий, вынужденно погрузились в тему дистанционной торговли. Почему все знают, но сегодня поговорим не об этом. Перейдём сразу к ключевому звену коммерции приёму платежей. В статье поделюсь несколькими рекомендациями о том, как с ним работать.

image

Меня зовут Андрей Шубин, работаю в VK в команде разработки e-commerce в сентябре мы запустили Маркет ВКонтакте. Занимаюсь веб-разработкой десять лет, из них семь работаю с интернет-магазинами, маркетплейсами и другими проектами, продающими через сеть. Последние три года возглавлял разработку для электронной коммерции в компании Siberian Wellness у неё есть представительства в большинстве стран СНГ, ЕС, немного в Юго-Восточной Азии и Северной Америке. Именно из-за такой обширной географии мне довелось познакомиться с локальными платёжными агрегаторами, а также с законодательством некоторых стран.

Эта статья будет полезна разработчикам уровня middle, которые взялись за новое для себя направление ступили на скользкий путь e-commerce. Если вы в этой сфере давно, некоторые кейсы могут показаться очевидными. Буду рад любому фидбэку в комментариях.

Типы данных


Только ленивый не говорил, что вычислительная техника может допускать некоторую погрешность при работе с числами с плавающей точкой. Да-да, попробуйте сложить 0,1 и 0,2, если раньше этого не делали, будете удивлены. Но в работе с финансами точность и однозначность это то, что должно быть на первом месте. Если где-то в расчётах проскользнёт лишняя копейка, последствия могут быть печальными. Конечно, за одну копейку с вами связываться никто не будет хотя известны случаи, когда ФНС таки отправляла налогоплательщику требование уплатить недоимку в 0,01. Но при относительно больших оборотах разница между фактически полученной суммой и той, что отображена в чеках, может принять критическое значение и тогда знакомство с налоговым инспектором вам гарантировано. Особенно если всё происходит в Германии или Чехии.

Кстати, раз уж речь зашла о других странах и типах данных. В мире есть очень дешёвые валюты и с ними обороты компании могут измеряться суммами с целым паровозом нулей. В моей практике это были вьетнамский донг и белорусский рубль до 2017 года. Если вам предстоит работать с одной из таких валют, проверьте своё хранилище на предмет разрядности. Иначе может случиться то же самое, что сделал с ютубом клип Gangnam Style в декабре 2014-го.

Общение с платёжной системой


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

  1. Принимаем заказ.
  2. Формируем ссылку на платёжный шлюз, в который вшиваем сумму к оплате, номер заказа и что там ещё попросит платёжная система.
  3. Редиректим клиента по этому адресу.
  4. Клиент вводит данные карты и перенаправляется на страницу благодарности (thank you page) магазина.
  5. Банк списывает со счёта клиента деньги и делает callback-запрос на специальный эндпоинт с нашей стороны.
  6. Меняем статус заказа и проводим попутные манипуляции.

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

  • В Казахстане система не признавала редирект методом GET. Она требовала сначала на бэкенде сформировать зашифрованную строку с параметрами платежа, а затем нарисовать в магазине HTML-форму, где единственным параметром должна быть эта строка. И уже форму нужно было отправлять синхронно после этого пользователь попадал на страницу, где мог ввести номер карты. Но просто дождаться callback-запроса недостаточно: необходимо было сделать ещё один запрос с бэкенда, чтобы дать банку понять, что мы готовы принять оплату.
  • В Чехии ввели онлайн-кассы и из-за этого требовалось кроме суммы передать на сторону платёжного шлюза полный список позиций на чешском языке, включая цены и рассчитанные значения НДС. И если вы даёте клиенту скидку, то не можете просто взять и закинуть её в чек отрицательным числом. Так не работает. Нужно пересчитать стоимость всех позиций с учётом этой скидки или распределить её размер по конкретным позициям. И подойти к этому с умом потому что у разных товаров может отличаться ставка НДС, а если вы случайно дадите скидку на товары с большей ставкой, этим может заинтересоваться налоговый инспектор во время камеральной проверки.
  • Нацбанк Молдовы переложил на продавца обязанность отправлять клиенту электронный чек. При этом в нём требуется отображать определённую банковскую информацию. Получать её нужно из callback-запроса от банка либо отдельным методом API. А ещё местные ребята не особо следили за актуальностью документации и не могли ответить на наши вопросы в письмах так что пришлось ехать туда лично и общаться с техдиром.
  • В Узбекистане пошли дальше: решили, что один цикл запрос ответ это слишком просто. Считаем вместе:

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

    И только в этот момент мы наконец меняем статус заказа у себя и движемся дальше. Четыре запроса вместо одного!
  • Вьетнамский оператор эквайринга вообще не присылает callback-запрос. Мы должны сами запросить статус транзакции. И при этом использовать ID транзакции, который присваивается оператором и передаётся GET-параметром на thank you page.

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

Не ждите! Пишите первыми


Сделать на своей стороне эндпоинт, на который будет стучаться платёжная система со своими запросами, это очень заманчиво. Но будем честны никто из нас не в состоянии обеспечить 100%-й аптайм: в какой-то момент может отвалиться связь с сервером базы данных или с каким-то другим сервисом, и тогда ваше приложение не сможет отдать ожидаемый ответ. И здесь уже надо смотреть по документации, как поведёт себя конкретная платёжная система (если об этом, конечно, там написано).

Например, в том же Казахстане местный банк делал три запроса с разницей в секунду и после третьего фейла считал транзакцию неуспешной. При этом деньги клиента зависали где-то между счётом клиента и нашим р/с. Сдвинуть их в ту или иную сторону можно было двумя способами: либо делать отдельный API-запрос в течение ограниченного времени, либо идти в отделение банка, с которым заключён договор эквайринга, и писать заявление от руки. Знакомо?

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

Если такой возможности нет, то callback-запросы от банков лучше принимать на отдельный сервер, которому нужно обеспечить максимальный аптайм. В идеале это должен быть простой веб-сервер, который пишет таски в брокер сообщений. Совсем круто, если будет кластер таких серверов. Эх, мечты На самом деле такой монстр, скорее всего, не потребуется, но направление вы поняли.

Откройте глаза! Мониторинг наше всё


В работе с платежами есть скрытые процессы, от которых зависит вся цепочка электронной торговли. Чтобы не быть голословным, приведу пример из практики. У нас был договор интернет-эквайринга со Сбером и договор аренды онлайн-касс с Orange Data. Интеграции из коробки между этими двумя конторами нет, поэтому пришлось делать её на своей стороне. Обработали callback от Сбера, сформировали запрос к оператору фискальных данных, который генерирует чек и отправляет его в налоговую и клиенту на электронную почту. И вот в один не самый прекрасный день Orange Data аннулирует токен авторизации и чеки перестают выписываться и отправляться. Чтобы вы понимали, штраф за невыдачу одного чека 40000 рублей. А узнали мы о сбое в процессе совершенно случайно, спустя два месяца: один дотошный пользователь позвонил в контакт-центр спросить, почему ему не пришёл чек Спасибо, добрый человек! Мы успели всё пофиксить и разослать зависшие чеки до конца отчётного периода. А если бы не сделали этого, размер штрафов не оставил бы компании шансов на выживание.

Чем именно мониторить? Решайте сами. ВКонтакте мы используем Sentry и несколько крутых самописных инструментов. Но даже десятистрочный скрипт на питоне, отправляющий алярм в ваш любимый мессенджер, будет лучше, чем ничего. С деньгами шутки плохи их нужно контролировать максимально.

Иногда нужно поработать ручками


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

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

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

Вместо заключения


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

P. S. Обращаюсь от имени всех разработчиков из e-commerce к финтех-разработчикам. Ребят, ну сядьте вы уже, договоритесь, выработайте единый стандарт взаимодействия с мерчантом. Все же от этого выиграют!
Подробнее..

Категории

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

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