論文の重要なアイデア

重要だと思った論文のアイデアを書き溜めていきます.

GAN

Generative Adversarial Networks (GAN)[1] では,次のようなBinary Cross-Entropy誤差を最大化することで,識別器Dを学習します.

J(D;G) = \mathbb{E}_{x \sim p(x)} [\log D(x)] + \mathbb{E}_{y \sim p(y)}[\log(1-D(y))]

Dは単に入力が分布p(x)から生成されたものか,分布p(y)から生成されたものかを見分けているだけです. この目的関数に現れる期待値をデータセットを用いてモンテカルロ近似すると,識別器Dのパラメータについての勾配が求められます.

次に,この識別器Dを騙すような生成器Gの学習を考えます.もしyがy=G(・)のような形で出力されるなら

 \nabla_{G} = \frac{\partial J(D;G)}{\partial y} \frac{\partial y}{\partial G}

のように,生成器G(のパラメータ)に対する勾配も直接計算できます. この勾配を用いて,識別器Dと生成器Gを交互に学習することで,p(y)がp(x)を近似する,というのがGANのアイデアです.

GAIfO

ここで, \frac{\partial y}{\partial G} を計算できない場合を考えてみます. つまり,生成器Gは確かにp(y)に何らかの形で影響を及ぼしていますが,その関係が微分できないような状況です.

具体的な例がGAIfO[2]で取り上げられています.

J(D;G) = \mathbb{E}_{z \sim p(z)}[\log D(T(G(z)))] + \mathbb{E}_{x \sim p(x)} [\log(1-D(x))]

今まで通り,識別器Dは直接勾配を計算することで上式を最大化します. 一方で,識別器は模倣したい分布p(x)からの元らしさではなく,偽物の分布p(G(・))らしさを学習しています. この式では,第二項の期待値がp(z)の分布に変わっていますが,確率変数zは生成器Gを通って変換されるので,結局p(G(z))の分布をp(x)に一致させることが目標になります.しかし,ここではさらにGの出力は関数Tに入力されています.この関数Tが微分不可能な場合,\frac{\partial J(D;G)}{\partial G}を計算できません.

GAIfOでは,このような場合に,次のような目的関数を定義します.

-\mathbb{E}_{z \sim p(z)}[\log D(T(G(z)))]

つまり,-\log D(T(G(z)))は,「T(G(z))のp(x)らしさ」を表現しており,この期待値を最大化するようにGを学習します. Gの学習には,PPOのような強化学習アルゴリズムを用います. 実際には,zを状態・G(z)を行動,-\log D(T(G(z)))を即時報酬と考えて強化学習を実施することができます.

参考文献

[1] [1406.2661] Generative Adversarial Networks
[2] [1807.06158] Generative Adversarial Imitation from Observation

実験管理(2)

筆者個人用のメモ書きです.

Gitオブジェクト

Gitにおけるスナップショットの単位であるコミットは,実態として,主に3種類のオブジェクト:blob, tree, commitから構成される. ステージングされたファイル:git addで管理下に入ったファイルは,blobオブジェクトとして保管される. ファイルの実態としてblobより小さい単位は無いため,サイズの大きなファイルの一部だけを更新した場合でも,次のスナップショットには新たにサイズの大きなblobがオブジェクトが格納される.[1]

git pushコマンドは,更新されたローカルリポジトリを構成するのに必要なオブジェクトをリモートリポジトリに送信する. git push時,あるいは定期的・自動的に,gitはパッキングと呼ばれる仕組みを用いて,複数のblobオブジェクトについてその差分だけを保存するような整理を行う. これにより,通信量とストレージ容量の削減を行う.[3]

ただし,このパッキングが走る以前には,変更が行われたファイルの容量(をzlibで圧縮した分+α)だけ,リポジトリの容量が増加する. このような状況において,大容量のファイルの一部だけを繰り返し更新するようなケースでは,ファイルを分割して,一部のファイルだけに更新が発生するような設計にするのが賢い.

実験管理のためのGit

複数の実験サーバーを抱えている状況で,そのうち一台でモデルの学習・評価が完了したタイミングを想像する. この時,自動的にgit add / commit / pushが行われれば,コード・学習済みモデル・メタ情報の3点がセットでリモートリポジトリに保管される. あとから特定のコミットを指定してチェックアウトすれば,この時の環境を復元することも可能.

構成

  • 前提: サーバーAが自動pushを実行したのと前後して,サーバーBもpushをする状況.
  • 1) それぞれ同名の実験結果ファイルを追加する場合: コンフリクトするので実験のトライアルごとにbranchを切って,各ブランチはマージされない運用を考える.[4]
  • 2) 別名の実験結果を保存していく場合: サーバーAとサーバーBが同じブランチに保存しても良い

1の構成を採用する場合には大量のブランチが作成されるが,ファイルの内容が同じであれば(パッキングが効くかどうかは別にして)同じblobオブジェクトになるので,無駄な複製は起きない.[1]

次の懸念は,実験によって作成されるブランチ/コミットは,何を親とするべきか,という点にある. コード・モデル・メタの3点をセットにして保存するというだけならGoogle Driveにでも入れておけばよいが,ここではGitによるblobオブジェクト・パッキング・zlib圧縮による恩恵に与りたい. そこで,Gitそのものの「親・子」の概念を尊重してGitを使うことにする. ひいては,各実験トライアルに共通している,ready to executeな実験コードを派生元とするのがわかりやすい.
そこで,次のような運用を検討している:

  • (1) コーディング・デバッグ
  • (2) 完成したready to executeなコードをmasterにgit add/commit/push
  • (3) 各実験サーバーでgit fetch origin master + git stash + git checkout masterを実行
    • git fecth origin masterでリモートのmasterを構成する情報をローカルに落とす
    • git stashで各実験サーバーで未コミットの状態を退避(checkout時のエラー回避目的)
    • git checkoutで各実験サーバーをready to executeな新たな実験コードの状態にする
  • (4) 各実験サーバーで学習・評価を行い,結果をローカルファイルに保存
  • (5-選択肢1) 各実験サーバーでそれぞれmasterを派生元としてbranchを切る / 結果ファイルは各サーバーで同名
  • (5-選択肢2) 各実験サーバーでそれぞれ別名の結果ファイルを保存し,ブランチはmasterのまま
    • 選択肢1をマージすると考えても良い
  • (6) git fetch / add / commit / push

実験管理ツール採用の余地

gitを使う構成を検討したが,結局,「実験の直前に実験コードをリモートのmasterから同期してくる」「実験によって生まれた差分(実験結果)をadd/commit/pushする」という運用になっている. コードはこのgit運用でうまく管理できているので,実験によって生成されたメタ情報と学習済みモデルを実験管理ツールのほうに送信して保存しても良いのではないか. そうすることによって,メタ情報の俯瞰・比較が行いやすくなる.

参考文献

[1] Gitのオブジェクトの中身. https://zenn.dev/kaityo256/articles/objects_of_git
[2] git pushの裏側で何が起こっているのか。~javascriptでgitをリバースエンジニアリングで実装する(4)~. https://zenn.dev/hirokihello/articles/d5866bc64f13e3f3b8c7
[3] 10.4 Gitの内側 - Packfile. https://git-scm.com/book/ja/v2/Git%E3%81%AE%E5%86%85%E5%81%B4-Packfile
[4] 複数の作業環境から変更が同一ブランチにpushされるGitリポジトリについて、リモートにある内容を確実に手元に反映する. https://www.clear-code.com/blog/2016/9/2.html

深層学習のためのDocker(3)

Dockerエンジンをsudoなしで使う

毎回sudoでdockerを使うのは共有サーバーとかだと好ましくない(?)のでsudoなしでdockerを使いたい. ホストOSで対象ユーザーをdockerグループに追加する.

# dockerグループが存在するか
$ cat /etc/group | grep docker

# ユーザーをグループに追加
$ sudo usermod -aG docker <user>

# ユーザーにログインし直す

これでsudoなしで実行できるようになる.

Dockerコンテナ内で生成したファイルをホストのユーザーが編集する

前章でsudoなしでコンテナを起動できるようになったが,普通にdocker runするとコンテナ内にrootでログインしている. この状態で例えばモデルを学習して学習済みパラメータをマウントしたディレクトリに保存すると,ホストOSのユーザーからするとsudoしないとそのファイルを編集できない. これだと結局ホストOSでsudoが必要になるので嬉しくない.

簡単な解決策は,コンテナ内で生成したファイル全般を,コンテナ内からchmodで権限を777に変更してしまうこと. 誰でも編集できてしまうがとりあえずこれで解決できる.

他にもホストとコンテナ内でユーザーとグループのidを一致させる方法がある.これだとホストOSで該当ユーザーだけが編集できる状態になる. ただ,コンテナ内に該当ユーザーを生成する手順が若干面倒なので,これについては後日やる.

Dockerfileの準備

以下は個人的に研究で使うためのもの.

FROM nvidia/cuda:11.4.1-cudnn8-runtime-ubuntu18.04

# Python install
RUN apt-get update && \
    apt-get install python3-pip -y

# Python libs install
RUN pip3 install cupy-cuda114 && \
    pip3 install chainer && \
    pip3 install pillow && \
    pip3 install matplotlib && \
    pip3 install pybullet && \
    pip3 install gym

# ViZDoom install; https://github.com/mwydmuch/ViZDoom#python-quick-start
RUN apt-get install cmake libboost-all-dev libsdl2-dev libfreetype6-dev libgl1-mesa-dev libglu1-mesa-dev libpng-dev libjpeg-dev libbz2-dev libfluidsynth-dev libgme-dev libopenal-dev zlib1g-dev timidity tar nasm -y && \
    pip3 install vizdoom

# others
RUN pip3 install torch && \
    pip3 install pyyaml

Dockerfileからイメージを作成

$ docker build -t <付けたいイメージの名前> <Dockerfileのディレクトリ>

NVIDIA-Dockerを使ってイメージをコンテナ化してbashに入る

$ docker run --gpus all -v $HOME:$HOME -i -t <イメージ名> /bin/bash
  • -v $HOME:$HOMEを指定すると,ubuntuならホストの/home/<username> がコンテナ内の/home/<username>の位置からアクセスできる
  • ボリュームマウントとバインドマウントがあって,これは後者.

深層学習のためのDocker(2)

インストール手順

Dockerコンテナ内とホストOSのNVIDIAドライバーの接続が面倒らしく,それをNVIDIA Container Toolkitが解決してくれる. ホストOSに(1)Docker Engine, (2)NVIDIA Driver, (3)NVIDIA Container Toolkitをインストールする. 順番は123もしくは213で行う. ホストOSにはUbuntuを想定.

1) Docker Engineのインストール

- [1]を参考に.

2) NVIDIA Driverのインストール(更新)

- [2]を参考に.

リポジトリを追加
$ sudo add-apt-repository ppa:graphics-drivers/ppa
$ sudo apt update

インストール可能なバージョンを確認
$ apt-cache search nvidia | grep nvidia-driver

インストール(バージョン470)
$ sudo apt install nvidia-driver-470

再起動と確認
$ sudo reboot
$ nvidia-smi

3) NVIDIA Container Toolkitのインストール

- [3]を参考に.

動作確認.

$ sudo docker run --rm --gpus all nvidia/cuda:9.0-base cat /usr/local/cuda/version.txt

Dockerコンテナ内のCUDAのバージョンが表示される.この例だと9.0. nvidia-smiの表示するCUDAのバージョンは実際に使われるものとは別らしいので,この方法で確認. 一般にはnvccでも確認できるが,ここでは使えない.

イメージ・コンテナの準備

Docker Engineを使って,イメージとして保存された実行環境をコンテナとして起動したい. NVIDIAGPUを利用する場合には,NVIDIA Container Toolkit(NVIDIA Docker)によってホストOSのNVIDIAドライバを利用できる. あとはCUDAより上のレイヤをDockerイメージが含んでいる必要がある.

Dockerイメージの選択

NVIDIAがCUDAインストール済みのイメージを公開している. いずれ必要なライブラリを入れていく必要はあるが,まずはベースのイメージを選ぶ.

  • nvidia/cudaが提供するDockerイメージのうち,runtimeフレーバーのやつはcuDNNが入っている.[6]
    • nvidia/cuda:11.4.1-cudnn8-runtime-ubuntu18.04 これが良さそう.cuDNNは8.2が入る[8]

深層学習ライブラリ・GPUの世代を基準にした互換性チェック:

  • RTX30台を使うには,CUDA11.1以降が必要[5].最新版は11.4
  • Chainerのlatestはv7.8.0.これは部分的にCUDA11.1以降/Cupy8,9をサポートしている.[4]
    • cupyの最新版は10台だけどchainerが未対応かもしれない.cupy9台の最新版は9.4.0
  • cupy 9.4.0に対応する最新のcuDNNは8.2 [7]
  • Chainer7.8.0, CUDA11.4, Cupy9.4.0, cuDNN8.2の組み合わせが,互換性のある最新の構成(2021/9/26現在)

参考文献

[1] Install using the repository. https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository
[2] ubuntuにCUDA、nvidiaドライバをインストールするメモ. https://qiita.com/porizou1/items/74d8264d6381ee2941bd
[3] Setting up NVIDIA Container Toolkit. https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html#setting-up-nvidia-container-toolkit
[4] Chainer. https://github.com/chainer/chainer/releases
[5] GPUs supported. https://en.wikipedia.org/wiki/CUDA#GPUs_supported
[6] Overview of Images. https://hub.docker.com/r/nvidia/cuda
[7] Cupy v9.4.0. https://docs.cupy.dev/en/v9.4.0/install.html
[8] nvidia/cuda. https://hub.docker.com/layers/nvidia/cuda/11.4.1-cudnn8-runtime-ubuntu18.04/images/sha256-b8441f7e65e345b8b73f5464387711a2b82f60911ac55ed203ea5a1bf0061bc6?context=explore

深層学習のためのDocker(1)

機械学習のために利用可能な実験サーバーが複数ある状況を考える. この時,コードの実行に必要なライブラリをそれぞれのサーバーにおのおのインストールすることには,いくつかの問題がある:

  • 手間: 全てのサーバーにいちいちライブラリをインストール/更新していくのは面倒
  • 副作用: サーバーを他者と共有している場合,ライブラリのバージョン変更などの影響がお互いに気になる

Docker利用の意義

以上の議論から,いくつかのライブラリをまとめてdocker imageとして持っておくと便利だと言える.

この時,それぞれの実験サーバーが異なるcompute capabilityのGPUを搭載していることが想定されるが,CUDAより上のレイヤをできるだけ新しいバージョンにしておけば,いずれのノードにおいても同じDockerイメージを利用できる.これについて次の章で述べる.

GPU/ドライバ/CUDA/cuDNN/深層学習ライブラリの互換性

NVIDIAGPUで深層学習をやるには,5つのレイヤ: GPU/ドライバ/CUDA/cuDNN/深層学習ライブラリ を理解する必要があり,これを復習する.

  • ドライバはGPUに対して後方互換性がある.つまりGPUを固定してドライバを更新し続けることができる.[1]
  • ドライバはCUDAに対しても後方互換性がある.つまりCUDAを固定してドライバを更新し続けることができる.
  • 特定のCUDAを使うには,対応するドライバの最低バージョンがある.つまり新しいCUDAには一定以上新しいドライバしか使えない.[2]
  • Compute capabilityの概念によれば,新しいGPUに対応しているCUDAは新しいバージョンだけ.つまりGPUをアップグレードするとCUDAも必然的にアップグレードしないといけない.[3]
    • 逆に,新しいCUDAは割と古いGPUにも対応している
  • 大抵,新しいCUDAには新しい深層学習ライブラリしか対応していない.CUDA/cuDNN/深層学習ライブラリはセットでバージョンを揃える.
  • つまり:
    • ドライバは常に最新
    • 新しいGPUには新しいCUDA/cuDNN/深層学習ライブラリしか使えない
    • 新しいリソース: コード/深層学習ライブラリ/cuDNN/CUDA,はドライバさえ更新していれば古いGPUでも基本的には使える(はず)
    • 逆に,古いリソースは新しいGPUでは基本的に動かない
  • コード/深層学習ライブラリ/cuDNN/CUDAはセットにして,最新を追従するよう努める

NVIDIA Container Toolkit

DockerコンテナとNVIDIA driverの接続が面倒らしい. そこを自動でやってくれるのがNVIDIA Container Toolkit (NVIDIA Docker). ドライバはホストOSにインストールしておく必要がある.[4]
前述の通りこれは最新を入れれば良い. ベースとなるimageはNVIDIAが色々公開しているので利用すると楽できる.

参考文献

[1] NVIDIAドライバダウンロード. https://www.nvidia.co.jp/Download/index.aspx?lang=jp
[2] NVIDIA CUDA Toolkit Release Notes. https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html
[3] GPUs supported. https://en.wikipedia.org/wiki/CUDA#GPUs_supported
[4] NVIDIA Container Toolkit. https://github.com/NVIDIA/nvidia-docker

実験管理(1)

概要

機械学習の研究開発においては,プログラムやハイパーパラメータの変更・モデルの学習・モデルの評価,というサイクルを繰り返す.本稿では,より素早く質の高いアウトプットを目指し,実験管理:洗練された方法で実験結果を管理する方法を紹介する.

実験管理の方針

筆者は,実験管理に次のような特徴を要請する:

  • 集約: コード・学習済みモデル・メタ情報(実験の概要や評価値など)の3点をセットにして保存できる
  • 再現: 所望のコード・学習済みモデルにロールバックして再学習・再評価を行える
  • 俯瞰: 複数の実験のメタ情報を俯瞰し,比較できる

これらを実現するシンプルな方法を検討する.

1

実験が完了した時点で,コード・学習済みモデル・メタ情報の3点をセットにして適当なドライブのフォルダにまとめて保存しておく方法が考えられる.最もシンプルに「集約」と「再現」を達成できる方法である. 一方で,実験間で共通するプログラムやデータセットについて,これらの共通性は意識されることなく複製される. したがって,非効率的にドライブの容量を消費してしまう懸念がある.

2

Gitを利用して3点を保存する方法が考えられる.Gitの有するオブジェクト・zlib圧縮・パッキングの仕組みにより,一つ目の方法より効率的に容量を圧縮しながらコードを保存することができる. また,Gitに特有の「コミット(スナップショット)の親子関係」の概念により,コードを変更して別の実験を実施していくとき,それらの実験がどのように派生してきたのかを理解しやすい. 一方で,gitの問題点として,gitにコミットするだけだと結局スプレッドシートでメタ情報の一覧を作る羽目になることが挙げられる.単にgitを利用するだけでは,「俯瞰」は達成できない. コードは差分を意識しながら効率よく保存できるが,学習済みモデルのパラメータは共通する部分が皆無なので,git以外に保管する手も考えられる.

TODO

A.pyでモデルAを学習して評価指標A'を計算する. 同様に,B.pyでモデルBを学習して評価指標B'を計算する. これが完了したのち,C.pyでモデルAとモデルBを用いてモデルCを学習して評価指標C'を計算する,というプロセスを考える.

この時,モデルA,Bの学習それ自体も一つの実験であるし,これを前提にしたモデルCの学習も一つの実験であるが,この関係をうまく取り扱う方法はあるか?

Appendix: 実験管理ツールの調査

実験管理のフロー: コーディング -> 実験サーバーに転送 -> モデルを学習・評価 -> 3点セットを保存 -> メタ情報を一覧に追加

WandB

  • wandbは学習曲線の可視化と比較・レポーティングに強みのあるサービス
  • スタンドアローン版もある[2]
  • [3]によれば,特定のディレクトリの内容を保存できるので,コードや学習済みモデルのバージョン管理もできそう
    • いわゆるtrain.pyをartifactsとして保存しておけばバージョン管理できる?
    • ただ,そのへんの機能の紹介に乏しいのでコード管理はgit前提かも?

MLFlow

  • [4]によれば,サーバーは自分で立てないといけない
    • 複数台の結果を統合する時にサーバーが必要なはず
    • 実験サーバーの1台をtrackingサーバーにすれば良いのでは?
    • そんなことしなくてもwandbなら自分でサーバー建てられる上に最初からクラウドも提供されているのに・・・
      • databricksのcommunity editionなら楽できる?[5][6].無料で建てられるっぽいけどやや面倒

Comet.ml

  • self hosting機能がなさそう?
    • 自分らで鯖を立てられない? クラウドに出られない環境とかだと逆にself hosting機能が必要になるので・・・

Neptune.ai

  • [7]によればself hostingできそう

参考文献

[1] 試行回数の増やし方2021年度版. https://speakerdeck.com/butsugiri/increasing-number-of-attempts-ver-2021
[2] Self-Hosting. https://docs.wandb.ai/guides/self-hosted
[3] Model Versioning. https://docs.wandb.ai/guides/artifacts/model-versioning
[4] MLflowを利用して機械学習チームで共有可能な実験管理をする方法. https://qiita.com/MasafumiTsuyuki/items/9e03e285d4b9e0c41a7c
[5] MLflow Tracking. https://mlflow.org/docs/latest/tracking.html#how-runs-and-artifacts-are-recorded
[6] Managed MLflow Now Available on Databricks Community Edition. https://databricks.com/jp/blog/2019/10/17/managed-mlflow-now-available-on-databricks-community-edition.html
[7] Deploying Neptune on your server (on-premises). https://docs.neptune.ai/administration/on-premises-deployment

TryOnGAN

TryOnGAN: Body-Aware Try-On via Layered Interpolationに関する雑なメモ

  • ACM SIGGRAPH 2021 (2021.1にarXiv公開)
  • Virtual Try-On (VITON, VTON)に関する最近の研究

問題設定:

- 2枚の入力画像が与えられる
    - I^pには人物Aが服Bを着用した様子
    - I^gには人物Cが服Dを着用した様子
- 人物Aが服Dを着用したphotorealisticな画像I^tを出力する

概要:

  • photo-realismとhigh resolutionの性質を持つStyleGAN2をベースとして用いる
  • 10万枚のunpairdな画像から学習
  • これまでの手法には,学習にペアデータが必要なものがある
    • 人物Aが服BをポーズC,D,E...で着用している画像
    • 人物Aが服Bを着ている画像と,服B単体の画像
  • ペアデータ不要な手法(O-VITON, Zanfir et al.)と比較して,photo-realismが増している

手法:

  • (1) pose-conditionedなStyleGAN2を学習

    • ファッション画像についてStyleGAN2を学習
    • 各解像度において,RGB画像に加えて,セグメンテーション画像も出力させる
    • 識別器として,poseとsegmentationの正しさを判別するものも導入

    • 潜在空間のポーズとスタイルのentanglementを防ぐため,StyleGAN2をポーズでconditiningして学習する

    • StyleGAN本来の定数のlatentを,ポーズを表現する4x4のpose representationで置き換える
      • posenetを用いて,17箇所のキーポイントを抽出
      • この出力から64x64x17チャネルのヒートマップを作成する
      • ヒートマップをさらにencoderに入力し,最終的に4x4のサイズまで圧縮する
  • (2) 各層でのinterpolation coefficientを探索する

    • IpとIgについてのwを探索して,linear interpolationすることを目指す
    • 各解像度でのスタイルの結合係数q_nをそれぞれ求めたい
    • それぞれのq_nが収束するまで,L_localizationと2つのconsistencyの加重和を最小化する
      • L_localizationは,興味のある領域についてのみスタイルの内挿をとりたい
      • これはつまり,semantic segmentationした結果,衣服(segmentation i)に影響するようなwの一部分だけ内挿したい
      • M^{CxK}は,どのチャネルがどのセグメントに影響するかの指標
      • これを使って,衣服に関係あるiについて影響のあるチャネルだけを内挿するようにqを求める(詳細未理解)
    • 2つのconsistency lossを採用
      • IgとItにsegmentationマスクをかけ,衣服の部分だけを残す
      • VGGのfeature上で,2枚が近づくような誤差を導入(Germent Loss)
      • また,IpとItについて,顔と髪型の領域の特徴が近づくような誤差を導入(Identity Loss)

実験:

  • Tesla V100 (FP32だと1080Tiと同程度?) x 8 で12日学習

感想:

  • StyleGAN2という強力なモデルを用いて,スタイルの編集を通してVITONを実現するところがキモ
    • 画像空間で編集してしまうとphotorealisticでないものが生成されがち.あくまで完成品の画像はStyleGANによって生成されるので,ここでphoto-realismが保証される