1. GANの紹介「食べるために一生懸命働く人、食べるために一生懸命働く人こそが人々の中で最も優れている。」 このGAANのライスイーターは、そんなパサパサしたライスイーターではありません。この記事で説明する GAN は、Goodfellow2014 によって提案された生成的敵対モデル、つまり Generative Adversarial Nets です。では、GAN の何がそんなに不思議なのでしょうか? 画像分類、オブジェクト検出、セマンティックセグメンテーション、インスタンスセグメンテーションなどの従来のディープラーニングタスクでは、これらのタスクの結果は予測に帰属します。画像分類では単一のカテゴリが予測され、オブジェクト検出では bbox とカテゴリが予測され、セマンティック セグメンテーションまたはインスタンス セグメンテーションでは各ピクセルのカテゴリが予測されます。 GANは画像などの新しいものを生成します。 GAN の原理は一文で説明できます。 - 敵対的アプローチを通じて、データ分布の生成モデルを学習します。 GAN は、データセットの分布をキャプチャして、ランダムノイズから同じ分布を持つデータを生成できる教師なしプロセスです。
GANの構成: 識別モデルと生成モデル間の左手ゲーム - D 判別モデル: 真偽の境界を学習し、データが真か偽かを判断する
- GGenerative Model: データ分布を学習し、データを生成する
GANの典型的な損失は次のとおりです(minmaxは対立を反映します) 2. 実践的なCycleGANスタイル転送GANの役割がわかったところで、GANの魔法のような効果を体験してみましょう。ここでは、画像のスタイル変換を実現するために、cycleGAN を例に挙げます。いわゆるスタイル転送とは、元の画像のスタイルを変更することです。下の図に示すように、左側が元の画像、真ん中がスタイル画像(ゴッホの絵画)、生成された画像は右側がゴッホのスタイルを加えた元の画像です。生成された画像は全体として元の画像の内容をほとんど保持していることがわかります。 2.1 サイクルGANの紹介CycleGAN は本質的に GAN と同じであり、データセット内の基礎となるデータ分布を学習します。 GAN はランダムノイズから同じ分布の画像を生成し、cycleGAN は学習した分布を意味のある画像に追加して別のフィールドから画像を生成します。 CycleGAN は、画像間の 2 つの領域の間に潜在的な接続があると想定します。 周知のとおり、GAN のマッピング機能では、生成された画像の有効性を保証することは困難です。 CycleGAN は、サイクルの一貫性を使用して、生成された画像と入力画像の構造の一貫性を確保します。 cycleGANの構造を見てみましょう。 特徴は以下のようにまとめられます。 - 双方向 GAN: 2 つのジェネレーター [G:X->Y、F:Y->X] と 2 つの識別器 [Dx、Dy]。G と Dy はオブジェクトを生成することを目的としていますが、Dy (正のクラスは Y ドメイン) は識別できません。 F と Dx についても同様です。
- 循環一貫性: G は Y を生成するジェネレータであり、F は X を生成するジェネレータです。循環一貫性とは、G と F によって生成されるオブジェクトの範囲を制約し、G によって生成されたオブジェクトをジェネレータ F を介して元のドメインに戻すことができるようにすることです。例: x->G(x)->F(G(x))=x
敵対的損失は次のとおりです。 2.2 サイクルGANの実装2.2.1 ジェネレータ 上記の紹介から、ジェネレータには 2 つのジェネレータがあり、1 つは前進用、もう 1 つは後進用であることがわかります。この構造は、論文「リアルタイム スタイル転送と超解像度における知覚的損失: 補足資料」に基づいています。これは、次の図(論文より)に示すように、大まかに、ダウンサンプリング + 残差ブロック + アップサンプリングに分けられます。 アップサンプリングとダウンサンプリングの実装は、ストライド = 2 の畳み込みであり、アップサンプリングでは nn.Upsample を使用します。 - # 残余ブロック
- クラス ResidualBlock(nn.Module):
- def __init__(self, in_features):
- super(ResidualBlock, self).__init__()
- 自己ブロック= nn.シーケンシャル(
- nn.ReflectionPad2d(1)、
- nn.Conv2d(in_features, in_features, 3),
- nn.InstanceNorm2d(in_features)、
- nn.ReLU(インプレース= True )、
- nn.ReflectionPad2d(1)、
- nn.Conv2d(in_features, in_features, 3),
- nn.InstanceNorm2d(in_features)、
- )
- def forward(self, x):
- x + self.block(x) を返す
- クラス GeneratorResNet(nn.Module):
- def __init__(self, input_shape, num_residual_blocks):
- super(GeneratorResNet、self).__init__()
- チャンネル= input_shape [0]
- # 初期畳み込みブロック
- アウトフィーチャー= 64
- モデル= [
- nn.ReflectionPad2d(チャンネル)、
- nn.Conv2d(チャンネル、out_features、7)、
- nn.InstanceNorm2d(out_features)、
- nn.ReLU(インプレース= True )、
- ]
- in_features = out_features
- # ダウンサンプリング
- _ が範囲内(2)の場合:
- アウトフィーチャ *= 2
- モデル += [
- nn.Conv2d(in_features, out_features, 3,ストライド= 2 ,パディング= 1 ),
- nn.InstanceNorm2d(out_features)、
- nn.ReLU(インプレース= True )、
- ]
- in_features = out_features
- # 残余ブロック
- _ が範囲内(num_residual_blocks)の場合:
- モデル += [ResidualBlock(out_features)]
- # アップサンプリング
- _ が範囲内(2)の場合:
- 出力フィーチャ //= 2
- モデル += [
- nn.アップサンプル(スケール係数= 2 )、
- nn.Conv2d(in_features, out_features, 3,ストライド= 1 ,パディング= 1 ),
- nn.InstanceNorm2d(out_features)、
- nn.ReLU(インプレース= True )、
- ]
- in_features = out_features
- # 出力層
- モデル += [nn.ReflectionPad2d(チャネル), nn.Conv2d(out_features, チャネル, 7), nn.Tanh()]
- self.model = nn.Sequential (*モデル)
- def forward(self, x):
- self.model(x) を返す
2.2.2 識別器 従来のGAN識別子は、真偽の度合いを判定する値を出力します。 patchGAN の出力は N*N 値で、それぞれが元の画像上の特定のサイズの受容野を表します。直感的に言えば、元の画像上の切り抜きの下の繰り返し可能な領域の真正性を判定します。これは、pix2pix (条件付き敵対的ネットワークによる画像から画像への変換) で最初に提案された完全な畳み込みネットワークと考えることができます。利点はパラメータが少ないことであり、もう 1 つの利点は高周波情報をローカルでより適切にキャプチャできることです。 - クラス Discriminator(nn.Module):
- def __init__(self, input_shape):
- super(ディスクリミネータ、self).__init__()
- チャンネル、高さ、幅= input_shape
- # 画像識別器(PatchGAN)の出力形状を計算する
- self.output_shape = (1, 高さ // 2 ** 4, 幅 // 2 ** 4)
- def discriminator_block(in_filters, out_filters, normalize = True ):
- """各識別ブロックのダウンサンプリング レイヤーを返します"""
- レイヤー= [nn.Conv2d(in_filters, out_filters, 4,ストライド= 2 ,パディング= 1 )]
- 正規化する場合:
- レイヤーを追加します(nn.InstanceNorm2d(out_filters))
- レイヤーを追加します(nn.LeakyReLU(0.2, inplace = True ))
- リターンレイヤー
- 自己.モデル= nn.シーケンシャル(
- * discriminator_block(チャネル、64、正規化= False )、
- *識別子ブロック(64, 128)、
- *識別子ブロック(128, 256)、
- *識別子ブロック(256, 512)、
- nn.ZeroPad2d((1, 0, 1, 0))、
- nn.Conv2d(512, 1, 4,パディング= 1 )
- )
- def forward(self, img):
- self.model(img) を返します。
2.2.3 トレーニング 損失とモデルの初期化 - # 損失
- criterion_GAN = torch.nn.MSELoss ()
- criterion_cycle = torch.nn.L1Loss () の
- 基準_identity = torch.nn.L1Loss ()
- cuda = torch.cuda.is_available () です
- input_shape = (opt.channels、opt.img_height、opt.img_width) です。
- # ジェネレータとディスクリミネータを初期化する
- G_AB =ジェネレーターResNet (入力シェイプ、opt.n_residual_blocks)
- G_BA =ジェネレーターResNet (入力シェイプ、opt.n_residual_blocks)
- D_A =判別器(input_shape)
- D_B =判別器(input_shape)
最適化とトレーニング戦略 - # オプティマイザー
- オプティマイザー_G = torch.optim.Adam (
- itertools.chain(G_AB.parameters(), G_BA.parameters()), lr = opt .lr, betas =(opt.b1, opt.b2)
- )
- optimizer_D_A = torch.optim.Adam (D_A.parameters(), lr = opt.lr 、ベータ=(opt.b1, opt.b2))
- optimizer_D_B = torch.optim.Adam (D_B.parameters(), lr = opt.lr 、ベータ=(opt.b1, opt.b2))
- # 学習率更新スケジューラ
- lr_scheduler_G = torch.optim.lr_scheduler.LambdaLR (
- optimizer_G、 lr_lambda = LambdaLR (opt.n_epochs、opt.epoch、opt.decay_epoch).step
- )
- lr_scheduler_D_A = torch.optim.lr_scheduler.LambdaLR (
- optimizer_D_A、 lr_lambda = LambdaLR (opt.n_epochs、opt.epoch、opt.decay_epoch).step
- )
- lr_scheduler_D_B = torch.optim.lr_scheduler.LambdaLR (
- optimizer_D_B、 lr_lambda = LambdaLR (opt.n_epochs、opt.epoch、opt.decay_epoch).step
- )
トレーニングの反復 - トレーニング データはペア データですが、ペアではないデータです。つまり、A と B には直接的な接続がありません。 Aはオリジナル画像、Bはスタイル画像
- 発電機のトレーニング
- GAN損失: 識別器は、AとBによって生成されたfake_A、fake_B、およびGTの損失を区別します。
- サイクル損失: 逆に、fake_A と fake_B によって生成された画像は、A と B のピクセルとは異なります。
- 識別器のトレーニング:
- loss_real: A/B と GT を区別するための MSELoss
- loss_fake: 生成された fake_A/fake_B を GT と区別するための MSELoss
- 範囲内のエポックの場合(opt.epoch、opt.n_epochs):
- i の場合、enumerate(dataloader) でバッチ処理します。
- # データはペアデータですが、ペアではないデータです。つまり、AとBは直接接続されていません。
- real_A =変数(batch["A"].type(Tensor))
- real_B =変数(batch["B"].type(Tensor))
- # 敵対的グラウンドトゥルース
- 有効=変数(Tensor(np.ones((real_A.size(0), *D_A.output_shape)))、 requires_grad = False )
- fake =変数(Tensor(np.zeros((real_A.size(0), *D_A.output_shape))), requires_grad = False )
- # ------------------
- # 列車発電機
- # ------------------
- G_AB.train()
- G_BA.train()
- オプティマイザー_G.ゼロ_grad()
- # アイデンティティの喪失
- loss_id_A =基準の同一性(G_BA(real_A), real_A)
- loss_id_B =基準の同一性(G_AB(real_B), real_B)
- 損失ID = (損失ID_A + 損失ID_B) / 2
- # GAN損失
- 偽のB = G_AB (本物のA)
- loss_GAN_AB = criterion_GAN (D_B(fake_B)、有効)
- 偽のA = G_BA (本物のB)
- loss_GAN_BA = criterion_GAN (D_A(fake_A)、有効)
- 損失GAN = (損失GAN_AB + 損失GAN_BA) / 2
- # サイクル損失
- recov_A = G_BA (偽のB)
- 損失サイクルA =基準サイクル(回復A、実数A)
- recov_B = G_AB (偽のA)
- 損失サイクルB =基準サイクル(回復B、実数B)
- 損失サイクル= (損失サイクルA + 損失サイクルB) / 2
- # 全損
- loss_G = loss_GAN + opt.lambda_cyc * loss_cycle + opt.lambda_id * loss_identity
- loss_G.後方()
- オプティマイザ_G.ステップ()
- # -----------------------
- # 列車識別器 A
- # -----------------------
- オプティマイザー_D_A.ゼロ_勾配()
- # 本当の損失
- loss_real = criterion_GAN (D_A(real_A), 有効)
- # 偽の損失(以前に生成されたサンプルのバッチ)
- # fake_A_ = fake_A_buffer .push_and_pop(fake_A)
- loss_fake = criterion_GAN (D_A(fake_A_.detach()), フェイク)
- # 全損
- 損失_D_A = (損失_実数 + 損失_偽数) / 2
- loss_D_A.後方()
- オプティマイザー_D_A.ステップ()
- # -----------------------
- # 列車識別器 B
- # -----------------------
- オプティマイザーD_B.ゼロ勾配()
- # 本当の損失
- loss_real = criterion_GAN (D_B(real_B)、有効)
- # 偽の損失(以前に生成されたサンプルのバッチ)
- # fake_B_ = fake_B_buffer .push_and_pop(fake_B)
- loss_fake = criterion_GAN (D_B(fake_B_.detach()), フェイク)
- # 全損
- 損失_D_B = (損失_実数 + 損失_偽数) / 2
- loss_D_B.後方()
- オプティマイザー_D_B.ステップ()
- 損失_D = (損失_D_A + 損失_D_B) / 2
- # --------------
- # ログの進行状況
- # --------------
- # 残り時間の目安を決定する
- batches_done =エポック* len(データローダー) + i
- batches_left = opt .n_epochs * len(データローダー) - batches_done
- time_left = datetime.timedelta (秒= batches_left * (time.time() - prev_time))
- prev_time = 時間.時間()
- # 学習率を更新する
- lr_scheduler_G.ステップ()
- lr_scheduler_D_A.ステップ()
- lr_scheduler_D_B.ステップ()
2.2.4 結果の表示 この記事では、次の図に示すように、モネスタイルの変換をトレーニングします。1行目と2行目はモネスタイルの絵画を通常の写真に変換したもので、3行目と4行目は通常の写真からモネスタイルの絵画に変換したものです。 実際の携帯電話の写真を見てみましょう: 2.2.5 cycleGANのその他の用途 3. 結論この記事では、GAN の応用例の 1 つである cycleGAN を詳しく紹介し、それを画像のスタイル転送に適用します。要約すると: - GAN はデータ内の分布を学習し、同じ分布を持つ新しいデータを生成します。
- CycleGAN は、2 つのジェネレーターと 2 つの識別器を備えた双方向 GAN です。ジェネレーターによって生成された画像が入力画像と特定の関係を持ち、ランダムに生成された画像ではないことを確認するために、サイクル一貫性が導入され、A->fake_B->recove_A と A の違いが判断されます。
- ジェネレーター: ダウンサンプリング + 残差ブロック + アップサンプリング
- 識別器:グラフから判定値を生成する代わりに、patchGAN方式ではN*Nの値を生成し、平均をとります。
|