物理のバス停 by salt22g

とある物理学見習いの備忘録。

Windows PCをUbuntuにする際の注意 (Secure Boot設定)

備忘録

企業から購入したWindows11インストール済みPCのOSをUbuntu22.04に変更し、機械学習用の環境構築を進めていたところ...

nvidia-diriverの変更時に突然ターミナルがピンク色になってパニックに。

UEFI BIOSのSecure Boot設定が有効になっているためドライバの変更時にキー認証等を求められるようでした。

再起動してUEFI BIOSのAdvanced MenuからBootの設定画面へ

Secure Bootを "UEFI Windows OS" -> "Other OS"に変更。

その後、環境構築を最初からやり直したら警告灯が出ることなく、うまくいきました。

VS Code + Docker + GPUサーバーによる開発環境の設定 (Tiny ImageNetを題材に)

目的

学生ゼミの課題を題材にVS Codeを用いたサーバーへのログインDockerでの作業手順のチュートリアルを作成する。

Tiny imagenetについてはスタンフォード大の以下のページを参照してください。
cs231n.stanford.edu

Tiny ImageNet https://medium.com/@lokeshpara17/tiny-imagenet-using-pytorch-42a3f2ee3c9d

Tiny ImageNet using PyTorch. I joined in FreeCodeCamp course where… | by Lokesh Para | Medium


セットアップ、実行の手順を示します。
間違いや非推奨なものがあればコメント等でぜひご指摘・指導ください。

以下の記載ルール&注意事項
  • $で始まるのはターミナル(シェル)に打ち込むコマンド。コピぺするときは$の後ろからコピーしてください。
  • 今回実行した環境のCUDAは諸事情でバージョンが古いためdocker imageやコードのバージョンも古くなっています。
  • ご指摘があった場合、記載内容・推奨方法を変更することがあります。

GPUマシンに接続

$ ssh hogehoge
$ cd # <作業するdirectoryに移動>

CUDA Versionの確認

$ nvidia-smi

+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.33.01    Driver Version: 440.33.01    CUDA Version: 10.2     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  ************...  On   | 00000000:18:00.0 Off |                  N/A |
| 27%   24C    P8    13W / 250W |      0MiB / 11019MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

docker imageをpull(ダウンロード)

このマシンでは"CUDA Version: 10.2"とあるので対応するdocker imageを以下のサイトから探す。
tecsingularity.com


hub.docker.com

$ docker pull tensorflow/tensorflow:2.1.0-gpu-py3

以下のコマンドを実行してpullしたimageが表示されればok

$ docker images
REPOSITORY                     TAG                 IMAGE ID            CREATED             SIZE
tensorflow/tensorflow          2.1.0-gpu-py3       e2a4af785bdb        3 years ago         4.11GB

docker containerの生成

dockerfileを作成する。以下のリポジトリにDockerfileやコードをいくつか置いてみました。

github.com

$ git clone https://github.com/ayumisalt/tiny_imagenet_tutorial.git
$ cd tiny_imagenet/docker

4つのファイルがこのディレクトリに存在しています。

  • Dockerfile : build時に実行するコマンドがまとまっている。
  • requirements_apt.txt: apt-getで取得するコマンドを入れたリスト
  • requirements_pip.txt: pip install で取得するパッケージのリスト
  • zshrc: おすすめのzshrcの設定

zshrcはこちらのサイトから拝借
少し凝った zshrc · GitHub

Dockerfileの中身をlessコマンドなどで眺めてみるとrequirements_*.txtやzshrcが読み込まれている部分を確認できると思います。
必要があればrequirements_*.txtに自分が作業に使いたいパッケージを入れてください。

docker buildコマンドでimageを生成
docker build . -t <作成するimageの名前>
docker runコマンドでコンテナを作成
$ docker run --name <コンテナの名前> --gpus all -it --ulimit memlock=-1 --ulimit stack=67108864 -v <作業dirがあるdisk (例:data00)> --workdir=<作業dirのpath> --ipc=host <先ほど作成したimageの名前> /usr/bin/zsh

上記コマンドにて、コンテナ内で使用するGPUについては以下のように記載します。

  • --gpus all: 全てのGPUを使用
  • --gpus '"device=0"': 特定の番号のGPUを使用
gpuが使用できることを確認する。

コンテナ内でpythonインタープリターモードで起動し、次のコマンドを実行。

>>> from tensorflow.python.client import device_lib
>>> device_lib.list_local_devices()

以下のような出力が得られればok

$ python
Python 3.6.9 (default, Nov  7 2019, 10:44:02)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from tensorflow.python.client import device_lib
2023-07-28 07:55:32.779747: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libnvinfer.so.6
2023-07-28 07:55:32.781640: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libnvinfer_plugin.so.6
>>> device_lib.list_local_devices()
physical_device_desc: "device: XLA_GPU device"
, name: "/device:GPU:0"
device_type: "GPU"
memory_limit: 10732738970
locality {
  bus_id: 1
  links {
  }
}

