pix2pix(unet版)の過学習の制御は可能か？？～計画編～

[前回の課題](https://qiita.com/MuAuan/items/858b7fad825f6b186682)「pix2pix(encoder-decoder版)で、unet版程度のLossになるように改善するにはどうすべきかということになります」
って、この命題はかなり荷が重い。
とはいえ、最初に戻って、考察すると何が必要か見えてくるはずです。
[以前記載したように](https://qiita.com/MuAuan/items/940b586804a13bc30aab)pix2pix(unet版)の特徴は以下３点
> （１）pix2pixは、cGANである
 （２）Generatorにu-netを使っている
※一応、普通のencoder-decoderでもよさそうですが、論文中ではよい結果は得られていないようです
（３）patchGANを利用して、L1相関を強化
 論文見るとL1相関強くして、全体的な変化を均一化してDiscriminatorを欺く戦略とのこと。確かに効果が表れている根拠図が示されています。

ここで、（１）は全体的な構造、と（２）のunet版は汎化には強く影響するし、Lossを急速に収束させることに寄与しているのは明白で、それを使わずにLossを改善するのが今回の課題である。逆にencoder-decoder版から見ると、一部層間写像を導入してその写像が効きすぎないようにL1L2ノルムで制御してLossは下げつつ、汎化は維持しつつフィッティングできればよい結果が得られそうです。

ということで、上記の特徴から推論される素直な戦略は以下の二点である。
①patchGANにより部分的に構造分割する手法
②encoder-decoderモデルについて層間写像を導入しLossを減少しつつ、L1、L2に着目して汎化特性を維持させる
これらがpix2pixの構造の中で変更できる有力なパラメータとなっている。
実は、*patchサイズを変更して収束性を見る*は実施したが、これはDiscriminator側なためかLoss減少にはあまり効果は見いだせなかった。そこで、ここでは、②のL1ノルム,L2ノルム正則化項を用いる回帰について、原理的なことを見つつ適用することとする。
【参考】[回帰モデルにおけるL1正則化とL2正則化の効果](http://breakbee.hatenablog.jp/entry/2015/03/08/041411)
#L1ノルムとL2ノルムによる過学習の制御
まず、L1ノルムとL2ノルムは過学習に対してどのように作用するかという問題がある。
上記の参考では、
「L1ノルムはパラメータが0になりやすく、正則化の影響が非常に強いと感じられました。 対してL2ノルムは過学習になりやすく、正則化の影響が少し弱いと感じられました。Elastic Netは当てはまりの良いモデルとなることが多かったと思います。」
とまとめています。これは多項式で少数データでの実験の結果である。
一方、以下のサイトによると
【参考】[将棋でディープラーニングする その44(L2正則化)](http://tadaoyamaoka.hatenablog.com/entry/2017/11/08/231038)
「正則化なしでも、trainとlossにそれほど差がないが、L2正則化を行うとtrainとtestの差が縮まっている。その代わりに、policy、vlaueともに2%くらい一致率が下がっている。」
とあるので、L2正則化の効果は凡化であり、正則化である。
さらに、以下の記載がある。つまり、L1ノルムは不必要なパラメータを０にし、過学習を抑える効果、L2ノルムは大きすぎるパラメータ（テンソルパラメータや出力など）の抑制となる。
【参考】[ニューラルネットワークの学習の工夫 ](http://s0sem0y.hatenablog.com/entry/2017/01/31/041148)
ここは、考え方の問題だが、今回の課題は
「元論文ではunet版でかなりな過学習になっており、L1のみを導入している。そのunetを不採用にしたencoder-decoder版では正則化項が効きすぎて、Loss減少が遅れている」
という仮説に基づいて、話を進めることにします。
pix2pix(unet版)は、もともと過学習になりやすいため、L1ノルムのみを
```generator(loss='mae'...)
DCGAN(loss=['l1_loss','binary_crossentropy'...)
```に導入しているのかもしれません。
いずれにしても、この部分を変更して汎化特性を見るのはためになる。
【参考】[keras / keras / regularizers.py](https://github.com/keras-team/keras/blob/master/keras/regularizers.py)
そこで、KerasDocumentationと上記の【参考】コードを参照しつつl1_lossのコード変更して適用することにした。
L1とL2を分離して関数にすることもできるが、今回は最小限のコード変更として、L2ノルムも含むようにl1_lossを書き換えた。適用はもともとのコードと同じようにDCGANのみに適用するのと、Generatorにも適用する場合と両者をやってみた。

```l1l2_loss
def l1l2_loss(y_true, y_pred):
    s=K.abs(y_pred - y_true)*(1 + 0.5*K.abs(y_pred - y_true)) 
    return K.sum(s, axis=-1)

loss = [l1l2_loss, 'binary_crossentropy']
```
#もう一つの正則化導入方法
これは、正統派と呼べると思うが、KerasDocumentationの通り、層毎に導入する。
【参考】[Usage of regularizers](https://keras.io/regularizers/)
ここでは、Denseに導入する例が掲載されているが、今回のpix2pixのコードでは、まず過学習の主な原因と考えられる以下のunetのところに導入するのが自然である。

```
def up_conv_block_unet(x, x2, f, name, bn_axis, bn=True, dropout=False):
    x = Activation('relu')(x)
    x = UpSampling2D(size=(2, 2))(x)
    x = Conv2D(f, (3,3), name=name, padding='same', kernel_regularizer=　regularizers.l1_l2(0.00001), activity_regularizer=regularizers.l1_l2(0.00001))(x)
    if bn: x = BatchNormalization(axis=bn_axis)(x)
    if dropout: x = Dropout(0.5)(x)
    x = Concatenate(axis=bn_axis)([x, x2])
    return x
```
上記で、`kernel_regularizer=regularizers.l1_l2`は重みに正則化項を追加しており、`activity_regularizer=regularizers.l1_l2`は出力に正則化項を追加しています。
それらが、どの程度必要かはやってみないとわからない状況だが、今はGeneratorの重みを制御したい。
#全体のLossと層間写像にL1L2正則化項およびDropoutを導入して汎化特性を制御する
unet版は過学習であり、encoder-decoder版はある程度汎化特性を有している。ということで、一番いいのはunet版でありながら汎化特性を持っているモデルを作れるかということである。過学習を抑える方法として有力なものとして、Dropoutがある。
pix2pixではそもそもi < 2の写像に対しては、Dropoutを導入して過学習を抑える工夫をしているが、それがまだまだ有効に機能していないと考えられる。
そこで、ここでは、この二つのモデル（unet版とencoder-decoder版）がそれぞれの特性の極にいると考えて、一つはunet版に正則化項やDropoutを導入する。
もう一つは、encoder-decoder版の一部の層で写像を追加したモデルに正則化項やDropoutを導入する。という戦法を実施する。
ということで、具体的に残ったモデルは以下の二つである。
①unet版にl1l2正則化項およびDropoutを導入したモデル
②unet版のうち、（例　i < 2）の層でconcatenateをはずし、上位の層ではconcatenateすることとし、そこにl1l2正則化項およびDropoutを導入する。すなわち以下のコードがその例である。

```
for i, f in enumerate(list_filters_num[1:]):
        name = "unet_upconv2D_" + str(i+2)
        if i<2: 
            d = True
            up_conv = up_conv_block_unet_alt(list_decoder[-1], list_encoder[-(i+3)], f, name, axis_num, dropout=d)
        else:
            d = False
            up_conv = up_conv_block_unet(list_decoder[-1], list_encoder[-(i+3)], f, name, axis_num, dropout=d)
        list_decoder.append(up_conv)
```
ここで`up_conv_block_unet_alt()`は、`concatenate`しない`up_conv_block`である。
なお、l1l2正則化は、上記コードのように`up_conv_block_unet()`内で導入した。

#まとめ
ということで、上記までの考察の結果、全体の探索予定は以下の表のとおりとする。
ここでGenratorへのL2正則化項の寄与は全体への適用や層間写像への適用とかぶると考え、適用しない。また、Dropoutは`up_conv_block_unet()`適用と対に導入し、探索予定には明にあらわさない。
※当初はこの作戦で実施する予定とする
L1L2ノルムの導入は、以下の表のとおりであるが、それぞれ強度パラメータを変化させる必要があり、実験回数は膨大になりがちである。以下の表ではL1L2ノルムの適用を○で示す。AllはLossへの適用であり、Layerは層間写像へのL1L2とDropout適用を○で示す。

|l1l2適用  | en-de|　| ・・・i < 2・・・|   |    unet|    |
|:--------|:-------|:--------|:-----|:-------|:-----|:------|
|All      | ○       |    X    |  ○    |  X     | ○     |  X    |
|Layer    | ○       | ○       |○      |○       |○      | ○     |
|Layer    | X       | X       |X      |X       |X      | X     |

#課題
次回は上記の探索結果について記載する予定である。