WSL (Ubuntu)  上の Jupyter Lab で複数の IPython Kernel を使う

# はじめに

Jupyter notebook (Lab) を使って機械学習を勉強していると 各Notebook で 環境 (IPython Kernel) を分離したい場面がでてきます。

本記事では **WSL (Ubuntu) 上の JupyterLab 環境で 複数の IPython Kernel を使う**  方法について記載します。

# 環境

- Python 3.8+  venv
- WSL2[^1] (Ubuntu 20.04 LTS)

 
# やりたいこと

WSL上に JupyterLab 環境を作って 複数の Jupyter Kernel を管理することが目的です。

そのためには Python の 仮想環境(`venv`) を作成して分離すれば良いのですが、JupyterLab の設定や拡張機能など共通で使いたいものもあります。

そこで、共通で使用するものはユーザ個別の環境(`~/.local/`) に、Notebook 用途で分けたいものは `venv` にインストールすることにしました。

| 目的 | 方法 | 
| ------------ | ---- |
| Jupyterの**設定や拡張機能は共通**で使いたい | JupyterLab は、ユーザ個別環境(`~/.local`)にインストールする。|
| Notebook 用途で**環境を分離**したい | 仮想環境(`venv`) 内で Jupyter Kernel (ipython kernel) を分離する。| 


```text:フォルダツリーの例
/home/ubuntu/
├── .local ( Pythonの共通環境: JupyterLab / Extension インストール)
├── notebook
│   ├── notebook1
|   |   └─venv  (仮想環境 / IPython Kernel分離)
│   ├── notebook2
|   |   └─venv  (仮想環境 / IPython Kernel分離)
..
```

# WSL2環境

Ubuntu 20.04 をインストールし、`ubuntu` ユーザを作成しました。 以降の例では `ubuntu` ユーザでの操作となります。


## ホームディレクトリの場所

ユーザのホームディレクトリは 既定の場所 (Linux のルートファイルシステム:rootfs) を使用します。rootfs 以外の場所に変更すると、パーミッション変更できないなど面倒なことがあるので、特別な理由がなければ既定の場所で良いかと思います。

Ubuntu-20.04 の場合 Windows から見える WSL home の場所は以下の通りです。

```sh
# Windows10
\\wsl$\Ubuntu-20.04\home
# Windows11
\\wsl.localhost\Ubuntu-20.04\home
```

## パスワードなしで sudo

ここは任意の設定です。利便性を優先してパスワードなしで sudo を使えるようにします。

```sh:/etc/sudoers.d/ubuntu
$ sudo vi /etc/sudoers.d/ubuntu

# enable NOPASSWD for user
ubuntu ALL=(ALL) NOPASSWD:ALL
```

パーミッションの変更

```sh
# all files in this directory should be mode 0440.
$ sudo chmod 440 /etc/sudoers.d/ubuntu
```

# Python 環境の準備

Python3 と venv をインストールします。

```sh
# Python 3.8.10
$ sudo apt update -y
$ sudo apt install python3-pip python3-venv -y

# upgrade pip
$ python3 -m pip install --upgrade pip
```


# Jupyter 環境の準備

## JupyterLab のインストール

JupyterLab 環境 は 各 notebook 共通で使いたいので  `--user` オプションを付けて、ホームディレクトリ配下(`~/.local/`) にインストールします。

```sh
$ python3 -m pip install jupyterlab --user
```

バージョンは `3.4.2` でした。(2022/6 現在)

```sh
$ python3 -m pip show jupyterlab
Name: jupyterlab
Version: 3.4.2
..
```

`~/.local/` に初めてインストールする場合は PATH が通っていないので おそらく以下のような警告がでます。

```sh
WARNING: The script pyjson5 is installed in 
'/home/ubuntu/.local/bin' which is not on PATH.
```

でも、この PATH(`~/.local/bin`) は `~/.profile` によって ログイン時に環境変数の PATH に追加されるので問題ありません。 

```sh:~/.profile
...
# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/.local/bin" ] ; then
    PATH="$HOME/.local/bin:$PATH"
fi
..
```

この先作成する仮想環境でも　共通の JupyterLab を使うには、この PATH が通っていることが必要になります。WSLをログインし直して パスが追加されているのを確認します。

```sh
$ env |grep PATH
PATH=/home/ubuntu/.local/bin:/usr/local/sbin:/usr/local/bin:.......　<以下略> 
```


## ブラウザ連携

JupyterLab  を起動してもブラウザが連動して起動しない場合、設定ファイルに `c.NotebookApp.use_redirect_file = False` を追加します。

コンフィグを作成を作成します。

```sh
$ jupyter lab --generate-config
```

設定ファイルに に下の項目を追加します。

```sh:~/.jupyter/jupyter_lab_config.py
$ vi ~/.jupyter/jupyter_lab_config.py
#  Disabling this setting to False will disable this behavior, allowing the
#  browser to launch by using a URL and visible token (as before).
#  Default: True
# c.ServerApp.use_redirect_file = True
c.ServerApp.use_redirect_file = False
```