VS CodeからGPUマシン->コンテナに接続

サーバーへの接続、dockerコンテナの起動、コーディング、学習の管理などについてVScodeを使って管理することができます。
以下のような記事が参考になります。

weseek.co.jp
buzz-server.com

Visual studio codeのインストール

code.visualstudio.com
上記のリンクからダウンロード・解凍することでアプリケーションとして使用できます。
(ここからの説明はMacの場合の例です。)

VS Codeを起動したら画面左の四つの四角が並んだボタンをクリックして検索欄に"ssh"と入力
拡張機能のRemote SSHをインストールします。

拡張機能のRemote SSHをインストール

ターミナルからsshでサーバーに接続できている前提なので".ssh/config内の設定については省略します。
左下の青い><のボタンを押すと"Connect to Host"という表記が出てくるのでこちらをクリック。

左下の青い> <マークを押した時の画面

ターミナル側での設定が完了していれば、接続したいサーバーが候補として表示されると思います。

Connect to Hostで表示される接続先候補
接続先での拡張機能インストール

ターミナルでの設定がうまくいっていればここまでの手順でVS Codeでサーバーに接続できます。
続いて作業のために先ほど作ったdockerコンテナに接続しましょう。

拡張機能の検索欄に"docker"と入力します。一番上に出てくる拡張機能をインストールします。

接続先サーバーでの拡張機能インストール

ここで自分のマシンとサーバーのどちらに拡張機能を入れたか混乱することもありますが検索欄に何も書いていない状態であれば現在どの拡張機能がインストールされているか確認できます。

インストールされた拡張機能の確認

Dockerの拡張機能をクリックすると先ほど作成したDockerコンテナを見つけることができるはずです。
右クリックすると"Attach Visual Studio Code"という表示が見つかるのでこれをクリックします。
すると新しいWindowが立ち上がります。(再度パスワードなどを求められることもあるかもしれません。)

拡張機能Dockerでコンテナに接続

Macであれば⌘ + Oで開く作業ディレクトリを指定できます。
上部のメニューバーのFile/Open folder...でも同じく作業ディレクトリを探して開くことができます。

ディレクトリを開く際、別windowが立ち上がり再度パスワードを求められる場合がありますが、最近使った項目に保存されれば次回以降は一回で接続できると思います。
この画面までこればコードの編集・実行検索をVS CodeGUIベースで行うことができます。

VS Codeの編集画面

(ファイル名が黄色くなってるのは目をつむってください...)

"python"や"Pylance"の拡張機能によってコーディング時のコマンド補完も可能です。
いろいろ拡張機能があるらしい...
yurupro.cloud

実行

git clone したディレクトリのsrc以下にchat gptに書いてもらったコードがあります。
序文で述べた通り、少し古いtensorflow 1系での実装になっているところがあるのでご了承ください。
実行の際はメニューバーのTerminalを押すとzsh(もしくはbash)が立ち上がります。

例えばbase_model.pyを一枚目のGPUで実行する場合は

CUDA_VISIBLE_DEVICES=0 python base_model.py

で実行できます。

学習の監視

学習の監視、管理ツールにはいろいろと種類がありますが、可視化についてだけならtensorboardが簡単です。
www.tensorflow.org

VS Code上で⌘ + Shift + Pを押すと以下のような画面(コマンドパレット)が出てきます。

コマンドパレットからtensorboardを実行

ここでは一番上に出てきている"Python: Launch TensorBoard"をクリックすると学習のパラメータや結果を保存しているディレクトリを指定することができます。

logディレクトリーの指定画面

このリポジトリで作成したコードでは"logs/fit"がlogを保存するディレクトリになっているので、Select another folderをクリックした後、該当のパスを指定します。

Tensorboardでは学習の様子やモデルの構造を可視化・比較することができます。

LossやAccuracyを監視・比較
モデル構造の可視化

まとめ

長くなりましたが、Dockerコンテナの構築、サーバーへの接続、コーディング、学習の監視までの手順を記載してみました。
経験上、ネットの記事通りにやってもうまくいかないことがよくあります。
その度に複数のサイトを調べたり、ドキュメントやコードを読んだり、先輩やChat GPTに聞くことで自分の理解が深まると思います。
使えるGPUサーバーがある人はこれを機にGoogle colabから脱却してみましょう。

改めて、間違いの指摘などお待ちしております。

分散共分散行列で定義された楕円体の断面算出

ある生物の神経細胞画像データを機械学習解析で使える形として抽出する。

アノテーションのデータがMATLABで解析されたファイルで与えられていた。
github.com
figshare.com


.matファイルから辞書データとして抜き出したところ、細胞の形が中心の座標と三次元の分散共分散行列で表されていた。

