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

«Модуляризация» кода в проектах машинного обучения - это сложная задача.

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

Однако я не предлагаю перейти к противоположному принципу «Не повторяйся». Я видел очень беспорядочные и неорганизованные каталоги блокнотов Jupyter. Однако мы должны стремиться понять, какие компоненты следует использовать повторно. В этой статье я выделю компоненты, которые, как правило, повторно используются в проекте машинного обучения, основываясь на моем опыте предварительной обработки и моделирования данных в течение 2 лет + с использованием записных книжек Jupyter.

Прежде всего, почему мы используем Jupyter?

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

Это итеративный процесс. Чем быстрее вы пройдете этот цикл, тем быстрее вы продвинетесь.

- Эндрю Нг, "Тоска по машинному обучению"

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

Обзор

Вот обзор того, что мы собираемся осветить в этом посте:

  1. Помогает наличие «малых данных» - зачем и как использовать небольшой набор данных при написании кода.
  2. Использование git - как использовать git для управления версиями ваших записных книжек.
  3. Разделение проблем - как структурировать каталог файлов Jupyter.
  4. Блокнот для предварительной обработки, моделирования и блокнот для отчетов - здесь мы говорим о том, как структурировать 3 блокнота и что включить в блокноты.
  5. The MASTER Notebook - как вызывать другие записные книжки из записной книжки и как регистрировать вывод

Наличие небольшого объема данных помогает

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

Например, если мы надеемся обучить модель на 10 миллионах изображений, попробуйте выбрать только 50 изображений для каждого класса для написания кода. Или, если мы обучаем 100 миллионов строк данных о продажах, мы можем попробовать выбрать 2000 строк данных о продажах в качестве «небольших данных».

Насколько большими должны быть «мелкие данные», зависит от репрезентативности выборки и времени ее обработки. Что касается выборки, постарайтесь получить не менее 5 образцов на класс. С точки зрения времени, эмпирическое правило гласит, что время, необходимое для того, чтобы «небольшие данные» перешли от обработки данных до завершения обучения модели, должно выполняться менее чем за 10 минут.

Вы можете использовать следующий код в начале записной книжки для включения и выключения SMALL_DATA_MODE.

SMALL_DATA_MODE = True
if SMALL_DATA_MODE:
    DATA_FILE = "path/to/smallData.csv"
else:
    DATA_FILE = "path/to/originalData.csv"

Используя git

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

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

Чтобы установить git,

В Windows перейдите на https://git-scm.com/download/win.

В macOS запустите git --version в терминале. Если вы не установили git, вам будет предложено установить его.

В Linux Ubuntu запустите sudo apt install git-all

После установки запустите в каталоге вашего проекта следующее:

git init

Также давайте укажем файлы, которые мы не хотим отслеживать с помощью git. Создайте новый файл с именем .gitignore и поместите в него следующие тексты. Мы собираемся игнорировать контрольные точки Jupyter, кеш Python и каталог данных.

.ipynb_checkpoints/
data/
__pycache__/

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

git add .
git commit -m "Give a clear message here on what's changing compared to last time you commit"
# remember to set a remote named `origin` for this:
git push origin master

Разделение проблем

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

Это моя любимая файловая структура:

  • data/ - сегмент хранения для всех видов данных (сырые, предварительно обработанные и т. Д.)
  • exp/ - сюда выводятся результаты экспериментов (сохраненные модели + фактические и прогнозируемые метки)
  • logs/ - просто место для наших файлов журналов от предварительной обработки данных и моделирования
  • a_MASTER0.ipynb — «главный» блокнот Jupyter, который может вызывать другие «подчиненные» блокноты (предварительная обработка, моделирование, отчетность). В следующем разделе мы покажем, как вызвать другой блокнот из блокнота.
  • a_MASTER1.ipynb - Еще одна «мастерская» записная книжка для параллельного проведения еще одного эксперимента. Вы можете добавить столько основных записных книжек, сколько вам нужно.
  • b_preprocess.ipynb - Блокнот предварительной обработки, который принимает необработанные данные из data/raw и выводит данные в data/{dir}
  • c_model_svm.ipynb - Эта записная книжка принимает выходные данные предварительной обработки, вносит небольшие изменения, чтобы соответствовать модели SVM, затем выводит результаты моделирования (например, параметры изученной модели, прогнозы и т. Д.) В exp/.
  • c_model_randomForest.ipynb — Если у вас есть другая модель, просто назовите ее так.
  • d_reporting.ipynb — Это будет читать из exp/ и строить таблицы или визуальные элементы для вашего отчета.

