ayukat1016 / gan_sample

MIT License
78 stars 25 forks source link

EfficientGANをgary-scaleで試してますが、画像が不鮮明で_img_from_Zの画像のコントラストが低い #11

Open wan2355 opened 3 years ago

wan2355 commented 3 years ago

本書のEfficientGANを試しています。 以下の症状ですが、知見がございましたら、よろしくお願いします。

1. gray-scale画像、約250枚。サイズは256x256pxです。 このためmodelを少し修正してます。 layerを追加したり、kernel-sizeを変更したりしましたが、結果は同じでした。 10000 epochほど学習させました。

2. reconstructした画像はお見せできませんが、ほぼ元の画像を再現できています。 しかしやや不鮮明です(medianをかけたような画像。)。 またimage_from_zの画像もコントラストが低く、不鮮明な画像が生成されます。

Encoderがうまく学習されていないのでしょうか? また生成される画像のレンジ(0~256に収まっていない?)がおかしいのでしょうか。

3. 上記のためか、異常画像から生成される異常検知画像が不鮮明で、元画像の正常な部分もあまり正確に再現できていません。 このため、差分の画像も異常部は多少表現されていますが、不十分です。 Anomaly_scoreは20000~40000です。

昨年、別のEfficientGANを試したことがありましたが、異常検知画像は元画像の正常な部分をほとんど再現できませんでした。 Anomaly_scoreは4000~6000でした。

4. 参考までに、テンソルボードの結果は以下です(まだ学習途中です)。 ・赤がmodelのlayerを1つ増やしたもの。 ・橙色はlayerは増やさずに、kernelの大きさを修正したもの。

2021-04-01_19 46 29_tbd

・・・・・・・・・・・・・・・・・・・・・・・・・・・・ 4/2追記

確認の為、上記のkernel修正modelを使い、「鬼灯(ホオズキ)」約250枚をグレースケールにし、さらに100pxから256pxにして検証しみました。 2000epochの学習をさせました。

reconstrutの方(中段)は元の「鬼灯」(上段)がやや微けてますが、再現できています。 image_from_zもボケてはいますが、鬼灯らしい形が見えてきてます。 異常部の検出も悪くない。

すると、私のdataが問題なのでしょうか。

以下の画像を参照ください。

↓ reconstrust 2021-04-02_14 23 20

↓ image_from_z 2021-04-02_14 23 32

↓ 異常部の検出 2021-04-02_15 28 23

↓ Tensorboard 青が鬼灯(RGB) 空色が鬼灯(Gray) 赤色が私のdata

2021-04-02_14 25 15

wan2355 commented 3 years ago

異常画像の検出のところで、画像を作成する際に、以下のアラートが出るので少し修正しました。

Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

追加部:

    # joined_imageの値が-1~1 なので、値の範囲をを0~1に変更する。
    joined_image = np.clip(joined_image, 0.0, 1.0)

    # 以下はもとのまま 
    plt.figure(figsize=(14, 6))
    plt.imshow(joined_image)
   ...
massy103 commented 3 years ago

ありがとうございます。

chapter8_grayscaleというブランチを切って、私の方でもgrayscaleで学習してみるコードを追加しました。

commitしたnotebook

確かに、そのまま用いると再構成した画像は問題ないもののzから生成した画像はぼやけているようでした。

epoch_998

epoch_998

再構成だけうまくいくという事はおそらく、L1_loss(loss_ge_2)が効きすぎている事が原因だと考えられるので、以下のコード部分のalphaをもっと小さい値にしてみてください。

        loss_ge_1 = criterion(p_fake, y_true) + criterion(p_true, y_fake)
        loss_ge_2 = criterion_L1(x, G_E_x) +  criterion_L1(z, E_G_z)

        alpha = 0.01

        loss_ge = (1 - alpha)*loss_ge_1 + alpha*loss_ge_2
        loss_ge.backward(retain_graph=True)
        optimizer_ge.step()

試しにalpha=0.001に変更して学習してみたところ、epoch500時点ですが↓のようにzから生成した画像の質は高くなっているようでした。

ただしalphaを小さくしすぎるとL1_lossを導入していないEfficientGANと同様に再構成性能が低くなってしまうので、注意してください。

epoch_522

massy103 commented 3 years ago

ほおずきの画像をgrayscaleにする際は以下のように、RGBをgrayscaleにした後に3channelsに変換しています。 もし元々grayscaleの画像の場合はtransforms.Grayscale(num_output_channels=1), # RGB to grayscaleの箇所を外してください。

# 画像を読み込む際の前処理

transform_dict = {
    "train": transforms.Compose(
        [
            transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),  # IMAGE_SIZEにreshape
            transforms.RandomHorizontalFlip(), # ランダムに左右反転を行う
            transforms.ToTensor(),
            transforms.Grayscale(num_output_channels=1), # RGB to grayscale
            transforms.Lambda(lambda x: x.repeat(3, 1, 1)), # grayscale to 3channels
        ]
    ),
    "test": transforms.Compose(
        [
            transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),  # IMAGE_SIZEにreshape
            transforms.ToTensor(),
            transforms.Grayscale(num_output_channels=1), # RGB to grayscale
            transforms.Lambda(lambda x: x.repeat(3, 1, 1)), # grayscale to 3channels
        ]
    ),
}
wan2355 commented 3 years ago

massy様

ご丁寧な回答ありがとうございます。 また結果までご提示いただき、大変参考になります。 会社のPCで計算しているため、ご提案の以下2点は月曜日に確認させていただきます。

> alphaを小さくする。例 0.01 > 0.01 など。

(お手を煩わせて恐縮ですが、)いくつか質問させてください。

Q1: 再構成画像ですが、3000epochあたりで飽和しているようで、これ以降は画質が向上しません。 画質を向上させる方法があれば、お教えください。