# 例
    # 中心座標
    center = [50, 50, 0]

    # 分散共分散行列
    # [x_var, xy_covar, zx_covar],
    # [xy_covar, y_var, yz_covar],
    # [zx_covar, yz_covar, z_var],

    cov_matrix = np.array(
        [
            [37.67218941864915, 9.97966493551884, 2.1263842045250754],
            [9.97966493551884, 14.353317888367672, 5.0364599497266935],
            [2.1263842045250754, 5.0364599497266935, 15.805485755173178],
        ]
    )

描画するとこんな感じ。

def show_ellipsoid(center, cov_matrix):
    # 固有値と固有ベクトルを計算
    eigenvalues, eigenvectors = np.linalg.eig(cov_matrix)

    # 楕円体を描画するためのパラメータ
    u = np.linspace(0, 2 * np.pi, 100)
    v = np.linspace(0, np.pi, 100)
    x = np.outer(np.cos(u), np.sin(v))
    y = np.outer(np.sin(u), np.sin(v))
    z = np.outer(np.ones(np.size(u)), np.cos(v))

    # 固有値に応じて楕円体を変形
    for i in range(len(eigenvalues)):
        x = np.sqrt(eigenvalues[i]) * (eigenvectors[0, i] * x)
        y = np.sqrt(eigenvalues[i]) * (eigenvectors[1, i] * y)
        z = np.sqrt(eigenvalues[i]) * (eigenvectors[2, i] * z)

    # 中心座標を追加
    x += center[0]
    y += center[1]
    z += center[2]

    # 3Dプロットを作成
    fig = plt.figure()
    ax = fig.add_subplot(111, projection="3d")

    # 楕円体をプロット
    ax.plot_surface(x, y, z, color="b", alpha=0.2)

    # 中心座標をプロット
    ax.scatter(center[0], center[1], center[2], color="r", s=100)

    # 軸ラベルの設定
    ax.set_xlabel("X")
    ax.set_ylabel("Y")
    ax.set_zlabel("Z")

    # グラフの表示
    plt.show()
細胞の形を表す楕円体

顕微鏡で撮影された画像では上記のような楕円体の細胞が任意のzでスライスした楕円として観察される。

ChatGPTに助けてもらって関数を実装した。

def get_ellipse_intersection(center, cov_matrix, z):
    # 平面との交点で分散共分散行列を更新
    shifted_cov_matrix = cov_matrix.copy()
    shifted_cov_matrix[:2, :2] -= (z - center[2]) ** 2 * np.linalg.inv(
        cov_matrix[:2, :2]
    )

    # 2次元の分散共分散行列を抽出
    cov_2d = shifted_cov_matrix[:2, :2]

    # 固有値と固有ベクトルを計算
    eigenvalues, eigenvectors = np.linalg.eig(cov_2d)

    # 楕円のパラメータを計算
    semi_major_axis = np.sqrt(eigenvalues[0])
    semi_minor_axis = np.sqrt(eigenvalues[1])
    angle = np.arctan2(eigenvectors[1, 0], eigenvectors[0, 0])

    return semi_major_axis, semi_minor_axis, angle


def add_ellipse(canvas, x, y, semi_major_axis, semi_minor_axis, angle):
    center = (int(x), int(y))  # 中心座標を整数に変換
    axes = (int(semi_major_axis), int(semi_minor_axis))  # 軸の長さを整数に変換
    angle_degrees = int(np.degrees(angle))  # 角度を度数法に変換
    cv2.ellipse(
        canvas,
        center,
        axes,
        angle_degrees,
        0,
        360,
        255,
        thickness=-1,
    )

結果

断面として切り取られる楕円
opencvで画像化(y軸は反転する)


多分あってるはず...

コードの全体図は以下の通りです。

import numpy as np
import matplotlib.pyplot as plt
import cv2
import math
import os


def show_ellipsoid(center, cov_matrix):
    # 固有値と固有ベクトルを計算
    eigenvalues, eigenvectors = np.linalg.eig(cov_matrix)

    # 楕円体を描画するためのパラメータ
    u = np.linspace(0, 2 * np.pi, 100)
    v = np.linspace(0, np.pi, 100)
    x = np.outer(np.cos(u), np.sin(v))
    y = np.outer(np.sin(u), np.sin(v))
    z = np.outer(np.ones(np.size(u)), np.cos(v))

    # 固有値に応じて楕円体を変形
    for i in range(len(eigenvalues)):
        x = np.sqrt(eigenvalues[i]) * (eigenvectors[0, i] * x)
        y = np.sqrt(eigenvalues[i]) * (eigenvectors[1, i] * y)
        z = np.sqrt(eigenvalues[i]) * (eigenvectors[2, i] * z)

    # 中心座標を追加
    x += center[0]
    y += center[1]
    z += center[2]

    # 3Dプロットを作成
    fig = plt.figure()
    ax = fig.add_subplot(111, projection="3d")

    # 楕円体をプロット
    ax.plot_surface(x, y, z, color="b", alpha=0.2)

    # 中心座標をプロット
    ax.scatter(center[0], center[1], center[2], color="r", s=100)

    # 軸ラベルの設定
    ax.set_xlabel("X")
    ax.set_ylabel("Y")
    ax.set_zlabel("Z")

    # グラフの表示
    plt.show()


