Введение в использование Enums в Python для проектов ML

Обзор вашего путешествия

  1. "Введение"
  2. Наивный подход
  3. Энумы спешат на помощь!
  4. Подведение итогов

1. Введение

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

  • Гиперпараметры. В машинном обучении гиперпараметры используются для обучения нескольких моделей с небольшими вариациями. Гиперпараметром может быть количество деревьев в случайном лесу. Другим примером является параметр релаксации для регрессии Риджа или Лассо.
  • Информация о доступе. При подключении к базе данных или облачной учетной записи хранения требуется информация о доступе. Сюда входят имя, пароль и другие дополнительные свойства системы хранения. Точно так же предположим, что вы подписываетесь на публикацию/подсистему, такую ​​как Kafka или брокер MQTT. Затем имя темы, пароль и номера портов являются необходимым кодом конфигурации.
  • Информация о конвейере. При создании конвейера обработки данных необходим код конфигурации. Это могут быть соотношения для разделения тестовых и обучающих наборов или выражение CRON для определения времени запуска конвейера.

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

Часто имеет смысл хранить код конфигурации в отдельных файлах (в таких форматах, как .env, .yaml или .json). Тем не менее, этот код конфигурации по-прежнему должен быть доступен в коде Python. Есть много способов организовать код конфигурации в ваших скриптах Python 😕

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

2. Наивный подход

Прежде чем мы углубимся в то, как Python Enums может сделать код конфигурации более читабельным, давайте сначала рассмотрим наивный подход, который часто используется. Одним из распространенных способов хранения информации о конфигурации является использование глобальных переменных. Допустим, у вас есть модель машинного обучения, которая использует RandomForestClassifier из scikit-learn. Вы хотите сохранить количество деревьев в глобальной переменной с именем N_ESTIMATORS. Это в сочетании с именем параметра, которое принимает RandomForestClassifier. Вы можете определить эту переменную в верхней части нашего скрипта следующим образом:

# Configuration for RandomForestClassifier
N_ESTIMATORS = 100

С единственным параметром конфигурации это не проблема. Однако давайте представим еще несколько:

# Configuration for RandomForestClassifier
N_ESTIMATORS = 100
MAX_DEPTH = 8
N_JOBS = 3

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

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

# Configuration for RandomForestClassifier
N_ESTIMATORS = 100
MAX_DEPTH = 8
N_JOBS = 3

# Configuration for AdaBoostClassifier
N_ESTIMATORS = 50
LEARNING_RATE = 1.0

Вы видите, что произошло? Вы случайно перезаписали глобальную переменную N_ESTIMATORS! Хотя здесь это легко заметить, представьте, что скрипт Python состоит из 500 строк и написан кем-то другим. Возможно, это ускользнуло бы от вас. Python, вероятно, не дал бы никаких указаний на то, что что-то не так. Это не похоже на то, что установка N_ESTIMATORS = 50 для RandomForestClassifier дает синтаксическую ошибку 😧

Как бы вы решили это? Вы можете ввести расширенное имя, указывающее, к какой модели принадлежит глобальная переменная N_ESTIMATORS:

# Configuration for RandomForestClassifier
N_ESTIMATORS_RANDOM_FOREST_CLASSIFIER = 100
MAX_DEPTH = 8
N_JOBS = 3

# Configuration for AdaBoostClassifier
N_ESTIMATORS_ADA_BOOST_CLASSIFIER = 50
LEARNING_RATE = 1.0

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

# Configuration for RandomForestClassifier
N_ESTIMATORS_RANDOM_FOREST_CLASSIFIER = 100
MAX_DEPTH_RANDOM_FOREST_CLASSIFIER = 8
N_JOBS_RANDOM_FOREST_CLASSIFIER = 3

# Configuration for AdaBoostClassifier
N_ESTIMATORS_ADA_BOOST_CLASSIFIER = 50
LEARNING_RATE_ADA_BOOST_CLASSIFIER = 1.0

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

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

Нет ли лучшего способа справиться с кодом конфигурации в Python?

3 — Enums спешат на помощь!

Python Enums предоставляет более элегантное решение для хранения информации о конфигурации. Перечисления (сокращение от enumerations) — это, по сути, способ определения набора именованных констант. Лучший способ быстро понять Enums — адаптировать код конфигурации машинного обучения из предыдущего раздела. Предыдущий код, который у вас был:

# Configuration for RandomForestClassifier
N_ESTIMATORS_RANDOM_FOREST_CLASSIFIER = 100
MAX_DEPTH_RANDOM_FOREST_CLASSIFIER = 8
N_JOBS_RANDOM_FOREST_CLASSIFIER = 3

