Jupyter笔记本的版本控制

Jupyter笔记本的版本控制版本控制,也被称为源代码控制,用于跟踪软件开发和数据科学工作中的代码和其他工件的变化。版本控制允许对工件进行检查点,比较不同的版本,并分支到不同的开发路径。我们大多数人已经知道版本控制,但如何在Jup

版本控制,也被称为源代码控制,用于跟踪软件开发和数据科学工作中的代码和其他工件的变化。版本控制允许对工件进行检查点,比较不同的版本,并分支到不同的开发路径。我们大多数人已经知道版本控制,但如何在Jupyter笔记本中有效地使用它并不明显。由于Jupyter笔记本只是包含代码、元数据输出的JSON文档,在使用git等标准版本控制工具时,比较笔记本的版本可能会很笨重。但对于使用Jupyter笔记本的开发者和数据科学家来说,有一些方法可以使生活变得更容易。

一个简单的差异例子告诉我们它有多混乱

理解这些问题的最好方法是看一下简单的例子。在这个例子中,我将演示Jupyter笔记本的三个部分如何在开发过程中改变数值,以及这些文件如何与默认的git工具互动。

如果你想跟着学,你可以手动创建一个笔记本和git仓库,模仿下面的代码和例子,或者你可以克隆我的git仓库–特别是编辑测试笔记本,然后用它来工作。

在这个例子中,我们用下面的代码做了一个简单的笔记本。

import matplotlib.pyplot as plt
plt.plot([x**2 for x in range(100)])

Jupyter笔记本的版本控制

x**2的Matplotlib图

如果我们执行这个笔记本,保存它,然后把它添加到git,我们就有了我们的基础版本。

> git add jupyter_git_example.ipynb
> git commit -m "initial git commit for example, for diffing"

我不会在这里展示笔记本的全部源代码,但你可以自己看一下源代码(使用文本编辑器或unix shell中的less ),你会发现它只是一个JSON文档。但如果我们重新执行包含代码的单元格并保存笔记本,会发生什么?有什么变化?这就是我所看到的差异。

> git diff jupyter_git_example.ipynb
diff --git a/tools/jupyter_git_example.ipynb b/tools/jupyter_git_example.ipynb
index 906fb11..750fe85 100644
--- a/tools/jupyter_git_example.ipynb
+++ b/tools/jupyter_git_example.ipynb
@@ -2,17 +2,17 @@
  "cells": [
   {
    "cell_type": "code",
- "execution_count": 1,
- "id": "28c01064",
+ "execution_count": 2,
+ "id": "e2c08a0a",
    "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
- "[<matplotlib.lines.Line2D at 0x1187977c0>]"
+ "[<matplotlib.lines.Line2D at 0x11891e100>]"
       ]
      },
- "execution_count": 1,
+ "execution_count": 2,
      "metadata": {},
      "output_type": "execute_result"
     },
@@ -33,6 +33,14 @@
     "import matplotlib.pyplot as plt\n",
     "plt.plot([x**2 for x in range(100)])"
    ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2915ced8",
