メロスは激怒した。メロスにはJupyter Notebookがわからぬ。メロスは、JSONの牧人である。

f:id:aidemy-blog:20211203110403p:plain

AidemyのJupyter Notebook

AidemyではJupyter Notebookを利用してユーザーへの学習コンテンツを提供しています。
そもそもJupyter Notebookを利用した理由は展開可能性にあります。
Jupyter Notebookで書いたドキュメントはそのままファイルとしてユーザーへ提供可能なため、
将来的に学習済みのコンテンツやオリジナルコンテンツをユーザー企業様へ配布し、二次利用してもらう想定がありました。

しかし、時は流れ流れ
そういった活用をされないまま、学習コンテンツをJupyter Notebookで書くことの辛みを覚えてきました。

そもそもAidemyではJupyter NotebookをJSONファイルへ変換し、それをDBへ投入してフロントから呼び出して利用しています。
その変換処理はJavascriptを用いていますが、サービス立ち上げ当初に書かれたコードであり、ドキュメントも不足していました。
そこで、将来的にJupyter Notebookからの脱却を夢見つつ、まずはこのJupyter Notebook → JSON変換スクリプトの読み解きが始まりました。

Jupyter Notebookの内部構造

Jupyter Notebookの基本構造は下記のようになっています。
The Notebook file format

{
  cells: [
    {
      cell_type: "",
      metadata: {},
      source: []
    }
  ]
}

cells の中にcell Objectが配列として格納され、それぞれ cell_type, meta_data, source が存在します。
sourceは配列を格納しますが、これは1行ずつの文字列が入っています。
また、metadataには独自のパラメータを設定することができ、Aidemyでは { type: "courseId" } などを設定しています。

これまでの変換ではcellを配列として回しながら内部でJSONを構築していくという方式でしたが、indexを用いて参照していたため、可読性が低く、メンテナンス性がとても悪いものでした。 そこで、これらをJSONとして扱いやすくするために、まずはcellsを展開し、metadataの type をパラメータのkey, sourceをparamとする処理を行うように修正しました。

const fs = require('fs');
const jupyterFile = fs.readFileSync('some.ipynb')
const jupyterJson =JSON.parse(jupyterFile.toString())
jupyterJson.cells.reduce((pre, curr) => ({...pre,  [curr.metadata.type]: curr.source}), {})

これにより、typeをkey、sourceをparamとして保持することができます。

{
  cells: [
    {
      cell_type: "",
      metadata: {
        type: 'courseId'
        
      },
      source: [ "1" ]
    }
  ]
}

=>

{
  "courseId": ["1"]
}

以降は通常のJSONとして扱うことができ、とても扱いやすいものになります。 もちろん、Jupyter Notebookでは自由なパラメータを設定できますので、お手元のJupyter Notebookファイルによって詰め替えるkeyやparamを設定してみてください。

この記事ではJupyter NotebookをJSONに変換する処理について書きましたが、これはまだリファクタリングの途中です。 Jupyter Notebookはとても自由で便利なものではありますが、その分環境構築やデータ構造としての扱いやすさに難があり、チームで扱う際に使い勝手が気になるところではあります。 今後よりコンテンツの拡充を図るため、Jupyter Notebookから脱却し、チーム内共有や反映の迅速化ができるよう、さらに使いやすい方式を求めていきます。