def get_ellipse_intersection(center, cov_matrix, z):
    # 平面との交点で分散共分散行列を更新
    shifted_cov_matrix = cov_matrix.copy()
    shifted_cov_matrix[:2, :2] -= (z - center[2]) ** 2 * np.linalg.inv(
        cov_matrix[:2, :2]
    )

    # 2次元の分散共分散行列を抽出
    cov_2d = shifted_cov_matrix[:2, :2]

    # 固有値と固有ベクトルを計算
    eigenvalues, eigenvectors = np.linalg.eig(cov_2d)

    # 楕円のパラメータを計算
    semi_major_axis = np.sqrt(eigenvalues[0])
    semi_minor_axis = np.sqrt(eigenvalues[1])
    angle = np.arctan2(eigenvectors[1, 0], eigenvectors[0, 0])

    return semi_major_axis, semi_minor_axis, angle


def add_ellipse(canvas, x, y, semi_major_axis, semi_minor_axis, angle):
    center = (int(x), int(y))  # 中心座標を整数に変換
    axes = (int(semi_major_axis), int(semi_minor_axis))  # 軸の長さを整数に変換
    angle_degrees = int(np.degrees(angle))  # 角度を度数法に変換
    cv2.ellipse(
        canvas,
        center,
        axes,
        angle_degrees,
        0,
        360,
        255,
        thickness=-1,
    )


if __name__ == "__main__":
    os.makedirs("test_figs", exist_ok=True)
    os.makedirs("test_imgs", exist_ok=True)

    # 中心座標
    center = [50, 50, 0]

    # 分散共分散行列

    # [x_var, xy_covar, zx_covar],
    # [xy_covar, y_var, yz_covar],
    # [zx_covar, yz_covar, z_var],

    cov_matrix = np.array(
        [
            [37.67218941864915, 9.97966493551884, 2.1263842045250754],
            [9.97966493551884, 14.353317888367672, 5.0364599497266935],
            [2.1263842045250754, 5.0364599497266935, 15.805485755173178],
        ]
    )

    show_ellipsoid(center, cov_matrix)

    # 任意のz座標で交わる断面の楕円を求める
    for i, z in enumerate(range(-15, 15)):
        semi_major_axis, semi_minor_axis, angle = get_ellipse_intersection(
            center, cov_matrix, z
        )
        # 楕円を描画
        theta = np.linspace(0, 2 * np.pi, 100)
        x = semi_major_axis * np.cos(theta)
        y = semi_minor_axis * np.sin(theta)
        x_rotated = x * np.cos(angle) - y * np.sin(angle)
        y_rotated = x * np.sin(angle) + y * np.cos(angle)

        plt.figure(figsize=(5, 5))
        plt.plot(x_rotated + center[0], y_rotated + center[1])
        plt.xlim(40, 60)
        plt.ylim(40, 60)
        plt.xlabel("X")
        plt.ylabel("Y")
        plt.title(f"Ellipse Intersection with z={z} Plane")
        plt.grid(True)
        plt.savefig(f"test_figs/{i:03}.png")
        plt.close()

        xy_canvas = np.zeros((100, 100), dtype="uint8")
        if (
            math.isnan(semi_major_axis) == False
            and math.isnan(semi_minor_axis) == False
        ):
            add_ellipse(
                xy_canvas, center[0], center[1], semi_major_axis, semi_minor_axis, angle
            )

        cv2.imwrite(f"test_imgs/{i:03}.png", xy_canvas)

PyTorch-Lightningのチュートリアルがちゃんと動かない

チュートリアルが動かない?意味ねえじゃん

結論から言うと最初の実行セルを

!pip install segmentation-models-pytorch
# !pip install pytorch-lightning==1.5.4
!pip install pytorch-lightning==1.9.5

と変更すればよかった。

以下のチュートリアルgoogle colabで動かそうとした。

セグメンテーションタスクのサンプルとして以下のサイトで紹介されていた。

note-tech.com

https://colab.research.google.com/github/qubvel/segmentation_models.pytorch/blob/master/examples/binary_segmentation_intro.ipynb

!pip install segmentation-models-pytorch
!pip install pytorch-lightning==1.5.4

import os
import torch
import matplotlib.pyplot as plt
import pytorch_lightning as pl
import segmentation_models_pytorch as smp

from pprint import pprint
from torch.utils.data import DataLoader

---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
<ipython-input-2-495d7f80e44f> in <cell line: 4>()
      2 import torch
      3 import matplotlib.pyplot as plt
----> 4 import pytorch_lightning as pl
      5 import segmentation_models_pytorch as smp
      6 

4 frames
/usr/local/lib/python3.10/dist-packages/pytorch_lightning/utilities/apply_func.py in <module>
     28 if _TORCHTEXT_AVAILABLE:
     29     if _compare_version("torchtext", operator.ge, "0.9.0"):
