web-dev-qa-db-ja.com

Jupyterノートブックをpythonスクリプトに変換するためのベストプラクティス

Jupyter(iPython)ノートブックは、コードのプロトタイプを作成し、あらゆる種類の機械学習をインタラクティブに行うための優れたツールとしてふさわしく知られています。しかし、私がそれを使用するとき、私は必然的に以下に遭遇します:

  • ノートブックはすぐに複雑すぎて面倒になり、ノートブックとして維持し、改善することはできません。また、スクリプトをpython
  • 本番コード(たとえば、毎日再実行する必要があるコード)の場合、ノートブックは最適な形式ではありません。

Jupyterで機械学習パイプライン全体を開発し、さまざまなソースから生データを取得し、データをクリーニングし、機能エンジニアリングを行い、トレーニングモデルを作成したとします。効率的で読みやすいコードでスクリプトを作成するのに最適なロジックは何ですか?私はこれまでいくつかの方法で取り組んできました。

  1. .ipynbを.pyに変換し、わずかな変更を加えるだけで、ノートブックからのすべてのパイプラインを1つのpythonスクリプトにハードコーディングします。

    • 「+」:クイック
    • '-':汚れ、柔軟性がなく、保守が便利ではありません
  2. 多くの関数(1つまたは2つのセルごとに約1つの関数)を含む単一のスクリプトを作成し、パイプラインのステージを別々の関数で構成して、それに応じて名前を付けます。次に、argparseを介してすべてのパラメーターとグローバル定数を指定します。

    • '+':より柔軟な使用法。より読みやすいコード(パイプラインロジックを関数に適切に変換した場合)
    • '-':多くの場合、パイプラインは論理的に完成した部分に分割できず、コード内での奇妙な動作なしに機能になる可能性があります。これらのすべての関数は、通常、ループ、マップなどの中で何度も呼び出されるのではなく、スクリプトで1回だけ呼び出される必要があります。さらに、各関数は、通常、関数。
  3. ポイント(2)と同じですが、クラス内のすべての関数をラップします。これで、すべてのグローバル定数と各メソッドの出力をクラス属性として保存できます。

    • '+':各メソッドに多くの引数を渡す必要はありません-以前の出力はすべて属性として既に保存されています
    • '-':タスクの全体的なロジックはまだキャプチャされていません。クラスだけでなく、データと機械学習のパイプラインです。クラスの唯一の目標は作成され、すべてのメソッドを1つずつ順番に呼び出してから削除されることです。さらに、クラスの実装にはかなり時間がかかります。
  4. ノートブックをいくつかのスクリプトを使用してpythonモジュールに変換します。これを試してはいませんでしたが、これが問題に対処する最も長い方法であると思われます。

この全体的な設定はデータサイエンティストの間では非常に一般的であると思われますが、驚くべきことに、役に立つアドバイスが見つかりません。

皆さん、アイデアと経験を共有してください。この問題に遭遇したことはありますか?どのように取り組みましたか?

37
kurtosis

同様の問題が発生しています。ただし、結果のプロトタイプを作成するためにいくつかのノートブックを使用していますが、これはやはりいくつかのpythonスクリプトになります。

私たちのアプローチは、これらのノートブック間で繰り返されるコードを脇に置くことです。これをpythonモジュールに配置します。このモジュールは各ノートブックによってインポートされ、本番環境でも使用されます。このモジュールを継続的に改善し、プロトタイピング中に見つかったテストを追加します。

ノートブックは、構成スクリプト(結果のpythonファイル)に単純にコピーする)と、プロダクションでは必要のないいくつかのプロトタイピングチェックと検証のようになります。

すべてのほとんどは、リファクタリングを恐れていません:)

11
Radek

ライフセーバー:ノートブックを作成しているときに、コードを関数にリファクタリングし、最小限のassertテストとドキュメント文字列を作成します。

その後、ノートブックからスクリプトへのリファクタリングは自然です。それだけでなく、長いノートを書くとき、それを他のものに変える計画がない場合でも、あなたの人生を楽にします。

「最小限の」テストとdocstringを使用したセルのコンテンツの基本的な例:

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ファイルにエクスポートすると、コードはおそらくクラスに構造化されません。ただし、テスト用に簡単にtests.pyに移動できる一連の単純なassertステートメントを備えた、ドキュメント化された関数のセットを持つようにノートブックをリファクタリングする価値はあります。 pytestunittest、またはあなたが持っているもの。それが理にかなっている場合、これらの関数をクラスのメソッドにバンドルすることはその後簡単です。

すべてがうまくいけば、その後に行う必要があるのは、 if __== '__main__': とその「フック」を書くことだけです。端末から呼び出されるスクリプトを作成する場合は、 コマンドライン引数を処理する 、モジュールを書いている場合は、 __init__.pyファイルを使用したAPI などについて考えてください。

もちろん、意図するユースケースが何であるかによって異なります。ノートブックを小さなスクリプトに変換することと、本格的なモジュールまたはパッケージに変換することには、かなりの違いがあります。

ノートブックからスクリプトへのワークフローに関するいくつかのアイデアがあります

  1. GUIを使用してJupyter NotebookをPythonファイル(.py)にエクスポートします。
  2. 実際の作業を行わない「ヘルパー」行を削除します:printステートメント、プロットなど。
  3. 必要に応じて、ロジックをクラスにバンドルします。必要な追加のリファクタリング作業は、クラスのdocstringと属性を書くことだけです。
  4. if __== '__main__'を使用して、スクリプトの入り口を書きます。
  5. 関数/メソッドごとにassertステートメントを分離し、tests.pyの最小限のテストスイートを具体化します。
8

この問題に対処するために、最近モジュールを作成しました( 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'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ファイルをサポートします。vscodeのipythonのnbconvertによって生成される可能性のある.pyファイルを簡単に使用できます。インタラクティブな使用に適した方法でコードを整理し、必要に応じて外部で再利用/カスタマイズすることもできます。

2
Ben