Блокнот предварительной обработки

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

# PARAMETER
#-------------
# check if IS_MASTER exists, this variable will only exist if it's being called by MASTER notebook.
# if it does not exist, set it to False
try: IS_MASTER
except: IS_MASTER = False
# The code below will only run if it's NOT being called from MASTER notebook
if IS_MASTER:
    DATA_DIR = './data/temp/' # 
    RAW_FILE = f'/path/to/smallData.csv' # use "small data" here
    PROCESSED_FILE = f'{DATA_DIR}processed.pkl' # always use pickle for fast I/O!
    OTHER_PREPROCESS_PARAMETER = ... # e.g. batch size, sliding window size, etc

Приведенный выше код устанавливает параметры по умолчанию для нашей записной книжки. Мы будем использовать только временный каталог TMP_DIR (в каталоге data/) для хранения нашего вывода. Это необходимо для быстрой итерации. Когда мы пишем код, мы всегда должны использовать «небольшие данные».

Вы можете продолжить кодирование части предварительной обработки. Поскольку вы используете «небольшие данные» на этом этапе «разработки», вы должны действовать быстро! Когда вы закончите предварительную обработку, не забудьте вывести файл Pickle с помощью pickle библиотеки:

import pickle
with open(PROCESSED_FILE, 'wb') as f:
    pickle.dump(python_object, f)

Или используйте ярлык pandas ’:

df.to_pickle(PROCESSED_FILE)

Мы используем Pickle вместо формата CSV для сохранения и быстрого чтения и записи. Этот PROCESSED_FILE будет прочитан The Modeling Notebook в следующей части.

Блокнот для моделирования

Начнем наш модельный блокнот с этого:

# PARAMETER
#-------------
# check if IS_MASTER exists, this variable will only exist if it's being called by MASTER notebook.
# if it does not exist, set it to False
try: IS_MASTER
except: IS_MASTER = False
# The code below will only run if it's NOT being called from MASTER notebook
if IS_MASTER:
    DATA_DIR = './data/temp/'
    EXP_DIR = './exp/temp/'
    PROCESSED_FILE = f'{DATA_DIR}processed.pkl'
    MODEL_FILE = f'{EXP_DIR}model.pkl'
    PREDICTION_FILE = f'{EXP_DIR}ypred.pkl'
    OTHER_MODEL_PARAMETERS = ... # like N_ESTIMATOR, GAMMA, etc

Обратите внимание, что DATA_DIR и PROCESSED_FILE - то же самое, что и предыдущий вывод Preprocessing Notebook, и подключены к нему.

В этом Блокноте для моделирования вы должны сделать 3 вещи (здесь не показаны, поскольку они будут разными для каждой модели):

  1. Прочтите обработанные данные и внесите небольшие изменения, чтобы они соответствовали модели.
  2. Обучите и оцените модель
  3. Выведите изученный параметр модели MODEL_FILE и прогноз PREDICTION_FILE в EXP_DIR каталог. Для вывода прогнозов поместите как фактические, так и прогнозируемые метки в один и тот же фрейм данных (проще для составления отчетов).

Блокнот для отчетности

Записная книжка отчетов - это быстро, ее просто нужно читать из каталога exp/. Его вход подключен к выходу Блокнота моделирования через EXP_DIR, MODEL_FILE и PREDICTION_FILE.

# PARAMETER
#-------------
# check if IS_MASTER exists, this variable will only exist if it's being called by MASTER notebook.
# if it does not exist, set it to False
try: IS_MASTER
except: IS_MASTER = False
# The code below will only run if it's NOT being called from MASTER notebook
if IS_MASTER:
    EXP_DIR = './exp/temp/'
    MODEL_FILE = f'{EXP_DIR}model.pkl'
    PREDICTION_FILE = f'{EXP_DIR}ypred.pkl'

Здесь вы берете предсказанные метки и сравниваете их с фактическими метками. Здесь вы можете использовать такие показатели, как «Точность», «Отзыв» или «ROC AUC». Здесь вы также можете запустить коды для графиков и графиков.

Блокнот MASTER

Наконец-то МАСТЕР Ноутбук!

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

Мастер-записная книжка - это также место, где вы вызываете другие (хорошо протестированные) записные книжки предварительной обработки и моделирования для запуска реальных «больших данных». Я также представлю трюк с журналированием (потому что, эй, если «большие данные» получили ошибку, мы хотим знать, почему).

Мы также будем использовать здесь волшебную команду Jupyter %run.

Сначала создайте файл с именем print_n_log.py и вставьте в него приведенный ниже код:

"""
Returns a modified print() method that returns TEE to both stdout and a file
"""
import logging
def run(logger_name, log_file, stream_level='ERROR'):
    stream_level = {
        'DEBUG': logging.DEBUG,
        'INFO': logging.INFO,
        'WARNING': logging.WARNING,
        'ERROR': logging.ERROR,
        'CRITICAL': logging.CRITICAL,
    }[stream_level]
    
    # create logger with 'logger_name'
    logger = logging.getLogger(logger_name)
    logger.setLevel(logging.DEBUG)
    # create file handler which logs even debug messages
    fh = logging.FileHandler(log_file)
    fh.setLevel(logging.DEBUG)
    # create console handler with a higher log level
    ch = logging.StreamHandler()
    ch.setLevel(stream_level)
    # create formatter and add it to the handlers
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    fh.setFormatter(formatter)
    ch.setFormatter(formatter)
    # add the handlers to the logger
    logger.addHandler(fh)
    logger.addHandler(ch)
    def modified_print(*args):
        s = ' '.join([str(a) for a in args])
        logger.info(s)
    return modified_print

Приведенный выше код создаст модифицированный метод print(), который перенаправляет вывод (stdout) и ошибку (stderr) как на вывод ячейки Master Notebook, так и на файл журнала.

Затем импортируйте этот модуль в свой мастер-блокнот:

import print_n_log

В следующей ячейке попробуем вызвать Блокнот предварительной обработки:

# Parameters for Preprocessing Notebook
#---------------------------------------------
IS_MASTER = True # Remember this? We need to set this to True in MASTER Notebook so that it does not use the default parameters in processing notebook.
RAW_FILE = f'/path/to/smallData.csv' # use "small data" here
PROCESSED_FILE = f'{DATA_DIR}processed.pkl' # always use pickle for fast I/O!
OTHER_PREPROCESS_PARAMETER = ... # like batch size, sliding
# Let's save the original print method in ori_print
#---------------------------------------------------
ori_print = print
# Now we set the print method to be modified print
#--------------------------------------------------
print = print_n_log.run('preproc', './logs/preprocess.log', 'DEBUG')
# Now, we run the Preprocessing Notebook using the %run magic
#-------------------------------------------------------------
%run 'c_preprocess.ipynb'
# Finally, after running notebook, we set the print method back to the original print method.
#-----------------------------------------------------
print = ori_print

Обратите внимание, что мы используем %run магию для запуска Блокнота предварительной обработки. Это магия IPython, которая позволяет нам запускать другие файлы python и записные книжки Jupyter из нашей текущей записной книжки. Мы используем эту команду для запуска всех кодов в Блокноте предварительной обработки.

Вызывая print_n_log.run('preproc', './logs/preprocess.log', 'DEBUG'), мы изменяем исходный встроенный в Python метод print() для перенаправления вывода как на экран, так и в файл журнала. 'preproc' - это просто имя нашего регистратора, вы можете использовать любое другое имя. После запуска вы можете перейти к './logs/preproc.log', чтобы увидеть зарегистрированный вывод при запуске Preprocessing Notebook. Последний параметр 'DEBUG' просто говорит «печатать каждый вывод на экран». Вы также можете использовать 'ERROR', если хотите видеть только ошибки на экране (без вывода).

И да, вот и все! Вы можете использовать тот же шаблон для вызова Блокнота моделирования и Блокнота отчетов. Тем не менее, просто совет: вы можете не захотеть регистрировать вывод Reporting Notebook, поэтому вы можете использовать для этого исходный метод print ().

Заключение

Ключевая идея здесь состоит в том, чтобы «разбить на модули» части записной книжки Jupyter, которые не связаны с другими частями, и в то же время иметь обзор всего конвейера данных.

В отличие от программной философии инкапсуляции, мы на самом деле не хотим инкапсулировать наш код в файлы Python и не видеть их снова (хотя мы можем сделать это для некоторых служебных методов). Мы хотим сохранить большую часть кода в записных книжках, потому что при разработке эксперимента каждая часть должна быть изменяемой. Когда деталь изменяется, мы также хотим сделать ее тестируемой. Оставаясь в записной книжке Jupyter, мы знаем, что наш код можно тестировать, когда мы запускаем его с «небольшими данными». Следуя этим рекомендациям, мы получим проект Jupyter, который будет организован и разбит на модули, но при этом каждая его часть легко редактируется и тестируется.

Чтобы получать уведомления о моих публикациях, подпишитесь на меня на Medium, Twitter или Facebook.