作者:京东科技隐私计算产品部 孙晓军

1. Jupyter Notebook 介绍





图 1 Jupter 项目整体架构

[https://docs.jupyter.org/en/latest/projects/architecture/content-architecture.html]

Jupyter Notebook 是一套基于 web 的交互式开发环境。用户可以在线开发和分享包含代码和输出的交互式文档,支持实时代码,数学方程,可视化和 markdown 等。用途包括:数据清理和转换,数值模拟,统计建模,机器学习等等。

Jupyter Notebook 内部通过内核维护状态并运行代码片段,浏览器显示代码片段和其执行的结果。Jupyter Notebook 提供了一个用户交互式的开发环境,用户可以通过执行一部分代码片段,并观察执行结果。这种交互式设计,使得 Jupyter Notebook 非常适合数据科学和机器学习的开发工作。

注意本文的代码和脚本,均基于 Jupyter Notebook v6.5.2 稳定版本。

2. Jupyter 的工作方式





图 2 Jupter Notebook 工作方式

[https://docs.jupyter.org/en/latest/projects/architecture/content-architecture.html]

Jupyter 的主要工作单元是 Jupyter Server 和 Kernel。其中 Jupyter Server 用来提供基于 Web 的界面和 API 服务,Kernel 用来执行代码片段。浏览器通过 Http 和 Websockets 的方式和 Jupyter Server 进行交互,Jupyter Server 和 kernel 之间,通过 ZeroMQ 进行数据通信。

Jupyter Server 采用经典的 MVC 模式,使用了 tornado 作为 web 服务器,用来提供地址映射和控制器逻辑,使用 jinja2 来提供模板视图功能。

Jupyter Notebook(v6.5.2)项目的主要模块结构如下:

模块 说明
notebook Notebook 功能模块。
terminal 终端模块。为 Jupyter 提供控制台交互能力。
view 文件可视化模块。比如 pdf 文件的显示。
tree 工作区目录树
nbconvert 格式转换模块,可以把 Jupyter Notebook 转换成 html,pdf 等格式
kernel Jupyter Notebook 内核
services Jupyter Notebook REST API 模块
i18n Jupyter Notebook 多语言资源

3. 安装 Jupyter Notebook

前置条件

Python 和 pip

不同的 Jupyter Notebook 对 Python 有不同的版本要求。我们安装的最新的稳定版本 v6.5.2 的 Jupyter Notebook,要求 Python 的最低版本为 3.6。注意这个 Python 的版本,不同于内核的 Python 版本。对于 Jupyter 内核来说,支持的 Python 版本和 Jupyter Notebook 依赖的 Python 版本没有关系。

在 Linux 系统下安装 Jupyter Notebook

使用 pip 安装 Jupyter notebook 非常简单。如果服务器同时拥有 Python2 和 Python3 的 pip,注意需要使用 pip3 来替换命令中的 pip。

# 更新pip
pip install --upgrade pip
# 安装jupyter
pip install jupyter
# 检查安装的jupyter
jupyter --version
//输出 notebook  : 6.5.2

4. 配置和启动 Jupyter

Jupyter 提供了大量的启动参数,用来配置 Jupyter Server。我们可以在启动 Jupyter 服务时,通过命令行参数的方式配置当前启动的服务,但更普遍的方式是使用 Jupyter 的配置文件。

# 生成配置文件
jupyter notebook --generate-config
// 默认生成的配置文件位置:
/root/.jupyter/jupyter_notebook_config.py

# 修改Jupyter配置文件...

# 启动jupyter
jupyter notebook

Jupyter 直接使用一个 Python 文件来配置 Jupyter 服务,所有的配置项均通过 Python 代码来完成。常用的配置项及其说明如下:

名称 默认值 说明
c.NotebookApp.allow_root False 为了安全,Jupyter 默认不允许使用 root 用户启动。如果需要以 root 用户的身份启动 Jupyter,需要开启此设定
c.NotebookApp.allow_origin '' 当需要 Jupyter 内嵌到 iframe 时,可以设置为 “*“来避免跨 origin 的限制
c.NotebookApp.ip localhost 当需要通过外网地址来访问 Jupyter 服务时,需要设置一个有效的服务器 IP 地址。
c.NotebookApp.port 8888 Jupyter server 对外服务端口
c.NotebookApp.notebook_dir / Jupyter 的工作空间,默认可以访问服务器上当前用户的所有文件系统
c.NotebookApp.open_browser True 启动服务后是否立即通过浏览器打开服务地址
c.NotebookApp.default_url /tree Jupyter 服务的默认地址
c.NotebookApp.extra_static_paths [] 扩展静态文件目录
c.NotebookApp.extra_template_paths [] 扩展模板文件目录
c.KernelSpecManager.allowed_kernelspecs set() 默认允许使用所有的 kernel
c.NotebookApp.nbserver_extensions {} 允许加载的 Jupyter Server 扩展

5. 使用 Jupyter

5.1. 创建 Notebook

启动 Jupyter 后,在浏览器内输入 http://服务器地址:端口/,Jupyter 会默认重定向到.default_url 指定的工作区目录树地址,默认是工作区目录树的界面。

如果在访问的过程中,使用了默认的 token 作为其认证方式,那么在首次打开时,需要输入 Jupyter Notebook 的 token 值,这个值可以在启动 Jupyter 时的控制台输出中找到,或者使用 Jupyter 命令来查询

# 查询运行的jupyter notebook
jupyter notebook list
//返回结果中包含了http://x.x.x.x:8899?token=ABC 的信息,其中的ABC就是我们需要的token





图 3 Jupter Notebook 的默认工作区目录树页面

Jupyter Notebook 通过 Jupyter Server 提供基于 Web 的平台无关的工作方式,这使得跨平台开发和协作,代码分享等能力变得比传统 IDE 更加容易。

在 Jupyter 工作区管理界面,用户可以灵活地以类似文件系统的方式管理工作区的数据。可以创建文件和文件夹,编辑文件和文件夹,可以上传和下载文件。通过选择一个 Jupyter 内核,可以创建一个 Notebook 文件。





图 4 通过 Jupyter 内核创建一个 Notebook

5.2. 使用 Notebook

使用 Python3 内核创建一个 Notebook 后,我们得到一个 xxx.ipynb(IPython Notebook)文件。这个文件是一个 json 格式的文本文件,其中包含了我们在 Notebook 中编写的代码和文本内容,也包含了界面上没有显示的元数据信息。通过在工作区目录界面选择一个 notebook 文件,点击编辑,我们可以查看到 ipynb 文件的原始内容。





图 5 ipynb 文件的原始内容

我们可以像使用其它 IDE 类似的方式来使用 Notebook,在使用 Notebook 上,我们主要关注下 Jupyter 内核和单元格。

内核是执行单元格代码的核心进程,不同的内核,决定了我们在单元格中能够编写哪些语言的代码,以及对应了指定的编程语言的哪个版本等信息。

单元格是整个 Notebook 的核心组成部分,我们编写的代码和文本,通过一些列 Notebook 单元格来组成。Notebook 提供了 Code,Markdown, Raw NBConvert, Heading 四种类型的单元格。

•Code 单元格。用来编写内核指定语言的程序代码

•Markdown 单元格。使用 Markdown 编辑器的语法来编辑富文本

•Raw NBConvert 单元格。原始的文本,不会被当作代码或 markdown 被解释执行

•Heading 单元格。Heading 是 Mardown 的一个子集,对应了 Markdown 中的标题编写语法

Jupyter Notebook 使用了机器学习中检查点的概念,在我们修改 Notebook 的过程中,Jupyter 会自动保存我们的修改,我们也可以通过【文件】->【保存】来手动保存检查点。检查点文件包含了我们编写的 Notebook 内容,以及执行代码单元格之后的输出。我们可以在工作空间的 “.ipynb_checkpoints” 文件夹下,找到这些检查点文件。





图 6 使用 Jupyter 单元格来编写交互式代码

5.3. 分享 Notebook

相较于使用传统的 IDE 编写的代码,基于 Web 服务的 Jupyter Notebook 在代码分享上拥有着天然的优势。

在 Jupyter Notebook 中,我们可以通过两种不同的方式分享我们创作的 nootbook。

  1. 交互式 Notebook 文档

传统的技术文档或者说明书,通过静态的文本,配合图片和视频,来描述和讲解特定的技术或功能。有了 Jupyter Notebook 后,我们仍然可以使用 Notebook 来编写类似传统的技术文档。在此基础上,我们可以加入更生动的代码交互单元格,用户通过查看文档说明,并与文档中提供的代码进行互动,可以更生动地介绍产品中的功能和技术。每个 Jupyter Notebook 的 ipynb 文件,都对应了一个独立的访问地址: http://x.x.x.x:8899/notebooks/my_notebook.ipynb ,通过分享此文件的地址,其他用户可以方便地使用包含了富文本和可执行的代码的交互式 Notebook 文档。

  1. 离线 Notebook 文档

我们通过逐步执行文档中的所有单元格,得到一个包含了我们编写的说明和代码,以及代码执行的输出结果的完整文档。之后点击【文件】->【另存为】,选择一种合适的文件格式。我们可以把文档导出为一份静态文件,通过共享此静态文件,我们实现了 Notebook 文档的离线分享。

5.4. 魔法函数

Jupyter Notebook 提供了一些列魔法函数来增强 Jupyter Code 单元格的功能,通过魔法函数,我们能够执行 javascript 脚本,html 代码,运行另一个可执行程序等许多额外的功能。

我们可以在 Jupyter 代码单元格中使用 %lsmagic 命令来查看所有的魔法函数,如果要阅读详细的魔法函数的使用说明,可以参考: https://ipython.readthedocs.io/en/stable/interactive/magics.html

魔法函数分为行魔法函数,单元格魔法函数和会话魔法函数。顾名思义,行魔法函数只对当前行起作用,而单元格魔法函数则作用于整个单元格,会话魔法函数则作用于整个会话期间。

一些常用的魔法函数:

指令 说明
%matplotlib 设置 matplot 绘图的显示模式
%%javascript 单元格内的代码被识别为 javascript 代码
%%html 单元格内的代码被识别为 html 代码
%run 执行外部脚本文件
%pwd 获取当前工作的目录位置(非工作空间目录位置)
%writefile 以文件形式保存当前单元格代码
%timeit 获取本行代码的执行时间
%debug 激活调试模式

6. 管理 Jupyter

6.1. 多语言

Jupyter Notebook 使用 i18n 目录下的资源来进行多语言翻译。在 Jupyter Notebook 启动时,会加载 i18n 目录下的多语言资源。之后根据 http 请求指定的语言,为响应数据提供对应的多语言翻译。如果没有对应的翻译,则保留原始的多语言标签值(英文)。如果调整了多语言翻译,需要重新启动 Jupyter Notebook 才能使用新的语言包。

Jupyter Notebook 的翻译资源主要分布在三个 po 文件中:

•nbjs.po - js 文件中的多语言数据

•nbui.po - UI 界面中的多语言数据

•notebook.po - notebook 中的多语言数据

原始的 po 文件,需要通过 pybabel 工具,把 po 文件编译成 mo 文件,之后部署在 $notebook/i18n/${LANG}/LC_MESSAGES/目录下($notebook 是 notebook 的安装目录),才能在 Jupyter Notebook 中作为多语言的资源包来使用。

# 使用pybabel编译多语言po文件
pybabel compile -D notebook -f -l ${LANG} -i ${LANG}/LC_MESSAGES/notebook.po -o ${LANG}/LC_MESSAGES/notebook.mo
pybabel compile -D nbui -f -l ${LANG} -i ${LANG}/LC_MESSAGES/nbui.po -o ${LANG}/LC_MESSAGES/nbui.mo
pybabel compile -D nbjs -f -l ${LANG} -i ${LANG}/LC_MESSAGES/nbjs.po -o ${LANG}/LC_MESSAGES/nbjs.mo





图 7 使用了中文语言包后的中文 Notebook 界面

6.2. 内核管理

内核(kernel)是独立于 jupyter 服务和界面之外的用来运行 Jupyter 代码单元格的进程,Jupyter 默认提供了 ipykernel 内核来支持 Python 开发语言。Jupyter 社区提供了 jupyterC, IJava,xeus-cling, xeus-sql 等众多其它编程语言的内核,用来支持 C/C++, Java, SQL 等编程语言。

ipykernel 默认使用系统环境下的 Python 来提供服务。我们可以使用 ipykernel 安装多个 Python kernel 来提供 Python2.x, Python3.x 等多个 Python 内核环境。

安装 kernel 后,kernel 的信息被保存在 kernel.json 文件中,我们可以在 /usr/local/share/jupyter/kernels 目录,找到 Jupyter 安装的所有 kernel 以及对应的 kernel.json 文件。

kernel 可以直接继承自安装 kernel 的 Python 指令,也可以使用 Python 虚拟环境。

# 1.直接继承自Python指令的kernel安装
# 安装ipykernel
pip install ipykernel
# 安装kernel
python -m ipykernel install --name tensorflow2 --display-name "tensorflow2"

# 2. 在Python虚拟环境下的kernel安装
# 激活虚拟环境
source activate myenv
# 安装ipykernel
pip install ipykernel
# 安装kernel
python -m ipykernel install --name myenv --display-name "Python3 (myenv)"

如果需要查看当前的 kernel 列表,以及删除已经安装的 kernel,可以使用如下的 Jupyter 命令:

# 查看已经安装的kernel列表
jupyter kernelspec list
# 删除列表中指定的kernel
jupyter kernelspec remove kernelname

6.3. REST API

Jupyter 提供了 REST API 接口来和 Jupyter server 进行交互。借助 REST API 的能力,我们可以以编程的方式和 Jupyter Server 进行交互,灵活地管理 Jupyter Server。另外 REST API 为现代化的软件开发提供了一个优秀的能力:自动化。

借助 Jupyter Notebook REST API,可以实现文件的上传和下载,检查点管理,会话管理,内核管理,终端管理等一些列管理能力。完整的 Jupyter REST API 接口列表可以参考: https://jupyter-server.readthedocs.io/en/latest/developers/rest-api.html

要使用 REST API,需要在请求中携带认证信息。Jupyter 支持直接把 token 作为 query string 的方式来认证,也可以使用标准的 Http Authorization 头信息来完成认证。使用 Authorization 头来认证的格式如下:

Authrozation: token 527a9f1430ccfed995ebcf15517583a2547c2469bc3c47a6





图 8 使用 Postman 来调用 Jupyter REST API 接口

6.4. 安全管理与多人协作

Jupyter 提供了灵活强大的能力,用以支持在线的交互式文档和代码的编写。但 Jupyter 项目自身没有提供精细化的安全管理体系,用以支持多用户下灵活地使用 Jupyter Notebook 的功能。对于文件安全,Jupyter 依赖于启动服务的 linux 用户,合理地配置启动 Jupyter 的用户的权限,才能保证使用 Jupyter 的用户,不会对系统或项目造成破坏。Jupyter 工作空间的设定,仅起到了方便 Jupyter 使用者管理必要文件的易用性,不能阻挡用户访问和管理工作空间外的文件系统。另外,配合使用 Python 虚拟环境,可以防止 Jupyter Notebook 提供的 pip install ,pip uninstall 功能,对现有项目环境造成破坏。

在多人协作方面,JupyterHub 项目提供了多人协作 Jupyter Notebook 和 Jupyter lab 开发的能力。使用 JupyterHub,不同职能的用户可以在自己独立的空间内进行 Notebook 的编写工作,不同用户间也可以方便地分享各自的 Notebook。

7. 扩展 Jupyter

7.1. 前端扩展

Jupyter Notebook 前端扩展 (front end extension) 是使用 Javascript 语言编写的异步模块,可以用来绘制 Jupyter 界面的仪表盘,Notebook,工具栏等,。定义一个前端扩展必须要实现一个 load_ipython_extension 方法,当前端控件被加载时,Jupyter client 会调用 load_ipython_extension 方法。

Jupyter Notebook 前端扩展能力目前还不是一个稳定的版本,不保证代码能够向后兼容。Jupyter 的 JS API 目前也没有官方的文档,需要通过源代码或者实际加载的 JS 来查看 Jupyter 前端脚本的成员和方法。

我们实现一个简单的前端扩展脚本,在 jupyter 前端的工具条中,添加一个自定义工具,当点击自定义工具时,弹出提示信息。

define([
    'base/js/namespace'
], function(
    Jupyter
) {
    function load_ipython_extension() {

        var handler = function () {
            alert('欢迎使用前端扩展!');
        };

        var action = {
            icon: 'fa-comment-o',
            help    : '前端扩展',
            help_index : 'zz',
            handler : handler
        };
        var prefix = 'my_extension';
        var action_name = 'show-alert';

        var full_action_name = Jupyter.actions.register(action, action_name, prefix); // returns 'my_extension:show-alert'
        Jupyter.toolbar.add_buttons_group([full_action_name]);
    }

    return {
        load_ipython_extension: load_ipython_extension
    };
});

完前端扩展代码后,我们把脚本保存到 main.js 文件,放置在/opt/my_extension 目录下。接下来我们使用 jupyter nbextension 工具来安装和启用前端扩展

# 安装前端扩展
jupyter nbextension install /opt/my_extension
# 启用前端扩展
jupyter nbextension enable my_extension/main
# 禁用前端扩展
jupyter nbextension disable my_extension/main
# 查看前端扩展列表
jupyter nbextension list
# 卸载前端扩展
jupyter nbextension uninstall my_extension





图 9 在 Notebook 工具条中加入的前端扩展

7.2. 服务端扩展

Jupyter 服务端扩展(server extension)是使用 Python 语言编写的模块,可以用来处理发送到 Jupyter Server 的 Http 请求。使用 Jupyter 服务端扩展,可以更改现有 Jupyter 请求的数据和行为,也可以为 jupyter Server 定义新的服务处理程序。

定义一个服务端扩展模块要实现一个 load_jupyter_server_extension 方法,其中包含一个类型为 notebook.notebookapp.NotebookApp 的参数 serverapp,serverapp 的详细属性和方法可以通过 Jupyter Notebook 源代码中的 notebookapp.py 文件来查看。当服务端扩展被加载时,Jupyter Server 会调用 load_jupyter_server_extension 方法。在 load_jupyter_server_extension 方法中,我们可以通过调用 serverapp 的 web_app 属性的 add_handlers 方法来注册处理程序,用来处理特定的服务端请求。处理程序类需要继承自 Jupyter 的 IPythonHandler 类。在处理程序的方法中,可以使用 Jupyter 提供的@web.authenticated装饰器来为方法增加身份认证保护。

通过服务端扩展,还可以与前端扩展联动,实现一个功能丰富的 Jupyter Notebook 前端控件。

# 定义一个处理程序
from tornado import (
    gen, web,
)
from notebook.base.handlers import IPythonHandler

class HelloWorldHandler(IPythonHandler):
    @web.authenticated
    @gen.coroutine
    def get(self):
        self.finish(f'Hello, world!')


# 实现load_jupyter_server_extension方法并注册处理程序
def load_jupyter_server_extension(serverapp):
    handlers = [
        ('/myextension/hello', HelloWorldHandler)
    ]
    serverapp.web_app.add_handlers('.*$', handlers)

完成服务端扩展代码后,我们把代码保存为init.py 文件,要在 Jupyter Notebook 中使用处理程序,我们还需要进行服务端扩展的安装和启用。不同于前端扩展,服务端扩展不能直接使用指令来安装,需要我们手动编写安装程序。此外,Jupyter 提供了自动启用服务端扩展和前端扩展的方法,需要我们在脚本的根目录提供启用扩展的配置文件。

jupyter-config/
├── jupyter_notebook_config.d/
│   └── my_server_extension.json
└── nbconfig/
    └── notebook.d/
        └── my_front_extension.json
 setup.py

加入了自动启用扩展的配置,我们的服务端扩展目录结构如下:

hello-extension/
├── __init__.py
jupyter-config/
├── jupyter_notebook_config.d/
    └── hello_extension.json

hello_extension.json 文件的内容为:

{
    "ServerApp": {
        "jpserver_extensions": {
            "hello_extension": true
        }
    }
}

接下来我们通过安装程序,安装服务端扩展的信息保存在/root/.jupyter/jupyter_notebook_config.json 文件中。在安装完成后,我们可以通过 jupyter serverextesion 工具来股那里服务端扩展

# 启用服务端扩展
jupyter serverextension enable hello_extension
# 禁用服务端扩展
jupyter serverextension disable hello_extension

# 服务端扩展直接卸载的方法,需要我们通过pip uninstall 卸载安装程序,
# 再通过手工修改/root/.jupyter/jupyter_notebook_config.json文件删除扩展信息来完成卸载





图 10 在浏览器中测试安装的服务端扩展程序

7.3. 界面定制

Jupyter 没有提供标准的界面定制的能力,但我们可以手工调整 jupyter 生成的模板视图文件和样式文件,达到整条调整 jupyter notebook 的界面的能力。

Jupyter Notebook 模板文件的位置为:$notebook/templates,样式和脚本定制推荐的方案是使用~/.jupyter/custom/custom.css 和~/.jupyter/custom/custom.js 文件。我们可以直接在此基础上对文件进行修改,还可以通过 extra_template_paths 和 extra_static_paths 来引入其它位置的模板和其它静态文件。





图 11 通过直接调整模板文件加入的界面定制按钮

7.4. 小部件

小部件(Widgets)是 Jupyter 交互式可视化数据呈现部件。Jupyter Widgets 同时包含了访问后端数据和前端呈现的能力,可以用于在 Jupyter Notebook 上生动地展示服务端的数据和数据变化。

在 v6.5.2 稳定版本上,我们目前只能使用系统提供的小部件,还不能开发自定义小部件。在 Jupyter notebook7.x 版本中,开始提供了小部件的自定义开发能力。

# 确保安装了ipywidgets和traitlets
pip install --upgrade traitlets
pip install --upgrade ipywidgets

# 安装和启用小部件
jupyter nbextension install --py widgetsnbextension
jupyter nbextension enable --py widgetsnbextension

在安装和启用了小部件后,我们可以在 notebook 中直接使用系统提供的小部件。





图 12 在 Notebook 中使用小部件

完整的小部件列表和使用方式可以参考: https://ipywidgets.readthedocs.io/en/7.x/examples/Widget%20List.html

8. 总结

Jupyter Notebook 以其丰富的功能,简单易用,强大的交互能力和扩展能力,成为数据科学和机器学习开发中的神器。目前,Jupyter Notebook 支持超过 40 种编程语言,被应用于 Google Colab, Kubeflow, 华为云,kaggle 等多个知名项目中,大量机器学习和数据科学的论文中使用到了 Jupyter。Jupyter 在数据可视化,提升工作效率,改善用户体验和丰富文档功能方面显现了巨大的威力。除此之外,Jupyter 还提供的灵活强大的扩展能力,更是为 Jupyter 的深层次使用提供了更广阔的想象空间。如果你还没有开始接触 Jupyter,那么就从现在开始吧。


↙↙↙阅读原文可查看相关链接,并与作者交流