+ "metadata": {},
+ "outputs": [],
+ "source": []
   }
  ],
  "metadata": {

这里发生了什么?你可以看到,单元格的执行数发生了变化(还有一些标识符)。它还在最上面的单元格下面添加了一个新的空白单元格(在我执行第一个单元格时创建)。实际上没有代码发生变化,但我们必须非常仔细地看才能弄清楚,如果你以前没有见过笔记本文件,这可能会让人困惑。

视觉输出,特别是图像,使差异变得更加混乱

现在,如果我改变代码(比如从x**2x**3 )并执行和保存笔记本,代码输出都会改变。

对我来说,它看起来像这样

> git diff jupyter_git_example.ipynb
diff --git a/tools/jupyter_git_example.ipynb b/tools/jupyter_git_example.ipynb
index 906fb11..6a8084e 100644
--- a/tools/jupyter_git_example.ipynb
+++ b/tools/jupyter_git_example.ipynb
@@ -2,23 +2,23 @@
  "cells": [
   {
    "cell_type": "code",
- "execution_count": 1,
- "id": "28c01064",
+ "execution_count": 3,
+ "id": "57e4b20e",
    "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
- "[<matplotlib.lines.Line2D at 0x1187977c0>]"
+ "[<matplotlib.lines.Line2D at 0x11897a160>]"
       ]
      },
- "execution_count": 1,
+ "execution_count": 3,
      "metadata": {},
      "output_type": "execute_result"
     },
     {
      "data": {
- "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYMAAAD4CAYAAAAO9oqkAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZ
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXQAAAEDCAYAAAAlRP8qAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZ
       "text/plain": [
        "<Figure size 432x288 with 1 Axes>"
       ]
@@ -31,8 +31,16 @@
    ],
    "source": [
     "import matplotlib.pyplot as plt\n",
- "plt.plot([x**2 for x in range(100)])"
+ "plt.plot([x**3 for x in range(100)])"
    ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "09023a82",
+ "metadata": {},
+ "outputs": [],
+ "source": []

像上次一样,执行次数和ID都发生了变化,但现在在diff中既有代码的变化,也有输出的变化。另外,由于matplotlib图像是base64编码的,所以很难说出什么变化–是输出中的image/png 。如果我们想让我们的差异尽可能的干净,我们应该怎么做?我使用两种策略中的一种,选择使用每种策略有不同的理由。

简单的策略。只把笔记本当作源代码

最简单的策略是只提交没有输出的已清除笔记本。要做一个没有输出的已清除的笔记本,选择内核菜单,并选择 “重启和清除输出”。如果你保存这个笔记本,你会看到笔记本文件中没有输出,唯一出现的差异是代码和元数据。这种策略的优点是你不会处理混乱的输出数据的差异,特别是视觉数据。你的差异(可能还有大多数合并)将不会很难理解。

对于笔记本的某些用途,这将有很大的意义。例如,如果你的笔记本是用于papermill的参数化笔记本,你有一个基本的笔记本,用于使用不同的参数创建几个甚至几千个不同的笔记本。保存其中一个参数的随机输出不一定有什么帮助,而且保存这些笔记本的每个版本通常也没有意义。然而,保存基本版本(没有输出)可能是一个不错的选择。这个方案主要是把笔记本当作源代码,而不是当作数据或结果。

如果你使用这个选项,你可能要考虑为你的笔记本代码创建单元测试。你也可以考虑把它拉出到一个Python模块中(在一个.py文件中),如果这对你的用例有意义的话。

替代策略。将笔记本视为代码和数据

理想情况下,你想把你的笔记本和结果一起保存,这确实是使用笔记本的主要原因之一

nbdime使差异和合并更加容易

如果你把一个笔记本同时当作代码和数据,你可以使用一个简单的工具来使你的生活更容易。安装nbdime工具–笔记本的差异和合并。这个工具是Jupyter项目的一部分,它知道如何正确地对笔记本文件进行比较,并与用于版本控制的git或mercurial,以及Jupyter服务器和JupyterLab以及其他工具集成。

安装

你可以用pip安装nbdime。理想情况下,你会在一个开发笔记本的虚拟环境中进行安装。

pip install nbdime

简单的差异

要比较一个笔记本,只需运行nbdiff。我在运行这个命令之前,先重新运行了笔记本并保存了它(因为我之前已经清除了输出)。对于这样一个小的变化,输出有点冗长,但它被分成了几个部分。你可以看到,单元格ID、单元格输出的文本、单元格的图形输出和单元格的来源都有变化。它还表明在笔记本的最后有一个新的代码单元。这对人来说更容易理解!

nbdiff jupyter_git_example.ipynb
nbdiff tools/jupyter_git_example.ipynb (HEAD) tools/jupyter_git_example.ipynb
--- tools/jupyter_git_example.ipynb (HEAD) (no timestamp)
+++ tools/jupyter_git_example.ipynb 2021-09-02 17:25:50.889444
## modified /cells/0/id:
- 28c01064
+ 2f344fee

## modified /cells/0/outputs/0/data/text/plain:
- [<matplotlib.lines.Line2D at 0x1187977c0>]
+ [<matplotlib.lines.Line2D at 0x10f789a60>]

## inserted before /cells/0/outputs/1:
+ output:
+ output_type: display_data
+ data:
+ image/png: iVBORw0K...<snip base64, md5=86ab879409f2b42a...>
+ text/plain: <Figure size 432x288 with 1 Axes>
+ metadata (unknown keys):
+ needs_background: light

## deleted /cells/0/outputs/1:
- output:
- output_type: display_data
- data:
- image/png: iVBORw0K...<snip base64, md5=d93fe1c011d9afe8...>
- text/plain: <Figure size 432x288 with 1 Axes>
- metadata (unknown keys):
- needs_background: light

## modified /cells/0/source:
@@ -1,2 +1,2 @@
 import matplotlib.pyplot as plt
-plt.plot([x**2 for x in range(100)])

+plt.plot([x**3 for x in range(100)])


## inserted before /cells/1:
+ code cell:

视觉差异

还有一个基于网络的差异工具,它允许你将变化可视化。

nbdiff-web jupyter_git_example.ipynb

这将在一个新的浏览器标签或窗口中启动一个可视化差异工具。对于我上面的笔记本例子,它看起来是这样的。

Jupyter笔记本的版本控制

使用nbdiff-web的可视化差异

与Git集成

你可以配置git来使用nbdiff工具对笔记本文件进行比较。

从你的git项目的根部,运行enable命令。

nbdime config-git --enable

默认情况下,它只在当前版本库中启用 nbdime。你可以全局启用它 (--global) 或在系统级启用 (--system),你也可以禁用它 (--disable)。一旦启用,git diff 将使用 nbdiff 格式输出。

合并

一旦你在版本控制中跟踪你的笔记本的变化,你很可能会遇到这样的情况:你必须合并另一个版本的变化–要么是你创建的分支,要么是其他人做的变化,并作为另一个.ipynb文件交付给你。如果你试图用默认的git工具来合并结果,或者用JSON文件本身来手工合并,这可能是一个难以置信的麻烦。在某些合并中,git会在JSON文件中间的代码中插入合并冲突标记(即<<<<===== ),在这些地方使其成为一个无效的文件。如果是这样的话,它就不能被加载到任何正常的Jupyter笔记本编辑工具中,它将不得不被检查并手工修复。

对于一个只是合并两个笔记本的简单例子,我复制了上面的笔记本,但在一个地方改变了源代码,然后将其保存为jupyter_git_example2.ipynb 。合并工具知道如何将这两个改动合并成一个有效的笔记本文件,我可以用Jupyter打开。

nbmerge jupyter_git_example.ipynb jupyter_git_example2.ipynb  > merged.ipynb

如果我在Jupyter中打开merged.ipnb ,我看到代码单元包含了合并后的代码–它有一个冲突,我可以轻松解决。

import matplotlib.pyplot as plt
<<<<<<< local
plt.plot([x**3 for x in range(100)])
=======
plt.plot([x**4 for x in range(100)])
>>>>>>> remote

合并工具支持更多的选项,并且可以进行三向合并。它还支持一个可视化工具,就像nbdiff-web。当合并工具与git集成并在git工作流程中使用时,它们是最有用的。

例如,你可能会选择建立一个特性分支,并在一个笔记本上做一些工作。然后,你可能最终在主分支上做了一些修改。这时就需要进行合并,把这些改动移到主干分支上。nbdime的合并工具知道如何在笔记本的背景下进行这种合并。你也可以用视觉方式进行合并。

下面是一个带注释的例子,说明我们通常会遇到这种情况,这可能是一个或几个开发人员在多个分支上提交。

> git branch nbdime-example # make a feature branch
> git checkout nbdime-example # checkout branch
# work on this branch, make a notebook change, save the notebook
> git add jupyter_git_example.ipynb
> git commit -m "changes on feature branch"
> git checkout main # switch back to main branch
# make a change to jupyter_git_example.ipynb, but in the same lines as on nbdime-example branch
> git add jupyter_git_example.ipynb
> git commit -m "changes on main branch"

这时,我们可能想合并这两个分支。一旦我们安装了nbdime,我们就可以使用他们的diff工具来更好地了解这些变化。

> git checkout master
> git merge nbdime-example
# get message about merge conflicts on command line
> git mergetool --tool=nbdime
# now we see the merges in context

你可以查看文档,了解更详细的配置。你也可以在你的Jupyter笔记本菜单栏中启用一个nbdiff按钮(在Jupyter笔记本或JupyterLab中)。如果你不想在命令行中运行差异,这可能是最好的选择。完整的说明在这里。请注意你是想只在你的虚拟环境中安装扩展,还是在整个系统中安装。

一些最佳实践

在版本控制中使用Jupyter笔记本时,以下是对我来说相当有效的方法。请注意,更大的工作组可能需要更严格的标准,但一般来说,这些准则对我来说很有效。

尽可能多地将代码提取到Python文件中

将代码放在Python文件中会使它更容易被访问、测试和广泛使用。使用像git这样的版本控制工具,它也更容易进行差异和合并。但对于某些代码来说,把它放在笔记本中更方便。给予笔记本作者灵活性可能是值得的。所以强迫所有的 Python 代码进入单独的 Python 文件并不总是合理的。

在提交笔记本之前,重新启动它并重新运行它,然后保存。

笔记本的开发可能会引入错误,因为单元格有时会在运行后被打乱顺序或删除。这是一个好主意,从头开始重新运行笔记本。这可以确保所有的代码都在笔记本中,并以正确的顺序运行。对于运行时间很长的笔记本,重新运行可能并不实用。你更好的选择是将笔记本分成多个小的笔记本。选择不运行一个完整的笔记本是自找麻烦。一个额外的好处是,大多数单元的元数据(如执行数)在同一文件的提交之间不会改变。

如果一个笔记本是一个模板,清除输出并保存

如果一个笔记本是供papermill使用的参数化笔记本,我通常在保存时不保留输出。这些笔记本是一个有多个输入的模板运行,所以你可以把它们当作简单的代码。

使用nbdime来区别和合并Jupyter笔记本代码

即使你不需要使用合并工具,只要看到一个漂亮的diff,显示代码的变化,对较大的变化就有很大帮助。如果你在笔记本开发的虚拟环境中安装nbdime,你会发现额外的信息和便利在提交修改、恢复代码或与合作者分享笔记本时非常有帮助。

尽管笔记本格式确实很有帮助,因为它将代码与输出保持在一起,但跟踪笔记本的变化可能是一件很痛苦的事情。但我们可以使用策略和工具来帮助我们很好地利用版本控制工具。

The postVersion control for Jupyter notebooksappeared first onwrighters.io.

今天的文章Jupyter笔记本的版本控制分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/23081.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注