Вы уже теряли лучшую версию модели, потому что все эксперименты проводили в одном ноутбуке, не сохраняя предыдущие результаты? Или пытались вспомнить, какие гипотезы не сработали полгода назад? Тогда этот пост для вас.
❓Зачем логировать эксперименты?
- Чтобы не искать «тот самый ноутбук» с лучшей итерацией обучения.
- Чтобы удобно сравнивать разные версии моделей: алгоритмы, параметры, метрики, наборы признаков.
- Чтобы не держать в голове, что, как и с каким результатом было сделано.
- Чтобы где-то безопасно хранить все свои модели.
- Чтобы не пересылать коллегам ноутбуки и pickle-файлы по почте.
❓Куда логировать?
Существует много инструментов для хранения артефактов экспериментов. Мы в Точке выбрали MLflow — он покрывает наши основные потребности.
❓Что логировать?
Зависит от стандартов и практик, которые приняты в вашей команде. Мы рекомендуем хранить всё (или почти всё):
📌 Description: текстовое описание эксперимента и краткие выводы по нему.
📌 Parameters: параметры модели — но только те, которые отличаются от дефолтных.
📌 Metrics: метрики и лоссы.
📌 Tags: любая информация, которая поможет раскрыть детали эксперимента, например:
- временной период данных, на которых обучалась и тестировалась модель;
- пропорция классов;
- дата проведения эксперимента;
- контактные данные создателя;
- путь к обучающим и тестовым данным.
📌 Artifacts: информация в виде файлов S3, которая поможет воспроизвести эксперимент и использовать модель, например:
- сама модель и зависимости;
- кривые обучения (loss, metric);
- графики (shap, feature importance, roc-curve, pr-curve);
- архитектура модели — если это нейронная сеть, которую вы написали сами;
- confusion matrix, перечень и описание признаков, ваши комментарии и всякое полезное;
- примеры данных, на которых обучалась и тестировалась модель.
Подробно рассказывать о том, как пользоваться MLflow, мы не будем: в интернете есть много гайдов на всех языках. А ещё у MLflow неплохая документация с примерами и туториалами — изучайте и делитесь мнениями!
❓Зачем логировать эксперименты?
- Чтобы не искать «тот самый ноутбук» с лучшей итерацией обучения.
- Чтобы удобно сравнивать разные версии моделей: алгоритмы, параметры, метрики, наборы признаков.
- Чтобы не держать в голове, что, как и с каким результатом было сделано.
- Чтобы где-то безопасно хранить все свои модели.
- Чтобы не пересылать коллегам ноутбуки и pickle-файлы по почте.
❓Куда логировать?
Существует много инструментов для хранения артефактов экспериментов. Мы в Точке выбрали MLflow — он покрывает наши основные потребности.
Плюсы MLflow:
- open-source и большое коммьюнити;
- низкий порог входа;
- удобные API и стандартизированные методы;
- позволяет сравнить результаты нескольких экспериментов в одном окне;
- легко использовать в продуктивных решениях.
Минусы MLflow:
- нетривиальное обновление до более свежих версий;
- не оптимизирован для хранения кода;
- отсутствует ролевая модель доступа.
❓Что логировать?
Зависит от стандартов и практик, которые приняты в вашей команде. Мы рекомендуем хранить всё (или почти всё):
📌 Description: текстовое описание эксперимента и краткие выводы по нему.
📌 Parameters: параметры модели — но только те, которые отличаются от дефолтных.
📌 Metrics: метрики и лоссы.
📌 Tags: любая информация, которая поможет раскрыть детали эксперимента, например:
- временной период данных, на которых обучалась и тестировалась модель;
- пропорция классов;
- дата проведения эксперимента;
- контактные данные создателя;
- путь к обучающим и тестовым данным.
📌 Artifacts: информация в виде файлов S3, которая поможет воспроизвести эксперимент и использовать модель, например:
- сама модель и зависимости;
- кривые обучения (loss, metric);
- графики (shap, feature importance, roc-curve, pr-curve);
- архитектура модели — если это нейронная сеть, которую вы написали сами;
- confusion matrix, перечень и описание признаков, ваши комментарии и всякое полезное;
- примеры данных, на которых обучалась и тестировалась модель.
Подробно рассказывать о том, как пользоваться MLflow, мы не будем: в интернете есть много гайдов на всех языках. А ещё у MLflow неплохая документация с примерами и туториалами — изучайте и делитесь мнениями!
mlflow.org
MLflow Overview | MLflow
Stepping into the world of Machine Learning (ML) is an exciting journey, but it often comes with
🔥22👍9🤓3
В этом году коллеги активно нанимали промпт-инженеров для разных проектов Точки.
У нас они:
📌 Пишут промпты и их системы, чтобы языковая модель генерировала релевантные ответы.
📌 Помогают DS специалистам обучать и тренировать новые модели.
📌 Разрабатывают и поддерживают библиотеки промптов, чтобы использовать их повторно.
📌 Много тестируют.
В статье на Хабре рассказали про процесс поиска промпт-инженеров: какие вопросы задавали на собеседованиях, что было самым сложным для кандидатов и кого в итоге наняли.
У нас они:
📌 Пишут промпты и их системы, чтобы языковая модель генерировала релевантные ответы.
📌 Помогают DS специалистам обучать и тренировать новые модели.
📌 Разрабатывают и поддерживают библиотеки промптов, чтобы использовать их повторно.
📌 Много тестируют.
Самое сложное — это найти действительно подходящего кандидата. Промпт-инженеру нужно хорошо знать русский и английский языки, понимать принципы работы разных LLM, RAG, понимать бизнес-специфику финтеха, а в идеале — знать языки программирования.
В статье на Хабре рассказали про процесс поиска промпт-инженеров: какие вопросы задавали на собеседованиях, что было самым сложным для кандидатов и кого в итоге наняли.
Хабр
Бесполезные курсы и помешательство на GPTs: как мы искали prompt-инженеров
Сейчас Точка активно развивает свою LLM, поэтому нам очень нужны prompt-инженеры. В этом году было целых три волны найма, но из 400 человек мы взяли только 9. Почему в этой сфере так сложно...
🔥20🥰9👏6❤3😎3
Векторные базы данных (ВБД) — это NoSQL решения для хранения, индексирования и поиска похожих векторов.
С их помощью можно:
- Строить рекомендательные и поисковые системы.
- Делать анализ изображений и видео.
От базы данных требуется две операции: сохранять данные и читать информацию, записанную ранее. Как работают эти процессы в векторных базах:
📝 Запись
📖 Чтение
Поиск наиболее подходящих соседей может работать долго, ведь нужно просмотреть все записи на диске. Решение этой проблемы — индексация. Мы чуть замедлим запись, но чтение будет работать сильно быстрее. Про разные алгоритмы индексации подробнее расскажем в следующем посте.
С их помощью можно:
- Строить рекомендательные и поисковые системы.
- Делать анализ изображений и видео.
От базы данных требуется две операции: сохранять данные и читать информацию, записанную ранее. Как работают эти процессы в векторных базах:
📝 Запись
1. На вход поступает какой-то объект — например, текст.
2. Этот объект нужно превратить в вектор с помощью обученного векторизатора.
3. Полученный вектор и метаданные можно сохранить на диск.
📖 Чтение
1. К нам приходит приложение с новым объектом и запросом сделать для него рекомендацию.
2. Нужно проделать весь процесс обработки: векторизовать объект запроса той же моделью, получить вектор той же размерности.
3. По сгенерированному вектору можно найти наиболее близкий вектор. На этом этапе можно сделать предварительную фильтрацию по метаданным — например, искать соседей только с длинной текста больше n.
Поиск наиболее подходящих соседей может работать долго, ведь нужно просмотреть все записи на диске. Решение этой проблемы — индексация. Мы чуть замедлим запись, но чтение будет работать сильно быстрее. Про разные алгоритмы индексации подробнее расскажем в следующем посте.
👍15🔥12❤7
Несколько алгоритмов индексирования, которые ускоряют поиск наиболее близких векторов:
Уменьшение размерности с помощью случайной проекции
Нужно уменьшить размерность векторов, но сохранить свойство схожести. Это можно сделать согласно лемме Джонсона-Линденштрауса:
Для этого нужно сгенерировать матрицу случайной проекции, на которую будем скалярно умножать входные вектора. На выходе получаем вектора меньшей размерности, которые сохраняют свойство подобия. Из-за небольшой потери информации снижается точность, но увеличивается скорость из-за уменьшения размер векторов.
Хэширование с учетом местоположения
Хэш-функция определяет, в какой из бакетов попадёт вектор. Она позволяет группировать похожие вектора в одни и те же бакеты. Чтобы найти ближайших соседей для вектора запроса, нужно определить его бакет при помощи хэширования. Выполнить поиск можно среди данного бакета, не рассматривая предварительно неблизкие вектора. Этот метод намного быстрее, чем поиск по всему набору данных, потому что в бакете меньше векторов, чем во всем пространстве.
Качество этого метода зависит от свойств выбранной функции хэширования точно так же, как и в устройстве обычной хэш мапы.
Подробнее про алгоритмы индексирования и их применение на практике — в нашей статье на Хабре .
Уменьшение размерности с помощью случайной проекции
Нужно уменьшить размерность векторов, но сохранить свойство схожести. Это можно сделать согласно лемме Джонсона-Линденштрауса:
«Небольшой набор точек в пространстве большой размерности может быть вложен в пространство гораздо меньшей размерности таким образом, что расстояния между точками почти сохраняются».
Для этого нужно сгенерировать матрицу случайной проекции, на которую будем скалярно умножать входные вектора. На выходе получаем вектора меньшей размерности, которые сохраняют свойство подобия. Из-за небольшой потери информации снижается точность, но увеличивается скорость из-за уменьшения размер векторов.
Хэширование с учетом местоположения
Locality-sensitive hashing (LSH) — метод индексации, который похож на устройство обычной хэш-мапы. Суть в том, чтобы отобразить вектора в бакеты похожести, используя набор функций хеширования.
Хэш-функция определяет, в какой из бакетов попадёт вектор. Она позволяет группировать похожие вектора в одни и те же бакеты. Чтобы найти ближайших соседей для вектора запроса, нужно определить его бакет при помощи хэширования. Выполнить поиск можно среди данного бакета, не рассматривая предварительно неблизкие вектора. Этот метод намного быстрее, чем поиск по всему набору данных, потому что в бакете меньше векторов, чем во всем пространстве.
Качество этого метода зависит от свойств выбранной функции хэширования точно так же, как и в устройстве обычной хэш мапы.
🔥16👍7🤓4❤2👨💻2
Топ-3 просчёта в А/B-тестировании: культурологические
1. Проводить эксперименты без дизайна и action plan
Запущенный A/B-тест без предварительно выполненного дизайна — редкость, а хорошо продуманный action plan попадается нечасто.
Наш совет: до запуска эксперимента продумайте, какое решение вы будете принимать в разных сценариях. Например:
Составление Action plan до старта эксперимента помогает не только принимать решения, но и оставаться честным перед собой, коллегами и продуктом.
2. Расстраиваться, если нет прокраса
Если бы каждый эксперимент завершался прокрасом и ростом продукта, было бы слишком скучно жить, а A/B-тесты потеряли бы смысл: зачем делать их, если каждая гипотеза ведёт к успеху? Но мы знаем, что в реальности большая часть идей не срабатывает:
Поэтому, если эксперимент не взлетел, как можно скорее доходим до стадии «принятия», и идём дизайнить следующий A/B-тест.
3. A/B-тесты замедляют T2M, или Жизнь слишком коротка, чтобы проводить A/B-тесты
Понятно, что ничего не делать быстрее, чем что-то делать. Но катить фичи без экспериментов ≠ развивать продукт быстрее:
Чтобы ускорить процесс выкатки фичи, нужно верно настроить циклы discovery и delivery в команде — например, с помощью фреймворка Double Diamond. Тогда запуск A/B-тестов встанет на поток, гипотезы будут проверяться, фичи — выкатываться, а метрики продукта — расти.
Profit!
1. Проводить эксперименты без дизайна и action plan
Запущенный A/B-тест без предварительно выполненного дизайна — редкость, а хорошо продуманный action plan попадается нечасто.
Наш совет: до запуска эксперимента продумайте, какое решение вы будете принимать в разных сценариях. Например:
- Если не прокрасилась основная метрика, а вспомогательные показывают разноплановую динамику.
- Если все метрики серые, но это изменение открывает путь для новых фичей и гипотез.
- Если основная метрика зеленая, а защитная упала заметно, но не статистически значимо.
Составление Action plan до старта эксперимента помогает не только принимать решения, но и оставаться честным перед собой, коллегами и продуктом.
2. Расстраиваться, если нет прокраса
Если бы каждый эксперимент завершался прокрасом и ростом продукта, было бы слишком скучно жить, а A/B-тесты потеряли бы смысл: зачем делать их, если каждая гипотеза ведёт к успеху? Но мы знаем, что в реальности большая часть идей не срабатывает:
“80% of the time you/we are wrong about what a customer wants.”
“Netflix considers 90% of what they try to be wrong.”
“Microsoft is no different … only about one-third (experiments) were successful at improving the key metric!”
Поэтому, если эксперимент не взлетел, как можно скорее доходим до стадии «принятия», и идём дизайнить следующий A/B-тест.
3. A/B-тесты замедляют T2M, или Жизнь слишком коротка, чтобы проводить A/B-тесты
Понятно, что ничего не делать быстрее, чем что-то делать. Но катить фичи без экспериментов ≠ развивать продукт быстрее:
- Долго ли просуществует такой продукт?
- Какими будут его метрики роста?
- Сколько упущенных возможностей и шагов назад мы сделаем, если не будем проверять гипотезы экспериментами?
Чтобы ускорить процесс выкатки фичи, нужно верно настроить циклы discovery и delivery в команде — например, с помощью фреймворка Double Diamond. Тогда запуск A/B-тестов встанет на поток, гипотезы будут проверяться, фичи — выкатываться, а метрики продукта — расти.
Profit!
❤20🔥8🤓3👍1
This media is not supported in your browser
VIEW IN TELEGRAM
И вдогонку картинка — что делать, если нет прокраса
🔥17
Топ-3 просчёта в А/B-тестировании: технические
В предыдущей части мы рассказали про культурологические просчёты во время планирования, запуска и проведения A/B-тестов. Продолжаем техническими.
1. Убираем выбросы только после эксперимента
Снизив дисперсию, мы сокращаем длительность эксперимента или увеличиваем его чувствительность. Но не всё так просто:
- С выбросами можно случайно выкинуть из эксперимента 1-2% пользователей, которые в некоторых продуктах могут генерировать до 50-70% всей выручки.
- Часто с выбросами начинают бороться только на этапе подведения итого, когда эксперимент не даёт прокрас. Это в корне неверно. Чтобы избежать этой ошибки, следуем золотому правилу: как задизайнили эксперимент, так его и проводим. Если на этапе дизайна вы заложили отбросить по 1% выбросов с обеих сторон, значит то же самое нужно сделать, когда подводите итоги. Так не нарушается методология, и мы продолжаем контролировать ошибки первого и второго рода.
2. Стратификация только в сплите
Стратификация работает за счёт того, что разные группы пользователей ведут себя по-разному. Если мы делаем стратифицированный сплит, чтобы сократить дисперсию — в этом случае надо подводить итоги эксперимента с помощью стратифицированного среднего в расчётах p-value. Использование стратифицированного семплирования и обычного среднего — прямой путь к потере контроля вероятности ошибки первого рода.
3. Работа с метриками отношений
Метрики отношений — это отношение сумм двух пользовательских метрик. Например, чтобы посчитать средний чек, нам надо знать сумму покупок клиента (числитель) и их количество (знаменатель). Стоит обратить внимание, что различные заказы для одного клиента не будут независимыми: кто-то в среднем покупает больше, а кто-то меньше. Получается, что для метрик отношений нарушается принцип независимости.
Но t-test рассчитан на использование для независимых данных. С помощью симуляций АА-тестов мы легко можем убедиться, что зависимые данные ломают процедуру теста. Поэтому не стоит забывать использовать бутстрап, дельта-метод или линеаризацию, если работаете с метриками отношений. Тогда результаты ваших экспериментов останутся надёжными, а выводам по ним можно будет доверять.
В предыдущей части мы рассказали про культурологические просчёты во время планирования, запуска и проведения A/B-тестов. Продолжаем техническими.
1. Убираем выбросы только после эксперимента
Дисперсия — заклятый враг А/В-тестеров во всём мире. Один из самых простых способов борьбы с дисперсией — удаление выбросов. Что может быть проще, чем выкинуть часть выбивающихся данных?
Снизив дисперсию, мы сокращаем длительность эксперимента или увеличиваем его чувствительность. Но не всё так просто:
- С выбросами можно случайно выкинуть из эксперимента 1-2% пользователей, которые в некоторых продуктах могут генерировать до 50-70% всей выручки.
- Часто с выбросами начинают бороться только на этапе подведения итого, когда эксперимент не даёт прокрас. Это в корне неверно. Чтобы избежать этой ошибки, следуем золотому правилу: как задизайнили эксперимент, так его и проводим. Если на этапе дизайна вы заложили отбросить по 1% выбросов с обеих сторон, значит то же самое нужно сделать, когда подводите итоги. Так не нарушается методология, и мы продолжаем контролировать ошибки первого и второго рода.
2. Стратификация только в сплите
Стратификация работает за счёт того, что разные группы пользователей ведут себя по-разному. Если мы делаем стратифицированный сплит, чтобы сократить дисперсию — в этом случае надо подводить итоги эксперимента с помощью стратифицированного среднего в расчётах p-value. Использование стратифицированного семплирования и обычного среднего — прямой путь к потере контроля вероятности ошибки первого рода.
3. Работа с метриками отношений
Метрики отношений — это отношение сумм двух пользовательских метрик. Например, чтобы посчитать средний чек, нам надо знать сумму покупок клиента (числитель) и их количество (знаменатель). Стоит обратить внимание, что различные заказы для одного клиента не будут независимыми: кто-то в среднем покупает больше, а кто-то меньше. Получается, что для метрик отношений нарушается принцип независимости.
Но t-test рассчитан на использование для независимых данных. С помощью симуляций АА-тестов мы легко можем убедиться, что зависимые данные ломают процедуру теста. Поэтому не стоит забывать использовать бутстрап, дельта-метод или линеаризацию, если работаете с метриками отношений. Тогда результаты ваших экспериментов останутся надёжными, а выводам по ним можно будет доверять.
👍18🔥7❤5
Когда делаем новую фичу, A/B-тестирование кажется чем-то обыденным и очевидным. Но запустить хороший A/B-тест не всегда возможно:
📌 Иногда запуск только ради оценки эффекта экономически нецелесообразен.
📌 Иногда продуктовая команда хочет раскатить фичу сразу на всех пользователей.
📌 Иногда слишком дорого внедрять инфраструктуру для сплита пользователей.
Во всех этих случаях мы хотим как-то оценить сколько пользы причиняем бизнесу.
Как это сделать?
📖 Difference-in-Difference
В методе Diff-in-diff мы подбираем группу, в которой динамика целевой метрики на предэкспериментальном периоде максимально совпадает с группой, на которую мы раскатили фичу. Остаётся только рассчитать разницу целевой метрики между группами во время эксперимента и на пре-периоде — она даст нам оцениваемый эффект. Для этого метода важна параллельность трендов двух групп на пре-периоде.
📖 Синтетический контроль
Идея синтетического контроля предельно простая: если мы не можем выделить контрольную группу, то синтезируем ее. Давайте научимся моделировать поведение тестовой группы на предэкспериментальном периоде, когда никакого воздействия на группу не было. Тогда, вычитая из фактических результатов прогноз модели на экспериментальный период, мы получим желаемую оценку.
📖 Двойное машинное обучение
Часто мы оказываемся в ситуации, когда выбор группы для внедрения фичи или воздействия не был случайным. В таких случаях обычно наблюдается набор факторов (конфаундеров), которые влияют и на решение о получении фичи, и на целевую метрику.
Двойное машинное обучение основано на теореме Фриша-Во-Ловелла:
✏️ На первом этапе строятся две модели, которые прогнозируют воздействие и результирующие значения на основе конфаундеров.
✏️ Далее рассчитывается разница фактических значений воздействия и результата с их смоделированными значениями. Так происходит «объяснение» влияния конфаундеров на воздействие и результат.
✏️ В конце строим регрессию: Δy= w\* ΔT. Коэффициент “w” даст оценку влияния воздействия на результирующую метрику.
📌 Иногда запуск только ради оценки эффекта экономически нецелесообразен.
📌 Иногда продуктовая команда хочет раскатить фичу сразу на всех пользователей.
📌 Иногда слишком дорого внедрять инфраструктуру для сплита пользователей.
Во всех этих случаях мы хотим как-то оценить сколько пользы причиняем бизнесу.
Как это сделать?
📖 Difference-in-Difference
В методе Diff-in-diff мы подбираем группу, в которой динамика целевой метрики на предэкспериментальном периоде максимально совпадает с группой, на которую мы раскатили фичу. Остаётся только рассчитать разницу целевой метрики между группами во время эксперимента и на пре-периоде — она даст нам оцениваемый эффект. Для этого метода важна параллельность трендов двух групп на пре-периоде.
📖 Синтетический контроль
Идея синтетического контроля предельно простая: если мы не можем выделить контрольную группу, то синтезируем ее. Давайте научимся моделировать поведение тестовой группы на предэкспериментальном периоде, когда никакого воздействия на группу не было. Тогда, вычитая из фактических результатов прогноз модели на экспериментальный период, мы получим желаемую оценку.
📖 Двойное машинное обучение
Часто мы оказываемся в ситуации, когда выбор группы для внедрения фичи или воздействия не был случайным. В таких случаях обычно наблюдается набор факторов (конфаундеров), которые влияют и на решение о получении фичи, и на целевую метрику.
Двойное машинное обучение основано на теореме Фриша-Во-Ловелла:
✏️ На первом этапе строятся две модели, которые прогнозируют воздействие и результирующие значения на основе конфаундеров.
✏️ Далее рассчитывается разница фактических значений воздействия и результата с их смоделированными значениями. Так происходит «объяснение» влияния конфаундеров на воздействие и результат.
✏️ В конце строим регрессию: Δy= w\* ΔT. Коэффициент “w” даст оценку влияния воздействия на результирующую метрику.
🔥15❤6✍4👍4
Запуск маркетинговых кампаний – дорогое удовольствие, даже если стоимость воздействия на одного клиента невысокая. Поэтому часто бизнес пытается ограничить аудиторию кампании: иногда ограничения выбираются по наитию, реже выбор делают аналитики, ещё реже для этого используют ML-инструменты.
В чём проблема такого подхода?
Модель будет отдавать предпочтения лояльным клиентам, которые бы совершили целевое действие и без маркетингового воздействия. На практике это означает, что, например, раздавая скидку на товары в онлайн-магазине, мы дадим скидку большому количеству постоянных покупателей, которые и так часто совершают покупки. Это приведёт к неоптимальной трате бюджетов.
Для решения этой проблемы на помощь приходит uplift-моделирование. Его задача — предсказание, на сколько вырастет вероятность совершения целевого действия при воздействии на клиента:
Основное препятствие — невозможность получить для одного и того же клиента Y(T=1) и Y(T=0), так как нельзя одновременно воздействовать и не воздействовать на человека. Соответственно, нельзя для каждого пользователя получить U и построить модель, предсказывающую этот показатель. Поэтому обычно приходят к раздельной оценке поведения пользователей с воздействием и без него:
Главное условие в таком подходе — независимость T от X в обучающей выборке, то есть, случайное распределение воздействия на клиентов.
С помощью ранжирования по оценке uplift, мы сможем выбрать топ клиентов, на которых маркетинговая компания окажет наибольшее влияние.
В следующих постах мы расскажем про специфичные способы оценки Y[T=1| X] и Y[T=0| X], а если вам не терпится узнать про них прямо сейчас, читайте документацию CasualML.
Стандартный подход выбора ограничений — построение модели отклика, то есть предсказание вероятности целевого действия клиентом при маркетинговом воздействии на него.
В чём проблема такого подхода?
Модель будет отдавать предпочтения лояльным клиентам, которые бы совершили целевое действие и без маркетингового воздействия. На практике это означает, что, например, раздавая скидку на товары в онлайн-магазине, мы дадим скидку большому количеству постоянных покупателей, которые и так часто совершают покупки. Это приведёт к неоптимальной трате бюджетов.
Для решения этой проблемы на помощь приходит uplift-моделирование. Его задача — предсказание, на сколько вырастет вероятность совершения целевого действия при воздействии на клиента:
U = Y[T=1] – Y[T=0], где T — флаг маркетингового воздействия на клиента.
Основное препятствие — невозможность получить для одного и того же клиента Y(T=1) и Y(T=0), так как нельзя одновременно воздействовать и не воздействовать на человека. Соответственно, нельзя для каждого пользователя получить U и построить модель, предсказывающую этот показатель. Поэтому обычно приходят к раздельной оценке поведения пользователей с воздействием и без него:
U = Y[T=1| X] – Y[T=0| X].
Главное условие в таком подходе — независимость T от X в обучающей выборке, то есть, случайное распределение воздействия на клиентов.
С помощью ранжирования по оценке uplift, мы сможем выбрать топ клиентов, на которых маркетинговая компания окажет наибольшее влияние.
В следующих постах мы расскажем про специфичные способы оценки Y[T=1| X] и Y[T=0| X], а если вам не терпится узнать про них прямо сейчас, читайте документацию CasualML.
🔥10❤5👏4👍3⚡1
Архитектура LLM
Что вообще такое эти ваши LLM и чем они отличаются от привычных трансформеров? Давайте разбираться.
Если пытаться дать определение, то LLM — это большая языковая модель, которая была обучена генерировать ответ на какую-либо инструкцию.
Тут два ключевых момента, не считая размер модели: то, что модель генеративная, и то, что она умеет принимать на вход какие-либо инструкции.
📝 Разбираемся с генеративностью
Какая часть трансформера умеет в генерацию текста? Правильно, декодер. Собственно, LLM — это просто жирный (с большим количеством параметров) transformer decoder.Или encoder-decoder, но это только у старых моделей, типа T5. Новые GPT-like архитектуры от энкодеров отошли.
Способность же принимать на вход инструкцию обусловлена пайплайном обучения модели, включая специфичные инструкционные данные, а не какими-либо архитектурными модификациями.
Особенность этого пайлайна — после этапа pre-train модели проводят этап alignment, дообучая модель на инструкционных датасетах. В таких датасете каждый сэмпл — это диалог человека с LLM, который может включать в себя системный промпт (как раз-таки инструкцию), сообщения от лица человека и сообщения от лица LLM, зачастую промаркированные на предмет «хорошести» ответа. Сейчас самые популярные инструкционные датасеты — это Nectar и UltraFeedback.
Итого, LLM — это просто здоровенный transformer decoder, дообученный на инструкционном датасете.
Если углубляться в детали, то популярными архитектурными особенностями современных LLM являются:
- Rotary Positional Encoding (RoPE) и его модификации в качестве позиционного кодирования — вот наш пост про это.
Почему? Помогает работать с более длинным контекстом без значимой потери качества.
- RMSNorm вместо LayerNorm для нормализации.
Почему? Работает сопоставимо по качеству, но проще (быстрее) вычислять — а скорость нам важна.
- Sliding Window, Grouped-Query или Multi-Query вместо ванильного Multi-Head Attention:
Почему? Чем меньше параметров, тем быстрее вычислять.
- Может использоваться Mixture-of-Experts, но это скорее частные случаи.
Почему? Увеличиваем количество параметров модели, не увеличивая при этом сложность вычислений (хоть и страдаем по памяти).
P.S.: если вы увидели много незнакомых слов — не переживайте, в следующих постах расскажем про то, как именно работают все эти навороты.
Эти же архитектурный особенности характерны и для негенеративных современных моделек: например, для энкодеров. Так что нельзя сказать, что это что-то LLM-специфичное — скорее архитектурная база любых современных трансформеров.
Что вообще такое эти ваши LLM и чем они отличаются от привычных трансформеров? Давайте разбираться.
Если пытаться дать определение, то LLM — это большая языковая модель, которая была обучена генерировать ответ на какую-либо инструкцию.
Тут два ключевых момента, не считая размер модели: то, что модель генеративная, и то, что она умеет принимать на вход какие-либо инструкции.
📝 Разбираемся с генеративностью
Какая часть трансформера умеет в генерацию текста? Правильно, декодер. Собственно, LLM — это просто жирный (с большим количеством параметров) transformer decoder.
Способность же принимать на вход инструкцию обусловлена пайплайном обучения модели, включая специфичные инструкционные данные, а не какими-либо архитектурными модификациями.
Особенность этого пайлайна — после этапа pre-train модели проводят этап alignment, дообучая модель на инструкционных датасетах. В таких датасете каждый сэмпл — это диалог человека с LLM, который может включать в себя системный промпт (как раз-таки инструкцию), сообщения от лица человека и сообщения от лица LLM, зачастую промаркированные на предмет «хорошести» ответа. Сейчас самые популярные инструкционные датасеты — это Nectar и UltraFeedback.
Итого, LLM — это просто здоровенный transformer decoder, дообученный на инструкционном датасете.
Если углубляться в детали, то популярными архитектурными особенностями современных LLM являются:
- Rotary Positional Encoding (RoPE) и его модификации в качестве позиционного кодирования — вот наш пост про это.
Почему? Помогает работать с более длинным контекстом без значимой потери качества.
- RMSNorm вместо LayerNorm для нормализации.
Почему? Работает сопоставимо по качеству, но проще (быстрее) вычислять — а скорость нам важна.
- Sliding Window, Grouped-Query или Multi-Query вместо ванильного Multi-Head Attention:
Почему? Чем меньше параметров, тем быстрее вычислять.
- Может использоваться Mixture-of-Experts, но это скорее частные случаи.
Почему? Увеличиваем количество параметров модели, не увеличивая при этом сложность вычислений (хоть и страдаем по памяти).
Эти же архитектурный особенности характерны и для негенеративных современных моделек: например, для энкодеров. Так что нельзя сказать, что это что-то LLM-специфичное — скорее архитектурная база любых современных трансформеров.
✍22❤17👨💻17👍4🥴1🤓1
Часто в современных CV/NLP-системах мы используем тяжёлые модели для обработки. А где тяжёлые модели, там и потребность в масштабировании, параллелизме и возможности управлять зоопарком оборудования. Например, раньше у нас в Точке ASR запускалась на видеокартах только в одном ЦОДе, а второй ЦОД обрабатывал аудиозаписи только на CPU.
Но меня не отпускала мысль — что же будет, если мы засунем наше видео/аудио в Kafka?
Первый вопрос, на который предстояло ответить — как делить видео/аудио. В целом, уже существует batch inference, и изобретать что-то новое не имеет смысла.
Архитектура нашего PoC очень простая: на стороне продьюсера каждый батч мы отправляем в топик, а на стороне консьюмера начинаем слушать этот топик и разбирать из него батчи. Их мы можем кормить в модель, выводить на экран или делать любые непотребства, которые попросит менеджер.
❓ В чём преимущества батчирования и как оно решает нашу проблему
Представим, что у нас есть кластер железок, который мы делим с другими командами. Мы не можем постоянно занимать все X машин, но в пиковые часы было бы хорошо поскейлиться и занять как можно больше тачек. Мы делим наш топик на X партиций и спокойно скейлимся вплоть до X консьюмеров. Наше приложение даже не знает, сколько инстансов сейчас запущено, и нам не нужно адаптировать логику под это, но мы оставляем достаточно большой простор для up/down-скейлинга.
А теперь эпопея с батчированием
На входе у нас есть h264 видео. Логично было бы использовать батч размером в один кадр, но тут мы встречаемся с очередной проблемой — «P» и «B» кадры.
Здесь можно написать отдельный пост про то, как работает h264. Если коротко:
Эту проблему решить достаточно просто — на источнике видео нужно перекодировать каждый кадр в JPEG. Этот подход можно применять для любого видео и использовать только I кадры (аргумент "-intra" для libx264). Или можно использовать MJPEG.
Когда мы разобрали основные вопросы, можем переходить к написанию кода.
Недавно увидели статью про сравнение производительности брокеров сообщений в зависимости от их количества и других параметров. Но так как у нас в команде никто не работал с Kafka, решили использовать RabbitMQ, который за нас админят специально обученные люди.
Но меня не отпускала мысль — что же будет, если мы засунем наше видео/аудио в Kafka?
Первый вопрос, на который предстояло ответить — как делить видео/аудио. В целом, уже существует batch inference, и изобретать что-то новое не имеет смысла.
Архитектура нашего PoC очень простая: на стороне продьюсера каждый батч мы отправляем в топик, а на стороне консьюмера начинаем слушать этот топик и разбирать из него батчи. Их мы можем кормить в модель, выводить на экран или делать любые непотребства, которые попросит менеджер.
Представим, что у нас есть кластер железок, который мы делим с другими командами. Мы не можем постоянно занимать все X машин, но в пиковые часы было бы хорошо поскейлиться и занять как можно больше тачек. Мы делим наш топик на X партиций и спокойно скейлимся вплоть до X консьюмеров. Наше приложение даже не знает, сколько инстансов сейчас запущено, и нам не нужно адаптировать логику под это, но мы оставляем достаточно большой простор для up/down-скейлинга.
А теперь эпопея с батчированием
На входе у нас есть h264 видео. Логично было бы использовать батч размером в один кадр, но тут мы встречаемся с очередной проблемой — «P» и «B» кадры.
Здесь можно написать отдельный пост про то, как работает h264. Если коротко:
- «P» кадры «смотрят» на один кадр назад и реконструируют изображение, «накладывая» только изменения без сохранения целого кадра.
- «B» кадры «смотрят» и назад, и вперёд. То есть, если мы попытаемся батчировать «в лоб», то некоторые кадры потеряется, так как они не будут содержать почти никакой полезной информации.
Эту проблему решить достаточно просто — на источнике видео нужно перекодировать каждый кадр в JPEG. Этот подход можно применять для любого видео и использовать только I кадры (аргумент "-intra" для libx264). Или можно использовать MJPEG.
Когда мы разобрали основные вопросы, можем переходить к написанию кода.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12🔥9✍3👍1
This media is not supported in your browser
VIEW IN TELEGRAM
На видео — простенькая демонстрация. Потенциально, в этом проекте нужно решить несколько вопросов:
📌 Скорость чтения и отправки батча.
📌 Упорядочивание кадров.
📌 Нормальное конфигурирование Kafka, а не просто дефолтные настройки в docker-образе.
Наше собранное на коленке решение даёт средний размер батча в 150 Кб — этого достаточно для дефолтной конфигурации Kafka. Так же можно использовать и любой другой транспорт — даже RabbitMQ, если отключить репликацию. Но, как говорилось в том меме:
P.S. Не рекомендуем использовать такое в продакшене без предварительной консультации с вашими кафководами (мы вот не консультировались).
📌 Скорость чтения и отправки батча.
📌 Упорядочивание кадров.
📌 Нормальное конфигурирование Kafka, а не просто дефолтные настройки в docker-образе.
Наше собранное на коленке решение даёт средний размер батча в 150 Кб — этого достаточно для дефолтной конфигурации Kafka. Так же можно использовать и любой другой транспорт — даже RabbitMQ, если отключить репликацию. Но, как говорилось в том меме:
«Вот так, с помощью нехитрых приспособлений буханку белого (или чёрного) хлеба можно превратить в троллейбус… Но зачем?».
🔥13❤9🍾5😁4🤓2👍1
Ранее мы обсуждали с вами RoPE, а теперь поговорим о его модификациях. Собрали много интересного, поэтому будет целых три поста по этой теме. Enjoy!
Как развивалось позиционное кодирование:
📆 2017 год
С появлением ванильного трансформера позиции токенов кодировались тригонометрической функцией, значение которой зависело от позиции и просто прибавлялось к эмбеддингу соответсутвующего слова.
Плюсы — мы умеем кодировать любую позицию, в том числе превосходящую максимальную длину, на которой тренировались.
Минусы — не очень работает на длинных последовательностях, да и вообще не очень хорошо работает.
📆 2018 год
Потом появился гугловский BERT, а вместе с ним новый подход позиционного кодирования: авторы предложиди выкинуть тригонометрию и вместо этого добавить в модель ещё один обучаемый слой nn.Embedding — такой же, как для получения эмбеддингов слов. Он должен кодировать — то есть, превращать в вектор — позицию токена.
Итоговый вектор токена, который будет передан следующим слоям модели — это сумма векторов токена и его позиции. Работает лучше, чем тригонометрия, но при этом никак не экстраполируется: так как векторы выучиваемые, то для позиций, превосходящих максимальную тренировочную длину, мы кодировать не умеем — она вне ключей нашего словаря эмбеддингов, так же, как мы не можем закодировать и незнакомый модели токен.
В это же время впервые появилась идея о том, что нам важны не столько абсолютные позиции слов, сколько относительные. Авторы статьи решили кодировать не абсолютную позицию, а только относительную (Relative Position Encoding, или RPE), то есть близость каждой пары токенов. Здесь же появилась идея, что позицонное кодирование стоит добавлять не в момент создания эмбеддингов слов, а на этапе Attention, добавляя знание о позициии в queries и keys.
Для начала напомним, что Positional Encoding (кодирование позиций слов/токенов) нужен, чтобы передать модели или трансформеру информацию о позициях слов — относительную или же абсолютную.
Как развивалось позиционное кодирование:
📆 2017 год
С появлением ванильного трансформера позиции токенов кодировались тригонометрической функцией, значение которой зависело от позиции и просто прибавлялось к эмбеддингу соответсутвующего слова.
Плюсы — мы умеем кодировать любую позицию, в том числе превосходящую максимальную длину, на которой тренировались.
Минусы — не очень работает на длинных последовательностях, да и вообще не очень хорошо работает.
📆 2018 год
Потом появился гугловский BERT, а вместе с ним новый подход позиционного кодирования: авторы предложиди выкинуть тригонометрию и вместо этого добавить в модель ещё один обучаемый слой nn.Embedding — такой же, как для получения эмбеддингов слов. Он должен кодировать — то есть, превращать в вектор — позицию токена.
Итоговый вектор токена, который будет передан следующим слоям модели — это сумма векторов токена и его позиции. Работает лучше, чем тригонометрия, но при этом никак не экстраполируется: так как векторы выучиваемые, то для позиций, превосходящих максимальную тренировочную длину, мы кодировать не умеем — она вне ключей нашего словаря эмбеддингов, так же, как мы не можем закодировать и незнакомый модели токен.
В это же время впервые появилась идея о том, что нам важны не столько абсолютные позиции слов, сколько относительные. Авторы статьи решили кодировать не абсолютную позицию, а только относительную (Relative Position Encoding, или RPE), то есть близость каждой пары токенов. Здесь же появилась идея, что позицонное кодирование стоит добавлять не в момент создания эмбеддингов слов, а на этапе Attention, добавляя знание о позициии в queries и keys.
❤14🔥9👍7👨💻3🤓1
Продолжаем рассказывать про развитие позиционного кодирования — предыдущий пост выше ⬆️
📆 2021 год
Тут появился RoPE — великий и ужасный убийца остальных методов позионного кодирования. Он унаследовал от предшественников всё лучшее: из ванильного трансформера — идею, что тригонометрия хороша для кодирования позиции, а из RPE — что проворачивать всё это кодирование надо на этапе аттеншена, и не забывать про важнность относительных позиций. В итоге, RoPE реализует и абсолютное, и относительное кодирвоание одновременно.
В это же время зародилась идея о том, что хорошо бы нам уметь работать на инфренсе на текстах, которые превосходят длиной те, которые модель видела на этапе обучения, так как учить модель на длинных текстах изначально нереально из-за нехватки памяти. Предлагается Attention with Linear Biases (ALiBi) — метод позицонного кодирования, который учитывает только лишь относительные позиции слов, которые не выучиваются, а являются статичными. Перед софтмаксом в Attention мы прибавляем значение растяния между токенами, домноженное на некоторый коэффициент, который является гиперпараметром.
В это же время начали активно развиваться LLM-ки, в том числе опенсорсные. И вот, с появлением LLAMA в 2023 году разработчики по всему миру получили возможность руками поработать с LLM-кой и что-то в ней докрутить под себя. Благодаря этому следующая веха развития позиционок началась, внезапно, на Reddit.
📆 2021 год
Тут появился RoPE — великий и ужасный убийца остальных методов позионного кодирования. Он унаследовал от предшественников всё лучшее: из ванильного трансформера — идею, что тригонометрия хороша для кодирования позиции, а из RPE — что проворачивать всё это кодирование надо на этапе аттеншена, и не забывать про важнность относительных позиций. В итоге, RoPE реализует и абсолютное, и относительное кодирвоание одновременно.
В это же время зародилась идея о том, что хорошо бы нам уметь работать на инфренсе на текстах, которые превосходят длиной те, которые модель видела на этапе обучения, так как учить модель на длинных текстах изначально нереально из-за нехватки памяти. Предлагается Attention with Linear Biases (ALiBi) — метод позицонного кодирования, который учитывает только лишь относительные позиции слов, которые не выучиваются, а являются статичными. Перед софтмаксом в Attention мы прибавляем значение растяния между токенами, домноженное на некоторый коэффициент, который является гиперпараметром.
В это же время начали активно развиваться LLM-ки, в том числе опенсорсные. И вот, с появлением LLAMA в 2023 году разработчики по всему миру получили возможность руками поработать с LLM-кой и что-то в ней докрутить под себя. Благодаря этому следующая веха развития позиционок началась, внезапно, на Reddit.
🔥12❤9✍5👍3
Продолжаем рассказывать про развитие позиционного кодирования — вот первый и второй посты.
📆 2023 год
Пользователь Reddit размещает пост о том, что если чуть уменшить основание RoPE-аттеншна, то модель будет отлично работать на текстах длиннее, чем те, на которых она училась. Такой подход называет NTK-Aware Scaled RoPE.
Спустя пару недель после реддитовского NTK уже на ACL, большой и уважаемой конференции, появляется xPos: Extrapolatable Position Embedding. Идея тут схожая: мы хотим уметь экстраполироваться на более длинные тексты. Авторы пытаются решить следующую проблему: функция поворота в RoPE не монотонна, так как её монотонность нарушается для углов больше 180 градусов, и поэтому плохо экстраполируется. Решают в статье эту проблему так: уже после домножения queries и keys на матрицы поворота мы так же домножаем их на матрицу «смягчения», которая чуть замедляет вращение и не даёт нам получать углы больше 180 градусов. Так функция получается монотонной, благодяря чему дальние позиции кодируются немного лучше.
Ещё спустя месяц после xPos появился YaRN, или Yet another RoPE extensioN method. Идея у него примерно такая же, как и у предыдущих: давайте перед софтмаксом будем умножать произведение наших queries и keys на некий коэффициент (или scale factor), просто сам этот scale factor считать будем немного по-другому.
📆 2023 год
Пользователь Reddit размещает пост о том, что если чуть уменшить основание RoPE-аттеншна, то модель будет отлично работать на текстах длиннее, чем те, на которых она училась. Такой подход называет NTK-Aware Scaled RoPE.
Суть следующая: мы медленнее поворачиваем слова и потому получается, что позиции, выходящие за пределы длины на трейне, получают углы, которые были у крайних позиций на трейне — то есть углы, с которыми мы умеем работать.
Спустя пару недель после реддитовского NTK уже на ACL, большой и уважаемой конференции, появляется xPos: Extrapolatable Position Embedding. Идея тут схожая: мы хотим уметь экстраполироваться на более длинные тексты. Авторы пытаются решить следующую проблему: функция поворота в RoPE не монотонна, так как её монотонность нарушается для углов больше 180 градусов, и поэтому плохо экстраполируется. Решают в статье эту проблему так: уже после домножения queries и keys на матрицы поворота мы так же домножаем их на матрицу «смягчения», которая чуть замедляет вращение и не даёт нам получать углы больше 180 градусов. Так функция получается монотонной, благодяря чему дальние позиции кодируются немного лучше.
Ещё спустя месяц после xPos появился YaRN, или Yet another RoPE extensioN method. Идея у него примерно такая же, как и у предыдущих: давайте перед софтмаксом будем умножать произведение наших queries и keys на некий коэффициент (или scale factor), просто сам этот scale factor считать будем немного по-другому.
Резюмируя: до RoPE методы позиционного кодирования развивались в сторону того, чтобы позиция как можно лучше кодировалась в пределах той длины текста, на которой модель училась. После появления RoPE озиции и так кодируются довольно быстро и эффективно, поэтому новый челендж — уметь эти позиции экстраполировать на длинные последовательносто. Новые подходы (обычно, производные RoPE) направлены именно на это.
👨💻12✍9❤7🔥3
Вспоминаем основы работы с временными рядами
Кажется, в 2025 можно писать посты только про LLM, но бизнес всё так же продолжает прогнозировать спрос и нагрузку на колл-центры, пытается угадать будущую стоимость активов. И пока мир обсуждает новые архитектуры нейросетей, классические методы анализа временных рядов остаются незаменимыми. А прежде, чем строить сложные модели, нужно понять, с каким рядом мы имеем дело.
Прежде всего вспоминаем, что временной ряд состоит из:
📌 Тренда — долгосрочного роста или спада.
📌 Cезонности — повторяющихся паттернов.
📌 Всего остального (по классике «шум»).
Выделение из ряда этих компонент может стать отправной точкой для дальнейшего анализа и моделирования.
Разберёмся по порядку!
Тренд
Выделять тренд можно разными способами. Самый простой — обучить линейную регрессию на данных ряда, которая покажет глобальную тенденцию. Вдобавок получаем инструмент прогноза, ведь линейную регрессию можно экстраполировать.
Но часто временной ряд ведёт себя сложнее: он растёт, падает и снова растёт. В таких случаях одной регрессией не отделаешься. Например, STL-декомпозиция выделяет трендовую компоненту, сглаживая ряд через непараметрическую регрессию.
Зачем это нужно?
📌 Для интерпретации — тренд помогает понять, как ряд меняется в долгосрочной перспективе.
📌 Для ML-моделей — бустинги над деревьями и случайный лес плохо работают с трендовыми данными, потому что не умеют экстраполировать. Перед их обучениемполезно выполнить детрендинг: например, вычесть трендовую компоненту.
Сезонность
Анализ сезонностей временного ряда — ключ к пониманию его структуры, формированию гипотез и уточнению прогнозных моделей.
Сезонность в данных — это регулярные повторения, например:
🗓 Недельная: люди больше слушают музыку по пятницам в день выхода новых релизов на стримингах.
📆 Месячная: рост переводов и покупок в дни выдачи зарплаты.
📆 Годовая: рост покупок фитнес-абонементов в начале нового года.
Тот же STL, помимо тренда, автоматически выдаёт и сезонную компоненту.
Для выделения отдельных сезонностей подходит:
📌 Автокорреляция — помогает увидеть, через какие интервалы значения ряда повторяются.
📌 Периодограмма — её пики подсветят наиболее сильные сезонности.
Зачем это нужно?
📌 Для интерпретации — выделение сезонности помогает увидеть закономерности в данных: когда происходят всплески или спады, какие циклы повторяются.
📌 Для ML-моделей — источник важных признаков, без которых модель скорее всего будет совершать систематические ошибки. По аналогии с детрендингом можно cделать десезонализацию, вычитая сезонную компоненту, и попробовать моделировать только остаток.
Кстати, для анализа и прогнозирования временных рядов есть удобный Python-фреймворк etna. Он позволяет легко применять трансформации к ряду, строить прогнозы и тестировать модели — мы в Точке активно его используем. Для тех, кто работает с time-series и любит sklearn-api, но ещё не пробовал etna – рекомендуем.
Линк на туториалы 🌋
Кажется, в 2025 можно писать посты только про LLM, но бизнес всё так же продолжает прогнозировать спрос и нагрузку на колл-центры, пытается угадать будущую стоимость активов. И пока мир обсуждает новые архитектуры нейросетей, классические методы анализа временных рядов остаются незаменимыми. А прежде, чем строить сложные модели, нужно понять, с каким рядом мы имеем дело.
Прежде всего вспоминаем, что временной ряд состоит из:
📌 Тренда — долгосрочного роста или спада.
📌 Cезонности — повторяющихся паттернов.
📌 Всего остального (по классике «шум»).
Выделение из ряда этих компонент может стать отправной точкой для дальнейшего анализа и моделирования.
Разберёмся по порядку!
Тренд
Выделять тренд можно разными способами. Самый простой — обучить линейную регрессию на данных ряда, которая покажет глобальную тенденцию. Вдобавок получаем инструмент прогноза, ведь линейную регрессию можно экстраполировать.
Но часто временной ряд ведёт себя сложнее: он растёт, падает и снова растёт. В таких случаях одной регрессией не отделаешься. Например, STL-декомпозиция выделяет трендовую компоненту, сглаживая ряд через непараметрическую регрессию.
Зачем это нужно?
📌 Для интерпретации — тренд помогает понять, как ряд меняется в долгосрочной перспективе.
📌 Для ML-моделей — бустинги над деревьями и случайный лес плохо работают с трендовыми данными, потому что не умеют экстраполировать. Перед их обучениемполезно выполнить детрендинг: например, вычесть трендовую компоненту.
Сезонность
Анализ сезонностей временного ряда — ключ к пониманию его структуры, формированию гипотез и уточнению прогнозных моделей.
Сезонность в данных — это регулярные повторения, например:
🗓 Недельная: люди больше слушают музыку по пятницам в день выхода новых релизов на стримингах.
📆 Месячная: рост переводов и покупок в дни выдачи зарплаты.
📆 Годовая: рост покупок фитнес-абонементов в начале нового года.
Тот же STL, помимо тренда, автоматически выдаёт и сезонную компоненту.
Для выделения отдельных сезонностей подходит:
📌 Автокорреляция — помогает увидеть, через какие интервалы значения ряда повторяются.
📌 Периодограмма — её пики подсветят наиболее сильные сезонности.
Зачем это нужно?
📌 Для интерпретации — выделение сезонности помогает увидеть закономерности в данных: когда происходят всплески или спады, какие циклы повторяются.
📌 Для ML-моделей — источник важных признаков, без которых модель скорее всего будет совершать систематические ошибки. По аналогии с детрендингом можно cделать десезонализацию, вычитая сезонную компоненту, и попробовать моделировать только остаток.
Кстати, для анализа и прогнозирования временных рядов есть удобный Python-фреймворк etna. Он позволяет легко применять трансформации к ряду, строить прогнозы и тестировать модели — мы в Точке активно его используем. Для тех, кто работает с time-series и любит sklearn-api, но ещё не пробовал etna – рекомендуем.
Линк на туториалы 🌋
🔥26👍14❤8🍾1
Как упростить оптимизацию гиперпараметров?
В большинстве алгоритмов машинного обучения, чтобы достичь максимального качества, нужно настраивать гиперпараметры. Например:
От правильной настройки гиперпараметров зависит качество обучения модели. Но традиционные методы — например, поиск по сетке на большой выборке — требуют много ресурсов. Например, если в вашем датасете примерно миллиард образцов, то подбор 200 миллионов параметров может занять гораздо больше времени, чем само обучение модели.
Что мы хотим в идеале? Чтобы человек вообще не принимал участие в подборе гиперпараметров. Для этого нужно:
Один из гиперпараметров — это learning rate, который используют в градиентной оптимизации, в частности — в градиентном спуске. Но есть ситуации, когда можно обойтись без него и при этом получить оптимальное качество. Например, в линейной регрессии:
||Xw-Y||^2 + λ||w||^2
В более сложных моделях без методов градиентной оптимизации практические не обойтись. Одним из таких методов — классический градиентный спуск. Это итеративный процесс, где мы спускаемся вдоль антиградиента:
xₜ₊₁ = xₜ - η ∇ f(xₜ),
где η — это размер шага (stepsize)
Подбор η — это целая наука, потому что фиксированный шаг всегда приводит к колебаниям вокруг решения. Поэтому для сходимости использовать уменьшающий шаг, который поможет постепенно приблизиться к решению.
Таким образом, величина оптимального шага в градиентном спуске зависит от:
- Расстояния до решения — если возьмем точку подальше, можем не сойтись.
- Константы Липшица — если не учтём, будем сходиться очень медленно.
Правильная настройка параметров поможет оптимизировать работу алгоритмов и увеличит скорость обучения модели.
В большинстве алгоритмов машинного обучения, чтобы достичь максимального качества, нужно настраивать гиперпараметры. Например:
- Параметр λ в L2 или L1 регуляризации.
- Число соседей в kNN.
- Количество слоёв в нейронных сетях.
От правильной настройки гиперпараметров зависит качество обучения модели. Но традиционные методы — например, поиск по сетке на большой выборке — требуют много ресурсов. Например, если в вашем датасете примерно миллиард образцов, то подбор 200 миллионов параметров может занять гораздо больше времени, чем само обучение модели.
Что мы хотим в идеале? Чтобы человек вообще не принимал участие в подборе гиперпараметров. Для этого нужно:
- Разработать алгоритмы, которые могут автоматически подбирать гиперпараметры.
- Автоматизировать контроль обучения, чтобы не следить за процессом градиентного спуска вручную, а создать алгоритм, который будет сам определять, когда модель вышла на плато или начинает переобучаться.
- Создать методы, которые обеспечивают сходимость на всех датасетах и для всех алгоритмов.
Один из гиперпараметров — это learning rate, который используют в градиентной оптимизации, в частности — в градиентном спуске. Но есть ситуации, когда можно обойтись без него и при этом получить оптимальное качество. Например, в линейной регрессии:
||Xw-Y||^2 + λ||w||^2
В более сложных моделях без методов градиентной оптимизации практические не обойтись. Одним из таких методов — классический градиентный спуск. Это итеративный процесс, где мы спускаемся вдоль антиградиента:
xₜ₊₁ = xₜ - η ∇ f(xₜ),
где η — это размер шага (stepsize)
Подбор η — это целая наука, потому что фиксированный шаг всегда приводит к колебаниям вокруг решения. Поэтому для сходимости использовать уменьшающий шаг, который поможет постепенно приблизиться к решению.
Не забывайте про поведение градиента. Если функция меняется медленно, то шаг должен быть больше, и наоборот. За это отвечает специальная константа, которая называется константой Липшица. Функция f называется G-Липшицевой, если для любых двух точек Х и Y выполнено неравенство G ≥ ||f(x) - f(y)|| / ||x-y||
Таким образом, величина оптимального шага в градиентном спуске зависит от:
- Расстояния до решения — если возьмем точку подальше, можем не сойтись.
- Константы Липшица — если не учтём, будем сходиться очень медленно.
Правильная настройка параметров поможет оптимизировать работу алгоритмов и увеличит скорость обучения модели.
👍25⚡10❤8👎1
Как оценить оптимальный шаг при градиентном спуске?
В прошлом посте мы расписали математически, как минимизировать функцию в евклидовом пространстве. Но на практике мы обычно не знаем расстояние до решения и константу Липшица. Это неизвестные параметры, которые трудно измерить.
Вот один из способов, как можно их оценить:
📌 Приближаем константу Липшица
Константа Липшица очень напоминает норму градиента. Возьмём её как среднее квадратичное этой нормы за T итераций.
📌 Оцениваем расстояние до оптимума
Можем использовать максимальное расстояние от начальной точки до одной из точек, достигнутой во время спуска. Это поможет понять, как далеко мы ушли от исходной позиции.
Но возникает проблема — чтобы подсчитать эти величины, нужно сделать T итераций. Для этого нужен шаг, который ещё не измерили
📌 Используем итеративный подход:
1. Выполняем несколько итераций градиентного спуска с произвольным шагом и собираем статистику для оценки G и расстояния до оптимума: k=2,4,8,16…
2. Задаём общее число подсчётов градиента: T_k ← [B / (2k)]
3. Допустим, у нас есть функция, которая по собранным статистикам возвращает некоторый шаг. Назовём её Root Finding Bisection. Если собранные статистики оказались достаточно хорошими, она вернёт нам близкий к оптимальному шаг, и по нему мы уже окончательно спустимся.
Формулу прикрепим в следующем посте картинкой ⬇️
Так мы можем вычислить среднюю точку из всех достигнутых во время градиентного спуска и получить более точное решение.
Подробнее про создание parameter-free SGD можно почитать тут.
В прошлом посте мы расписали математически, как минимизировать функцию в евклидовом пространстве. Но на практике мы обычно не знаем расстояние до решения и константу Липшица. Это неизвестные параметры, которые трудно измерить.
Вот один из способов, как можно их оценить:
📌 Приближаем константу Липшица
Константа Липшица очень напоминает норму градиента. Возьмём её как среднее квадратичное этой нормы за T итераций.
📌 Оцениваем расстояние до оптимума
Можем использовать максимальное расстояние от начальной точки до одной из точек, достигнутой во время спуска. Это поможет понять, как далеко мы ушли от исходной позиции.
Но возникает проблема — чтобы подсчитать эти величины, нужно сделать T итераций. Для этого нужен шаг, который ещё не измерили
📌 Используем итеративный подход:
1. Выполняем несколько итераций градиентного спуска с произвольным шагом и собираем статистику для оценки G и расстояния до оптимума: k=2,4,8,16…
2. Задаём общее число подсчётов градиента: T_k ← [B / (2k)]
3. Допустим, у нас есть функция, которая по собранным статистикам возвращает некоторый шаг. Назовём её Root Finding Bisection. Если собранные статистики оказались достаточно хорошими, она вернёт нам близкий к оптимальному шаг, и по нему мы уже окончательно спустимся.
Формулу прикрепим в следующем посте картинкой ⬇️
Так мы можем вычислить среднюю точку из всех достигнутых во время градиентного спуска и получить более точное решение.
Подробнее про создание parameter-free SGD можно почитать тут.
🔥16👍10❤6🖕2
Что такое Root Finding Bisection?
Мы определяем минимальный и максимальный шаг и постепенно сужаем диапазон, а именно:
📌 Определяем количество итераций T, которые мы будем выполнять.
📌 Задаём нижнюю и верхнюю границы шагов (η_lo и η_hi).
📌 Пока приближение не станет близким к идеальному, используем геометрическое среднее между этими границами для вычисления нового шага: формулу прикрепим отдельным постом.
Процесс можно представить как модификацию бинарного поиска:
📝 У нас есть отрезок [A, B], в котором предположительно находится корень функции. При этом A — это нижняя граница, B — верхняя.
📝 На каждом шаге вместо среднего арифметического берём среднее геометрическое:
√(A × B).
📝 Продолжаем этот процесс, пока не достигнем идеального шага, который зависит от константы Липшица и расстояния до оптимума.
Таким образом мы получаем хороший шаг, который обеспечит быструю сходимость. Хоть мы и выполняем больше итераций, на практике это не так критично. Ведь мы добавляем всего лишь log(log) к общей сложности, что не будет значительным увеличением.
Такой метод позволяет эффективно находить оптимальные шаги для градиентного спуска и улучшает общую производительность алгоритмов. Это значительно быстрее, чем перебор всех значений по сетке.
Это итеративный подход, который помогает подобрать оптимальный шаг для градиентного спуска.
Мы определяем минимальный и максимальный шаг и постепенно сужаем диапазон, а именно:
📌 Определяем количество итераций T, которые мы будем выполнять.
📌 Задаём нижнюю и верхнюю границы шагов (η_lo и η_hi).
📌 Пока приближение не станет близким к идеальному, используем геометрическое среднее между этими границами для вычисления нового шага: формулу прикрепим отдельным постом.
Процесс можно представить как модификацию бинарного поиска:
📝 У нас есть отрезок [A, B], в котором предположительно находится корень функции. При этом A — это нижняя граница, B — верхняя.
📝 На каждом шаге вместо среднего арифметического берём среднее геометрическое:
√(A × B).
📝 Продолжаем этот процесс, пока не достигнем идеального шага, который зависит от константы Липшица и расстояния до оптимума.
Таким образом мы получаем хороший шаг, который обеспечит быструю сходимость. Хоть мы и выполняем больше итераций, на практике это не так критично. Ведь мы добавляем всего лишь log(log) к общей сложности, что не будет значительным увеличением.
Такой метод позволяет эффективно находить оптимальные шаги для градиентного спуска и улучшает общую производительность алгоритмов. Это значительно быстрее, чем перебор всех значений по сетке.
🔥16👍9🤝5❤1