ただのメモ

開発で得た知識のアウトプット

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

はじめに

 今回はAdversarial Learning for Semi-Supervised Semantic Segmentation[7]という論文を参考に、小規模のデータセットでSemantic Segmentationを行いました。これにより、pix2pixでラベルマップからイラスト生成ができるような疑似ラベルを作れるようになることを期待しています。また、イラストを描くためのソフトウェアとして、レイヤを後からパーツごとに分割するといったことも可能になります。

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

レム

結果から言うと、ラベル無しの1万枚のデータセットに対して、ある程度のクオリティのラベルが半数ほど得られました。

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


事前知識

Semantic Segmentation

 まず、最も基本的なタスクである分類について考えます。

手書数字認識

 この図は、数字の画像がどのクラスに属するか判断するニューラルネットワークを概念的に表しています。一般的にはソフトマックス関数などで確率(のようなもの)を出力します。

 対して、Semantic Segmentationは一枚の画像を1ピクセルに置き換えたものと言えます。つまり、ピクセル単位でどのクラスに属するのかを判別する分類です。

Semantic Segmentation

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

Semantic Segmentationの代表的なネットワークのアーキテクチャを見ていきます。

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の具体例です。

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を変えて重ねることでミクロからマクロのスケール(ピラミッド状)で情報を得ようという手法です。(DeepLabのバージョンによって若干実装が異なる)

Atrous Spatial Pyramid Pooling


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

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

手法

 まず、GANを用いた半(弱)教師ありSemantic Segmentationの概要について述べます。

概要

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とResNet101-DeepLab v3を用いました。前者はFCNにAtrous機構をくっつけただけで事前学習なしです。後者はImageNetで事前学習アリです。ただし、アップサンプリングには、Pixel Shuffler[8]を用いています。また、Discriminatorは元の論文と同じです。


損失関数

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

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に入れた結果、中心部分が本物だろうという予想がなされたとします。

confidence mapの値分布(gnuplotより作成. 画像の大きさは適当です)

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

threshold面で切り取る

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

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

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で逆に通した結果を一番上にオマケで載せておきます。一番下が後からアノテーションした正しいラベルです。

生成結果(上からpix2pixHD, AdvDeepLab, SemiAdvDeepLab, ground-truth)

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

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

三段目が、ラベルなしとして用意した訓練データの結果です。この結果を見ると、左から3番目のような小さな眼や、一番右のように眼鏡をかけた場合は、うまく判別ができていないことが分かります。

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

 先ほどの単純なモデルでは、満足のいく結果が得られなかったため、ネットワークやデータの規模を増やし再度訓練しました。
 ここでは、追加でアノテーションを行った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イテレーションあたりで発散してしまい、単色の画像となりましたので、その直前の結果を次に示します。

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

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

未知のデータセットでは、次のような結果となりました。まず、比較的成功したものです。

比較的良いもの

次に失敗例です。(グロい)

失敗例

 学習データセットから当然の結果ですが、高い精度が期待できるのは、背景が白く、頭から肩が見えるようなイラストだと言えます。逆に、背景が複雑なものであったり、画像いっぱいに顔があるような場合では、極めて精度が落ちています。

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

IoU等で定量的な評価を行っていない点については、お許しください。


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

参考文献

[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