Релизные головоломки: как собрать несколько улучшений ML-модели в релиз
Привет! Меня зовут Юра, я из Лаборатории Speech&NLP Инфраструктуры ИИ Контура. Одна из наших крупных задач — это модель для распознавания речи в текст, которая используется для транскрибации звонков и записей рабочих встреч. Мы постоянно работаем над улучшением качества моделей — предлагаем гипотезы и проводим эксперименты. Но для того, чтобы пользователи увидели результаты наших исследований, их надо доставить в продакшен. А это не так-то просто!
Сложность в том, что гипотез много — несколько человек в лаборатории постоянно работают над поиском новых вариантов улучшения модели. Гипотеза обычно касается отдельного этапа пайплайна обучения (получение базовой модели, файнтюн, этапы самообучения) и эффективность гипотезы доказывается проверкой на этом этапе.
Делать релиз целой модели для каждой гипотезы нереально — полное обучение всех этапов занимает 2–4 недели и требует участия других ролей для тестирования. Поэтому в релиз попадает сразу несколько гипотез. Но тут еще один подводный камень: они могут начать непредсказуемо влиять друг на друга.
Поэтому, чтобы управлять этой сложностью с релизом гипотез, сэкономить на комбинаторном переборе вариантов и регулярно доставлять улучшения модели до пользователей мы составили для себя свод гигиенических правил. И хотим ими поделиться.
История из жизни:
Однажды в процессе подготовки модели релиза мы не смогли улучшить метрики текущей модели и начали искать проблему. Причина крылась в том, что в целях ускорения проверки отдельные гипотезы проверялись не на релизной версии процесса обучения, а на упрощенной — меньше данных и более короткое обучение. И когда мы скомбинировали 4–5 изменений разом, модель обучилась до худших метрик качества.
Нам удалось распутать клубок и выкатить релиз. Но поиски оказались непростыми, и мы не могли выкатить релиз полгода. После этого решили зафиксировать методологию, которая поможет снижать риски при комбинировании нескольких гипотез.
Ограничиваем срок
Мы решили, что хотим выпускать релиз примерно раз в квартал. Время выступает ограничивающим фактором, который не позволит накопить десяток экспериментов полугодовой давности на сетапах, отставших от основной ветки. Дополнительный плюс — регулярность релизов помогает тренировать навыки, чтобы они не забывались и не утрачивались.
Чаще выкатывать новую версию не получится, потому что полный цикл обучения занимает до месяца, а для согласования релиза нужно участие других ролей: мы просим основных внутренних пользователей сравнить качество новой и старой версии и заапрувить изменения.
Сейчас время в квартале мы делим на две части: обучение релизной модели (до двух месяцев) и релиз модели в продакшен. На каждом из этапов могут возникнуть неожиданные трудности, поэтому мы закладываем бюджет времени на каждую из двух частей. Такое деление помогает более точно спланировать процесс и делает проще отслеживание динамики.
Но мы решили поменять подход и сделаем два процесса вместо одного — отвяжем обучение модели от релиза. В одном квартале будем готовить новую релизную модель, а для подготовленной в прошлом квартале модели только протаскивать ее по релизному циклу. Появится лаг из-за того, что мы релизим результат с разницей в квартал, но зато горячки и стресса у нас будет меньше.
Назначаем ответственного за релиз
Важно выбрать фичалида, который будет чувствовать свою персональную ответственность за релиз и у которого выделяется достаточное время на задачи. Его приоритет в квартале будет на этих задачах.
Что делает ответственный:
Конечно, это делается вместе с командой, но ответственный имеет большое влияние на решение. Из плюсов быть ответственным — ты можешь протолкнуть гипотезы, которые тебе важны. :)
Ответственный решает, что конкретно он делает, сколько времени ему на это понадобится. Если возникают проблемы, решает, как с ними работать, например, копать проблему или выкидывать проблемную гипотезу и идти дальше.
Собирает пайплайн обучения с выбранными гипотезами, контролирует, чтобы обучение не развалилось в процессе. Остальные коллеги занимаются другими задачами и подключаются только при необходимости. Например, если чью-то гипотезу добавили в релиз, и потом в ней нашли какие-то проблемы, то привлекут того, кто проверял гипотезу, чтобы он разъяснил и помог разобраться.
Ответственные меняются каждый квартал, чтобы не терялся навык и busfactor оставался высоким. Если кто-то назначен ответственным в первый раз, то ему даем наставника из тех, кто уже проходил процесс.
Выбираем гипотезы и определяем ожидания
Выбор направления
Не стоит пытаться достичь нескольких целей одновременно: и уменьшить потребление ресурсов модели, и улучшить качество, и поменять формат хранения модели. Если поставленные цели достигаются одним шагом, например, заменой архитектуры модели, замечательно. Но в остальных случаях, чтобы упростить себе жизнь, лучше двигаться итеративно: выбрать одну цель за раз и сосредоточиться на ее достижении.
Мы заранее выбираем и согласуем направление релиза и ожидания того, что и на сколько хотим улучшить. Если договариваемся улучшать качество, то гипотезы про улучшение производительности оставляем на вторую очередь или переносим на следующий квартал. А крупное изменения архитектуры, для упрощения, не смешиваем с побочными улучшениями.
Пример направления: «Заменить архитектуру JasperNet на архитектуру Сonformer»
На основании чего можем выбирать направление релиза:
По запросам от менеджмента на конкретные улучшения. Например, по аналитике проблем или по запросам от пользователей.
Исходя из списка гипотез, которые уже проверены и хочется доставить их результаты до пользователей.
Исходя из наших собственных планов по развитию технологии.
Отбор гипотез
Набираем список гипотез, которые согласуются с выбранным направлением. Чтобы гипотеза была принята в релизный цикл, она должна быть уже проверена в условиях эксперимента, максимально приближенного к прошлому релизу или его части (например, на этапе файнтюна). Это необходимо, чтобы получить достоверные ожидания по приросту качества.
Важно критически подходить к постановке гипотез и заранее проверять их так, чтобы при релизе не было неожиданностей. Поэтому мы приняли решение не использовать упрощенные сетапы (по времени или с ограниченным числом данных), чтобы исключить необходимость допроверки гипотезы во время подготовки финальной модели.
У нас несколько валидационных датасетов (например, звонки менеджеров по продажам и технической поддержки, записи встреч коллег по видеосвязи) и по каждой гипотезе необходимо получить результаты валидации на каждом наборе данных. Ограниченное число данных используем только в начальном этапе работы над гипотезой, если на то есть необходимость.
Если гипотезу проверили на первом этапе длинного пайплайна, то задаем себе вопрос — доказывает ли это, что гипотеза действительно подтверждена и мы можем рассчитывать на ее метрики? Или для этой гипотезы необходима проверка на всём пайплайне?
Иногда список гипотез получается слишком большим для одного релиза, тогда мы приоритезируем и фильтруем его на основании здравого смысла и паучьего чутья. Например, можем отбирать по оценке прироста качества от гипотезы или повысить приоритет улучшения, в котором заинтересованы пользователи или менеджеры. А если по оценке ответственного какие-то гипотезы несут дополнительные риски или их результаты кажутся неустойчивыми — понижаем приоритет гипотез или не берем их в релиз.
Пример важной для пользователя гипотезы:
Выявили проблему с недостаточно качественным распознавание имен и отчеств. Собрали датасет под эту проблему и планируем доиспользовать его в обучении.
Формирование блоков гипотез
Для каждой гипотезы нереально проводить полный цикл обучения, потому что такой подход займет слишком много времени — обучение всего пайплайна занимает несколько недель. Но и слишком много гипотез опасно сразу добавлять в обучение, потому что в случае проблем будет очень сложно раскопать, в каких гипотезах или их сочетаниях что-то идет не так.
Поэтому мы группируем гипотезы в блоки по срочности/важности/проверенности. Обычно в блок берем не более трех гипотез. Для каждого блока мы проводим полный цикл обучения модели, поэтому не требуется проверять полную комбинаторику того, как согласуются друг с другом все гипотезы. А если проблемы все-таки случатся и придется копать, то надо будет думать об ограниченном числе вещей.
Фиксация ожидаемых метрик
У нас есть ожидания по приросту качества от каждой из гипотез, потому что каждая гипотеза перед принятием ее в релизный цикл уже проверена в условиях эксперимента, максимально приближенного к прошлому релизу или его части (например на этапе файнтюна). Зафиксированные ожидания важны, так как если они разошлись с фактом, то это флаг того, что что-то идет не так. В этом случае для ответственного появляется развилка:
Если не достигли ожидаемого качества, но получили прирост метрик, то можно согласиться, что этого достаточно и можно идти дальше.
Если метрики слишком сильно отличаются от запланированных, то можно покопать проблему. Для таких ситуаций мы заранее закладываем бюджет времени. Но здесь возникает элемент случайности, потому что непонятно в чем проблема и сколько времени займет ее исправление — может снова начаться исследование в рамках релиза. Поэтому важно сразу установить, сколько мы готовы потратить на попытки исправить проблему.
Если метрики слишком сильно отличаются от запланированных, то ответственный имеет право решить, что какую-то гипотезу проще исключить из релиза и перезапустить обучение без нее.
Обучаем пайплайн модели
После того, как гипотезы выбраны и разделены на блоки, переходим к обучению и работаем пошагово.
Проводим обучение одного этапа. Этап/прогон — часть пайплайна обучения: получение базовой модели, ее файнтюн, этапы самообучения (selftrain). В одном прогоне могут участвовать несколько проверенных гипотез из блока. Мы считаем, что все хорошо, если суммарный прирост от комбинации гипотез оказался лучше, чем самая лучшая гипотеза в отдельности. Если метрики соответствуют ожиданиями, то переходим к следующему этапу обучения.
Так мы проводим обучение всего пайплайна и получаем релиз-кандидат, который потенциально можно катить. Переходим к следующему блоку гипотез, выполняем те же шаги и получаем нового кандидата на релиз. Проверяем, пока не закончатся блоки гипотез или отведенное время.
За счет промежуточных кандидатов на релиз повышаем шансы, что вообще сможем что-то новое выкатить.
Чек-лист вопросов для подхода к релизу
Бонусом привожу чек-лист вопросов. Он помогает новичкам, первый раз отвечающим за релиз, не пропустить важные пункты, каждый из которых — плод собранных нами граблей:
Есть ли тема релиза?
Что будем релизить? Собран ли список гипотез? Есть ли срочные/важные?
Все ли гипотезы проверены в продовом пайплайне?
Разбиты ли гипотезы на блоки?
Есть ли новые валидационные датасеты? (Здесь скрывается опасность, так как нужно получить метрики на этапах старого релиза, чтобы ухудшение качества на новом валсете не было неожиданностью)
Есть ли ожидания по приросту качества для каждого этапа/блока гипотез?
Влезает ли проверка гипотез в отведенное время?
Можно ли что-то выкинуть?
Есть ли риски того что что-то пойдет не так? Что с этим делать?
Необходима ли дополнительная (помимо уже имеющихся правил) проверка модели в продовом окружении?
Call-to-Action
Как вам наша схема работы — расскажите, какие пробелы и возможности для улучшений вы в ней видите? И как вы собираете релизы из большого количества гипотез?