generatorのmodelを他のものにするのは1つの方法だと思います。 一方、このmodelのまま、parameterやcodeの修正で対応可能でしょうか。

Q2: L1正則を使う意味は、dataのsparse性を利用するということでしょうか。 その場合、L1正則のかわりに、L2正則など少し緩い条件を利用するのは如何でしょうか。 ただpytorchにL2正則があるのかどうかは調べてないとわかりませんが。

数学的なところは正確に理解してませんが、他のところで似たような経験があり、気になったので質問した次第です。 もしご存知でしたらお教えください。

Q3: 手元のPCでdataサイズを256x256pxにすると、時々 の以下のようなアラート(error?)が出ます(手元にPCがないので、正確には少し違うかもしれません)。 結果は大丈夫そうなので気にしてませんが、もしアラートを無くす方法があればお教えください。 ただcolabでは試してません。

File "/home/hoge/anaconda3/lib/python3.7/multiprocessing/connection.py", line 368, in _send n = write(self._handle, buf) BrokenPipeError: [Errno 32] Broken pipe

対策として、以下などを見出しましたが、正確に理解できてません。 (hardwareよりな問題のようで、私は知識が不足しております。)

対応として以下が提案されてますが、本programに利用できるのでしょうか。

From my point of view there could be 2 solutions for these, 1st ) Increasing Swap 2nd ) Re-run the optimisation program every time after n number of simulations(200 in this case).

上記について、もしご存知でしたらお教えください。

massy103 commented 3 years ago

Q1: 再構成画像ですが、3000epochあたりで飽和しているようで、これ以降は画質が向上しません。 画質を向上させる方法があれば、お教えください。 generatorのmodelを他のものにするのは1つの方法だと思います。 一方、このmodelのまま、parameterやcodeの修正で対応可能でしょうか。

A1: anomaly_scoreが徐々に下がっていっているけれど一定の値からは下がらなくなっているのであればモデルとしての表現力の限界なような気がします。

そのため、一番はやはりモデルをよりリッチな構造にするのが解決策となりそうです。 Residual Blockなどを導入してみると良いかもしれません。 また、基本的にはgeneratorを変えるだけでなく、discriminatorと(おそらく)encoderも一緒に変更した方が良いです。

Q2: L1正則を使う意味は、dataのsparse性を利用するということでしょうか。 その場合、L1正則のかわりに、L2正則など少し緩い条件を利用するのは如何でしょうか。 ただpytorchにL2正則があるのかどうかは調べてないとわかりませんが。 数学的なところは正確に理解してませんが、他のところで似たような経験があり、気になったので質問した次第です。 もしご存知でしたらお教えください。

A2: L1_lossを利用しているのは基本的にこの論文を参考にしました。L2_lossでも問題無いと思います。

試しにalpha=0.001でL2_lossを用いてみるnotebookもコミットしました。

ただ、結果はanomaly_scoreでL1_lossと比較すると、少し劣っていて生成画像を見るとヘタ部分などがややぼやけているようでした。 Lossの値のスケールが異なるのでalphaの値をいろいろ変更してみるとL1_lossより良い結果になる可能性はあると思います。

Q3: 手元のPCでdataサイズを256x256pxにすると、時々 の以下のようなアラート(error?)が出ます(手元にPCがないので、正確には少し違うかもしれません)。 結果は大丈夫そうなので気にしてませんが、もしアラートを無くす方法があればお教えください。 ただcolabでは試してません。

A3: このエラーログだけだと原因はわかりませんでした。もしよろしければどのコード箇所でこのエラーが出るのかと、詳細なエラーログを共有いただけますと助かります。

wan2355 commented 3 years ago

massy様

ご丁寧な回答ありがとうございます。 またいろいろ試していただき感謝します。

1.

一番はやはりモデルをよりリッチな構造にするのが解決策となりそうです。 ... また、基本的にはgeneratorを変えるだけでなく、discriminatorと(おそらく)encoderも一緒に変更した方が良いです。

generatorとdiscriminatorのほうはあてがあるのですが、encoderはどうしたら良いのかわかりません。 もし何か提案があれば、おねがいします。

2.

Residual Blockなどを導入してみると良いかもしれません。

どの辺りに挿入すれば良さそうでしょうか。

3.

ただ、結果はanomaly_scoreでL1_lossと比較すると、少し劣っていて生成画像を見るとヘタ部分などがややぼやけているようでした。 Lossの値のスケールが異なるのでalphaの値をいろいろ変更してみるとL1_lossより良い結果になる可能性はあると思います。

後ほど試してみます。

4.

このエラーログだけだと原因はわかりませんでした。もしよろしければどのコード箇所でこのエラーが出るのかと、詳細なエラーログを共有いただけますと助かります。

了解です。 会社にありますので、明日以降にご連絡します。

massy103 commented 3 years ago

1.

generatorとdiscriminatorのほうはあてがあるのですが、encoderはどうしたら良いのかわかりません。 もし何か提案があれば、おねがいします。

私も正直なにが正解なのかわかりませんが、おそらくgeneratorと対照的になるように組んでいくのが良いと思います。

2.

Residual Blockなどを導入してみると良いかもしれません。

どの辺りに挿入すれば良さそうでしょうか。

EfficientGANはネットワークの中身に対する制約や条件はないので、インプットとアウトプットさえ同様であれば他のGANのネットワーク構成を利用する事ができると考えています。そのため、SRGANの構成を真似してResidual Blockで組んでみたり、あるいはStyleGANなどを元にしたりするのが良い気がします。

wan2355 commented 3 years ago

おはようございます。

なぜかAlertが出ませんね。。。

connection.py...

の後に、BrokenPipeError が出ていたと思います。

