pit-rayの備忘録

知識のあうとぷっと

GANを使ってデータセットを増やしたい【Semi-Supervised Anime Semantic Segmentation】

はじめに

 今回はAdversarial Learning for Semi-Supervised Semantic Segmentation[7]という論文を参考に、小規模のデータセットでSemantic Segmentationを行いました。経緯としては、pix2pixでラベルマップからイラスト生成ができたら面白いだろうというのがあります。しかしながら、手作業のアノテーションは、60枚ほどにして心が折れましたので、自動化してやろうという魂胆です。

次のような256 x 256のラベルマップを用意しました。
背景顔と首その他
といった具合です。

f:id:pit-ray:20191115211101j:plain
レム

結果から言うと、ラベル無しの10000枚のデータセットに対して、ある程度のクオリティのラベルが半数ほど得られました。しかしながら、そのクオリティを判別することが容易でないため、正直にアノテーションしたほうが良かった気もします。

記事中に何度もリンクを載せていますが、ここにもGitHubへのリンクを置いておきます。
今は亡きChainerでの実装となります。
github.com


事前知識

Semantic Segmentation

 ニューラルネットワークを用いた最も基本的なタスクは、分類といえるでしょう。人間もそうですが、何かを認識しなくては何も進みません。まず、次の図をご覧ください。

f:id:pit-ray:20191120202727j:plain:w500
手書数字認識

 この図は、数字の画像がどのクラスに属するか判断するニューラルネットワークを概念的に表しています。一般的には一次元のデータのスコアをもとにソフトマックスで分類を行います。

 対して、Semantic Segmentationは一枚の画像を1ピクセルに置き換えようという試みです。つまり、ピクセル単位でどのクラスに属するのかを判別する検出です。

f:id:pit-ray:20191120204618j:plain:w400
Semantic Segmentation

 上の図で示すように、出力層は高さ(H) x 横(W) x クラス(C)のマップとして出力します。そして先ほど述べた通り、クラス方向にソフトマックスを適用することで、そのピクセルが何を表しているのかを判別させます。

それでは、ネットワークのアーキテクチャを見ていきます。

f:id:pit-ray:20191120211044j:plain
Different types of networks for semantic segmentation(出典[1])
 最も基本的なSemantic Segmentationのアーキテクチャは、FCN(Fully Convolutional Networks)[3]です。畳み込み層を連ね、クラス分けを行います。

 そのFCNの派生形として、U-NetやSegNetなどのEncoder-Decoder構成のネットワークがあります。これらは、位置情報を保持するためにスキップ構造を追加しています。上の図(b)でいう矢印のことです。

 そして現在のSOTAは、Dilated Convolutionを用いた構成で、DeepLabやFastFCN[1]などがあります。今回はDeepLab(Dilated FCN)を用いたネットワークを主として扱います


DeepLab

 DeepLabは2016年にLiang-Chieh Chen氏らが考案したものですが、その後v2[5], v3[4]ときてv3+と進化を続けてきました。特にFastFCN[1]はDeepLabの動作を近似することで、高速でより高い精度を出しています。今回は、DeepLab v3をベースとしました。

Atrous Convolution

 DeepLabを形成する根幹のメソッドは、Atrous Convolution (Dilated Convolution)です。これは、CNNなどで扱われる畳み込み演算を拡張したものとされています。元のフィルターの大きさをk × k、dilated rateをrとして

         k^{\prime}=k+(k-1)(r-1)

となる k^{\prime}×k^{\prime}のフィルターを用いた畳み込み演算です。

次の図は、 k=3, r=2の具体例です。

f:id:pit-ray:20191121003544j:plain:w200
Atrous Convolution Filter (k=3, r=2)

したがって、 r=1のときは通常の畳み込みと同じになります。

 Atrous Convolutionのメリットは、パラメータを増やすことなく、大きな視野をもつことができる点です。従来のFCNではダウンサンプリングをする代わりにチャンネル方向に深くすることで、ピクセルにRGB以上の情報を持たせるように畳み込みを行いました。しかしながら、ダウンサンプリングで解像度を落とすと位置的な情報が失われ、Semantic Segmentationにおいてはあまり嬉しくありません。そこで、Atrousな大きなフィルターで見渡すことにより、高解像度でも従来と同等の畳み込みができるようになったのです。

 DeepLabでは、従来通りにダウンサンプリングした後、出力層側でAtrous Convolutionを用いてある程度の解像度を保持します。その後は、v2では双線形補間(bilinear interpolation)で教師データ側の解像度を落とし、低解像度で損失を算出しますが、v3ではGeneratorの出力側をアップサンプリングして、教師データラベルの解像度に合わせます。

 ちなみに多くの機械学習フレームワークでは、Atrous ConvolutionはDilated Convolutionであったり、rを指定するDilationパラメータとして実装されています。

