Лучшие практики для превращения jupyter-ноутбуков в скрипты python
Jupyter (iPython) ноутбук заслуженно известен как хороший инструмент для прототипирования кода и интерактивного использования всех видов машинного обучения. Но когда я его использую, я неизбежно сталкиваюсь с следующим:
- ноутбук быстро становится слишком сложным и беспорядочным, чтобы его поддерживали и улучшали как записную книжку, и я должен сделать из него скрипты python;
- когда дело доходит до производственного кода (например, тот, который нужно повторно запускать каждый день), ноутбук снова не самый лучший формат.
Предположим, что я разработал конвейер для машинного обучения в jupyter, который включает в себя сбор исходных данных из разных источников, чистку данных, разработку функций и обучающих моделей. Теперь, какая лучшая логика для создания скриптов с помощью эффективного и понятного кода? До сих пор я использовал несколько способов:
-
Просто конвертируйте .ipynb в .py и, только с небольшими изменениями, скопируйте весь конвейер из ноутбука в один python script.
- '+': быстрый
- '-': грязный, негибкий, не удобно поддерживать
-
Сделайте одиночный script со многими функциями (приблизительно, 1 функция для каждой одной или двух ячеек), пытаясь включить этапы конвейера с отдельными функциями и назовите их соответственно. Затем укажите все параметры и глобальные константы через argparse
.
- '+': более гибкое использование; более читаемый код (если вы правильно преобразовали логику конвейера в функции)
- '-': часто трубопровод НЕ расщепляется на логически завершенные части, которые могут стать функциями без каких-либо причуд в коде. Все эти функции обычно необходимо вызывать только один раз в script, а не много раз повторяться внутри циклов, карт и т.д. Кроме того, каждая функция обычно выводит выходные данные всех функций, называемых ранее, поэтому приходится передавать много аргументов каждой функции.
-
То же самое, что и точка (2), но теперь оберните все функции внутри класса. Теперь все глобальные константы, а также выходы каждого метода могут быть сохранены как атрибуты класса.
- '+': вам не нужно передавать много аргументов каждому методу - все предыдущие выходы, уже сохраненные как атрибуты
- '-': общая логика задачи все еще не захвачена - это конвейер данных и машинного обучения, а не только класс. Единственная цель для класса должна быть создана, вызывать все методы последовательно один за другим, а затем удаляться. Кроме того, классы довольно долго реализуются.
-
Преобразование ноутбука в модуль python с несколькими скриптами. Я не пробовал это, но я подозреваю, что это самый длинный способ справиться с этой проблемой.
Я полагаю, эта общая настройка очень распространена среди ученых-ученых, но на удивление я не могу найти полезные советы.
Люди, пожалуйста, поделитесь своими идеями и опытом. Вы когда-нибудь сталкивались с этой проблемой? Как вы справились с этим?
Ответы
Ответ 1
У нас подобная проблема. Однако мы используем несколько ноутбуков для прототипирования результатов, которые должны стать также несколькими скриптами python.
Наш подход заключается в том, что мы откладываем код, который швы повторяются через эти ноутбуки. Мы помещаем его в модуль python, который импортируется каждым ноутбуком, а также используется в производстве. Мы итерационно совершенствуем этот модуль непрерывно и добавляем тесты того, что мы находим во время прототипирования.
Ноутбуки становятся скорее похожими на сценарии конфигурации (которые мы просто копируем в конечные файлы python) и несколько проверок и проверок прототипов, которые нам не нужны в производстве.
Больше всего мы не боимся рефакторинга:)
Ответ 2
Спасатель жизни: когда вы пишете свои записные книжки, постепенно реорганизуйте свой код в функции, написав несколько минимальных assert
и строк документации.
После этого рефакторинг от ноутбука к скрипту становится естественным. Мало того, это облегчает вашу жизнь при написании длинных тетрадей, даже если вы не планируете превращать их во что-то еще.
Базовый пример содержимого ячейки с "минимальными" тестами и строками документов:
def zip_count(f):
"""Given zip filename, returns number of files inside.
str -> int"""
from contextlib import closing
with closing(zipfile.ZipFile(f)) as archive:
num_files = len(archive.infolist())
return num_files
zip_filename = 'data/myfile.zip'
# Make sure 'myfile' always has three files
assert zip_count(zip_filename) == 3
# And total zip size is under 2 MB
assert os.path.getsize(zip_filename) / 1024**2 < 2
print(zip_count(zip_filename))
После того как вы экспортировали его в файлы .py
, ваш код, вероятно, еще не будет структурирован в классы. Но стоит потратить усилия на то, чтобы провести рефакторинг вашей записной книжки до такой степени, чтобы она имела набор документированных функций, каждая с набором простых assert
которые можно легко переместить в tests.py
для тестирования с помощью pytest
, unittest
или чем-то еще. вы. Если это имеет смысл, объединить эти функции в методы для ваших классов будет непросто после этого.
Если все идет хорошо, все, что вам нужно сделать после этого, это написать свой if __name__ == '__main__':
и его "ловушки": если вы пишете скрипт для вызова терминалом, вы захотите обработать команду- строковые аргументы: если вы пишете модуль, вам нужно подумать о его API с файлом __init__.py
и т.д.
Разумеется, все зависит от предполагаемого варианта использования: существует большая разница между преобразованием ноутбука в небольшой скрипт и превращением его в полноценный модуль или пакет.
Вот несколько идей для документооборота:
- Экспортируйте блокнот Jupyter в файл Python (.py) через графический интерфейс.
- Удалите "вспомогательные" строки, которые не выполняют реальной работы: операторы
print
, графики и т.д. - Если нужно, объедините свою логику в классы. Единственная дополнительная работа по рефакторингу должна заключаться в написании строк документации и атрибутов вашего класса.
- Напишите ваш сценарий для ввода с помощью
if __name__ == '__main__'
. - Отдельные операторы
assert
для каждой из ваших функций/методов и tests.py
минимальный набор тестов в tests.py
.
Ответ 3
Недавно я сделал модуль (NotebookScripter), чтобы помочь решить эту проблему. Это позволяет вам вызывать блокнот Jupyter с помощью вызова функции. Его так же просто использовать как
from NotebookScripter import run_notebook
run_notebook("./path/to/Notebook.ipynb", some_param="Provided Exteranlly")
Параметры ключевых слов могут быть переданы в вызов функции. Его легко адаптировать, чтобы его можно было настраивать с внешней стороны.
Внутри клетки.ipynb
from NotebookScripter import receive_parameter
some_param = receive_parameter(some_param="Return this value by default when matching keyword not provided by external caller")
print("some_param={0} within the invocation".format(some_param))
run_notebook() поддерживает файлы.ipynb или.py, что позволяет легко использовать файлы.py, которые могут быть сгенерированы с помощью nbconvert vscode ipython. Вы можете сохранить свой код организованным таким образом, который имеет смысл для интерактивного использования, а также повторно использовать/настраивать его внешне, когда это необходимо.