codeは少し修正しましたが、概ね書籍に記載のものです。 trainingの部分はshellの上で動くように少し書き足しており、元のcodeより倍以上に長いです。 必要でしたらのせますが、このコメントに添付すれば良いでしょうか。

set parameter
EPOCHS =  2000
alpha =  0.001
--------------------------------------------------
make dir & save parameter
--------------------------------------------------
load data
--------------------------------------------------
job start at :  210405-1113
--------------------------------------------------------------------------------

50/2000 epoch ge_loss: 23.876 d_loss: 0.000 anomaly_score: 14679.339
Traceback (most recent call last):
100/2000 epoch ge_loss: 12.711 d_loss: 0.002 anomaly_score: 8709.387
Traceback (most recent call last):
150/2000 epoch ge_loss: 7.085 d_loss: 0.000 anomaly_score: 7005.530
200/2000 epoch ge_loss: 5.269 d_loss: 0.001 anomaly_score: 6286.282
Traceback (most recent call last):
  File "/home/wan/.pyenv/versions/3.7.7/lib/python3.7/multiprocessing/queues.py", line 242, in _feed
    send_bytes(obj)
  File "/home/wan/.pyenv/versions/3.7.7/lib/python3.7/multiprocessing/connection.py", line 200, in send_bytes
    self._send_bytes(m[offset:offset + size])
250/2000 epoch ge_loss: 6.519 d_loss: 0.002 anomaly_score: 13678.796
Traceback (most recent call last):
300/2000 epoch ge_loss: 4.368 d_loss: 0.000 anomaly_score: 10557.472
Traceback (most recent call last):
350/2000 epoch ge_loss: 5.719 d_loss: 0.053 anomaly_score: 9013.415
Traceback (most recent call last):
  File "/home/wan/.pyenv/versions/3.7.7/lib/python3.7/multiprocessing/queues.py", line 242, in _feed
    send_bytes(obj)
  File "/home/wan/.pyenv/versions/3.7.7/lib/python3.7/multiprocessing/connection.py", line 200, in send_bytes
    self._send_bytes(m[offset:offset + size])
400/2000 epoch ge_loss: 4.226 d_loss: 0.000 anomaly_score: 12985.966
450/2000 epoch ge_loss: 3.975 d_loss: 0.002 anomaly_score: 9752.129
Traceback (most recent call last):
  File "/home/wan/.pyenv/versions/3.7.7/lib/python3.7/multiprocessing/queues.py", line 242, in _feed
    send_bytes(obj)
  File "/home/wan/.pyenv/versions/3.7.7/lib/python3.7/multiprocessing/connection.py", line 200, in send_bytes
    self._send_bytes(m[offset:offset + size])
--------------------------------------------------------------------------------
500/2000 epoch ge_loss: 4.030 d_loss: 0.002 anomaly_score: 10607.229
550/2000 epoch ge_loss: 4.692 d_loss: 0.002 anomaly_score: 11019.177
600/2000 epoch ge_loss: 3.528 d_loss: 0.001 anomaly_score: 12155.901
Traceback (most recent call last):
Traceback (most recent call last):
650/2000 epoch ge_loss: 3.483 d_loss: 0.003 anomaly_score: 11777.586
Traceback (most recent call last):
Traceback (most recent call last):
  File "/home/wan/.pyenv/versions/3.7.7/lib/python3.7/multiprocessing/queues.py", line 242, in _feed
    send_bytes(obj)
700/2000 epoch ge_loss: 3.026 d_loss: 0.005 anomaly_score: 10709.992
750/2000 epoch ge_loss: 2.851 d_loss: 0.002 anomaly_score: 11048.208
800/2000 epoch ge_loss: 3.836 d_loss: 0.016 anomaly_score: 11319.374
850/2000 epoch ge_loss: 3.183 d_loss: 0.003 anomaly_score: 10832.643
900/2000 epoch ge_loss: 2.866 d_loss: 0.012 anomaly_score: 12896.531
Traceback (most recent call last):
  File "/home/wan/.pyenv/versions/3.7.7/lib/python3.7/multiprocessing/queues.py", line 242, in _feed
    send_bytes(obj)
  File "/home/wan/.pyenv/versions/3.7.7/lib/python3.7/multiprocessing/connection.py", line 200, in send_bytes
    self._send_bytes(m[offset:offset + size])
950/2000 epoch ge_loss: 3.068 d_loss: 0.003 anomaly_score: 11054.521
Traceback (most recent call last):
Traceback (most recent call last):
  File "/home/wan/.pyenv/versions/3.7.7/lib/python3.7/multiprocessing/queues.py", line 242, in _feed
    send_bytes(obj)
Traceback (most recent call last):
--------------------------------------------------------------------------------
1000/2000 epoch ge_loss: 2.743 d_loss: 0.002 anomaly_score: 10287.133
1050/2000 epoch ge_loss: 2.587 d_loss: 0.004 anomaly_score: 10776.228
Traceback (most recent call last):
  File "/home/wan/.pyenv/versions/3.7.7/lib/python3.7/multiprocessing/queues.py", line 242, in _feed
    send_bytes(obj)
  File "/home/wan/.pyenv/versions/3.7.7/lib/python3.7/multiprocessing/connection.py", line 200, in send_bytes
    self._send_bytes(m[offset:offset + size])