---> 30         from torchtext.legacy.data import Batch
     31     else:
     32         from torchtext.data import Batch

ModuleNotFoundError: No module named 'torchtext.legacy'

importでこけるんかい

ひとまずtorchtextを追加でインストールしてみる。

!pip install segmentation-models-pytorch
!pip install pytorch-lightning==1.5.4
!pip install torchtext

同じエラーで進まず。

----> 4 import pytorch_lightning as pl
ModuleNotFoundError: No module named 'torchtext.legacy'


ググって見つけた情報を順番に試す。

www.datasciencelearner.com

!pip install segmentation-models-pytorch
!pip install pytorch-lightning==1.5.4
!pip install torchtext==0.10.0

ERROR: Could not find a version that satisfies the requirement torchtext==0.10.0 (from versions: 0.1.1, 0.2.0, 0.2.1, 0.2.3, 0.3.1, 0.4.0, 0.5.0, 0.6.0, 0.12.0, 0.13.0, 0.13.1, 0.14.0, 0.14.1, 0.15.1, 0.15.2)
ERROR: No matching distribution found for torchtext==0.10.0

💢

!pip install segmentation-models-pytorch
!pip install pytorch-lightning==1.5.4
!pip install torchtext==0.14.0

- 中略 -

ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
torchaudio 2.0.2+cu118 requires torch==2.0.1, but you have torch 1.13.0 which is incompatible.
torchdata 0.6.1 requires torch==2.0.1, but you have torch 1.13.0 which is incompatible.
torchvision 0.15.2+cu118 requires torch==2.0.1, but you have torch 1.13.0 which is incompatible.
Successfully installed torch-1.13.0 torchtext-0.14.0

エラーが出ているが0.14.0がインストールできているようなので続行してみる。

---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
<ipython-input-2-495d7f80e44f> in <cell line: 4>()
      2 import torch
      3 import matplotlib.pyplot as plt
----> 4 import pytorch_lightning as pl
      5 import segmentation_models_pytorch as smp
      6 

4 frames
/usr/local/lib/python3.10/dist-packages/pytorch_lightning/utilities/apply_func.py in <module>
     30         from torchtext.legacy.data import Batch
     31     else:
---> 32         from torchtext.data import Batch
     33 else:
     34     Batch = type(None)

ImportError: cannot import name 'Batch' from 'torchtext.data' (/usr/local/lib/python3.10/dist-packages/torchtext/data/__init__.py)

うーん、深みにハマってしまっている気がする。
一旦初期状態に戻してみる。

pytorch-lightningのversionを変更してみた。

!pip install segmentation-models-pytorch
# !pip install pytorch-lightning==1.5.4
!pip install pytorch-lightning==1.9.5

これでimportのエラーは出なくなった。
この後のtrainningも動いたのでよしとする。

ちなみに

バージョンを指定せずにinstallすると...

!pip install segmentation-models-pytorch
# !pip install pytorch-lightning==1.5.4
!pip install pytorch-lightning

importではエラーは出ないが

trainer = pl.Trainer(
    gpus=1, 
    max_epochs=5,
)

trainer.fit(
    model, 
    train_dataloaders=train_dataloader, 
    val_dataloaders=valid_dataloader,
)

のセルを実行すると

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-9-f1c61f5ae996> in <cell line: 1>()
----> 1 trainer = pl.Trainer(
      2     gpus=1,
      3     max_epochs=5,
      4 )
      5 

/usr/local/lib/python3.10/dist-packages/pytorch_lightning/utilities/argparse.py in insert_env_defaults(self, *args, **kwargs)
     67 
     68         # all args were already moved to kwargs
---> 69         return fn(self, **kwargs)
     70 
     71     return cast(_T, insert_env_defaults)

TypeError: Trainer.__init__() got an unexpected keyword argument 'gpus'

pytorch-lightning v2から仕様が変わっているらしい。

stackoverflow.com

このサイトに従って

trainer = pl.Trainer(max_epochs=5,accelerator="auto")

trainer.fit(
    model, 
    train_dataloaders=train_dataloader, 
    val_dataloaders=valid_dataloader,
)

INFO:pytorch_lightning.utilities.rank_zero:GPU available: True (cuda), used: True
INFO:pytorch_lightning.utilities.rank_zero:TPU available: False, using: 0 TPU cores
INFO:pytorch_lightning.utilities.rank_zero:IPU available: False, using: 0 IPUs
INFO:pytorch_lightning.utilities.rank_zero:HPU available: False, using: 0 HPUs

gpuは認識できたっぽいが

---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
<ipython-input-10-dbbdb5087c44> in <cell line: 3>()
      1 trainer = pl.Trainer(max_epochs=5,accelerator="auto")
      2 
