Solafune 超解像度化コンペ（5倍）3位解法

solafuneの超解像度化コンペに参加し、private LBで3位を獲得しました！
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3325964/e7338fa1-8c2c-72f4-9d12-b32d00d70ffd.jpeg">

やったことをまとめます。

# まとめ
モデル：
　[Swin2SR](https://github.com/mv-lab/swin2sr) (x1 -> x4) + バイキュービック補間(x4 -> x5)
データ分割：
　性能比較時はtrain:test=4:1, モデル作成時は99:1
データ拡張：
　1-350epoch：Brightness, Contrast, Flip, Rotate(90deg毎), CutMix
　350-450epoch：Brightness, Contrast, Flip, Rotate(90deg毎)

単一モデル+一部単純リサイズの組み合わせであるにも関わらず、比較的高いスコアに達しているので、Swin2SRはかなり相性が良いのではと思います。

# ベースラインと使用モデル
[前回大会のこちらの解法](https://zenn.dev/kwashizzz/articles/solafune-sr-swinir)をベースラインとして使わせていただきました。
参考にしたコードではSwinIRを使用していますが、Swin2SRはSwinIRと同じ構成となっているため、import先をSwin2SRに書き換えるだけで大体使えます。

batch size=8、patch size=64が、精度と計算時間のバランスから良好でした。
公式事前モデルを使い、最大学習率5E-5で350エポック、続いて上述の通りデータ拡張を減らして最大学習率1E-6で100エポック学習させました。
upsamplerはpixelshuffle、embed_dimは180にしました。

さらに、次のように処理を変更しました。

## データ分割
CVを切った方が良いのは間違いないのですが、時間（と電気代）の制約上、単一モデルで作成しました。
超解像度化タスクでは、その性質上、test(validaton)の枚数は少数で済むと思います。
コンペ前半ではtest比率を20%にして色々実験をし、モデル作成時にはtest比率を1%に絞ってその分訓練に充てました。

## データ拡張
効果があったのはBrightness, Contrast, Flip, Rotate, CutMixです。
ただし、Rotateは[前回大会解法のこちらの記事](https://yuta0306.github.io/mscup-feedback)で指摘されているように、エイリアシングの問題がありました。
そのため、90°毎の回転に留めました。

### CutMix
今回は色々なエリアの境界を写した画像も対象に含まれていました。
例えば、港の写真は海面と建物が混ざっているため、CutMixは特に有効なのではないかと思います。
単純比較はしていませんが、CutMixを加えることで、スコアが0.002〜0.005ポイント上がりました。
DatasetSRクラスの__getitem__関数に次の処理を追加しました。
~~~
mode = random.randint(0, 1)
if mode == 1:
    img2_idx = random.randint(0,self.len_index-1)
    imgL2 = self.imgs_L[img2_idx]
    imgH2 = self.imgs_H[img2_idx]
    imgH2 = imgH2[rnd_h_H:rnd_h_H + self.patch_size, rnd_w_H:rnd_w_H + self.patch_size, :]
    imgL2 = imgL2[rnd_h:rnd_h + self.L_size, rnd_w:rnd_w + self.L_size, :]
    x1 = random.randint(0,self.L_size-5)
    y1 = random.randint(0,self.L_size-5)
    x2 = self.L_size
    y2 = self.L_size
    img_L[y1:y2, x1:x2, :] = imgL2[y1:y2, x1:x2, :]
    img_H[4*y1:4*y2, 4*x1:4*x2, :] = imgH2[4*y1:4*y2, 4*x1:4*x2, :]
~~~
Mix後に再度rotate, brightness, contrast処理をランダムに加えています。

# 後処理
前回大会では4倍でしたが、今回は5倍です。この5倍というのが厄介でした！
多くのモデルは2のべき乗倍は対応しているのですが、直接5倍アップスケールする場合、kernelから書き直す必要があります。
色々と試したのですが、結局cv2.resize(interpolation=cv2.INTER_CUBIC)を超えることができませんでした(泣)

Swin2SRのUpsampleクラスに従って考えると、n倍にアップスケールする場合、その最小約数の2乗×中間特徴量数 がカーネルサイズになります。
2^n倍の場合は4×中間特徴量数ですが、5倍の場合は25×中間特徴量数 と比較的大きいサイズとなってしまいます。
（もし理解が間違っていたらご指摘いただけると嬉しいです）

このためか、一旦4倍にしてから5倍に単純リサイズする方が、事前学習モデルを使えることもあり、優位となりました。

# うまくいかなかったこと
#### 海・緑地・砂漠・市街地のハンドラベリングと複数モデル
前回のコンペとは違い、様々なエリアの衛星写真が混ざったコンペでした。
試しに、教師データを適当に4種類に分類し、スコアの出しやすさを比較しました。
下記の表の通り、特に海と緑地の超解像度化が難しいです。海面や木の表面構造を正確に生成するのは明らかに困難そうです。


| label| test SSIM | 
|:-----------|------------:|
| 建造物       | 0.870        |
| 砂漠   | 0.833      |
| 緑地       | 0.790        |
| 海         | 0.794          |

エリアを分類して、それぞれにエリア用にチューニングしたモデルを使う、ということも試しましたが、結局単一モデルを適用したほうが高スコアでした。

#### x4 -> x5へのMetaSRの使用
任意の倍率に拡大可能な[A Magnification-Arbitrary Network for Super-Resolution（Meta-SR）](https://github.com/XuecaiHu/Meta-SR-Pytorch)というモデルも公開されています。
x4からx5への1.25倍の拡大処理に適用を試みました。しかしながら、今回は単純リサイズを超えることはできませんでした。
拡大元となるのがSwin2SRによって出力された画像であるため、単なる低解像度画像よりもデータの一貫性が低いことから、重ねての処理が難しいのだと思っています。
ハイパーパラメータを十分振ったとは言えないので、調整すれば超えられる可能性はあります。

#### jpeg compression, jitter, 外部データ（練馬区航空写真）
これらのデータ拡張は効果がありませんでした。
前回大会の記事でも指摘されていましたが、色を変える系は全般的に意味が無いようです。

航空写真の使用は有意なスコア改善には繋がりませんでした。（ただし、後述する1位の方は活用し、効果を確認されています）
私の場合、本データと同じ縮尺に合わせる方法がわからず、目視でサイズを決めてしまっていました。そのため、縮尺が実際とずれてしまっていた可能性はあります。

# さいごに
推論では単一モデルを使っていました。
これに対し、TTAをやってみても良かったのでは、というのが1つの反省点です。

シンプルなタスクでしたが、色々と学びがあり、充実したコンペでした。
ありがとうございました。

# 追記　1位解法との比較
[1位のチームの解法](https://hack.nikkei.com/blog/solafune202304/)が公開されています。

モデルにSwin2SRを採用したことと、
データ拡張にCutMixを用いつつも学習の後半では使わなかったこと、
は共通しています。

一方、1位のチームは、様々なバリエーションのモデルでアンサンブルを行い、
Lion Optimizerを採用、またAdversarial Weight Perturbation（AWP）という学習手法を採用したことなどで、スコアをさらに伸ばしたとのことです。大変勉強になりました。