1100/2000 epoch ge_loss: 2.704 d_loss: 0.005 anomaly_score: 11400.406
Traceback (most recent call last):
1150/2000 epoch ge_loss: 2.775 d_loss: 0.009 anomaly_score: 10556.899
Traceback (most recent call last):
1200/2000 epoch ge_loss: 2.804 d_loss: 0.006 anomaly_score: 9656.309
1250/2000 epoch ge_loss: 2.428 d_loss: 0.002 anomaly_score: 10194.276
Traceback (most recent call last):
Traceback (most recent call last):
1300/2000 epoch ge_loss: 2.338 d_loss: 0.012 anomaly_score: 8073.060
Traceback (most recent call last):
Traceback (most recent call last):
1350/2000 epoch ge_loss: 2.808 d_loss: 0.004 anomaly_score: 7809.970
Traceback (most recent call last):
1400/2000 epoch ge_loss: 2.285 d_loss: 0.011 anomaly_score: 8665.330
Traceback (most recent call last):
1450/2000 epoch ge_loss: 2.325 d_loss: 0.062 anomaly_score: 7551.758
Traceback (most recent call last):
  File "/home/wan/.pyenv/versions/3.7.7/lib/python3.7/multiprocessing/queues.py", line 242, in _feed
    send_bytes(obj)
  File "/home/wan/.pyenv/versions/3.7.7/lib/python3.7/multiprocessing/connection.py", line 200, in send_bytes
    self._send_bytes(m[offset:offset + size])
Traceback (most recent call last):
--------------------------------------------------------------------------------
1500/2000 epoch ge_loss: 2.163 d_loss: 0.106 anomaly_score: 6416.862
Traceback (most recent call last):
1550/2000 epoch ge_loss: 2.181 d_loss: 0.024 anomaly_score: 7069.499
1600/2000 epoch ge_loss: 2.366 d_loss: 0.011 anomaly_score: 7249.777
1650/2000 epoch ge_loss: 2.240 d_loss: 0.007 anomaly_score: 8008.530
Traceback (most recent call last):
Traceback (most recent call last):
  File "/home/wan/.pyenv/versions/3.7.7/lib/python3.7/multiprocessing/queues.py", line 242, in _feed
    send_bytes(obj)
  File "/home/wan/.pyenv/versions/3.7.7/lib/python3.7/multiprocessing/connection.py", line 200, in send_bytes
    self._send_bytes(m[offset:offset + size])
Traceback (most recent call last):
1700/2000 epoch ge_loss: 2.225 d_loss: 0.012 anomaly_score: 7994.545
Traceback (most recent call last):
  File "/home/wan/.pyenv/versions/3.7.7/lib/python3.7/multiprocessing/queues.py", line 242, in _feed
    send_bytes(obj)
  File "/home/wan/.pyenv/versions/3.7.7/lib/python3.7/multiprocessing/connection.py", line 200, in send_bytes
    self._send_bytes(m[offset:offset + size])
1750/2000 epoch ge_loss: 2.233 d_loss: 0.013 anomaly_score: 8151.363
1800/2000 epoch ge_loss: 2.241 d_loss: 0.015 anomaly_score: 6139.832
Traceback (most recent call last):
Traceback (most recent call last):
Traceback (most recent call last):
1850/2000 epoch ge_loss: 2.477 d_loss: 0.007 anomaly_score: 8778.923
Traceback (most recent call last):
Traceback (most recent call last):
1900/2000 epoch ge_loss: 2.066 d_loss: 0.048 anomaly_score: 6449.593
1950/2000 epoch ge_loss: 2.393 d_loss: 0.016 anomaly_score: 7315.434
Traceback (most recent call last):
  File "/home/wan/.pyenv/versions/3.7.7/lib/python3.7/multiprocessing/queues.py", line 242, in _feed
    send_bytes(obj)
  File "/home/wan/.pyenv/versions/3.7.7/lib/python3.7/multiprocessing/connection.py", line 200, in send_bytes
    self._send_bytes(m[offset:offset + size])
--------------------------------------------------------------------------------
job time 16.0 min
wan2355 commented 3 years ago

先程tarnsformのところをご提案のように修正しましたところ、動かなくなりました。 よく見ると、dataloaderのところで、以下のようにRGBに変換されておりますので、

def load_image(self, path):
        image = Image.open(path).convert("RGB")
        tensor_image = self.transform(image)
        return tensor_image

先日ご提案の以下は不要と思います。

transforms.Grayscale(num_output_channels=1), # RGB to grayscale
transforms.Lambda(lambda x: x.repeat(3, 1, 1)), # grayscale to 3channels

ただ、PILでRGBに変換するのと、gray-scaleのlayerを3つ重ねるのはできる画像が異なるような気がしますので、

Image.open(path).convert("RGB") を Image.open(path) 

にして上記を試してみます。

wan2355 commented 3 years ago
Image.open(path).convert("RGB") を Image.open(path) 

で試してみました。 当初の convert("RGB") より良くないですが、 Alpha=0.01よりはよさそうです。

anomaly_scoreはAlpha=0.001が最も小さいです。 一方、Alphaを下げたため、生成される再構成画像が少し荒くなるようです(中央がAlpha=0.001)。

以上から、Alphaを下げると、image_from_zの画質が向上し、anomaly_scoreが小さくなるようですが、再構成画像の質は劣化するのですね。

ただし、Image.open(path) でgra-scale画像を読み込んで、 transforms.Lambda(lambda x: x.repeat(3, 1, 1)) でRGBにした場合に、Image.open(path).convert("RGB") より、悪いのが不明です。 私がcodeを間違ったかもしれませんので、後ほど確認します。

↓再構成 上:Image.open(path).convert("RGB") , Alpha=0.01 中:Image.open(path).convert("RGB") , Alpha=0.001 下: Image.open(path), Alpha=0.001 2021-04-05_11 57 50

↓image_from_z 上:Image.open(path).convert("RGB") , Alpha=0.01 中:Image.open(path).convert("RGB") , Alpha=0.001 下: Image.open(path), Alpha=0.001 2021-04-05_11 57 57