Atrous Spatial Pyramid Pooling (ASPP)

 前節ではAtrous Convolutionがrによって視野を広げられることを確認しました。ここで解説するAtrous Spatial Pyramid Pooling (以下ASPP)は、複数のAtrous Convolutionを用意し、それぞれのrを変えて重ねることでミクロからマクロのスケール(ピラミッド状)で情報を得ようという試みです。

f:id:pit-ray:20191210042944j:plain
Atrous Spatial Pyramid Pooling
(このイラストを描いた後に思いましたが、傾いてませんか?気のせいですかね...)

それぞれのスケールで畳み込み(atrous-BN-Relu)を行った後、concatenateして畳み込みでまとめます。詳しい実装はGitHubをご覧ください。

github.com


以上がDeepLab v3のメインメソッドです。
v2以前ではCRFsを導入していますが、v3以降はそうではないので割愛させていただきます。

それでは、今回の根幹となる手法について見ていきます。

手法

 まず、GANを用いた半(弱)教師ありSemantic Segmentationの概要をご覧ください。

概要

f:id:pit-ray:20191210050409j:plain
Semi-Supervised Semantic Segmentationの概要(出典:[7])

 上図のSegmentation NetworkとはGANでいうGeneratorに当たるネットワークであり、この訓練モデルを得るのが最終的な目的です。また、図中のDiscriminatorは本物か偽物かの確率をピクセルレベルでConfidence Mapとして出力します。したがって、Discriminatorの出力は0から1のグレイスケール画像となり、本物の場合は白くなり、偽物の場合は黒くなります。

ネットワーク構成

元の論文[7]では、GeneratorにはImageNetで訓練済みのResNet-DeepLab v2、DiscriminatorにはFCN(skip構造なし)を用いています。今回はGeneratorとしてDilated FCN DeepLab v3と、ImageNetで訓練済みのResNet101-DeepLab v3を用いました。なぜなら、私の勘違いにより、訓練されていないDilated FCNを用いてある程度の工程を進めてしまったため、本記事に載せざる負えなくなったからです。最初から訓練済みResNetを用いていればよかったと非常に後悔しております。また、Discriminatorは元の論文と同じです。

ネットワークの詳細は、それぞれFCNにAtrous機構をくっつけただけで、はじめに示した3種類のSemantic Segmentationネットワークの構成の右と同じです。ただし、Generatorに関しては、アップサンプリングにPixel Shuffler[8]を用いています。

損失関数

それでは、損失関数について見ていきます。ただし、論文中の記号はこちらで適宜変更しています。

Segmentation Network(以下Generator)とDiscriminatorの損失関数を以下のように設定します。

  L_D=L_{adv}

  L_G=L_{ce}+\lambda_{adv}L_{adv}+\lambda_{semi}L_{semi}

ただし、Generatorが生成したスコアのベクトルをg、教師onehotベクトルをxとします。

adversarial loss

 元の論文[7]では、GANの元の論文[2]で示されている損失関数と同じものを用いています。対して、今回はhinge lossを用いました

cross entropy loss

 上のL_{ce}に対応するものですが、分類でよく用いられるものと同じです。ピクセルごとに交差エントロピーを求め、平均か和をとります。というのも、Generatorの出力はチャンネル方向にクラス分けされたスコアのベクトルです。したがって、あるピクセルがそのクラスに属する確率の分布としてみなすことができます。

  L_{ce}=-x\log(g)


semi-supervised loss

 この損失は、半教師あり学習時に適用するものです。Generatorから生成した画像とそのDiscriminatorの出力のみで学習を行います。次式は1ピクセル分のlossを表しており、最終的にはこの平均か和を取ります。

  L_{semi}=-I(D(g)>T_{semi})*Y\log(g)

まず、T_{semi}は閾値(threshold)であり、Generatorが作ったラベルのどの部分を信頼するかというパラメータとなります。例えば、次のようにGeneratorが生成したラベルをDiscriminatorに入れた結果、中心部分が本物だろうという予想がなされたとします。

f:id:pit-ray:20191211233205j:plain
confidence mapの値分布(gnuplotより作成. 画像の大きさは適当です)

このとき、次のようにここまでは信頼できるという値を決め、それより上を本物だと断定させます。

f:id:pit-ray:20191211233208j:plain:w300
threshold面で切り取る

上で示したIという関数は、このような操作をする指示関数です。Pythonで実装する場合は、比較演算子で比べるだけで実現できます。

次に、Yについて考えます。まず、Generatorの生成物は、チャンネル方向にクラスラベルが決められているようなスコアのベクトルとなります。(下に再掲)

