Релизные головоломки: как собрать несколько улучшений ML-модели в релиз

318f2e1853ef186451c0b57708105f81.jpg

Привет! Меня зовут Юра, я из Лаборатории Speech&NLP Инфраструктуры ИИ Контура. Одна из наших крупных задач — это модель для распознавания речи в текст, которая используется для транскрибации звонков и записей рабочих встреч. Мы постоянно работаем над улучшением качества моделей — предлагаем гипотезы и проводим эксперименты. Но для того, чтобы пользователи увидели результаты наших исследований, их надо доставить в продакшен. А это не так-то просто!

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

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

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

История из жизни:

Однажды в процессе подготовки модели релиза мы не смогли улучшить метрики текущей модели и начали искать проблему. Причина крылась в том, что в целях ускорения проверки отдельные гипотезы проверялись не на релизной версии процесса обучения, а на упрощенной — меньше данных и более короткое обучение. И когда мы скомбинировали 4–5 изменений разом, модель обучилась до худших метрик качества.

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

Ограничиваем срок

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

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

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

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

Назначаем ответственного за релиз

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

Что делает ответственный:

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

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

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

Ответственные меняются каждый квартал, чтобы не терялся навык и busfactor оставался высоким. Если кто-то назначен ответственным в первый раз, то ему даем наставника из тех, кто уже проходил процесс.

Выбираем гипотезы и определяем ожидания

Выбор направления

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

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

Пример направления: «Заменить архитектуру JasperNet на архитектуру Сonformer»

На основании чего можем выбирать направление релиза:

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

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

  • Исходя из наших собственных планов по развитию технологии.

Отбор гипотез

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

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

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

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

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

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

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

Формирование блоков гипотез

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

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

Фиксация ожидаемых метрик

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

  • Если не достигли ожидаемого качества, но получили прирост метрик, то можно согласиться, что этого достаточно и можно идти дальше.

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

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

Обучаем пайплайн модели

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

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

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

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

Чек-лист вопросов для подхода к релизу

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

  • Есть ли тема релиза?

  • Что будем релизить? Собран ли список гипотез? Есть ли срочные/важные?

  • Все ли гипотезы проверены в продовом пайплайне?

  • Разбиты ли гипотезы на блоки?

  • Есть ли новые валидационные датасеты? (Здесь скрывается опасность, так как нужно получить метрики на этапах старого релиза, чтобы ухудшение качества на новом валсете не было неожиданностью)

  • Есть ли ожидания по приросту качества для каждого этапа/блока гипотез?

  • Влезает ли проверка гипотез в отведенное время?

  • Можно ли что-то выкинуть?

  • Есть ли риски того что что-то пойдет не так? Что с этим делать?

  • Необходима ли дополнительная (помимо уже имеющихся правил) проверка модели в продовом окружении?

Call-to-Action

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

© Habrahabr.ru