クラウドエンジニアのノート

情報技術系全般,自分用メモを公開してます。

mAP(mean Average Precision)を手っ取り早く上げるには

はじめに

signateの物体認識コンペ(魚群検知)に参加したので、そのときに得た知見をいくつか共有したいと思います。(複数記事に分ける予定)

signate.jp

新記事公開しました。(21.02.11)

tmyoda.hatenablog.com

結論

先に結論を言ってしまうと、mAPを上げるには、推論時のconfidenceスコアのフィルターの値を小さくして大量に予測値を出すことです。
このコンペでは、1枚1クラス20まで予測を提出できたので、その上限まで予測値を出すのが良いです。

理由

お恥ずかしながらmAPの計算方法をきちんと把握してなかったのですが、計算式を追えば自明のことでした。

mAPの算出方法

物体認識の場合、TN(True Negative)は特に考えません。また、PrecisionとRecallの算出方法も少し違います。

mAPなので名前の通り、AP(Average Precision)の平均です。クラスごとにAPを計算してその平均がmAPになります。

APの算出方法

クラスごとにAPを算出します。 まず、この記事の通り、APはprecision-recall曲線の下側の面積です。

precisionとrecallの算出方法は、通常のクラス分類と同じです。

  • Precisionの算出方法

$$ Precision = \frac{正しく予測できた数}{予測の数} $$

  • Recallの算出方法

$$ Recall = \frac{正しく予測できた数}{すべての正解の数} $$

物体検出の場合は、予測1つ1つに対してこのprecisionとrecallを算出し、recall値0から1まででprecisionを積分します。

りんごが3つ写っている画像に対する予測が以下の7つだったとします。

このときの、りんごクラスのAPを求めてみます。ここでCorrect?とは、iouしきい値を満たし正解ラベルと予測ラベルが一致している場合はTrue、それ以外はFalseです。

Confidence score (Sorted) Correct? Precision Recall
0.99 True 1/1 (1.0) 1/3 (0.3)
0.97 False 1/2 (0.5) 1/3 (0.3)
0.84 False 1/3 (0.3) 1/3 (0.3)
0.74 True 2/4 (0.5) 2/3 (0.7)
0.32 False 2/5 (0.4) 2/3 (0.7)
0.21 False 2/6 (0.3) 2/3 (0.7)
0.01 True 3/7 (0.4) 3/3 (1.0)

次にrecall値0から1までで積分を行います。グラフを滑らかにするために、各Recallの値で横に見た時に、Precisionの値が最大の値に置き換えます。

f:id:tontainoti:20210210002654p:plain
りんごAP

そして、離散値なので、複数の点をサンプリングして積分計算します。 例として、recallを0.1刻みずつ(0.0, 0.1, 0.2, ..., 1.0)の合計11点で計算する場合は、以下の式になります。(COCOの場合は101点で補間するらしい) $$ AP = \frac{1}{11} \times (1.0 + 1.0 + 1.0 + 1.0 + 0.5 + 0.5 + 0.5 + \frac{3}{7} + \frac{3}{7} + \frac{3}{7} + \frac{3}{7}) = 0.655... $$

あとは、クラスごとのAPの平均を取ればmAPが算出できます。
また、COCOやPascal VOCでもmAPの算出方法は若干異なるようです。

mAPを上げるには

もう一度言いますが、予測を増やせばAPが上がるのでmAPも上がります。

上の例のように、たとえconfidence scoreが0.01でも、それが正解ならば、APは上がるのは見ての通りだと思います。

もう少し深く考えてみると、すべて正解数しないとrecall 1のときのprecisionが0になります。 recallが低いところしかprecisionの値がなくて、recallが高くなるとprecisionが0に近くなり、積分計算で大きくロスします。 つまり、とにかく予測しまくって、低confidenceスコアでも正解bboxを予測してたら、高recallでの低precisionを回避できるという訳です。

singularityでcuda+pytorchのコンテナの作り方

はじめに

tmyoda.hatenablog.com

この記事の亜種です。

singularityは--nvを付ければホストのGPUをマウントするので、本来はホストのcudaを使いますが、harmo2とharmo5のcudaバージョンが微妙に違ったりして環境構築に手間取ったので、cudaが入ったsingularityコンテナを作りました。

構成

  • ubuntu18.04.5LTS(元のcuda dockerコンテナのデフォルト)
  • cuda11.0
  • pytorch1.7.1
  • python3.8.5
  • pyenv
  • その他よく使うコマンド(git, vim, wget, curl...)

が入ってます。 pytorchをそのまま使う目的と、一応pipenvを使えるようにpyenvを入れておきました。(不要でしたら、.defからpyenv抜いてbuildしてください)

使い方

以下のsingularity libraryで公開しました。 https://cloud.sylabs.io/library/tmyoda/default/cuda-torch-pyenv

実行方法

  • python_pyenvというsandboxを作成する例
  • -wオプションをつけることでpipaptなどを使って外部ライブラリをインストールできる

sandbox作成

singularity build --sandbox torch_cuda_pyenv library://tmyoda/default/cuda-torch-pyenv

shellに入る

その他run, execも可 (詳細は 過去記事参照)

singularity shell -w --nv torch_cuda_pyenv

.defファイル

man apt によると apt(8) ではなく apt-get(8) をスクリプトで使用するよう推奨されているため、defファイル等のスクリプトでインストールを動かす場合はapt-getを使用した方が良いみたいです。

Bootstrap: docker
From: pytorch/pytorch:1.7.1-cuda11.0-cudnn8-runtime


%environment
    export LC_ALL=C.UTF-8
    export Lang=C.UTF-8
    export PATH="/usr/local/opt/openssl/bin:$PATH"

    # pyenv
    export PYENV_ROOT="/pyenv"
    export PATH="$PYENV_ROOT/bin:$PATH"
    eval "$(pyenv init -)"