f:id:pit-ray:20191120204618j:plain:w300
Generatorの生成物(再掲)

このベクトルに対し、ピクセル単位でスコアが最も大きいクラスを、そのピクセルのクラスと断定します。1ピクセル目は車、2ピクセル目はネコといった具合です。Yは、その断定したクラスのみを1とした教師データと同形状のonehotベクトルを表しています。

以上により、Discriminatorが本物と断定したonehotマップと、Generatorがクラスを断定したonehotベクトルが用意できました。これらを要素ごとに掛け算して、疑似的な教師データを作り出します。そのうえで、L_{ce}と同様のcross entropy lossを計算するのです。

因みに、Chainerでは次のように実装できます。GitHubからコピペしただけです。

    #semi-supervised loss
    #HW-filter
    confidence_mask = F.sigmoid(unlabel_d).array > opt.semi_threshold

    #C-filter (which does pixels belong to each class)
    class_num = unlabel_g.shape[1]
    xp = cuda.get_array_module(unlabel_g.array)

    predict_index = unlabel_g.array.argmax(axis=1)
    predict_mask = xp.eye(class_num,
        dtype=unlabel_g.dtype)[predict_index].transpose(0, 3, 1, 2)

    #CHW-filter
    ground_truth = confidence_mask * predict_mask

    st_loss = -F.mean(ground_truth * F.log(unlabel_g + eps))

Anime-Semantic-Segmentation-GAN/loss.py at master · pit-ray/Anime-Semantic-Segmentation-GAN · GitHub

 L_{ce}L_{semi}は、教師アリの場合は前者、ナシの場合は後者を使うといった形に切り替えます。また、L_{semi}を計算する際にはある程度学習したDiscriminatorが必要なので、教師アリのみでしばらく学習したのちに、発火させ、教師ナシデータも含めるようにします。

Dilated FCNによる結果とその詳細

 教師アリデータは66枚で、教師ナシデータは約500枚です。ただし、教師アリデータにのみ、左右反転とγ調整によるData Augmentationを施し、4倍のデータ量で行いました。

ハイパーパラメータは次の通りです。ただし、GはGenerator、DはDiscriminatorを表します。

ハイパーパラメータ options.pyのオプション名
batch size 32 batch_size
G Adam learning rate 0.00025 g_lr
G Adam beta1 0.9 g_beta1
G Adam beta2 0.99 g_beta2
G weight decay 0.0004 g_wight_decay
D Adam learning rate 0.0001 d_lr
D Adam beta1 0.9 d_beta1
D Adam beta2 0.99 d_beta2
max epoch 2000 epoch max_epoch
iteration of starting semi-supervised learning 5000 iteration semi_ignit_iteration
use Spectral Normalization[6] False conv_norm
G network unit size 64 ngf
D network unit size 64 ndf
ASPP unit size 256 aspp_nf
adversarial loss hinge loss adv_conv_mode
\lambda_{adv} 0.01 adv_coef
\lambda_{adv} (semi-supervised) 0.001 semi_adv_coef
\lambda_{st} 0.1 semi_st_coef
T_{semi} 0.2 semi_threshold

これらの生成結果が次の通りです。pix2pixHDで逆に通した結果を一番上にオマケで載せておきます。一番下が後からアノテーションした正しいラベルです。

f:id:pit-ray:20191121190241j:plain
生成結果(上からpix2pixHD, AdvDeepLab, SemiAdvDeepLab, ground-truth)

当初、DeepLabをU-Netとして学習を行っていましたが、十分な結果は得られませんでした。

二段目は、L_{semi}の係数\lambda_{semi}を最後まで0にした時の結果です。したがって教師あり画像のみで学習した結果となります。ある程度のセグメンテーションはできていますが、pix2pixHD同様、ノイズが非常に多く、対応しきれていません。

三段目が、今回のメソッドで生成した結果ですが、データの拡張という観点から、教師ナシとして用意した訓練データからの生成結果です。というのも、今回は教師なしに対しての生成物が得られれば良く、非学習データに対する汎用性の高いモデルを必要としないためです。この結果を見ると、左から3番目のような小さな眼や、一番右のように眼鏡をかけた場合は、うまく判別ができていないことが分かります。

ここではこの後に、これらの生成データの小さなミスを修正するような軽いアノテーションを行いました。

f:id:pit-ray:20200124234229j:plain:w512
process flow(縦の幅: データ数)

これにより、教師アリデータを約600枚に増やしました。
(ここの作業をし終えてから訓練済みか否かに気が付いたのです...)


訓練済みResNetによる結果とその詳細

 ここでは、先ほど増やした600枚に加え、運よく入手できたデータセット100枚を加え、700枚の教師アリデータと、約10000枚の教師ナシデータで学習を行います。また、先ほどと同様に教師データにはDeta Augmentationを施し、4倍の2800枚としました。

これらの10000枚のデータセットは、safebooruでスクレイピングを行った30000枚のデータに対し、lbpcascade_animefaceや背景判定を行い、絞り込んだものです。

ハイパーパラメータは次の通りです。ただし、変更箇所だけ示します。

ハイパーパラメータ options.pyのオプション名
batch size 5 batch_size
G Adam beta1 0.0 g_beta1
G Adam beta2 0.9 g_beta2
D Adam beta1 0.0 d_beta1
D Adam beta2 0.9 d_beta2
max epoch 100 epoch max_epoch
iteration of starting semi-supervised learning 10000 iteration semi_ignit_iteration
use Spectral Normalization[6] True conv_norm


しかしながら、これらのハイパーパラメータとネットワークの構成では、30000イテレーションあたりで発散してしまい、単色の画像となりましたので、その直前の結果を次に示します。

f:id:pit-ray:20200124213414j:plain
pre-trained ResNet101によるSemantic Segmentation
イラストなどの著作物を研究やデータ分析の目的で用いるのは問題ありませんが、ここに張り付けるのは低解像度でもグレーゾーンであるため、ぼかしを加えております。
また、一度、700枚すべてにラベルに関して、顔と首を緑としていたところを、顔だけ緑と手作業で変更しました。

というのも、これと並行して行っていた、ラベル→イラスト生成においては首と顔の判別がうまくなされなかったからです、

 先ほどのDilated-FCNと比べると、特に服と重なっている髪の認識精度があがったような気がしますが、この例と同程度のクオリティはせいぜい5割といったところです。(といっても5000枚ですが)

 参考にした論文[7]ほどの性能が出ていない原因としては、独自のデータセットに対するハイパーパラメータの最適化を行っていないことや、Segmentationのクラスがざっくりしすぎていること、加えて、アノテーションの甘さが出てしまった可能性も大いに考えられます。

 したがって、まだ改善の余地は大いにあります。今回の内容が参考になれば幸いです。


念のため、PCの環境を記載いたします。
OS: Windows10 Home
CPU: AMD Ryzen 2600
GPU: MSI GTX 960 4GB
Lang: Python 3.7.1
framework: Chainer 7.0.0, cupy-cuda91 5.3.0


今回参考にした文献は次の通りです。

参考文献

[1] Huikai Wu, Junge Zhang, Kaiqi Huang, Kongming Liang, Yizhou Yu. FastFCN: Rethinking Dilated Convolution in the Backbone for Semantic Segmentation. arXiv preprint arXiv:1903.11816, 2019(v1)

[2] Ian J. Goodfellow, Jean Pouget-Abadie, Mehdi Mirza, Bing Xu, David Warde-Farley, Sherjil Ozair, Aaron Courville, Yoshua Bengio. Generative Adversarial Networks. arXiv preprint arXiv:1406.2661, 2014

[3] Jonathan Long, Evan Shelhamer, Trevor Darrell. Fully Convolutional Networks for Semantic Segmentation. arXiv preprint arXiv:1411.4038, 2015

[4] Liang-Chieh Chen, George Papandreou, Florian Schroff, Hartwig Adam. Rethinking Atrous Convolution for Semantic Image Segmentation. arXiv preprint arXiv:1706.05587, 2017 (v3)

[5] Liang-Chieh Chen, George Papandreou, Iasonas Kokkinos, Kevin Murphy, Alan L. Yuille. DeepLab: Semantic Image Segmentation with Deep Convolutional Nets, Atrous Convolution, and Fully Connected CRFs. arXiv preprint arXiv:1606.00915, 2017 (v2)

[6] Takeru Miyato, Toshiki Kataoka, Masanori Koyama, Yuichi Yoshida. Spectral Normalization for Generative Adversarial Networks. arXiv preprint arXiv:1802.05957, 2018

[7] Wei-Chih Hung, Yi-Hsuan Tsai, Yan-Ting Liou, Yen-Yu Lin, Ming-Hsuan Yang. Adversarial Learning for Semi-Supervised Semantic Segmentation. arXiv preprint arXiv:1802.07934, 2018 (v2)

[8] Wenzhe Shi, Jose Caballero, Ferenc Huszár, Johannes Totz, Andrew P. Aitken, Rob Bishop, Daniel Rueckert, Zehan Wang. Real-Time Single Image and Video Super-Resolution Using an Efficient Sub-Pixel Convolutional Neural Network. arXiv preprint arXiv:1609.05158, 2016