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

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

Проблема в том, что мы чрезвычайно привязаны к конкретному решению, которое устанавливаем внутри конструктора Model. К счастью, мы знаем решение этой проблемы из принципов проектирования программного обеспечения: буква D в SOLID предназначена для внедрения зависимостей, что (в конечном счете) является просто причудливым названием для параметризации. Мы хотим полностью параметризовать классификатор:

Здесь следует отметить две вещи: во-первых, мы называем classifier бэкэнд-моделью. Это приходит с осознанием того, что мы строим на библиотечном коде: это особый интерфейс для изучения классификаторов scikit. Другое дело, что модели fit и predict по сути являются делегированием серверной части. Мы используем это делегирование для реализации хорошо известного шаблона объектно-ориентированного проектирования: шаблона стратегия. Файл train.py также необходимо изменить, чтобы учесть это изменение.

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

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

При внедрении зависимостей одним из побочных эффектов было открытие кода scikit learn, который находился в пространстве библиотеки, в пространство приложение, а все, что принадлежит пространству приложение, легко модифицировать быстро (помните, что пространство приложения является областью влияния модификаций кода DS). Предоставление этой известной библиотеки DS упрощает процесс исследования. Цена, которую мы за это платим, станет ясно, когда мы применим эту концепцию к объекту Dataset (см. commit). Файлы dataset.py и train.py теперь выглядят так:

В реализации есть некоторые особенности, но давайте проанализируем, как наши абстракции влияют на рабочий процесс исследования. Из всех возможных определений «абстракции» мне больше всего нравится определение Джоэла Спольски: абстракция — это упрощение чего-то гораздо более сложного, что происходит под прикрытием. Мне нравится это определение, потому что оно ясно показывает субъективный характер абстракции: разные люди будут считать разные вещи «слишком сложными», которые должны принадлежать «под одеялом». Когда мы рассматривали саму модель как нечто, что находится под обложкой (в нашей терминологии, «библиотечный» код), фаза исследования была силой, которая последовательно пыталась раскрыть реализацию. В этом есть смысл: хотя с точки зрения разработки программного обеспечения мы можем сказать, что «важно то, что мы классифицируем, то, как мы это делаем, является деталью реализации», с точки зрения DS реализация — это все, что угодно, только не деталь: это самая важная часть.

В той же статье (и, пожалуйста, о, пожалуйста! Прочтите ее, если вы еще этого не сделали), в которой Спольски дает определение абстракции, он сформулировал Закон дырявых абстракций: все нетривиальные абстракции в той или иной степени негерметичны. Хотя это общий закон, и Спольски приводит несколько примеров, в данном случае ситуация еще более экстремальная: у нас есть постоянное стремление (исследование) к утечке абстракций. Наша стратегия подхода производство – это исследование – принять эту реальность и вместо того, чтобы бороться с утечкой, мы пытаемся направить ее в нужное русло. Мы знаем, что абстракции рано или поздно утекут, поэтому выбираем, как это сделать. Это цена, которую мы платим: из-за утечки абстракций API будет слишком широким и относительно неглубоким при взаимодействии с pandas, scikit-learn или любой другой бэкэнд-библиотекой, которую мы используем.

Давайте посмотрим, как этот подход позволяет нам выполнять наши четыре столпа:

Быстрое и простое исследование

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

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

Декларативность и выявление намерений

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

Разумные контрольно-пропускные пункты

В ходе этого развития мы откатили интерфейсы наших методов. Вместо того, чтобы использовать наш собственный класс `Dataset` для соответствия модели, мы решили использовать pandas фреймов данных напрямую. Это несколько спорное решение, которое открывает необходимую гибкость в контрольных точках: мы можем перенести в модель любой pandas кадр данных, созданный любым способом, даже загрузив сырой CSV-файл с диска. Это скользкий путь, и, вероятно, решение зависит как от команды, так и от проблемы, которую мы решаем. Мы должны, по крайней мере, рассмотреть возможность предоставления коду приложения базовых типов¹ вместо пользовательских абстракций. Но бойтесь переусердствовать и впасть в антипаттерн Примитивное одержимость.

Беспрепятственное отслеживание и мониторинг

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

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

¹: Базовые типы в этом контексте включают pandas фреймов данных и т.п.