%post
    apt-get update
    DEBIAN_FRONTEND=noninteractive apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev \
    libreadline-dev libsqlite3-dev wget curl llvm \
    xz-utils tk-dev libffi-dev liblzma-dev python3-distutils apt-utils\
    openssl git bzip2 vim  bash-completion

    git clone https://github.com/pyenv/pyenv.git ${PYENV_ROOT}

    export PYENV_ROOT="/pyenv"
    export PATH="$PYENV_ROOT/bin:$PATH"
    eval "$(pyenv init -)"


    # pipenv
    pip install -U pip

    # clean up
    apt-get clean
    rm -rf /var/lib/apt/lists/*

%labels
    Author tmyoda
    Version v1.0.1

ローカルでこの.defから.sifを作成する方法は過去記事に書きましたので、そちらを参照してください。

#6: 研究室機器の使い方/サーバ室/Singularityの使い方 #gpu

singularityでubuntu20.04+python(+pipenv)環境を構築

はじめに

pythonは仮想環境が豊富なので、わざわざコンテナ化する必要ある?って思っていましたが、 いざGPUクラスタ上で動かすときに環境構築ハマったので、そのとき作成したpythonを動かすコンテナをSingularity Libraryに公開しました。

構成

  • ubuntu20.04
  • pyenv
  • pipenv
  • その他よく使うコマンド(git, vim, wget, curl...)

が入ってます。

使い方

以下のsingularity libraryで公開しました。

実行方法

  • python_pyenvというsandboxを作成する例
  • -wオプションをつけることでpipaptなどを使って外部ライブラリをインストールできる

sandbox作成

singularity build --sandbox python_pipenv library://tmyoda/default/ubuntu-pyenv:20.04

shellに入る

その他run, execも可 (過去記事参照)

singularity shell -w --nv python_pipenv

.defファイル

man apt によると apt(8) ではなく apt-get(8) をスクリプトで使用するよう推奨されているため、defファイル等のスクリプトでインストールを動かす場合はapt-getを使用した方が良いみたいです。

Bootstrap: docker
From: ubuntu:20.04


%environment
    export LC_ALL=C.UTF-8
    export Lang=C.UTF-8
    export PIPENV_VENV_IN_PROJECT=1
    export PATH="/usr/local/opt/openssl/bin:$PATH"
    # pipenv property
    # export PIPENV_SKIP_LOCK=1

    # pyenv
    export PYENV_ROOT="/pyenv"
    export PATH="$PYENV_ROOT/bin:$PATH"
    eval "$(pyenv init -)"


%post
    # Change if you want
    INSTALL_PYTHON_VERSION=3.7.9

    apt-get update
    # 依存関係
    DEBIAN_FRONTEND=noninteractive apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev \
    libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \
    xz-utils tk-dev libffi-dev liblzma-dev python3-distutils \
    openssl git bzip2 vim  bash-completion

    git clone https://github.com/pyenv/pyenv.git ${PYENV_ROOT}

    export PYENV_ROOT="/pyenv"
    export PATH="$PYENV_ROOT/bin:$PATH"
    eval "$(pyenv init -)"

    # python setting
    pyenv install ${INSTALL_PYTHON_VERSION}
    pyenv global ${INSTALL_PYTHON_VERSION}

    # pipenv
    pip install -U pip
    pip install pipenv

    # clean up
    apt-get clean
    rm -rf /var/lib/apt/lists/*

%labels
    Author tmyoda
    Version v1.0.0

ローカルでこの.defから.sifを作成する方法は過去記事に書きましたので、そちらを参照してください。

tmyoda.hatenablog.com

docker, singularityでtzdata等の対話が必要なモジュールのインストールで止まるとき Please select the geographic area in which you live.

singularityをdefからbuildしていたのですが、以下ような画面でインストールが止まってました。

Configuring tzdata
------------------

Please select the geographic area in which you live. Subsequent configuration
questions will narrow this down by presenting a list of cities, representing
the time zones in which they are located.

どうやら、dockerからfromでubuntuを指定して、gitを一緒にインストールするときに発生するようです。

調べたところ、Dockerfileでの解決方法が見つかったので、参考にします。

qiita.com

解決方法(非推奨)

.defファイルの%postに以下の環境変数を追加

export DEBIAN_FRONTEND=noninteractive

なのですが、Docker公式によると、noninteractiveにし続けるのは非推奨みたいです。

解決方法

apt installのときだけ適用されるようにして、他のモジュールへの影響を抑えます。

DEBIAN_FRONTEND=noninteractive apt-get install -y git tzdata

pysparkの使い方に慣れるためにirisデータセットをいじってみる

はじめに

pysparkを触る機会があったので,irisデータセットで色々試してみました. 適当にメモ程度なのであしからず.

環境構築

sparkの環境をローカルに構築するのが大変そうだったので,以下のDockerコンテナをつかいました.

https://hub.docker.com/r/jupyter/pyspark-notebook

サンプル集

読み込み

from pyspark import *
from pyspark.sql import *
from pyspark.sql.types import *
import pyspark.sql.functions as F


conf = SparkConf()
sc = SparkContext.getOrCreate(conf=conf)
sqlContext = SQLContext(sc)


df = sqlContext.read.format('com.databricks.spark.csv') \
    .options(header='true', inferschema='true') \
    .load('/home/jovyan/work/iris.csv') 

df.show()
+-----------+----------+-----------+----------+-------+
|sepalLength|sepalWidth|petalLength|petalWidth|variety|
+-----------+----------+-----------+----------+-------+
|        5.1|       3.5|        1.4|       0.2| Setosa|
|        4.9|       3.0|        1.4|       0.2| Setosa|
|        4.7|       3.2|        1.3|       0.2| Setosa|
|        4.6|       3.1|        1.5|       0.2| Setosa|
|        5.0|       3.6|        1.4|       0.2| Setosa|
|        5.4|       3.9|        1.7|       0.4| Setosa|
|        4.6|       3.4|        1.4|       0.3| Setosa|
|        5.0|       3.4|        1.5|       0.2| Setosa|
|        4.4|       2.9|        1.4|       0.2| Setosa|
|        4.9|       3.1|        1.5|       0.1| Setosa|
|        5.4|       3.7|        1.5|       0.2| Setosa|
|        4.8|       3.4|        1.6|       0.2| Setosa|
|        4.8|       3.0|        1.4|       0.1| Setosa|
|        4.3|       3.0|        1.1|       0.1| Setosa|
|        5.8|       4.0|        1.2|       0.2| Setosa|
|        5.7|       4.4|        1.5|       0.4| Setosa|
|        5.4|       3.9|        1.3|       0.4| Setosa|
|        5.1|       3.5|        1.4|       0.3| Setosa|
|        5.7|       3.8|        1.7|       0.3| Setosa|
|        5.1|       3.8|        1.5|       0.3| Setosa|
+-----------+----------+-----------+----------+-------+

カラム確認

df.columns
['sepalLength', 'sepalWidth', 'petalLength', 'petalWidth', 'variety']

統計量

df.describe().show()
+-------+------------------+-------------------+------------------+------------------+---------+
|summary|       sepalLength|         sepalWidth|       petalLength|        petalWidth|  variety|
+-------+------------------+-------------------+------------------+------------------+---------+
|  count|               150|                150|               150|               150|      150|
|   mean| 5.843333333333335|  3.057333333333334|3.7580000000000027| 1.199333333333334|     null|
| stddev|0.8280661279778637|0.43586628493669793|1.7652982332594662|0.7622376689603467|     null|
|    min|               4.3|                2.0|               1.0|               0.1|   Setosa|
|    max|               7.9|                4.4|               6.9|               2.5|Virginica|
+-------+------------------+-------------------+------------------+------------------+---------+

スライシング

df[['sepalLength', 'sepalWidth']]
DataFrame[sepalLength: double, sepalWidth: double]

ランダムサンプリング

df.sample(False, fraction=0.1).show()
+-----------+----------+-----------+----------+----------+
|sepalLength|sepalWidth|petalLength|petalWidth|   variety|
+-----------+----------+-----------+----------+----------+
|        4.9|       3.0|        1.4|       0.2|    Setosa|
|        5.7|       4.4|        1.5|       0.4|    Setosa|
|        5.2|       3.4|        1.4|       0.2|    Setosa|
|        4.7|       3.2|        1.6|       0.2|    Setosa|
|        6.3|       3.3|        4.7|       1.6|Versicolor|
|        6.0|       2.2|        4.0|       1.0|Versicolor|
|        6.1|       2.9|        4.7|       1.4|Versicolor|
|        5.6|       2.5|        3.9|       1.1|Versicolor|
|        5.5|       2.4|        3.8|       1.1|Versicolor|
|        6.0|       2.7|        5.1|       1.6|Versicolor|
|        6.7|       2.5|        5.8|       1.8| Virginica|
|        6.4|       2.7|        5.3|       1.9| Virginica|
|        7.4|       2.8|        6.1|       1.9| Virginica|
|        7.9|       3.8|        6.4|       2.0| Virginica|
|        6.7|       3.3|        5.7|       2.5| Virginica|
+-----------+----------+-----------+----------+----------+

列の追加

df = df.withColumn('PetalMult', df['petalWidth'] * df['petalLength'])
df.show(5)
+-----------+----------+-----------+----------+-------+-------------------+----+------------------+
|sepalLength|sepalWidth|petalLength|petalWidth|variety|          PetalMult|  ID|        totalWidth|
+-----------+----------+-----------+----------+-------+-------------------+----+------------------+
|        5.1|       3.5|        1.4|       0.2| Setosa|0.27999999999999997|40.0|0.7000000000000001|
|        4.9|       3.0|        1.4|       0.2| Setosa|0.27999999999999997|45.0|0.6000000000000001|
|        4.7|       3.2|        1.3|       0.2| Setosa|               0.26| 9.0|0.6400000000000001|
|        4.6|       3.1|        1.5|       0.2| Setosa|0.30000000000000004|79.0|0.6200000000000001|
|        5.0|       3.6|        1.4|       0.2| Setosa|0.27999999999999997|27.0|0.7200000000000001|
+-----------+----------+-----------+----------+-------+-------------------+----+------------------+

ユーザ定義関数

データ更新毎に呼ばれるので注意!!

my_udf = F.UserDefinedFunction(lambda x: x + 5, DoubleType())
df.withColumn("my_col", my_udf("sepalLength")).show(5)
+-----------+----------+-----------+----------+-------+-------------------+----+------------------+------+
|sepalLength|sepalWidth|petalLength|petalWidth|variety|          PetalMult|  ID|        totalWidth|my_col|
+-----------+----------+-----------+----------+-------+-------------------+----+------------------+------+
|        5.1|       3.5|        1.4|       0.2| Setosa|0.27999999999999997|40.0|0.7000000000000001|  10.1|
|        4.9|       3.0|        1.4|       0.2| Setosa|0.27999999999999997|45.0|0.6000000000000001|   9.9|
|        4.7|       3.2|        1.3|       0.2| Setosa|               0.26| 9.0|0.6400000000000001|   9.7|
|        4.6|       3.1|        1.5|       0.2| Setosa|0.30000000000000004|79.0|0.6200000000000001|   9.6|
|        5.0|       3.6|        1.4|       0.2| Setosa|0.27999999999999997|27.0|0.7200000000000001|  10.0|
+-----------+----------+-----------+----------+-------+-------------------+----+------------------+------+

グルーピング

  • 列の値でグループ分けし、一列の合計を取得する場合:
df.groupBy('variety').sum().show()
+----------+------------------+------------------+------------------+------------------+------------------+
|   variety|  sum(sepalLength)|   sum(sepalWidth)|  sum(petalLength)|   sum(petalWidth)|    sum(PetalMult)|
+----------+------------------+------------------+------------------+------------------+------------------+
| Virginica| 329.3999999999999|             148.7|277.59999999999997|101.29999999999998| 564.8099999999997|
|    Setosa|250.29999999999998|171.40000000000003| 73.10000000000001|12.299999999999995|18.280000000000012|
|Versicolor|             296.8|138.50000000000003|212.99999999999997|              66.3|            286.02|
+----------+------------------+------------------+------------------+------------------+------------------+
  • 列の値でグループ分けし、一列をカウントする場合:
df.groupBy('variety').count().show()
+----------+-----+
|   variety|count|
+----------+-----+
| Virginica|   50|
|    Setosa|   50|
|Versicolor|   50|
+----------+-----+

groupBy→aggで集計

  • filterは行の抽出
  • selectは列の抽出(スライシングで代用可能)
df.groupBy('variety').agg({'petalWidth': 'min', 'sepalWidth': 'min'}).filter('min(sepalWidth) > 2.0').show()
df.groupBy('variety').agg({'petalWidth': 'min', 'sepalWidth': 'min'}).filter('min(sepalWidth) > 2.0').show()
+---------+---------------+---------------+
|  variety|min(petalWidth)|min(sepalWidth)|
+---------+---------------+---------------+
|Virginica|            1.4|            2.2|
|   Setosa|            0.1|            2.3|
+---------+---------------+---------------+
df.groupBy('variety').agg(F.min('petalWidth')).show()
+----------+---------------+
|   variety|min(petalWidth)|
+----------+---------------+
| Virginica|            1.4|
|    Setosa|            0.1|
|Versicolor|            1.0|
+----------+---------------+

groupBy→pivotで縦横変換

# groupBy("縦のままの列").pivot("縦から横へ変換したい列").sum("集計値の列")
df.groupBy('ID').pivot('variety').sum('totalWidth').show()
+--------------------+------+------------------+------------------+
|                  ID|Setosa|        Versicolor|         Virginica|
+--------------------+------+------------------+------------------+
|4c86ce73cf93883ac...|  null|3.6399999999999997|              null|
|1d0123419d79ed9f4...|  null|              null|               5.6|
|9ee6f2276bb903d07...|  0.68|              null|              null|
|feb96d4b9ba61a234...|  0.66|              null|              null|
|9d1bf310e18c3906a...|  null|              null|              6.16|
|083c4bf137c021384...|  null|              null| 7.359999999999999|
|e8249fb5d09a3670e...|  null|              3.75|              null|
|d2a5cfc97fc88bd4a...|  null|              null|6.6000000000000005|
|cff32af7114873f26...|  null|              3.12|              null|
|78c2373ceaeedd5f7...|  null|              null| 5.319999999999999|
|c0299202f49017bf2...|  1.02|              null|              null|
|cf2b784ce8118783c...|  null|              null| 6.510000000000001|
|1b4ac9d118b902964...|  null|              null|              5.13|
|1aeacde745c81b265...|  null|               4.5|              null|
|b6f96e27904f4f6de...|  null|              null|               6.0|
|abed0bc37e718a5e4...|  null|              3.12|              null|
|b89932d736fa67711...|  null|              5.28|              null|
|7d132d0ead06d6ba5...|  null|              3.77|              null|
|cfb737bf348cd3b2c...|  null|3.9199999999999995|              null|
|42209ddc3b698bb94...|  0.68|              null|              null|
+--------------------+------+------------------+------------------+

一意の識別子を付ける

df = df.withColumn("ID", F.monotonically_increasing_id())
df = df.withColumn('totalWidth', df['sepalWidth'] * df['petalWidth'])
df.show(5)
+-----------+----------+-----------+----------+-------+-------------------+---+------------------+
|sepalLength|sepalWidth|petalLength|petalWidth|variety|          PetalMult| ID|        totalWidth|
+-----------+----------+-----------+----------+-------+-------------------+---+------------------+
|        5.1|       3.5|        1.4|       0.2| Setosa|0.27999999999999997|  0|0.7000000000000001|
|        4.9|       3.0|        1.4|       0.2| Setosa|0.27999999999999997|  1|0.6000000000000001|
|        4.7|       3.2|        1.3|       0.2| Setosa|               0.26|  2|0.6400000000000001|
|        4.6|       3.1|        1.5|       0.2| Setosa|0.30000000000000004|  3|0.6200000000000001|
|        5.0|       3.6|        1.4|       0.2| Setosa|0.27999999999999997|  4|0.7200000000000001|
+-----------+----------+-----------+----------+-------+-------------------+---+------------------+

DFのJOIN

df1 = df[['ID', 'totalWidth']].sort('ID')
print(df1.show(3), df1.count())
+---+------------------+
| ID|        totalWidth|
+---+------------------+
|  0|0.7000000000000001|
|  1|0.6000000000000001|
|  2|0.6400000000000001|
+---+------------------+
df2 = df[['ID', 'variety', 'PetalMult']].sort('ID')
print(df2.show(3), df2.count())
+---+-------+-------------------+
| ID|variety|          PetalMult|
+---+-------+-------------------+
|  0| Setosa|0.27999999999999997|
|  1| Setosa|0.27999999999999997|
|  2| Setosa|               0.26|
+---+-------+-------------------+
  • 元のDataframe(こちらがLeftになる)でjoin methodを呼び、joinの相手(Rightになる)とjoinの条件を書くと、SQLのjoinの様にDataframeの結合が可能
df1.join(df2, df1['ID'] == df2['ID'], 'inner').show()
+---+-------------------+---+-------+-------------------+
| ID|         totalWidth| ID|variety|          PetalMult|
+---+-------------------+---+-------+-------------------+
|  0| 0.7000000000000001|  0| Setosa|0.27999999999999997|
|  1| 0.6000000000000001|  1| Setosa|0.27999999999999997|
|  2| 0.6400000000000001|  2| Setosa|               0.26|
|  3| 0.6200000000000001|  3| Setosa|0.30000000000000004|
|  4| 0.7200000000000001|  4| Setosa|0.27999999999999997|
|  5|               1.56|  5| Setosa|               0.68|
|  6|               1.02|  6| Setosa|               0.42|
|  7|               0.68|  7| Setosa|0.30000000000000004|
|  8|               0.58|  8| Setosa|0.27999999999999997|
|  9|0.31000000000000005|  9| Setosa|0.15000000000000002|
| 10| 0.7400000000000001| 10| Setosa|0.30000000000000004|
| 11|               0.68| 11| Setosa|0.32000000000000006|
| 12|0.30000000000000004| 12| Setosa|0.13999999999999999|
| 13|0.30000000000000004| 13| Setosa|0.11000000000000001|
| 14|                0.8| 14| Setosa|               0.24|
| 15| 1.7600000000000002| 15| Setosa| 0.6000000000000001|
| 16|               1.56| 16| Setosa|               0.52|
| 17|               1.05| 17| Setosa|               0.42|
| 18|               1.14| 18| Setosa|               0.51|
| 19|               1.14| 19| Setosa|0.44999999999999996|
+---+-------------------+---+-------+-------------------+

列を取り出す

  • df.selectじゃないと動作しない
  • .rddとは
    • Dataframeの各行がそれぞれRow OjbectなRDDに変換されます。Row ObjectはSpark SQLで一行分のデータを保持する為のObjectです
sorted(df.select('ID').distinct().rdd.map(lambda x: x[0]).collect())[:10]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
df.filter(df['ID'] == 2).show()
+-----------+----------+-----------+----------+-------+---------+---+------------------+
|sepalLength|sepalWidth|petalLength|petalWidth|variety|PetalMult| ID|        totalWidth|
+-----------+----------+-----------+----------+-------+---------+---+------------------+
|        4.7|       3.2|        1.3|       0.2| Setosa|     0.26|  2|0.6400000000000001|
+-----------+----------+-----------+----------+-------+---------+---+------------------+

参考文献

sinhrks.hatenablog.com

  • サンプル集(group by詳しい)

qiita.com

  • 関数まとめ

qiita.com

スライドの作りに便利なフリー素材サイト

はじめに

twitterでたまに回ってくる,スライド作成向け便利素材サイトをまとめました

画像系

freepik

Unsplash

unsplash.com

pixabay

https://pixabay.com/ja/

o-dan

https://o-dan.net/ja/

図系

isoflow

スライド作り注意点

ubuntu18.04 noVNC dockerコンテナを公開しました

はじめに

tmyoda.hatenablog.com

この記事のSingularityコンテナを作成するべく,参照元のdockerコンテナを作成しました.

以下の素晴らしいリポジトリを使おうと思ったのですが,あいにくubuntu18.04に対応しておらず,リポジトリのメンテナンスも終わりそうです.

github.com

issueを漁ったところ,Fromをubuntu18.04にすれば動くらしいので,そこだけを変更したDockerfileGitHubに公開しました.

また,DokerHubでもコンテナを公開しました.

CentOSのアップデートバージョンを公開するかは悩み中です.(要望があれば行います.)

公開したもの

DockerHub

GitHub

SingularityコンテナでOpenCV+Boostを使ったC++コードをコンパイル

はじめに

研究でC++コードをpybind11を使ってPythonから呼んで強化学習をしています.

強化学習GPU上で回したくなったのですが,残念ながらC++系ライブラリを導入するためのsudo権限が付与されておらず,サーバ上でC++コードが実行できません.

そこで,Singularityコンテナ上でビルド&実行までできる環境を作成して,そのままGPUを使った強化学習を行えるようにします.

コンテナ作成

作成したビルド&強化学習に必要な環境

  • Ninja build
  • OpenCV 3.4.6
  • Boost 1.65
  • Pipenv
  • python
  • VLC(動画出力するため)

実行方法は2つあります

  • Singularity Libraryからpull
  • .defファイルからローカルでbuild

Singularity Libraryからpull

Singularity Library(SyLabs)にコンテナを公開しました

cloud.sylabs.io

以下のコードでビルドできると思います

singularity pull library://tmyoda/default/headless-ubuntu-xfce-pipenv-opencv-boost
singularity build --sandbox cpp_vnc headless-ubuntu-xfce-pipenv-opencv-boost.sif

実行方法は以下の通りです

singularity instance start -w cpp_vnc xfce
singularity start -w instance://xfce

Singularityの詳しい使い方は以下の記事を参考にしてください

tmyoda.hatenablog.com

.defファイル

注意点

  • DockerHubで公開されている,/consol/ubuntu-xfce-vnc を使用したかったのですが,のubuntuバージョンが16.04までだったので,18.04に対応させたdocker container(tmyoda/ubuntu-xfce-vnc:18.04)を作成し,DockerHubに上げたものをベースに作成しています.

  • OpenCVは動画生成したかったので,その辺のオプション有効にしてます

Bootstrap: docker
From: tmyoda/ubuntu18-xfce-vnc:latest

%environment
    export LC_ALL=C.UTF-8
    export Lang=C.UTF-8
    export PIPENV_VENV_IN_PROJECT=1
    export PATH="/usr/local/opt/openssl/bin:$PATH"
    # pipenv property
    export PIPENV_VENV_IN_PROJECT=1
    export PIPENV_SKIP_LOCK=1


%post
    apt update
    # for jupyter and pipenv 
    apt install -y bzip2 ca-certificates curl git ffmpeg openssl libssl-dev \
    libsqlite3-dev libreadline6-dev libbz2-dev libssl-dev libsqlite3-dev libncursesw5-dev \
    libffi-dev libdb-dev libexpat1-dev zlib1g-dev liblzma-dev libgdbm-dev libmpdec-dev \
    vim-tiny build-essential inkscape jed libsm6 libxext-dev libxrender1 lmodern netcat tzdata unzip \
    wget build-essential gcc zlib1g-dev python3-distutils vlc bash-completion

    # pip
    curl -kL https://bootstrap.pypa.io/get-pip.py | python3
    # pipenv
    pip3 install virtualenv
    pip3 install pipenv


    apt install -y \
        libboost1.65-all-dev libboost1.65-dev libboost1.65-tools-dev ninja-build ccache \
        libncurses5-dev libavutil-dev libavcodec-dev libavfilter-dev libavformat-dev libavdevice-dev ffmpeg pkg-config \
        cmake libgtk-3-dev libjpeg-dev

    # opencv3.4.6
    wget https://github.com/opencv/opencv/archive/3.4.6.tar.gz
    tar zxvf 3.4.6.tar.gz
    cd opencv-3.4.6

    export SOURCE_DIR="../"
    export BUILD_DIR="/build"
    export GENERATOR_NAME="Unix Makefiles"

    # build opencv
    mkdir build
    cd build
    cmake \
    -G "${GENERATOR_NAME}" \
    --build ${BUILD_DIR} \
    -D BUILD_CUDA_STUBS=OFF \
    -D BUILD_DOCS=OFF \
    -D BUILD_EXAMPLES=OFF \
    -D BUILD_JASPER=OFF \
    -D BUILD_JPEG=ON \
    -D BUILD_OPENEXR=OFF \
    -D BUILD_PACKAGE=ON \
    -D BUILD_PERF_TESTS=OFF \
    -D BUILD_PNG=ON \
    -D BUILD_SHARED_LIBS=ON \
    -D BUILD_TBB=OFF \
    -D BUILD_TESTS=OFF \
    -D BUILD_TIFF=OFF \
    -D BUILD_WITH_DEBUG_INFO=ON \
    -D BUILD_ZLIB=OFF \
    -D BUILD_WEBP=OFF \
    -D BUILD_opencv_apps=ON \
    -D BUILD_opencv_calib3d=ON \
    -D BUILD_opencv_core=ON \
    -D BUILD_opencv_cudaarithm=OFF \
    -D BUILD_opencv_cudabgsegm=OFF \
    -D BUILD_opencv_cudacodec=OFF \
    -D BUILD_opencv_cudafeatures2d=OFF \
    -D BUILD_opencv_cudafilters=OFF \
    -D BUILD_opencv_cudaimgproc=OFF \
    -D BUILD_opencv_cudalegacy=OFF \
    -D BUILD_opencv_cudaobjdetect=OFF \
    -D BUILD_opencv_cudaoptflow=OFF \
    -D BUILD_opencv_cudastereo=OFF \
    -D BUILD_opencv_cudawarping=OFF \
    -D BUILD_opencv_cudev=OFF \
    -D BUILD_opencv_features2d=ON \
    -D BUILD_opencv_flann=ON \
    -D BUILD_opencv_highgui=ON \
    -D BUILD_opencv_imgcodecs=ON \
    -D BUILD_opencv_imgproc=ON \
    -D BUILD_opencv_java=OFF \
    -D BUILD_opencv_ml=ON \
    -D BUILD_opencv_objdetect=ON \
    -D BUILD_opencv_photo=ON \
    -D BUILD_opencv_python2=OFF \
    -D BUILD_opencv_python3=ON \
    -D BUILD_opencv_shape=ON \
    -D BUILD_opencv_stitching=ON \
    -D BUILD_opencv_superres=ON \
    -D BUILD_opencv_ts=ON \
    -D BUILD_opencv_video=ON \
    -D BUILD_opencv_videoio=ON \
    -D BUILD_opencv_videostab=ON \
    -D BUILD_opencv_viz=OFF \
    -D BUILD_opencv_world=OFF \
    -D CMAKE_BUILD_TYPE=RELEASE \
    -D WITH_1394=ON \
    -D WITH_CUBLAS=OFF \
    -D WITH_CUDA=OFF \
    -D WITH_CUFFT=OFF \
    -D WITH_EIGEN=ON \
    -D WITH_FFMPEG=ON \
    -D WITH_GDAL=OFF \
    -D WITH_GPHOTO2=OFF \
    -D WITH_GIGEAPI=ON \
    -D WITH_GSTREAMER=ON \
    -D WITH_GTK=ON \
    -D WITH_INTELPERC=OFF \
    -D WITH_IPP=ON \
    -D WITH_IPP_A=OFF \
    -D WITH_JASPER=ON \
    -D WITH_JPEG=ON \
    -D WITH_LIBV4L=ON \
    -D WITH_OPENCL=ON \
    -D WITH_OPENCLAMDBLAS=OFF \
    -D WITH_OPENCLAMDFFT=OFF \
    -D WITH_OPENCL_SVM=OFF \
    -D WITH_OPENEXR=ON \
    -D WITH_OPENGL=ON \
    -D WITH_OPENMP=OFF \
    -D WITH_OPENNI=OFF \
    -D WITH_PNG=ON \
    -D WITH_PTHREADS_PF=OFF \
    -D WITH_PVAPI=ON \
    -D WITH_QT=OFF \
    -D WITH_TBB=ON \
    -D WITH_TIFF=ON \
    -D WITH_UNICAP=OFF \
    -D WITH_V4L=OFF \
    -D WITH_VTK=OFF \
    -D WITH_WEBP=ON \
    -D WITH_XIMEA=OFF \
    -D WITH_XINE=OFF \
    ${SOURCE_DIR}

    make -j8
    make install
    echo /usr/local/lib > /etc/ld.so.conf.d/opencv.conf
    ldconfig -v

    # clean up
    apt clean
    rm -rf /var/lib/apt/lists/*

%labels
    Author tmyoda
    Version v0.0.3

Singularity + headless VNC + Pipenvを使ってサーバ上で強化学習環境を整える(gym, pybullet)

はじめに

強化学習をしてると何かとGUI問題に直面します. OpenAI GymもPyBulletもMoJoCoGUIです. というか,GUIで動作確認ぐらいしか,学習経過を把握できるものがありません.

ということで,GPUサーバ上でGUIが見れるような環境が欲しくなりました.

追記: Sylabs.ioにcontainerをpushしました.

headless VNCとは

VNCとは離れた環境のGUIを操作するリモートデスクトップのことを指しますが,headlessマシンとはディスプレイを持たない,つまりGUIを持たないマシンのことを指します.

つまり,GUIを持たない共有のGPU付きサーバ上に,xfce環境 (GUI) を作って,しかもブラウザ上からVNCできかつ,pytorchがGPU上で動く環境を作成します.

headless VNCを触ってみる

楽するために,Docker, もしくはSingularityを使って今回は進めます. また,私はxfceの方が好きなのでxfceでの例を示します.(もちろんIceWMでも良いです)

Singularityの解説記事はこちらから

tmyoda.hatenablog.com

https://hub.docker.com/r/consol/ubuntu-xfce-vnc/

docker

docker run -d -p 5901:5901 -p 6901:6901 consol/centos-xfce-vnc:latest

Singularity

# sandbox作成
singularity build --sandbox ubuntu-xfce docker://consol/ubuntu-xfce-vnc:latest

# instance作成 
singularity instance start -w --nv ubuntu-xfce xfce

# vnc実行
singularity run instance://xfce

singularityでそのままrunしても動きますが,プロセス管理が大変なのでinstanceで動かします.

あとはブラウザでlocalhost:6901/vnc.htmlへアクセスすればなんとGUIが見れます.(初期パスワードは vncpassword ) これでgymとかpybulletとかの動作を確認できます.

導入

docker

dockerの導入はかんたんで,先程のコンテナに好きなだけaptでほしいものを入れればokです.

singularity (rootless)

ルート権限サーバへの導入方法は3つあります. - singularity libraryからpull - ローカルPCでbuild - サーバ上でremote build

singularity libraryからpull (おすすめ!)

sylabにuploadしました.

singularity pull library://tmyoda/default/headless-ubuntu-xfce-pipenv

これで.sifファイルが手に入ります.

ローカルPCでbuild

rootlessだとaptコマンドが実行できないため,ローカルPCにsingularityを導入しました. そして,.defファイルを作成しましたので公開します.

Bootstrap: docker
From: consol/ubuntu-xfce-vnc:latest


%environment
    export LC_ALL=C.UTF-8
    export Lang=C.UTF-8
    export PIPENV_VENV_IN_PROJECT=1
    export PATH="/usr/local/opt/openssl/bin:$PATH"


%post
    apt-get -y update 
    # for jupyter and pipenv 
    apt-get install -y bzip2 ca-certificates curl git ffmpeg openssl libssl-dev \
    libsqlite3-dev libreadline6-dev libbz2-dev libssl-dev libsqlite3-dev libncursesw5-dev \
    libffi-dev libdb-dev libexpat1-dev zlib1g-dev liblzma-dev libgdbm-dev libmpdec-dev \
    vim-tiny build-essential inkscape jed libsm6 libxext-dev libxrender1 lmodern netcat tzdata unzip
    
    # pyton3.6 from source
    apt-get install -y wget build-essential gcc zlib1g-dev
    cd /root/
    wget https://www.python.org/ftp/python/3.6.8/Python-3.6.8.tgz
    tar zxf Python-3.6.8.tgz
    cd Python-3.6.8
    ./configure 
    make altinstall
    cd .. && rm -rf Python-3.6.8.tgz Python-3.6.8
    cd $HOME
    rm /usr/bin/python3
    ln -s /usr/local/bin/python3.6 /usr/bin/python3
    # install pip
    export PATH="/usr/local/opt/openssl/bin:$PATH"
    curl -kL https://bootstrap.pypa.io/get-pip.py | python3
    apt-get clean
    rm -rf /var/lib/apt/lists/*
    # pipenv
    pip3 install virtualenv
    pip3 install pipenv

%labels
    Author tmyoda
    Version v0.1.0

root権限付きローカルPCでbuildします.

sudo singularity build --fix-perms ubuntu-xfce-pipenv.sif ubuntu-xfce-pipenv.def

*.defからローカルでbuildするのはroot必須です. しかしsingularityには--remoteというオプションがあり,これを用いると非rootでもbuildできます.

出来上がったxfce-pipenv.sifをscp等でサーバへコピーします.

サーバ上での作業

pullもしくはdefからのbuildでサーバ上に*.sifが手に入っている状態かと思います.

1 サーバ上で--sandboxオプションをつけてビルドします. これしないとvnc動かないです.

singularity build --sandbox ubuntu-xfce ubuntu-xfce-pipenv.sif

2 サーバ上でinstance startしてrunします.

singularity instance start -w --nv  ubuntu-xfce xfce
singularity run -w --nv instance://xfce

あとは先程のようにlocalhost:6901/vnc/htmlにアクセスします.

そして,pipenvが導入されているので,gym, pybullet, jupyterなりを好きに入れ環境を整えます.

xfce4 パネル初期化コマンド

最後にxfce4のパネルが良く死ぬので初期化コマンド

www.achiachi.net

xfce4-panel --quit
pkill xfconfd
rm -rf ~/.config/xfce4/panel
rm -rf ~/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-panel.xml
xfce4-panel

singularityの使い方

はじめに

singularity v3.6.0時点での記事です

メリット

  • SingularityとはDockerと同じコンテナプラットフォームで,主にHPC向けに作られたオープンソースソフトウェア
  • Dockerより簡単にGPUを使うことができるらしい (最近はnvidia-dockerがdockerに統合されたため,このメリットは微妙)
  • Docker互換(Dockerfile, Docker imageを持ってくることができる)
  • rootlessでも動作
  • 自動で以下の領域をマウント(sudo付けないと一部マウントされないかも)
host singulalrity
$HOME $HOME
/sys /sys
/proc /proc
/tmp /tmp
/var/tmp /var/tmp
/etc/resolv.conf /etc/resolv.conf
/etc/passwd /etc/passwd
$PWD $PWD

Dockerとの違い

SingularityのアーキテクチャはDockerと少し異なります. Singularityのコンテナは

  • immutableな*.sif(Singularity Image File)
  • muutableなsandbox (フォルダ)

の2種類あります. Dockerのコンテナは変更可能なので,singularityで言うsandboxに該当します.

$HOMEなどが自動マウントされるので,基本的はsifを使うみたいですが,sifを使ったコンテナは後々pipでライブラリを追加したり出来ません.
Dockerコンテナライクな使い方をしたい場合や,環境を作って行きたい場合はsandboxを使うことをオススメします.

sandboxである程度環境が整った後に.sifに変換することも可能です. - sandboxで扱った後にimuutableにする方法

singularity build some-image.sif sandbox-dir

コンテナ作成方法

  • Docker Hub,Singularity Hub, Container Libraryからビルド
  • マシン上の既存コンテナからビルド(Docker Imageも可)
  • Singularity定義ファイル*.defからビルド

の3種類の方法があります.

buildコマンドを使用してコンテナを作成します.

Usage: singularity build [local options...] <IMAGE PATH> <BUILD SPEC>

コンテナ作成例

pytorch/conda-cudaをDocker Hubから.sif作成

  • conda-cuda-torch.sifという名前の.sifファイルを作成する例
singularity build conda-cuda-torch.sif docker://nablascom/cuda-pytorch:latest

sandboxでコンテナを作成するオプション

  • --sandboxオプションをつけてIMAGE PATH を任意のフォルダ名(ここではconda-cuda-torch)にすればOK
singularity build --sandbox conda-cuda-torch docker://nablascom/cuda-pytorch:latest

マシン上のdockerにあるhello-world imageから

singularity build hello-world-singularity.sif docker-daemon://hello-world:latest

conda-torch.defから

  • Dockerfileに相当するSingularity Definition File(.def)からビルドする例
  • sudoが必要
sudo singularity build conda-torch.sif conda-torch.def

コンテナ実行方法

実行コマンドは以下の3つあります.

この記事から引用すると

cmd 説明 Usage
exec ホストシステム上で直接実行されているかのように、
コンテナイメージ内の任意のコマンドを実行できます。
singularity exec [exec options...] <container> <command>
shell コンテナ内に対話型シェルを自動的に生成します。 singularity shell [shell options...] <container>
run コンテナがファイル名で直接実行または実行されたときに
実行されるカスタムアクションを定義できます。
singularity run [run options...] <container>

また,先程作成したコンテナを元にいくつか実行例を示します.

import torch
dev = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(dev)

conda-cuda-torch.sifの環境上でpythonスクリプトを実行

  • sandboxの場合は.sifの部分に対応するフォルダ名を指定すれば良い
singularity exec conda-cuda-torch.sif python check_torch_cuda.py
$ cpu

GPU環境でpythonスクリプト実行

--nvオプションをつける

singularity exec --nv conda-cuda-torch.sif python check_torch_cuda.py
$ cuda:0

shellに入る

singularity shell --nv  conda-cuda-torch.sif
$ Singularity> 

sandbox環境に書き込み可能な状態でshell実行(Dockerのコンテナはこの状態)

  • デフォルトは読み取り専用なので、--writableを付ける
singularity shell --nv --writable conda-cuda-torch
$ Singularity> 

run補足

  • sif作成時,%runscriptを指定することで,singularity run file_name.sifもしくは./file_name.sifのように実行形式ファイル風に実行できる

port forwardingについて

jupyterを使いたい場合があると思いますが,Singularityはデフォルトで多くの領域をマウントするので,勝手にhostのポートを使ってくれます.(webカメラ等も同様)

DBやWebサーバ等をバックグラウンドで実行したい時

Dockerライクなインターフェイスとして,singularity instanceコマンドがあります.

詳しくはこちらのDocumentへ - https://sylabs.io/guides/3.6/user-guide/cli/singularity_instance.html

singularity instanceでバックグラウンド実行する方法

  • 一応jupyter notebookをバックグラウンドで実行して停止する例を一つ出しておきます.
  • もちろんDockerのようなポートフォワーディングの記述は不要です.
# cuda-torchは INSTANCE NAME
singularity instance start --nv conda-cuda-torch.sif cuda-torch

# docker ps的なコマンド
singularity instance list
$ INSTANCE NAME    PID      IP    IMAGE
$ cuda-torch       14030          /home/oda/conda-cuda-torch.sif

# shell起動
singularity shell instance://cuda-torch
Singularity> jupyter notebook
...
# exitで抜ける
Singularity> exit

# インスタンスを停止
singularity instance stop cuda-torch

# インスタンスがないことを確認
singularity instance list
$ INSTANCE NAME    PID    IP    IMAGE

まとめ

  • Docker互換
  • コンテナはimmutableな.sifとsandboxがある
  • build時に--sandboxを指定しかつ,実行時に--writableを指定すると書き込み可能
  • 実行はexec, shell, runがある
  • 実行時,勝手にマウント
  • --nvコマンドだけでGPU対応
  • バックグラウンドで動かしたいときDockerライクなコマンドinstanceがある

参考