↓異常部検知 上:Image.open(path).convert("RGB") , Alpha=0.01 中:Image.open(path).convert("RGB") , Alpha=0.001 下: Image.open(path), Alpha=0.001 2021-04-05_11 51 42

↓tensorboard 赤色: Alpha+0.01 橙色:Image.open(path).convert("RGB") 灰色: Image.open(path) 

2021-04-05_11 34 28

massy103 commented 3 years ago

なぜかやたらとzから生成した画像の質が悪いのが気になりますね・・ 256×256用にwan2355様で変更されたネットワークの組み方があまり良くないのかも知れません。

96×96ですが、一度下のnotebookを元に試してもらってもよろしいでしょうか?

https://github.com/ayukat1016/gan_sample/blob/chapter8_grayscale/chapter8/section8_4-EfficientGAN_L1_grayscale.ipynb?flush_cache=true

wan2355 commented 3 years ago

早速の回答ありがとうございます。 後ほど、試してみます。

私の組み直したnetworkですが、originalから大きく変えてないですが、kernelとか一部修正してます。 encoder, generator, discriminatorは以下です。

class Encoder(nn.Module):
    def __init__(self):
        super().__init__()
        self.main = nn.Sequential(
            nn.Conv2d(3, 32, kernel_size=6, stride=4, padding=1, bias=False),
            nn.BatchNorm2d(32),
            nn.LeakyReLU(0.2), #256x256 > 128

            nn.Conv2d(32, 64, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(64),
            nn.LeakyReLU(0.2), #128 > 64

            nn.Conv2d(64, 128, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(128),
            nn.LeakyReLU(0.2), #64x64 > 32

            nn.Conv2d(128, 256, kernel_size=6, stride=4, padding=1, bias=False),
            nn.BatchNorm2d(256),
            nn.LeakyReLU(0.2), #32x32 > 8 ここだけ 1/4

            ## modify kernel 6 > 8
            nn.Conv2d(256, 512, kernel_size=8, stride=2, bias=False),
            nn.BatchNorm2d(512),
            nn.LeakyReLU(0.2), #8x8 > 1

            nn.Conv2d(512, 512, kernel_size=1, stride=1, bias=False),
            nn.BatchNorm2d(512),
            nn.LeakyReLU(0.2), #1x1
        )

        self.last = nn.Sequential(
            nn.Conv2d(512, EMBED_SIZE, kernel_size=1, stride=1, bias=False)
        )
class Generator(nn.Module):
    def __init__(self):
        super().__init__()

        self.main = nn.Sequential(
            ## kernel 6x6 >> 8x8
            nn.ConvTranspose2d(EMBED_SIZE, 256, kernel_size=8, stride=1, padding=0, bias=False), # 1x1 > 8x8
            nn.BatchNorm2d(256),
            nn.LeakyReLU(0.2),

            ## ここだけ4倍
            nn.ConvTranspose2d(256, 128, kernel_size=4, stride=4, bias=False), # 8 > 32
            nn.BatchNorm2d(128),
            nn.LeakyReLU(0.2),

            nn.ConvTranspose2d(128, 64, kernel_size=4, stride=2, padding=1, bias=False), # 32 > 64
            nn.BatchNorm2d(64),
            nn.LeakyReLU(0.2),

            nn.ConvTranspose2d(64, 32, kernel_size=4, stride=2, padding=1, bias=False), # 64 > 128
            nn.BatchNorm2d(32),
            nn.LeakyReLU(0.2),

            nn.ConvTranspose2d(32, 3, kernel_size=4, stride=2, padding=1, bias=False), # 128 > 256
            nn.Tanh()
        )

    def forward(self, z):
        out = self.main(z)
        return out
class Discriminator(nn.Module):
    def __init__(self):
        super().__init__()

        self.x_layer = nn.Sequential(
            nn.Conv2d(ch, 32, kernel_size=3, stride=1, padding=1),
            # nn.BatchNorm2d(32),
            nn.LeakyReLU(0.1, inplace=True), #256 > 128
            nn.Dropout2d(p=0.3),

            nn.AvgPool2d(2),
            nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(64),
            nn.LeakyReLU(0.1, inplace=True), # 128 > 64
            nn.Dropout2d(p=0.3),

            nn.AvgPool2d(2),
            nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(128),
            nn.LeakyReLU(0.1, inplace=True), # 64 >32
            nn.Dropout2d(p=0.3),

            nn.AvgPool2d(2),
            nn.Conv2d(128, 256, kernel_size=6, stride=4, padding=1),
            nn.BatchNorm2d(256),
            nn.LeakyReLU(0.1, inplace=True), #32 > 8 ここだけ 1/4 
            nn.Dropout2d(p=0.3),

            ## kernel 4 > 1
            nn.AvgPool2d(2),
            nn.Conv2d(256, 256, kernel_size=4, stride=1) # 8 > 1x1
        )
wan2355 commented 3 years ago

今試してますが、頂いたmodelではimage_from_zは私のよりよいです。 modelを修正してみます。

https://github.com/ayukat1016/gan_sample/blob/chapter8_grayscale/chapter8/section8_4-EfficientGAN_L1_grayscale.ipynb?flush_cache=true

image_form_z 上:私 下:massy103 様 2021-04-05_15 44 31

wan2355 commented 3 years ago

image_from_zが悪い理由がわかりました。 discriminatorが良くなかったようです。

以下の部分で画像サイズを1/8にしたのが原因のようです。

nn.AvgPool2d(2),                                          # input-size * 1/2
nn.Conv2d(128, 256, kernel_size=6, stride=4, padding=1),  # input-size * 1/4 

以下のようにkernelの大きさetcを変更して計算中。

nn.AvgPool2d(2),   # 64 > 32
nn.Conv2d(128, 256, kernel_size=3, stride=1, padding=1),

nn.AvgPool2d(2), # 16=32/2
nn.Conv2d(256, 256, kernel_size=16, stride=1) # 16 > 1x1

結果:image_from_z 良くなりました。ただもう少し良くしたい。Alphaを振ってみますか? それと上記のAlertもなくなりました。

上:修正前 下:修正後 2021-04-05_18 34 45

massy103 commented 3 years ago

改善したようでよかったです。

基本的に、discriminatorではconv2dでは画像サイズは変わらないようにして、nn.AvgPool2dで減らしていく方が(経験則的に)質が良いです。

さらに改善するとすれば、ネットワークをもっと深くするか、他のGANのアーキテクチャを真似するしかないと思います。

例えば以下でkernel_size=16で一気に1×1にしてしまっている箇所をより細かいkernel_sizeとnn.AvgPool2dの組み合わせで深くしていくようなところでしょうか。

nn.Conv2d(256, 256, kernel_size=16, stride=1) # 16 > 1x1
wan2355 commented 3 years ago

massy様

夜分失礼します。。。 回答ありがとうございます。

以下はかなり参考になりました。

discriminatorではconv2dでは画像サイズは変わらないようにして、nn.AvgPool2dで減らしていく方が(経験則的に)質が良いです。

帰り際に自分のdataを先程修正したmodelで計算させていたのですが、当初と大きく変わりませんでした。少し良くなった程度です。 alphaを0.0001まで減らしてみましたが、あまり改善しません。 再構成画像のほうが劣化してくだけのようでした。 がっかりしたので、月曜日ですが、ビールを買って帰宅しました。

明日以下で試してみようと考えていますが、他にご提案があればお願いします。 また無意味なものがありましたら(多くがそうかも知れませんが)、ご指摘ください。 無駄なことをせずにすみますので。 おやすみなさい。

  1. zの次元を上げてみる。(embeded...?)  私の画像はオセロのような白黒の円盤を盤面に並べたような感じです。ただ円盤が白黒ではなく、細かい階調のあり、形がもっと複雑なコマが多数乗っていると想像してください。  かなり直感的な推測ですが、demo画像と私のdataでは表現されるべき次元の数が違いすぎるのかなと思い、この項目を考えた次第です。

  2. gray-scaleなので、期待は少ないですが、 画像を標準化?してみる(平均=0, 標準偏差=1など)。  もしくは階調の幅を最大化する。

  3. 画像を読み込む際にgrayのまま読み込み、3層重ねてRGBにして計算してみる。

  4. modelの入力channelを1にする。

  5. 上記で駄目なら、他のmodelを試す。(かなり時間がかかりそう。)

massy103 commented 3 years ago

お疲れ様です。 大変苦労されているようですね。。

1のzの次元は改善できる可能性あると思いますが、(個人的な経験では)学習がうまくいかない時にzの次元をいじっても特に良くなった事はないです。とりあえず一度、256とかで試してみるのが良いかと思います。

2~4はあまり重要ではないと思います。

なので、大変だとは思いますがやはり5が一番改善に寄与すると思います。

あまり情報量が無いので参考にはならないかも知れませんが、styleGANを使ったanoGANでうまくいったという記事もありました。

massy103 commented 3 years ago

時間があるときに私の方でも、grayscaleのほおずきの画像で256×256でzからの生成画像が綺麗になるように調整してみます。

wan2355 commented 3 years ago

massyさま

回答ありがとうございます。 ご協力に感謝します。 modelを直すしかなそうですか。。。

難しい提案ですが、 私が検討しているdataの一部をお渡しできるなら、それでご検討は可能でしょうか。

massy103 commented 3 years ago

内容確認したので、削除しておきました!

2については少し検討したいので、お時間いただけますと幸いです。

wan2355 commented 3 years ago

massy様

削除していただき、ありがとうございます。 2についてもご検討よろしくおねがいします。 可能になりそうでしたら、私のemail-addressをご連絡します。

massy103 commented 3 years ago

@wan2355

2について、前向きに検討したいと思っているので一度詳しく内容をお聞きしてもよろしいでしょうか。

wan2355 commented 3 years ago

後ほどこちらから宛先にご連絡します。

wan2355 commented 3 years ago

massy さま

先日はお騒がせしたわり、良い結果にならず、申し訳ありませんでした。 私のdataをお見せできない状況は変わらないのですが、現在のprogramを修正して、画像の質をと異常検出の精度を向上せねばなりません。 generator, discriminator, eoncoderなどprogramをどのように改善してゆけばよいのか再度ご意見をいただけないでしょうか。 また可能でありましら、鬼灯などfruits-360の画像で結構ですので、さらに画質と異常検知の質を高めるようご検討頂けるとありがたいです。

StyleGANのprogramを本書で読んでみましたが、内容が高度で理解が難しいので、これを組み込むにはかなり時間がかりそうです。

お手数をおかけしますが、ご検討よろしくおねがいします。

wan2355 commented 3 years ago

massy様

ご無沙汰しております。 少し改良してみました。 小川雄太郎先生のに掲載されているSelf_Attentionを導入してみました。内容は正確には理解できていません。 GeneratorとDiscriminator さらにEncorderも導入してみましたが、Encoderの方はあまり効果はなさそうです。

結果: 以下のとおりです。 (閲覧後は図を削除して頂いてもよろしいでしょうか。tensorboradの結果は残しても良いです。)

図は 上: originalのまま. 中央: GeneratorとDiscriminator にself-attention 下: GeneratorとDiscriminator さらにEncorderにself attention

画像はお米のCT像(約500~600枚)を使いました。 (お米を使ったのは特に意味はありません。評価例です。) 画像サイズは256x256. epoch=2000 alpha = 0.001 (0.005でも大きな変化はないが、0.01では画像が劣化する。) self-attentionはメモリの消費が大きく、batch-size 3が最大です。 (私がoriginalよりもlayerを増やしていることも、影響してます。) colabでも試しましたが3が最大でした。

課題: 1.512x512以上の画像で行いたかが、どうすればメモリを消費を抑えられるか。 2.精度を上げたいので、zの画像をもう少し向上させたい?(これ以外に指標があればお教えください。) 3.今回は立てたお米のみを正常として利用したが、寝かせたものや上下逆さにしたものも撮影(もしくはaugmentation?)して、正常の画像の種類を増やしたほうが良いのか、それともこのままの画像で他のお米と比較したほうが良いのか。(撮影時にお米を一定の方向に向けるのは難しいため。) ただ寝かせた画像などを入れると、精度が低下しないか懸念される。 3.AnoGAN以外のmodelや評価方法の検討も必要か。 4.もし撮影して比較したい試料があればご提案頂けるとありがたいです。 サイズは数mm以下。 うち装置はX線のエネルギーが低いため、昆虫などでも撮影は可能です。逆に分厚い金属を含む試料は無理です。

また可能であれば、programをみて頂けるとありがたいのですが、如何でしょうか。

Tensorboard 2021-04-26_10_22_58b

massy103 commented 3 years ago

@wan2355 様

GeneratorとDiscriminator さらにEncorderも導入してみましたが、Encoderの方はあまり効果はなさそうです。

EncoderはAnoGANで必要な推論時の勾配法によるz探索をなくすためにあるので、AnoGANに導入してもあまり効果はないと思います。

1.512x512以上の画像で行いたかが、どうすればメモリを消費を抑えられるか。

基本的にはバッチサイズを下げるか、ネットワークを浅くするしかなさそうです。 バッチサイズはどれだけ下げても4は欲しいので、今回はネットワークを浅くするしかないかもです

2.精度を上げたいので、zの画像をもう少し向上させたい?(これ以外に指標があればお教えください。)

指標としてはFIDやInception Scoreがあり、最終的に生成された画像に対する定量的な評価には使えると思います。

3.今回は立てたお米のみを正常として利用したが、寝かせたものや上下逆さにしたものも撮影(もしくはaugmentation?)して、正常の画像の種類を増やしたほうが良いのか、それともこのままの画像で他のお米と比較したほうが良いのか。(撮影時にお米を一定の方向に向けるのは難しいため。) ただ寝かせた画像などを入れると、精度が低下しないか懸念される。

これについてはなんとも言えないですね・・ どちらかと言うとこのままの画像で他のお米と比較したほうが良いと思います。

3.AnoGAN以外のmodelや評価方法の検討も必要か。

個人的にはAnoGANは推論に時間がかかり、従来のEfficientGANは再構成画像の質が悪い(ずれる)ので、L1_Lossを導入したEfficientGANが使い勝手がよく、画像の質も良いと思っています。 L1_Lossを導入したEfficientGANで、ネットワークを今のシンプルなものからリッチなものにするのが一番筋が良い気がします。

4.もし撮影して比較したい試料があればご提案頂けるとありがたいです。 サイズは数mm以下。 うち装置はX線のエネルギーが低いため、昆虫などでも撮影は可能です。逆に分厚い金属を含む試料は無理です。 また可能であれば、programをみて頂けるとありがたいのですが、如何でしょうか。

逆に、もし可能であればお米の画像を共有していただけると時間のあるときに私の方で実験してみたいのですが可能でしょうか?

wan2355 commented 3 years ago

massy 様

早速のご連絡ありがとうございます。

もし可能であればお米の画像を共有していただけると時間のあるときに私の方で実験してみたいのですが可能でしょうか?

このdataは共有可能ですが、他の人には渡さないでください。見せる程度は大丈夫です。 jpgなので、100MBもありません。 どうやってお送りしましょうか。

補足ですが、米の両脇のものは米を保持するためのholderで、 本来は不要ですが、画像処理でうまく除去できていません。 これをうまく取り除けると精度が上がりそうです。

バッチサイズはどれだけ下げても4は欲しいので、今回はネットワークを浅くするしかないかもです 了解です。 こちらでも検討してみます。

個人的にはAnoGANは推論に時間がかかり、従来のEfficientGANは再構成画像の質が悪い(ずれる)ので、L1_Lossを導入したEfficientGANが使い勝手がよく、画像の質も良いと思っています。 L1_Lossを導入したEfficientGANで、ネットワークを今のシンプルなものからリッチなものにするのが一番筋が良い気がします。

了解です。 私の質問はEfficientGAN_L1以外のmodelの意味でした。 現状では今のmodelが良さそうですが、目標としてはもっと大きな画像で、画像の質も上げたい です。

よろしくおねがいします。

massy103 commented 3 years ago

@wan2355

このdataは共有可能ですが、他の人には渡さないでください。見せる程度は大丈夫です。 jpgなので、100MBもありません。 どうやってお送りしましょうか。

先日ご連絡したgmailのアカウントに添付していただくか、google driveにアップロードして私のgmailアカウントに権限付与していただく形だと良いかと思います!

wan2355 commented 3 years ago

先程共有しました。 ご確認願います。

万一 dataが別でしたら、ご連絡ください。

20:42 先程 評価用のtest画像も共有しました。

massy103 commented 3 years ago

ありがとうございます。 確認しました。

wan2355 commented 3 years ago

dataの共有は外しても大丈夫でしょうか。

massy103 commented 3 years ago

@wan2355

遅くなりました。 ダウンロードしたので外して大丈夫です!

wan2355 commented 3 years ago

massy103様 ご無沙汰しております。 少し進展しましたので、ご相談です。 以前と同様に閲覧後に画像と各modelのfileは削除頂けませんか。 お願いします。

今回の修正点。 小川先生のSelf-Attentionを利用させていただき、元のmodelを修正してみました。 Alphaも変化させてみたところ、以下の条件で画質が向上してきました。 ただ以前より計算に時間がかかります。

PS: いまcolabでも計算させてますが、異常に遅いです。 利用者が多いためですかね。 奇妙なことにcolabで計算させるとまともな画像が得られませんね?

ご相談内容:

1.良くなってきましたが、さらに画質を上げたい。またもっと大きな高精細な画像を扱いたい。 2.画像の明るさやコントラスがanoscoreに与える影響が大きいので、画像の明るさやコントラストをどのように決めれば、anoscoreが安定するのか。 3.予測画像が元の画像を向きと一致しないことがある。

条件: α=0.008 epoch =3101 batch size=5 残りはほぼdefaultのまま。

  1. z_image 以前より良くなったと思います。

  2. 異常検知画像 2-1. 割れたもの 割れが大きいほど、anoscoreも大きくなっており、興味深い。 予測画像が異なるのが気になる。

2-2. 傾いた試料への対応。 下の段の画像は試料の傾斜に予測画像がうまく対応しているが、 上の段では予測画像が元の画像の方向と一致しない。 学習画像にこちらの方向のdataが少ないためかもしれない。

2-3.明るさ・コントラストを変えると、Anoscoreが変わる。 どうやって画像の明るさやコントラストを決めるか難しい。

  1. tensorboard Alphaを変化させた時の結果。

参考までにdiscriminator, generatorの構造です。 いろいろ変えたいのですが、これ以上 各parameterの値を大きくするとメモリに乗りません。。。 また私の力量不足でこれ以上layerをへらすと画質が劣化します。

discriminator

generator

massy103 commented 3 years ago

確認したので画像を削除しました! いったん現時点での所感をコメントさせていただきます 週末になにか試せたらまた追記します

1.良くなってきましたが、さらに画質を上げたい。またもっと大きな高精細な画像を扱いたい。

zからの生成画像がかなりよくなったと感じました。 もう少し高品質でメモリ効率の良いモデルをリサーチしてみますね。

2.画像の明るさやコントラスがanoscoreに与える影響が大きいので、画像の明るさやコントラストをどのように決めれば、anoscoreが安定するのか。

これはanomaly scoreの測り方を変えるのが良いと思います。 おそらく、輪郭のみを見れば良いと思うのでanomaly scoreを測定する関数内部で二値化をしてから誤差を測るのが良いかもです。(あまり詳しくないので二値化より良い処理がありそうです) 二値化の閾値は米の主要な輪郭が残るように決めてください。

二値化について

3.予測画像が元の画像を向きと一致しないことがある。

(もしL1損失を損失関数に含めているのであれば、)これは予測画像の欠損・異常部分が無かったとした場合の(正常な)形状の画像が学習画像になく、そのような画像に対応する潜在変数が無いからだと思います。 異常検知的な観点でいくと、感覚的な話しですが部分的なパーツが欠損しているという異常よりも、見たことない向きという異常の方が異常度が高いので、ある意味で向きも再現できないのは自然な印象です。

wan2355 commented 3 years ago

massy 様

貴重なコメントありがとうございます。

もう少し高品質でメモリ効率の良いモデルをリサーチしてみますね。

期待しております。 私の方でも使えそうな物があれば試してみます。

これはanomaly scoreの測り方を変えるのが良いと思います。

codeをよく見て考えてみます。 今回も学習にかける前に手動で画像の背景が消えるようにはしてますが、限界があります。

また測定によって画像の明るさがばらつくため、学習にかける前に画像全体を規格化できる方法を考えています。 これはこれ以外の方法でも苦心しているところです。 検討はしてますが、良い方法が見つかっていません。

引き続きよろしくお願いします。

wan2355 commented 3 years ago

二値化してみましたが、目的にはあいませんでした。 (opencvで二値化しようとしたため、float32をunit8への変換は厄介でした。)

微細な形態が消えるのと、内部の構造(クラックなど)も画像によっては消失してしまうため、現状のほうが良さそうです。 また考えてみます。

wan2355 commented 3 years ago

massy 様

少し余談ですが、以下のように画像をtensorboardxに表示できるようにしては如何でしょうか。 tensorboardx以下の数行がそれです。

元のcodeとは少し変わっているかもしれませんが、おそらく大丈夫と思います。 lossと画像が一度に見れるので、便利と思います。

from torchvision.utils import make_grid, save_image

## save images every 50 epochs
            if epoch  % 50 == 0:

                model_G.eval()
                model_D.eval()
                model_E.eval()

                save_image_size_for_z = min(BATCH_SIZE, 8)
                save_images = model_G(z)
                save_image(save_images[:save_image_size_for_z], f"{SAVE_IMAGE_FROM_Z_PATH}/epoch_{epoch:05}.png", nro
w=4)
                ## tensorboardx
                z_img = make_grid(save_images[:save_image_size_for_z])
                writer.add_image( "0_z_img", z_img, epoch)

                save_image_size_for_recon = min(BATCH_SIZE, 8)
                images = x[:save_image_size_for_recon]
                G_E_x = model_G(model_E(images))
                diff_images = torch.abs(images - G_E_x)
                comparison = torch.cat([images , G_E_x, diff_images]).to("cpu")
                save_image(comparison, f"{SAVE_IMAGE_RECONSTRUCT}/epoch_{epoch:05}.png", nrow=save_image_size_for_recon)

                ## tensorboardx
                org_img = make_grid(images)
                writer.add_image( "1_org_img", org_img, epoch)

                E_img = make_grid(G_E_x)
                writer.add_image( "2_E_img", E_img, epoch)

                diff_img = make_grid(diff_images)
                writer.add_image( "3_diff_img", diff_img, epoch)