Jupyter Lab のバージョンが `Version: 2.0.x` の場合は、以下の設定となります。
参考： [Launching notebook to browser on WSL #4594](https://github.com/jupyter/notebook/issues/4594)

```sh:~/.jupyter/jupyter_notebook_config.py
$ vi ~/.jupyter/jupyter_notebook_config.py

#  Disabling this setting to False will disable this behavior, allowing the
#  browser  to launch by using a URL and visible token (as before).
#c.NotebookApp.use_redirect_file = True
c.NotebookApp.use_redirect_file = False
```

JupyterLab がブラウザと連動して起動すれば OK です。

```sh
$ jupyter lab
```

:::note info
ブラウザが連動して起動しない場合は、環境変数 `BROWSER` にブラウザのパス をしてしてみてください。詳細は [JupyterLab を起動してもブラウザが連動して起動しない](#jupyterlab--を起動してもブラウザが連動して起動しない) を参照してください。
:::



# 複数の IPython Kernel を管理する

## Kernel とは？

実は私は Kernel について よく分かっていなかったのですが、Jupyter で使える Kernel ([Jupyter kernels](https://github.com/jupyter/jupyter/wiki/Jupyter-kernels))　を見て直感的に理解できました。 IPython の他、R, Julia, bash や Ansible　も使えます。JupyterLab/Console 等  は フロントエンド、Kenrnel は バックエンドと考えると分かりやすいかと思います。

> The IPython kernel is the Python execution backend for Jupyter.
https://ipython.readthedocs.io/en/stable/install/kernel_install.html

Jupyter(IPython) 環境の構成要素は 以下のように整理できます。

| 構成要素 | 役割 |
| -------- | ---- |
| Python   | プロブラム言語 |
| IPython |  Pythonの実行環境 |
| IPython Kernel (ipykernel) | バックエンド（IPython をJupyter で使えるようにする） |
| Jupyter Lab/Notebook/Console.. | フロントエンド（ユーザインタフェース）|


## 仮想環境の作成
以降は、`IPython Kernel` を複数使う場合の手順です。手順は Qiita で記事を書かれている方と同じなので 少し詳しく書いてみたいと思います。

Kernel は 各 notebook 用の仮想環境で作成します。下の例では、`venv` という名前で作成していますが 分かりやすい名前を付けても良いです。

 `(venv)` は、仮想環境のプロンプトです。 

```sh
# Change directory to your notebook directory 
$ cd <your notebook>

# Use python3
$ python3 -m venv venv

# Activate
$ . venv/bin/activate

# Upgrade Pip 
# 仮想環境に入ったら venv 配下の Python に を参照するので Python3 としなくても良い
(venv) $ python -m pip install --upgrade pip
```

## IPython Kernel のインストール

`venv` 内で `ipykernel` をインストールします。 これを忘れると Kernel をインストールしたのに、module が見つからないトラブルに遭遇（後述) します。

**`ipykernel` パッケージのインストール**

```sh
(venv) $ pip install ipykernel
```

**`IPython Kernel` のインストール**

`--name` に kernel の名前を指定します。表示名を変えたい場合は `--display-name` を指定します。

```sh
(venv) $ ipython kernel install --user --name myenv --display-name "Python (myenv)"
```



`--user` を付けると `~/.local/share` にコピーされます。(オプションなしの場合は、 `/usr/local/share/jupyter`)

**Kernel の表示**

```sh
(venv) $ jupyter kernelspec list
Available kernels:
  coursera    /home/ubuntu/.local/share/jupyter/kernels/coursera
  myenv       /home/ubuntu/.local/share/jupyter/kernels/myenv
  python3     /home/ubuntu/.local/share/jupyter/kernels/python3
```

Kernel のフォルダには、resources フォルダの中身(ロゴの画像)と `kernel.json` があります。

```sh
(venv) $ ls -ltr /home/ubuntu/.local/share/jupyter/kernels/coursera
total 12
-rw-r--r-- 1 ubuntu ubuntu 2180 Mar 20 11:41 logo-64x64.png
-rw-r--r-- 1 ubuntu ubuntu 1084 Mar 20 11:41 logo-32x32.png
-rw-r--r-- 1 ubuntu ubuntu  183 Mar 20 11:46 kernel.json
```

`kernel.json` には、 Kernel を起動するときのパラメータが入っています。(仮想環境 の `python` が指定されています）

```json:kernel.json
{
 "argv": [
  "/home/ubuntu/notebook/Coursera/venv/bin/python",
  "-m",
  "ipykernel_launcher",
  "-f",
  "{connection_file}"
 ],
 "display_name": "Coursera",
 "language": "python"
```

余談ですが `kernel.json` にある `{connecton_file}` は、Kernel の 起動時に `kernel-<kernel uuid>.json` という名前で  `~/.local/share/jupyter/runtime/` に作成されます。

`kernel-<kernel uuid>.json`　には、Kernel の 各種サービスの port 番号 や session key など の情報が記載されています。このファイルは JupyterLab 終了時には 自動で 削除されます。[^4]

[^4]: https://github.com/jupyter/jupyter_client/blob/master/jupyter_client/connect.py


```sh
$ ls -ltr ~/.local/share/jupyter/runtime
total 16
-rw------- 1 ubuntu ubuntu  45 Mar 15 19:35 notebook_cookie_secret
-rw-r--r-- 1 ubuntu ubuntu 272 Mar 22 14:32 nbserver-1150.json
-rw-r--r-- 1 ubuntu ubuntu 670 Mar 22 14:32 nbserver-1150-open.html
-rw------T 1 ubuntu ubuntu 263 Mar 22 14:32 kernel-7018c3c8-dd15-4d9a-ada6-aabde48e2513.json
```

**Kernel の アンインストール**

Kernelは 以下のコマンドでアンインストールできます。

```sh
(venv) $ jupyter kernelspec uninstall <kernel name> 
```


#  JupyterLab でカーネルを選択


JupyterLab を起動すると、画面右上で Kernel が選択できるようになっています。

```sh
(venv) $ jupyter lab
```

![switch_kernel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118309/c6c76f7c-6e06-af7c-2240-dbf716677e05.png)

## 仮想環境でのパッケージインストール

あとは、仮想環境に pip でパッケージをインストールするだけです。JupyterLab の再起動は必要ありません。

各notebootk で requirements.txt に必要パッケージをまとめておくと、環境を再構築するときに便利です。

```sh
(venv) $ pip install -r requirements.txt
```

例：Coursera 用

```txt:requirements.txt
keras==2.0.7
Pillow==7.0.0
tensorflow==1.2.1
scipy==1.4.1
pydot==1.4.1
opencv-python==4.1.2.30
Faker==0.8.10
tqdm
babel
h5py
numpy
matplotlib
pandas
pydub
sklearn
```


# よくあるトラブル

##  Kernel を選択しているのに ModuleNotFoundError 

仮想環境に パッケージをインストールして、Kernel も選択できたのに `ModuleNotFoundError:` で import できない場合は、jupyter Lab で `site-packages` の path を確認してください。

仮想環境でインストールした パッケージは (`'<notebook path>/venv/lib/python3.8/site-packages'`) から読み込まれます。

```sh
# Jupyter Lab で確認
[1]: import sys; from pprint import pprint; pprint(sys.path)
['/home/ubuntu/notebook/kalman_filter',
 '/usr/lib/python38.zip',
 '/usr/lib/python3.8',
 '/usr/lib/python3.8/lib-dynload',
 '',
 '/home/ubuntu/notebook/kalman_filter/venv/lib/python3.8/site-packages'] # ←これ
```

この PATH がない場合は、仮想環境内で `ipykernel` がインストールされていない可能性があります。`ipykernel` を仮想環境に インストールして Kernel の再作成をしてみてください。

`sys.path` は手動で追加することができますが、Kernel を切り替えるだけ の方が分かりやすいと思います。

```sh
# 環境変数に追加する場合
$ export PYTHONPATH=<site-package path>

# JupyterLab/IPython 上 で追加する場合
import sys; sys.path.append(<site-package path>)
```

## JupyterLab  を起動してもブラウザが連動して起動しない

Jupyter lab を起動してもブラウザが起動しない場合があります。 (2023.4.15 追記)
```sh
$ jupyter lab
..
[W 2023-04-15 10:38:19.952 ServerApp] No web browser found: Error('could not locate runnable browser').
```

発生した環境:
```sh
Ubuntu 22.04.2 LTS (WSL2)
Python 3.10.6
jupyterlab 3.6.3
```

環境変数 `BROWSER` に ブラウザのパスを指定します。`~/.bashrc` に書いておきます。
```sh
# ~/.bashrc
# WSL2から見えるWindowsのパスを指定する。環境に合わせて変更してください。
export BROWSER="/mnt/c/Program Files/Google/Chrome/Application/chrome.exe"
```


ちなみに設定ファイル(`~/.jupyter/jupyter_lab_config.py`) の `c.ServerApp.browser` に指定してもダメでした。
```py
## Specify what command to use to invoke a web
#                        browser when starting the server. If not specified, the
#                        default browser will be determined by the `webbrowser`
#                        standard library module, which allows setting of the
#                        BROWSER environment variable to override it.
#  Default: ''
c.ServerApp.browser = '/mnt/c/Program Files/Google/Chrome/Application/chrome.exe'
```

# おわりに

Python環境が混沌としてきたので自分用のメモも兼ねてまとめました。これから機械学習を始めようと思っている方や、Windows 上での Python 環境構築を迷っている方の何らかのお役に立てればうれしいです。

# 参考記事

- [jupyter notebookでvenvを使う](https://qiita.com/John_sugai/items/80a5d36673ba2b6ef7f0)
- [JupyterLabのすゝめ](https://qiita.com/kirikei/items/a1639954ce5ccaf7ac3c)