以下の内容はhttps://cysec148.hatenablog.com/entry/2025/04/08/064623より取得しました。


第27回:CycleGAN

Hello there, ('ω')ノ

🧠 はじめに:CycleGANとは?

CycleGAN(Cycle-Consistent Adversarial Networks) とは、
ペアなしの画像データ画像変換(Image-to-Image Translation)
自己教師あり学習 によって実現するGANベースのモデルです。

提案者: 2017年、Jun-Yan Zhu らによる論文
目的:
- ペアデータなしで画像変換を実現
- 異なるドメイン間の画像変換を双方向で学習

CycleGANの特徴:
- 馬 ↔ シマウマ変換(Horse-to-Zebra)
- 写真 ↔ 絵画変換(Photo-to-Painting)
- 冬景色 ↔ 夏景色変換(Season Transfer)
- 昼 ↔ 夜画像の変換(Day-to-Night)


📚 1. CycleGANの基本概念と仕組み


🎨 ① CycleGANの基本構造

CycleGAN は、2つの生成器(GとF)
2つの識別器(D_XとD_Y) で構成されています。


🎯 【CycleGANのアーキテクチャ】

[ドメイン X] → [G:X → Y] → [ドメイン Y] → [F:Y → X] → [サイクル再構成 X']
[ドメイン Y] → [F:Y → X] → [ドメイン X] → [G:X → Y] → [サイクル再構成 Y']

生成器 G(X → Y):
- ドメイン X の画像を Y の画像に変換

生成器 F(Y → X):
- ドメイン Y の画像を X の画像に変換

識別器 D_X:
- 本物の XF(Y) の偽物 X を判別

識別器 D_Y:
- 本物の YG(X) の偽物 Y を判別


📚 ② Cycle Consistency(サイクル一貫性)

CycleGAN の核心は サイクル一貫性損失(Cycle Consistency Loss) にあります。
画像を X → Y → X' に変換した際に、
X と X' ができるだけ一致すること を学習します。


📚 【サイクル一貫性の数式】

[ L{\text{cyc}}(G, F) = \mathbb{E}{x \sim p{\text{data}}(x)} [| F(G(x)) - x |1] + \mathbb{E}{y \sim p{\text{data}}(y)} [| G(F(y)) - y |_1] ]

目的:
- X → Y → X' で X に戻る
- Y → X → Y' で Y に戻る

L1損失(Mean Absolute Error, MAE) を使用して誤差を最小化


📚 ③ CycleGANの損失関数

CycleGANの最終的な損失関数は、
cGAN損失サイクル一貫性損失 を組み合わせたものです。


📚 【CycleGANの最終損失関数】

[ L(G, F, D_X, D_Y) = L{\text{GAN}}(G, D_Y, X, Y) + L{\text{GAN}}(F, D_X, Y, X) + \lambda L_{\text{cyc}}(G, F) ]

1. cGANの損失(Adversarial Loss)

[ L{\text{GAN}}(G, D_Y, X, Y) = \mathbb{E}{y \sim p{\text{data}}(y)} [\log D_Y(y)] + \mathbb{E}{x \sim p_{\text{data}}(x)} [\log (1 - D_Y(G(x)))] ]

2. サイクル一貫性損失(Cycle Consistency Loss)

[ L{\text{cyc}}(G, F) = \mathbb{E}{x \sim p{\text{data}}(x)} [| F(G(x)) - x |1] + \mathbb{E}{y \sim p{\text{data}}(y)} [| G(F(y)) - y |_1] ]

3. 最終損失関数

  • λ(ラムダ): サイクル一貫性の重み(通常は λ = 10)
  • 目的: GANの精度向上と変換結果の一貫性を確保

📚 ④ 通常のPix2PixとCycleGANの違い

項目 Pix2Pix CycleGAN
データの種類 ペアデータ ペアなしデータ
学習方法 条件付きGAN(cGAN) サイクル一貫性のGAN
損失関数 cGAN + L1損失 GAN + サイクル一貫性損失
適用シナリオ 色付け・スケッチ変換 スタイル変換・画像領域変換
必要なデータ量 ペアデータ必須 ペアデータ不要

CycleGAN はペアなしデータで学習可能で、ドメイン間の変換タスクに強い!


🎨 2. CycleGANの具体的な活用例


🎯 ① 馬 ↔ シマウマ変換(Horse-to-Zebra)

概要:
- 馬の画像をシマウマに変換、またはその逆
- 動物の模様や特徴の自動変換

ユースケース:
- 動物研究、映像制作、バーチャルリアリティ(VR)


🎨 ② 写真 ↔ 絵画変換(Photo-to-Painting)

概要:
- 写真をアート風の絵画に変換、または逆変換
- スタイル変換(Style Transfer)の応用

ユースケース:
- アート作品生成、フォトエフェクト、デジタルアート


📚 ③ 季節変換(Winter ↔ Summer Transfer)

概要:
- 夏の風景を冬の風景に変換、または逆変換
- 景色の時間・季節変化を模倣

ユースケース:
- 観光産業、気候シミュレーション、ゲーム開発


📊 ④ 昼 ↔ 夜の画像変換(Day-to-Night Transformation)

概要:
- 昼間の画像を夜景に変換、またはその逆
- 明るさ、影、照明などの調整

ユースケース:
- 自動運転、監視カメラ解析、映像生成


🤖 3. CycleGANの実装(PyTorchで画像変換)


📚 ① 必要なライブラリのインストール

pip install torch torchvision matplotlib numpy

📚 ② 生成器(Generator)の定義

import torch
import torch.nn as nn

# U-Net ベースの生成器(Generator)
class ResnetGenerator(nn.Module):
    def __init__(self, input_channels, output_channels, num_residual_blocks=9):
        super(ResnetGenerator, self).__init__()
        
        # 入力層
        layers = [
            nn.Conv2d(input_channels, 64, kernel_size=7, stride=1, padding=3),
            nn.InstanceNorm2d(64),
            nn.ReLU(inplace=True)
        ]
        
        # エンコーダ
        in_channels = 64
        for _ in range(2):
            layers += [
                nn.Conv2d(in_channels, in_channels * 2, kernel_size=3, stride=2, padding=1),
                nn.InstanceNorm2d(in_channels * 2),
                nn.ReLU(inplace=True)
            ]
            in_channels *= 2
        
        # 残差ブロック
        for _ in range(num_residual_blocks):
            layers += [ResnetBlock(in_channels)]
        
        # デコーダ
        for _ in range(2):
            layers += [
                nn.ConvTranspose2d(in_channels, in_channels // 2, kernel_size=3, stride=2, padding=1, output_padding=1),
                nn.InstanceNorm2d(in_channels // 2),
                nn.ReLU(inplace=True)
            ]
            in_channels //= 2
        
        # 出力層
        layers += [
            nn.Conv2d(in_channels, output_channels, kernel_size=7, stride=1, padding=3),
            nn.Tanh()
        ]
        
        self.model = nn.Sequential(*layers)
    
    def forward(self, x):
        return self.model(x)

# 残差ブロックの定義
class ResnetBlock(nn.Module):
    def __init__(self, channels):
        super(ResnetBlock, self).__init__()
        self.block = nn.Sequential(
            nn.Conv2d(channels, channels, kernel_size=3, stride=1, padding=1),
            nn.InstanceNorm2d(channels),
           

 nn.ReLU(inplace=True),
            nn.Conv2d(channels, channels, kernel_size=3, stride=1, padding=1),
            nn.InstanceNorm2d(channels)
        )
    
    def forward(self, x):
        return x + self.block(x)

生成器は U-Net に基づく ResNet アーキテクチャ


📚 ③ 識別器(Discriminator)の定義

# PatchGAN ベースの識別器(Discriminator)
class PatchGANDiscriminator(nn.Module):
    def __init__(self, input_channels):
        super(PatchGANDiscriminator, self).__init__()
        self.model = nn.Sequential(
            nn.Conv2d(input_channels, 64, kernel_size=4, stride=2, padding=1),
            nn.LeakyReLU(0.2),

            nn.Conv2d(64, 128, kernel_size=4, stride=2, padding=1),
            nn.InstanceNorm2d(128),
            nn.LeakyReLU(0.2),

            nn.Conv2d(128, 256, kernel_size=4, stride=2, padding=1),
            nn.InstanceNorm2d(256),
            nn.LeakyReLU(0.2),

            nn.Conv2d(256, 1, kernel_size=4, stride=1, padding=1),
            nn.Sigmoid()
        )

    def forward(self, x):
        return self.model(x)

PatchGANベースの識別器で局所的な特徴を判別


📚 ④ CycleGANの損失関数の定義

# cGAN とサイクル一貫性損失
def cycle_gan_loss(d_real, d_fake, real_images, reconstructed_images, lambda_cycle=10):
    adversarial_loss = nn.MSELoss()
    cycle_loss = nn.L1Loss()

    # cGAN損失
    d_loss_real = adversarial_loss(d_real, torch.ones_like(d_real))
    d_loss_fake = adversarial_loss(d_fake, torch.zeros_like(d_fake))
    d_loss = (d_loss_real + d_loss_fake) / 2

    # サイクル一貫性損失
    cycle_loss_value = cycle_loss(reconstructed_images, real_images) * lambda_cycle

    # 生成器の損失
    g_loss = adversarial_loss(d_fake, torch.ones_like(d_fake)) + cycle_loss_value

    return d_loss, g_loss

📚 ⑤ CycleGANの学習ループ

import torchvision
from torchvision import datasets, transforms
from torch.utils.data import DataLoader

# データセットの読み込み
transform = transforms.Compose([
    transforms.Resize((256, 256)),
    transforms.ToTensor(),
    transforms.Normalize([0.5], [0.5])
])

dataset_X = datasets.ImageFolder(root="./data/domain_X", transform=transform)
dataset_Y = datasets.ImageFolder(root="./data/domain_Y", transform=transform)

dataloader_X = DataLoader(dataset_X, batch_size=64, shuffle=True)
dataloader_Y = DataLoader(dataset_Y, batch_size=64, shuffle=True)

# モデルの初期化
G = ResnetGenerator(3, 3)
F = ResnetGenerator(3, 3)
D_X = PatchGANDiscriminator(3)
D_Y = PatchGANDiscriminator(3)

# 最適化
lr = 0.0002
g_optimizer = torch.optim.Adam(G.parameters(), lr=lr)
f_optimizer = torch.optim.Adam(F.parameters(), lr=lr)
d_x_optimizer = torch.optim.Adam(D_X.parameters(), lr=lr)
d_y_optimizer = torch.optim.Adam(D_Y.parameters(), lr=lr)

# 学習ループ
num_epochs = 100
for epoch in range(num_epochs):
    for real_X, _ in dataloader_X:
        for real_Y, _ in dataloader_Y:
            real_X, real_Y = real_X.to('cuda'), real_Y.to('cuda')

            # ① 生成画像の生成
            fake_Y = G(real_X)
            fake_X = F(real_Y)

            # ② サイクル再構成
            rec_X = F(fake_Y)
            rec_Y = G(fake_X)

            # ③ 識別器の更新
            d_real_X = D_X(real_X)
            d_fake_X = D_X(fake_X.detach())
            d_real_Y = D_Y(real_Y)
            d_fake_Y = D_Y(fake_Y.detach())

            d_loss_X, _ = cycle_gan_loss(d_real_X, d_fake_X, real_X, rec_X)
            d_loss_Y, _ = cycle_gan_loss(d_real_Y, d_fake_Y, real_Y, rec_Y)

            d_x_optimizer.zero_grad()
            d_loss_X.backward()
            d_x_optimizer.step()

            d_y_optimizer.zero_grad()
            d_loss_Y.backward()
            d_y_optimizer.step()

            # ④ 生成器の更新
            d_fake_X = D_X(fake_X)
            d_fake_Y = D_Y(fake_Y)

            _, g_loss_X = cycle_gan_loss(d_real_X, d_fake_X, real_X, rec_X)
            _, g_loss_Y = cycle_gan_loss(d_real_Y, d_fake_Y, real_Y, rec_Y)

            g_optimizer.zero_grad()
            g_loss_X.backward()
            g_loss_Y.backward()
            g_optimizer.step()
    print(f"Epoch [{epoch+1}/{num_epochs}], D_X Loss: {d_loss_X.item():.4f}, D_Y Loss: {d_loss_Y.item():.4f}, G Loss: {g_loss_X.item():.4f}")

CycleGANの学習完了!


🎨 ⑥ 変換画像の表示

import matplotlib.pyplot as plt

# 生成画像の可視化
def show_transformed_images(generator, input_images):
    with torch.no_grad():
        generated_images = generator(input_images)
        grid = torchvision.utils.make_grid(generated_images, nrow=4, normalize=True)
        plt.imshow(grid.permute(1, 2, 0).cpu().numpy())
        plt.title("CycleGAN Generated Images")
        plt.axis("off")
        plt.show()

# 画像の変換と可視化
show_transformed_images(G, real_X[:4])

馬 ↔ シマウマ、写真 ↔ 絵画の変換結果が確認できました!


📚 4. CycleGANの応用とユースケース


🎯 ① 写真 ↔ 絵画のスタイル変換

応用:
- 写真から印象派の絵画に変換
- 異なるアートスタイルの画像生成

ユースケース:
- デジタルアート、映像制作、フォトエフェクト


📚 ② 馬 ↔ シマウマ変換

応用:
- 動物模様の自動変換
- 生物模様のシミュレーション

ユースケース:
- 動物行動研究、動物模様生成、AR/VRアプリ


📚 ③ 季節変換(冬 ↔ 夏)

応用:
- 風景画像の季節変換
- 観光地の異なる季節のシミュレーション

ユースケース:
- 観光案内、ゲーム開発、VR体験


📚 ④ 医療画像の領域変換

応用:
- MRI・CT画像の異常検知
- 医療画像の異なるモダリティへの変換

ユースケース:
- 自動診断、医療画像解析、異常検出


🎁 まとめ:CycleGANでペアなしデータの画像変換をマスターしよう!

CycleGANは、ペアなしの画像データで画像変換を実現する強力なGANモデル。
サイクル一貫性損失を導入することで、ドメイン間の一貫性を維持しながら画像変換を行う。
馬 ↔ シマウマ、写真 ↔ 絵画、季節変換、昼 ↔ 夜の画像変換など、多様な応用が可能。
PyTorch で CycleGAN を実装し、画像変換の幅広い応用を探求しよう!

Best regards, (^^ゞ




以上の内容はhttps://cysec148.hatenablog.com/entry/2025/04/08/064623より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14