----> 3 trainer.fit(
      4     model,
      5     train_dataloaders=train_dataloader,

5 frames
/usr/local/lib/python3.10/dist-packages/pytorch_lightning/trainer/configuration_validator.py in __verify_train_val_loop_configuration(trainer, model)
     77     # check legacy hooks are not present
     78     if callable(getattr(model, "training_epoch_end", None)):
---> 79         raise NotImplementedError(
     80             f"Support for `training_epoch_end` has been removed in v2.0.0. `{type(model).__name__}` implements this"
     81             " method. You can use the `on_train_epoch_end` hook instead. To access outputs, save them in-memory as"

NotImplementedError: Support for `training_epoch_end` has been removed in v2.0.0. `PetModel` implements this method. You can use the `on_train_epoch_end` hook instead. To access outputs, save them in-memory as instance attributes. You can find migration examples in https://github.com/Lightning-AI/lightning/pull/16520.

と言うことでまた関数名や仕様が変わっておりうまくいかない。
同じv1系の最新バージョンを使うことでなんとか動かした。
原理的にはv2系でも動かせるはずだが今日はここまで。

matplotlibで統計・系統誤差付きの図を作る(python)

かなり久々の更新。

matplotlibとpythonで統計誤差、系統誤差を描写する簡単なスクリプト
こんな感じの図が書けます(今回は横軸Event No、縦軸が測定値や誤差など)

統計誤差を棒、系統誤差をboxで描写


Eventごとに統計誤差、系統誤差が異なる場合の絵ですね。
同じ研究対象に対するいくつかの実験結果をまとめる際などに使われがちです。

他にいい方法をご存知の方がいましたらご教授ください。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import random

if __name__ == '__main__':

    mean_values = np.random.normal(100, 20, 10)  # 平均100、標準偏差20、event数10
    stat_errors = np.random.normal(20, 5, 10)  # 測定誤差の平均が20、ばらつきが5

    # 正負に非対称な誤差がつくことを想定
    syst_errors = []
    for i in range(len(mean_values)):
        syst_high = random.uniform(5, 10)
        syst_low = random.uniform(5, 10)
        syst_errors.append([syst_high, syst_low])


    fig, ax = plt.subplots()

    #Eventに番号付け
    events = list(range(len(mean_values)))

    # 誤差棒ありのplot
    plt.errorbar(events, mean_values, stat_errors, capsize=5, fmt='o', markersize=5, ecolor='black', markeredgecolor="black", color='k')

    # 各Eventで系統誤差を示すboxを描写
    event_counter = 0
    for meam, syst in zip(mean_values, syst_errors):
        r = patches.Rectangle(xy=(event_counter-0.2, meam-syst[1]), width=0.4, height=sum(syst), ec='k', fill=False)
        ax.add_patch(r)
        event_counter += 1

    ax.set_xticks(range(10))
    plt.show()


ポイントはmatplotlib.patchesです。
note.nkmk.me

matplotlibにはeventplotという関数があって名前がそれっぽいと思ったのですが全然違いました…

自前のMask画像からCOCO format jsonを作成

手作業でAnnotationなんてやってられるか!!!

ということで、画像処理でcoco formatのjsonを作るscriptを書きました。
簡易的なのでぜひ改造して使ってください。

ただしMask情報が二値化画像で取得できている前提です。
そもそも二値化できるなら物体検出いらないというツッコミはさておき…

f:id:salt22g:20201220204846p:plain
概念図

Mask R-CNN

機械学習において最も注目されている分野の一つが物体検出です。
自動運転や監視カメラなど、画像・映像情報から注目したい物体を抽出する技術で直感的かつ応用の幅が広い技術です。

その中でも有名かつ、開発が盛んなのがMask R-CNNと呼ばれる手法です。
論文はこちら
https://arxiv.org/abs/1703.06870

こんな感じで画像中から物体を検出し、それぞれがなんであるかを判断しています。

f:id:salt22g:20201220194651p:plain
https://github.com/matterport/Mask_RCNN/blob/master/assets/street.png

開発の歴史はこちらの記事によくまとまっています。
qiita.com

非常に優れた手法ですが、最初からこんな結果が得られるわけではなく、学習が必要です。
機械学習modelは正解の情報を持っている教師データを元に、最適な内部の重みを決定しています。
(機械学習について詳しく知りたい人はこちら。私も超初心者で勉強中です。)

ゼロから作るDeep LearningPythonで学ぶディープラーニングの理論と実装 斎藤 康毅
https://www.amazon.co.jp/dp/4873117585/ref=cm_sw_r_tw_dp_x_42Y3Fb1GA2SF8

機械学習スタートアップシリーズ これならわかる深層学習入門 (KS情報科学専門書) 瀧 雅人
https://www.amazon.co.jp/dp/4061538284/ref=cm_sw_r_tw_dp_x_e4Y3Fb8MTHZK4


先程の画像のように様々な物体を精度よく検出するためには大量の画像とそれぞれに正解の情報が必要です。
そこでデータセットとして学習および性能検証に用いられるベンチマークがCOCO datasetと呼ばれる巨大なdatasetです。

COCO dataset

cocodataset.org

"Common Objects in Context" 略してCOCO。
下の画像で示すように様々な画像と物体、それぞれが何であるか記述された正解情報をもっています。

f:id:salt22g:20201220200350p:plain
https://cocodataset.org/images/coco-examples.jpg

正解情報を持つjsonはこんな感じ。
https://docs.trainingdata.io/v1.0/Export%20Format/COCO/

{
  "info" : info, 
  "images" : [image], 
  "annotations" : [annotation], 
  "licenses" : [license],
}

info: {
  "year" : int, 
  "version" : str, 
  "description" : str, 
  "contributor" : str, 
  "url" : str, 
  "date_created" : datetime,
}

image: {
  "id" : int, 
  "width" : int, 
  "height" : int, 
  "file_name" : str, 
  "license" : int, 
  "flickr_url" : str, 
  "coco_url" : str, 
  "date_captured" : datetime,
}

license: {
  "id" : int, 
  "name" : str, 
  "url" : str,
}

このデータセットが基準となるformatをもっていることによって異なるmodelでも性能が比較できるようになっています。
またjsonのformatを揃えることで様々なデータを入れ替えても同じプログラム上で動くようになっています。

しかし、実際自分で使う際には専用の学習データを使って目的に特化したmodelを作成したいものです。
そのためには画像の中の正解情報を取得するAnnotation作業が必須になります。

Annotation作業

dev.classmethod.jp

f:id:salt22g:20201220201003p:plain
https://github.com/jsbroks/coco-annotator

めちゃくちゃめんどくさそう…

数枚ならまだしも学習データは数千枚になる場合が多いので手作業でぽちぽちしていると寿命が尽きてしまいます。
複雑な物体に対しては人間の手が必要ですが、簡易的な画像に対しては楽ができるはずです。

画像処理によるcoco format jsonの生成

用意した画像はこちらからダウンロードしました。
https://www.cis.upenn.edu/~jshi/ped_html/PennFudanPed.zip

簡単のため人がひとりしか写っていない画像だけをえらんでいます。
もちろん、複数の場合でもちょっとした改造を加えれば処理が可能です。

f:id:salt22g:20201220203804p:plain
左が入力画像、右が正解のMask情報の画像

Imageとmaskは同じ名前にしています。

  ┠ images
  ┃  ┠ 00001.png
  ┃  ┠ 00002.png
  ┃  ┠ 00003.png
  ┃  ┠ 以下略
  ┠ masks
  ┃  ┠ 00001.png
  ┃  ┠ 00002.png
  ┃  ┠ 00003.png
  ┃  ┠ 以下略
import json
import collections as cl
import numpy as np
import matplotlib.pyplot as plt
from scipy import ndimage
from skimage import measure
from skimage.segmentation import clear_border
from skimage.filters import threshold_otsu
import cv2
import glob
import sys
import os

### https://qiita.com/harmegiddo/items/da131ae5bcddbbbde41f

def info():
    tmp = cl.OrderedDict()
    tmp["description"] = "Test"
    tmp["url"] = "https://test"
    tmp["version"] = "0.01"
    tmp["year"] = 2020
    tmp["contributor"] = "salt22g"
    tmp["data_created"] = "2020/12/20"
    return tmp

def licenses():
    tmp = cl.OrderedDict()
    tmp["id"] = 1
    tmp["url"] = "dummy_words"
    tmp["name"] = "salt22g"
    return tmp

def images(mask_path):
    tmps = []
    files = glob.glob(mask_path + "/*.png")
    files.sort()

    for i, file in enumerate(files):
        img = cv2.imread(file, 0)
        height, width = img.shape[:3]

        tmp = cl.OrderedDict()
        tmp["license"] = 1
        tmp["id"] = i
        tmp["file_name"] = os.path.basename(file)
        tmp["width"] = width
        tmp["height"] = height
        tmp["date_captured"] = ""
        tmp["coco_url"] = "dummy_words"
        tmp["flickr_url"] = "dummy_words"
        tmps.append(tmp)
    return tmps


def annotations(mask_path):
    tmps = []

    files = glob.glob(mask_path + "/*.png")
    files.sort()
    
    for i, file in enumerate(files):
        img = cv2.imread(file, 0)
        tmp = cl.OrderedDict()
        contours = measure.find_contours(img, 0.5)
        segmentation_list = []

        for contour in contours:
            for a in contour:
                segmentation_list.append(a[0])
                segmentation_list.append(a[1])


        mask = np.array(img)
        obj_ids = np.unique(mask)
        obj_ids = obj_ids[1:]
        masks = mask == obj_ids[:, None, None]
        num_objs = len(obj_ids)
        boxes = []

        for j in range(num_objs):
            pos = np.where(masks[j])
            xmin = np.min(pos[1])
            xmax = np.max(pos[1])
            ymin = np.min(pos[0])
            ymax = np.max(pos[0])
            boxes.append([xmin, ymin, xmax, ymax])

        tmp_segmentation = cl.OrderedDict()

        tmp["segmentation"] = [segmentation_list]
        tmp["id"] = str(i)
        tmp["image_id"] = i
        tmp["category_id"] = 1
        tmp["area"] = float(boxes[0][3] - boxes[0][1]) * float(boxes[0][2] - boxes[0][0])
        tmp["iscrowd"] = 0
        tmp["bbox"] =  [float(boxes[0][0]), float(boxes[0][1]), float(boxes[0][3] - boxes[0][1]), float(boxes[0][2] - boxes[0][0])]
        tmps.append(tmp)
    return tmps

def categories():
    tmps = []
    sup = ["person"]
    cat = ["person"]
    for i in range(len(sup)):
        tmp = cl.OrderedDict()
        tmp["id"] = i+1
        tmp["name"] = cat[i]
        tmp["supercategory"] = sup[i]
        tmps.append(tmp)
    return tmps

def main(mask_path, json_name):
    query_list = ["info", "licenses", "images", "annotations", "categories", "segment_info"]
    js = cl.OrderedDict()
    for i in range(len(query_list)):
        tmp = ""
        # Info
        if query_list[i] == "info":
            tmp = info()

        # licenses
        elif query_list[i] == "licenses":
            tmp = licenses()

        elif query_list[i] == "images":
            tmp = images(mask_path)

        elif query_list[i] == "annotations":
            tmp = annotations(mask_path)

        elif query_list[i] == "categories":
            tmp = categories()

        # save it
        js[query_list[i]] = tmp

    # write
    fw = open(json_name,'w')
    json.dump(js,fw,indent=2)

args = sys.argv
mask_path = args[1]
#mask_path =  ""
json_name = args[2]
#json_name = "person_sample.json"

if __name__=='__main__':
    main(mask_path, json_name)


こちらの記事を参考にさせていただきました。ありがとうございました

qiita.com

www.slideshare.net

肝となるのはこの部分です。

contours = measure.find_contours(img, 0.5)

find_contoursは画像中の物体の輪郭のpix座標を取得してくれます。

f:id:salt22g:20201220202843p:plain
https://sabopy.com/py/scikit-image-72/

また、bboxについては

pos = np.where(masks[j])

この部分で取得したmask情報を持つpixの最大値最小値を使っています。
Mask R-CNNのdemo codeを参考にしています。
pytorch.org

取得したjson fileはこちら。

{
  "info": {
    "description": "Test",
    "url": "https://test",
    "version": "0.01",
    "year": 2020,
    "contributor": "salt22g",
    "data_created": "2020/12/20"
  },
  "licenses": {
    "id": 1,
    "url": "dummy_words",
    "name": "salt22g"
  },
  "images": [
    {
      "license": 1,
      "id": 0,
      "file_name": "FudanPed00011.png",
      "width": 459,
      "height": 420,
      "date_captured": "",
      "coco_url": "dummy_words",
      "flickr_url": "dummy_words"

   #〜中略〜#

  ],
    "annotations": [
      {
        "segmentation": [
          [
            48.0,
            129.00229357798165,
            47.96153846153846,
            129.0,
            48.0,
            128.9878048780488,
            48.016129032258064,
            129.0,
            48.0,

   #〜中略〜#

      "id": "0",
      "image_id": 0,
      "category_id": 1,
      "area": 62868.0,
      "iscrowd": 0,
      "bbox": [
        36.0,
        55.0,
        156.0,
        403.0
      ]
    },

   #〜中略〜#

  ],
  "categories": [
    {
      "id": 1,
      "name": "person",
      "supercategory": "person"
    }
  ],
  "segment_info": ""
}

元の画像とこのjson fileがあればほとんどのCOCOを基準に開発されている物体検出が動くはず。
ではまた。

sshで急にサーバーに繋げなくなったとき

サーバーがメンテナンス等を終えたあと再びsshで接続しようとするとこんなエラーにでくわすことがあります。

% ssh hogehoge
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@       WARNING: POSSIBLE DNS SPOOFING DETECTED!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
The RSA host key for hogehoge has changed,
and the key for the corresponding IP address hogehoge
is unknown. This could either mean that
DNS SPOOFING is happening or the IP address for the host
and its host key have changed at the same time.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
SHA256:hogehogehogehogehogehogehogehogehogehoge.
Please contact your system administrator.
Add correct host key in /Users/hoge/.ssh/known_hosts to get rid of this message.
Offending RSA key in /Users/hoge/.ssh/known_hosts:7
RSA host key for hogehoge has changed and you have requested strict checking.
Host key verification failed.
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
"誰かが厄介なことをしてるかもな!"
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
"誰かが盗聴してるかもな!(超意訳)"

かなりビビりますが、注目するのはこの部分。

Offending RSA key in /Users/hoge/.ssh/known_hosts:7

ホームディレクトリ以下にある隠しファイル.ssh/known_hostsを開いて該当の行を削除。
するとsshで接続できるようになりました。
めでたしめでたし。