图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 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多语言资源 |
前置条件
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
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扩展 |
启动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
使用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单元格来编写交互式代码
相较于使用传统的IDE编写的代码,基于Web服务的Jupyter Notebook在代码分享上拥有着天然的优势。
在Jupyter Notebook中,我们可以通过两种不同的方式分享我们创作的nootbook。
传统的技术文档或者说明书,通过静态的文本,配合图片和视频,来描述和讲解特定的技术或功能。有了Jupyter Notebook后,我们仍然可以使用Notebook来编写类似传统的技术文档。在此基础上,我们可以加入更生动的代码交互单元格,用户通过查看文档说明,并与文档中提供的代码进行互动,可以更生动地介绍产品中的功能和技术。每个Jupyter Notebook的ipynb文件,都对应了一个独立的访问地址: http://x.x.x.x:8899/notebooks/my_notebook.ipynb ,通过分享此文件的地址,其他用户可以方便地使用包含了富文本和可执行的代码的交互式Notebook文档。
我们通过逐步执行文档中的所有单元格,得到一个包含了我们编写的说明和代码,以及代码执行的输出结果的完整文档。之后点击【文件】-> 【另存为】,选择一种合适的文件格式。我们可以把文档导出为一份静态文件,通过共享此静态文件,我们实现了Notebook文档的离线分享。
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 | 激活调试模式 |
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界面
内核(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
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接口
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。
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工具条中加入的前端扩展
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 在浏览器中测试安装的服务端扩展程序
Jupyter没有提供标准的界面定制的能力,但我们可以手工调整jupyter生成的模板视图文件和样式文件,达到整条调整jupyter notebook的界面的能力。
Jupyter Notebook模板文件的位置为:$notebook/templates,样式和脚本定制推荐的方案是使用/.jupyter/custom/custom.css和/.jupyter/custom/custom.js文件。我们可以直接在此基础上对文件进行修改,还可以通过extra_template_paths和extra_static_paths来引入其它位置的模板和其它静态文件。
图11 通过直接调整模板文件加入的界面定制按钮
小部件(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 List.html
Jupyter Notebook以其丰富的功能,简单易用,强大的交互能力和扩展能力,成为数据科学和机器学习开发中的神器。目前,Jupyter Notebook支持超过40种编程语言,被应用于Google Colab, Kubeflow, 华为云,kaggle等多个知名项目中,大量机器学习和数据科学的论文中使用到了Jupyter。Jupyter在数据可视化,提升工作效率,改善用户体验和丰富文档功能方面显现了巨大的威力。除此之外,Jupyter还提供的灵活强大的扩展能力,更是为Jupyter的深层次使用提供了更广阔的想象空间。如果你还没有开始接触Jupyter,那么就从现在开始吧。