# Configuration for AdaBoostClassifier
N_ESTIMATORS_ADA_BOOST_CLASSIFIER = 50
LEARNING_RATE_ADA_BOOST_CLASSIFIER = 1.0

Теперь это можно записать как:

from enum import Enum

class RandomForest(Enum):
  """An Enum for tracking the configuration of random forest classifiers."""
  N_ESTIMATORS = 100
  MAX_DEPTH = 8
  N_JOBS = 3

class AdaBoost(Enum):
  """An Enum for tracking the configuration of Ada boost classifiers."""
  N_ESTIMATORS = 50
  LEARNING_RATE = 1.0

Некоторые преимущества

Давайте быстро посмотрим, какие преимущества дает нам Python Enums:

  • Каждый параметр (например, MAX_DEPTH) теперь хранится иерархически в модели, для которой он используется. Это гарантирует, что при введении дополнительного кода конфигурации ничего не будет перезаписано. Следовательно, также нет необходимости в слишком длинных именах переменных.
  • Различные параметры, используемые в RandomForestClassifier, теперь сгруппированы в RandomForest Enum. Таким образом, их можно повторять и коллективно анализировать на безопасность типов.
  • Поскольку перечисления являются классами, они могут иметь строки документации, как показано выше. Хотя это может быть не обязательно для данного примера, в других примерах это может прояснить, на что ссылается перечисление. Гораздо лучше иметь это в виде строки документации, связанной с классом, а не в свободном комментарии. Во-первых, программное обеспечение для автоматического документирования теперь определит, что строка документации принадлежит перечислению. Для свободного комментария это, вероятно, было бы просто потеряно.

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

from sklearn.ensemble import RandomForestClassifier

RandomForestClassifier(
  n_estimators=RandomForest.N_ESTIMATORS.value,
  max_depth=RandomForest.MAX_DEPTH.value,
  n_jobs=RandomForest.N_JOBS.value
)

Это выглядит аккуратно и легко читается 😍

Некоторые возможности Enums для Python

Давайте проиллюстрируем некоторые простые возможности перечислений Python на игрушечном примере:

from enum import Enum
class HTTPStatusCodes(Enum):
    """An Enum that keeps track of status codes for HTTP(s) requests."""
    OK = 200
    CREATED = 201
    BAD_REQUEST = 400
    NOT_FOUND = 404
    SERVER_ERROR = 500

Ясно, что информация, содержащаяся в перечислении, связана; все они касаются кодов состояния HTTP(s). Учитывая это, теперь вы можете использовать следующий простой код для извлечения имен и значений:

print(HTTPStatusCodes.OK.name)
>>> OK

print(HTTPStatusCodes.OK.value)
>>> 200

Python Enums также позволяет вам вернуться назад: учитывая, что значение кода состояния равно 404, вы можете найти кодовое имя состояния, просто написав:

print(HTTPStatusCodes(200).name)
>>> OK

Вы можете совместно работать с парами имен/значений в Enum, например, используя конструктор списка list():

print(list(HTTPStatusCodes))
>>> [
  <HTTPStatusCodes.OK: 200>, 
  <HTTPStatusCodes.CREATED: 201>, 
  <HTTPStatusCodes.BAD_REQUEST: 400>, 
  <HTTPStatusCodes.NOT_FOUND: 404>,
  <HTTPStatusCodes.SERVER_ERROR: 500>
]

Наконец, вы можете собирать и распаковывать перечисления в Python. Для этого просто используйте модель pickle так же, как и с другими знакомыми объектами в Python:

from pickle import dumps, loads
print(HTTPStatusCodes is loads(dumps(HTTPStatusCodes)))
>>> True

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

Конфиденциальный код конфигурации

При использовании конфиденциального кода конфигурации (например, паролей или ключей доступа) НИКОГДА не прописывайте их явно в коде. Они должны быть в отдельном файле, который игнорируется используемой системой контроля версий и импортируется в скрипт.

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

from enum import Enum
import os

class StorageAccount(Enum):
  ACCOUNT_NAME = "my account name"
  ACCESS_KEY = os.environ.get('ACCESS_KEY')
  CONTAINER_NAME = "my container name"

Здесь перечисление StorageAccount извлекает ACCESS_KEY из переменной среды. Это может быть установлено, например, в файле .env. Обратите внимание, что в скрипте Python конфиденциальная информация не раскрывается. Тем не менее вся информация об учетной записи хранения аккуратно организована в виде перечисления.

4 — Подведение итогов

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

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

Понравилось, что я написал? Ознакомьтесь с другими моими публикациями, чтобы узнать больше о Python: