簡體   English   中英

將 jupyter notebooks 轉為 python 腳本的最佳實踐

[英]Best practices for turning jupyter notebooks into python scripts

Jupyter (iPython) notebook 是當之無愧地被稱為原型代碼和交互地執行各種機器學習東西的好工具。 但是當我使用它時,我不可避免地會遇到以下情況:

  • 筆記本很快變得過於復雜和凌亂,無法作為筆記本進一步維護和改進,我必須用它制作 python 腳本;
  • 當涉及到生產代碼(例如需要每天重新運行的代碼)時,筆記本又不是最好的格式。

假設我已經在 jupyter 中開發了一個完整的機器學習管道,其中包括從各種來源獲取原始數據、清理數據、特征工程和訓練模型。 現在用高效可讀的代碼制作腳本的最佳邏輯是什么? 到目前為止,我曾經通過幾種方式解決它:

  1. 只需將 .ipynb 轉換為 .py,只需稍作更改,即可將筆記本中的所有管道硬編碼為一個 Python 腳本。

    • '+':快
    • '-':臟,不靈活,不方便維護
  2. 制作一個包含多個函數的腳本(大約,每個一兩個單元格一個函數),嘗試用單獨的函數組成管道的各個階段,並相應地命名它們。 然后通過argparse指定所有參數和全局常量。

    • '+':使用更靈活; 更具可讀性的代碼(如果您正確地將管道邏輯轉換為函數)
    • '-':通常,管道不能拆分成邏輯上完整的部分,這些部分可以成為代碼中沒有任何怪癖的功能。 所有這些函數通常只需要在腳本中調用一次,而不是在循環、映射等中多次調用。此外,每個函數通常都需要之前調用的所有函數的輸出,因此必須將許多參數傳遞給每個函數功能。
  3. 與第 (2) 點相同,但現在將所有函數包裝在類中。 現在所有全局常量以及每個方法的輸出都可以存儲為類屬性。

    • '+':你不需要向每個方法傳遞很多參數——所有之前的輸出都已經存儲為屬性
    • “-”:任務的整體邏輯仍未捕獲——它是數據和機器學習管道,而不僅僅是類。 類的唯一目標是創建,依次調用所有方法,然后刪除。 最重要的是,類的實現時間很長。
  4. 使用多個腳本將筆記本轉換為 python 模塊。 我沒有嘗試過,但我懷疑這是解決問題的最長方法。

我想,這種整體設置在數據科學家中很常見,但令人驚訝的是,我找不到任何有用的建議。

親們,請分享您的想法和經驗。 你有沒有遇到過這個問題? 你是怎么解決的?

救命稻草:在編寫筆記本時,逐步將代碼重構為函數,編寫一些最少的assert測試和文檔字符串。

之后,從 notebook 重構到腳本就很自然了。 不僅如此,即使您沒有計划將它們變成其他任何東西,它也能讓您在編寫長筆記本時生活更輕松。

具有“最少”測試和文檔字符串的單元格內容的基本示例:

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文件,你的代碼可能還沒有被構建到類中。 但是值得努力重構你的 notebook 到它有一組記錄的函數,每個函數都有一組簡單的assert語句,可以很容易地移動到tests.py以便使用pytestunittest或其他你。 如果有意義的話,在那之后將這些函數捆綁到您的類的方法中是非常容易的。

如果一切順利,之后您需要做的就是編寫您的if __name__ == '__main__':及其“鈎子”:如果您正在編寫由終端調用的腳本,您將需要處理命令-行參數,如果你正在編寫一個模塊,你會想 __init__.py文件等來考慮 它的 API

當然,這完全取決於預期的用例是什么:將筆記本轉換為小腳本與將其轉換為成熟的模塊或包之間存在很大差異。

以下是筆記本到腳本工作流程的一些想法

  1. 通過 GUI 將 Jupyter Notebook 導出到 Python 文件 (.py)。
  2. 刪除不做實際工作的“助手”行: print語句、繪圖等。
  3. 如果需要,將您的邏輯捆綁到類中。 唯一需要的額外重構工作應該是編寫您的類文檔字符串和屬性。
  4. 使用if __name__ == '__main__'編寫腳本的入口。
  5. 將每個函數/方法的assert語句分開,並在tests.pytests.py一個最小的測試套件。

我們有類似的問題。 然而,我們正在使用幾個筆記本來對結果進行原型設計,畢竟這些結果也應該成為幾個 python 腳本。

我們的方法是我們將代碼放在一邊,這些代碼在這些筆記本中不斷重復。 我們把它放到python模塊中,每個notebook都會導入這個模塊,在生產中也會用到。 我們不斷迭代改進這個模塊,並添加我們在原型設計過程中發現的測試。

Notebooks 變得很像配置腳本(我們只是簡單地將其復制到最終生成的 python 文件中)和一些我們在生產中不需要的原型檢查和驗證。

最重要的是,我們不害怕重構:)

我最近制作了一個模塊( NotebookScripter )來幫助解決這個問題。 它允許您通過函數調用來調用 jupyter notebook。 使用起來很簡單

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's 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 文件,因為它可能由 vscode 的 ipython 的 nbconvert 生成。 您可以以對交互使用有意義的方式組織代碼,並在需要時在外部重用/自定義它。

您應該以小步驟分解邏輯,這樣您的管道將更容易維護。 由於您已經有一個工作代碼庫,您希望保持代碼運行,因此進行小的更改、測試和重復。

我會走這條路:

  1. 向您的管道添加一些測試,對於 ML 管道這有點困難,但是如果您的筆記本訓練模型,您可以使用性能指標來測試您的管道是否仍然有效(您的測試可以是准確度 = 0.8,但請確保您定義一個可容忍的范圍,因為每次運行的數字幾乎不完全相同)
  2. 將您的單個筆記本拆分成更小的筆記本,其中一個的輸出應該是另一個的輸入。 創建拆分后,請確保為每個筆記本單獨添加一些測試。 要管理這種順序執行,您可以使用papermill來執行您的筆記本或工作流管理工具,例如與papermill集成的ploomber ,能夠解決復雜的依賴關系,並有一個鈎子可以在筆記本執行時運行測試(免責聲明:我是 ploomber 的作者)
  3. 一旦你有一個由多個筆記本組成的管道,通過所有測試,你可以決定是否要繼續使用 ipynb 格式。 我的建議是只保留具有豐富輸出的任務(例如表格或繪圖)作為筆記本,其余的可以重構為更易於維護的 Python 函數

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM