BERT一問一答

BERTとNLPの基礎について勉強したのでメモ。

  • paraphrasingとは? どんなタスク?

    • 言い換え。要約とか剽窃検出での応用がある。
  • named entity recognitionとは?

    • 文中の固有表現にタグをつけるタスク。地名とか時間とか人名とか
  • BERTの学習は穴埋めだという記憶があるが、具体的には?

    • Masked Language Model (MLM)というパラダイム。一定確率で入力単語をMASKして、これを予測するソフトマックス出力のモデルを学習する。
    • BERTの特徴はBidirectionalな文脈考慮をしていること。これを実現するためにlossも前方予測ではなくMask予測に置き換わっている。
    • ある文章の自然さを定量化するためにMaskedLMが用いられたりする[3]。[n番目の単語のBERT尤度]を全N個の単語について足し合わせたもの。
  • BERTと言語モデルはどう違う?

    • ?
  • huggingfaceのtokenizerとは?

    • 日本語を分割してくれるやつ。
  • tokenizerに種類があるのはなぜ?

  • 日本語について、tokenの種類はtokenizerによって変化する?

    • ?
  • 使用歴のあるモデルは?

    • cl-tohoku/bert-base-japanese[1]
      • bert-large-japaneseもあったのか / BERTの論文内でbaseとlargeが定義されている。
    • wikipediaのデータで学習されている
    • Mecab, IPA-Dict. vocab-size=32k
    • google cloud tpu v3 8coreで5日間かけて学習

[1] https://huggingface.co/cl-tohoku/bert-base-japanese
[2] Tokenizer の違いによる日本語 BERT モデルの性能評価. https://www.anlp.jp/proceedings/annual_meeting/2021/pdf_dir/P4-12.pdf
[3] https://zenn.dev/hellorusk/articles/f9e6c503dc54e2

StyleGAN一問一答

StyleGAN[1]について勉強したのでメモ。

  • なぜStyleGAN?

    • [2]のTable 1でも示されているように、現状では多くの研究がStyleGANをベースにして発展を遂げている。
  • Z-SpaceとW-Spaceの違いは?

    • Zの元をmapping networkと呼ばれる8層の全結合層で変換してWの元とする。
  • Z-SpaceとW-Spaceの次元は?

    • 一般にどちらも512
  • zを変換して得たwをどうやって使う?

    • まず、画像生成過程の各解像度において、それぞれ学習されたアフィン変換によってwを変換する。この変換結果をこの文脈で一般にstyleと呼び、像空間をS-Spaceと呼ぶ。
    • 次に、AdaINと呼ばれる操作によってこれを生成中の画像に注入する。
  • zはどうやって生成する?

    • 論文内での記述は見つけられていないが、著者実装や一般的な実装[5]を見た感じおそらく標準正規分布
  • zとwが同じく512次元なら、全結合層で変換する理由は何?

    • zが標準正規分布だと、確率が一定以上になる領域は球になるが、タスクによっては球だとdisentanglementをそもそも達成できないことがある。
      • ある属性が球の中の1本のベクトルとして表現できなくて、湾曲する
    • タスクによらずdisentangledな空間が欲しくて、これがWにあたる。
    • [1]のFig6参照
  • Disentanglementはどの程度達成される? なぜ?

    • [1]のTab3,4のseparabilityが低いほうが、あるattributeの有無を線形な分類器で判定できることを意味する。StyleGANのW-Spaceは従来よりdisentangledではある。
    • 理由は一つ前のQA以外に[1]の本文には書いていない?
      • 自分の考察として、まず各層でwを挿入する部分があり、Style Mixing Regularizationの工夫もあり、挿入する箇所ごとに担当する属性が独立しやすい。
      • それぞれの挿入箇所で大域的な特徴や詳細なテクスチャなどを決定するが、これらはwの線形変換で求められたスタイルに依存しているので、wの空間では線形にdisentangleしやすいのでは?
  • 従来の生成器では最初にzを入力するが、StyleGANでは?

    • 4x4x512の定数を入力する。潜在変数zによる画像の操作はwとAdaINを介して行われる。
  • もっと詳しくW-SpaceからS-Spaceへの変換を教えてほしい

    • [3]がかなり詳しい。Appendix Aとか
  • wをsに変換するアフィン変換で2つに分岐しているのは何?

    • AdaINの引数であるscaleとbiasをそれぞれ計算している。
  • Style Mixing Regularizationとは何? どんな効果がある?

    • z1とz2を生成し、n層まではw1で、n+1層以降はw2で生成するもの。隣接する層のアフィン変換が近いものになって、スタイルが相関することを防ぐ狙いがある。
    • 基本的にはすべての層でアフィン変換への入力に同じwが用いられるうえ、隣接する層は生成過程のfeatureも近いはずなので:入出力が類似している学習なので、似たようなスタイルが学習されてしまうと考えるのは自然。
  • W+ Spaceとは?

    • GAN Inversion[2] の分野でよく使われる。学習済みのStyleGANの表現力を向上させてInversionの質を上げるために、層毎に異なるwを用いる技法で、これをW+の元とする。[4]
  • W+の次元は?

    • 一般に18x512。StyleGANの生成器の18箇所のw挿入箇所にそれぞれ1x512を使用する。
  • GAN Inversionとは?

    • 学習済みのGAN生成器を用意して、適当な画像に対応する潜在変数を求める試み。画像の編集に活用できてうれしい。
    • 生成器を使って対応する潜在変数を推論時に求めるoptimization-basedな方法は、再構成の品質が高い傾向がある。一方で、画像に対応する潜在変数を出力するEncoderを学習するlearning-basedなアプローチは、より高速に潜在変数を求めることができる。質と時間のトレードオフが存在するといわれる。[2]
  • StyleGANとStyleGAN2の違いは何?

  • HFGI[6]は画像Xからどんな潜在変数を求めている?

    • E_0でW+を求める。
    • \tilde_\Delta = G(E_0(X)) - X が失われた情報。
    • 別のエンコーダE_cでC = E_c(\tilde_\Delta)を計算する。
    • CをAdaINに類似の方法で生成器の各層に挿入する。Cは拡張されたW+とみなせる。
    • 編集はW+に対してベクトルを足すことで行い、デコードしてこれを\hat X_editとする。残念ながら、\tilde_\Deltaが編集に置いていかれる。
    • ADA(X_edit, \tilde\Delta)で\tilde\Deltaにも編集を適用する。ADAの学習には視点を変換しただけの雑なデータを用いるが、うまくいくとのこと。
  • スタイルを編集する方法は?

    • 一般的にはその手法で考えている潜在空間にベクトルを重み付きで足す線形な操作が行われる
    • HFGIではInterFaceGAN[7]の結果が用いられている。
    • InterFaceGANでは、無数に生成した潜在変数から画像を生成し、これに識別器が属性ラベルを付与する。潜在空間で属性ラベルを用いて対応する潜在変数を線形識別すると、対応する単位法線ベクトルが属性を操作する方向となる。

[1] Tero Karras, Samuli Laine, Timo Aila. A Style-Based Generator Architecture for Generative Adversarial Networks. CVPR, 2019.
[2] Weihao Xia, Yulun Zhang, Yujiu Yang, Jing-Hao Xue, Bolei Zhou, Ming-Hsuan Yang. GAN Inversion: A Survey. https://arxiv.org/abs/2101.05278
[3] StyleSpace Analysis: Disentangled Controls for StyleGAN Image Generation. https://arxiv.org/abs/2011.12799
[4] Image2StyleGAN: How to Embed Images Into the StyleGAN Latent Space?. ICCV, 2019
[5] https://github.com/rosinality/stylegan2-pytorch/blob/master/generate.py
[6] High-Fidelity GAN Inversion for Image Attribute Editing. CVPR, 2022.
[7] Interpreting the Latent Space of GANs for Semantic Face Editing. https://github.com/genforce/interfacegan

PyTorch版StyleGAN2の編集

Introduction

StyleGANをはじめとする画像生成モデルが注目を集めている。生成モデルはノイズから画像への写像と捉えられるが、この逆写像を求める試みはGAN Inversionと呼ばれ、様々な手法が提案されている[1]。 GAN Inversionにおける実装では、前提とする生成モデルにPyTorch版のStyleGAN2がしばしば用いられる[2]。 このモデルでは、一部の関数をCUDAのカーネルで置き換えて高速化しているが、CPUのみの環境での実行を目指すと、この部分の実行にエラーが付きまとう。
本稿では、とりあえず通常のPython & PyTorchでこの処理を実施して、エラーを抑制する方法について検討する。

Method

編集個所は2か所ある。 大抵のリポジトリでは、modelsの下にstylegan2が置かれていて、stylegan2/opディレクトリ内に原因の箇所がある。

  • 一つ目はfused_act.pyで、FusedLeakyReLUと呼ばれる処理が記述されている。 [3]によれば、この活性化関数の処理は自明なので、オリジナルの定義を変更する。
  • 二つ目はupfirdn2d.pyで、こちらはファイル内に既にPython & PyTorch版の実装が用意されているので、こちらを利用する。
  • 両方とも、ファイルの上の方のmodule_pathやfusedの定義はコメントアウトしておく。

<追記>色々と編集個所をこの記事で示していたが、[4]の記述に従ってうまく実行できたのでそちらを参照してほしい。 特に、upfirdn2dのnativeはそのままではうまく動かないので、[4]に従う。

Experiments

[1]で紹介されているHFGI[5]と呼ばれるInversionモデルを利用してみる。このモデルでもPyTorch版のStyleGAN2が利用されているため、CPUでデモを動かすには上記の作業が必要である。

Dockerfile CPUでデモを動かすことを考えて、以下のようなDockerfileを準備した。

FROM pytorch/pytorch:1.6.0-cuda10.1-cudnn7-runtime
RUN pip install --upgrade pip
RUN pip install matplotlib

その他の準備

  • Methodのセクションの2オペレーションの編集
  • scripts/inference.pyの143行目をdevice = "cpu"に変更
  • [6]に従って学習済みモデルをダウンロードしてcheckpoint/ckpt.ptに配置する

以上の作業により、test_imgs/以下の12枚の画像が入力される。 HFGIではStyleの編集も簡単にできるため、以下で試みる。

  • inference.shを編集し、引数を変更する:--edit_attribute='smile'
  • inference.shを編集し、編集の強さを変更する。デフォルトでは0なので明示的に指定しないとスタイル編集されない:--edit_degree=1.0

以上が完了したのち、bash inference.shを実行することで、experiment/inference_results/ディレクトリに結果が出力される。

References

[1] GitHub - weihaox/awesome-gan-inversion: A collection of resources on GAN inversion.
[2] GitHub - rosinality/stylegan2-pytorch: Implementation of Analyzing and Improving the Image Quality of StyleGAN (StyleGAN 2) in PyTorch
[3] Definition of FusedLeakyRelu · Issue #77 · rosinality/stylegan2-pytorch · GitHub
[4] Bug in upfirdn2d_native and native pytorch version of it. · Issue #81 · rosinality/stylegan2-pytorch · GitHub
[5] GitHub - Tengfei-Wang/HFGI: CVPR 2022 HFGI: High-Fidelity GAN Inversion for Image Attribute Editing
[6] GitHub - Tengfei-Wang/HFGI: CVPR 2022 HFGI: High-Fidelity GAN Inversion for Image Attribute Editing

単調劣モジュラ最大化に関する最近の動向

[注意] このブログ記事の筆者は劣モジュラ最適化の専門家ではない.

  • [Chen+] Best of Both Worlds: Practical and Theoretically Optimal Submodular Maximization in Parallel, NeurIPS21
  • [Liu+] Cardinality constrained submodular maximization for random streams, NeurIPS21

NeurIPS2021のposter sessionに,Chenらの「Best of Both Worlds: Practical and Theoretically Optimal Submodular Maximization in Parallel」と題する論文が採択されている. この論文では,単調劣モジュラ最大化問題に対する新たな探索手法「LS+PGB」を提案している.この手法は,これまでのSOTAと同じ近似比をより高速に達成できるらしい.

確認ではあるが,この最大化問題は,目的関数(集合に対して実数の評価値を与える関数)が単調劣モジュラ関数であり,n個の候補の中からk個を選択した時の評価値の最大化を目的とする. (cardinality constraint k on a ground set of size n)

単調劣モジュラ最大化問題に対しては,Nemhausarらが1978年に,貪欲法による探索が1-1/e(およそ0.63)の近似比を与えることを示している. つまり,貪欲法という非常にシンプルな方法が,任意の単調劣モジュラ最大化問題について,最悪でも最適解の0.63倍の目的関数値を与える解を出力することを意味する.

Chenらの論文のTable1を見る限り,これまでのSOTA手法たちもこの近似比を上回ってはいない. 詳しいことは確認できなかったが,多項式時間アルゴリズムによる近似比として1-1/eはoptimalらしい,つまりこれを上回るのは多項式時間では無理,ということもNemhausarらが示しているらしい.

さて,これまでと同じ近似比を与える新しいアルゴリズムについて,競われているのは速さである. 詳しくいうと,「adaptive complexity (adaptivity)」と「query complexity (queries)」である.高速化のために並列化されるのが一般的で,前者は並列化の下でのアルゴリズムのラウンド数,後者は目的関数値の計算回数である.並列アルゴリズムのもとで各ラウンド内でクエリは独立に実行できると仮定されるので,adaptive complexityが低いほど,並列化が効いているアルゴリズムであるといえるらしい.

それぞれ,log n / log log n と nが下界であることが示されている.つまり,これより高速化はできない.

提案手法であるLS+PGBは,queriesに関してほぼ理論的に最適のオーダーである O(n/(epsilon2)) を達成している.また,adaptivityに関しても,ほぼ最適のO(1/(epsilon2) log(n/epsilon))を達成している.

同じくNeurIPS21において,Liuらの「Cardinality constrained submodular maximization for random streams」も提案されている. こちらは,streaming settingと呼ばれ,ground setの元がランダムな順序で一度だけアルゴリズムに与えられるような設定での劣モジュラ最大化手法を提案している. メモリが無限なら全て集めてから普通に最大化をすれば良さそうだが,流石にそういった方向とは対立していて,メモリ消費量のオーダーの削減に注力した手法となっている. cardinality constraint kに対して,O(k/\epsilon)らしい.選択する個数に対して線形と定数倍ってすごいのでは.

アルゴリズムの詳細は理解する前に力尽きたので,また今度.

論文の重要なアイデア

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

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>の位置からアクセスできる
  • ボリュームマウントとバインドマウントがあって,これは後者.