{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":21755,"databundleVersionId":1475600,"sourceType":"competition"}],"dockerImageVersionId":30732,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Generating Monet-Style Images with CycleGAN\n\nAs a participant in the Kaggle competition “I’m Something of a Painter Myself,” your task is to generate 7,000 to 10,000 images in the style of Monet. This challenge is intriguing because it involves creating art using Generative Adversarial Networks (GANs). One particularly effective type of GAN for this task is CycleGAN, which excels in style transfer between two image domains without requiring paired datasets.\n\n**Note**: This post serves as an introductory blog entry rather than a conventional code notebook. Our goal is to grasp a high-level understanding of the key concepts and to compile some essential code snippets that we will develop further in the upcoming series of notebooks.\n\n# Why CycleGAN?\n\nCycleGAN is a type of GAN that facilitates **unpaired image-to-image translation**. This means that it can learn to translate images from one domain to another without needing one-to-one pairs of images for training. This is ideal for our challenge, where we likely have a set of Monet paintings and a separate set of other images but no direct correspondence between them.\n\nHere’s why CycleGAN is perfect for this task:\n\n1. **Unpaired Data Handling**: Unlike traditional GANs that require paired datasets, CycleGAN can learn from unpaired data, making it suitable for tasks where such data is not available.\n  \n2. **Cycle Consistency Loss**: CycleGAN introduces the concept of cycle consistency loss, which helps maintain the structure and content of the original image while applying the target style.\n\n3. **Bidirectional Training**: It uses two generators and two discriminators to map images between two domains, ensuring that the generated images are realistic and consistent with the target style.\n\n---\n\n# Implementing CycleGAN with PyTorch\n\nLet’s dive into how to implement a CycleGAN using PyTorch to generate Monet-style images.\n\n#### 1. Setting Up the Environment\n\nFirst, ensure you have the necessary libraries installed. Use the following command to install PyTorch and other dependencies:\n\n```bash\npip install torch torchvision matplotlib\n```\n\n#### 2. Data Preparation\n\nLoad and preprocess your dataset. This involves normalizing the images and resizing them to a consistent size.\n\n```python\nimport os\nfrom PIL import Image\nimport torchvision.transforms as transforms\n\n# Transformation for training images\ntransform = transforms.Compose([\n    transforms.Resize((256, 256)),\n    transforms.ToTensor(),\n    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))\n])\n\ndef load_dataset(folder_path):\n    dataset = []\n    for file in os.listdir(folder_path):\n        if file.endswith('.jpg'):\n            img_path = os.path.join(folder_path, file)\n            image = Image.open(img_path)\n            dataset.append(transform(image))\n    return dataset\n\nmonet_images = load_dataset('path_to_monet_images')\nother_images = load_dataset('path_to_other_images')\n```\n\n#### 3. Building the CycleGAN\n\n##### **Generator Model**\n\nWe will build a generator that uses a U-Net-like architecture, effective for translating images between domains.\n\n```python\nimport torch\nimport torch.nn as nn\n\nclass Generator(nn.Module):\n    def __init__(self):\n        super(Generator, self).__init__()\n        # Encoder\n        self.encoder = nn.Sequential(\n            nn.Conv2d(3, 64, kernel_size=4, stride=2, padding=1),\n            nn.ReLU(),\n            nn.Conv2d(64, 128, kernel_size=4, stride=2, padding=1),\n            nn.ReLU(),\n            nn.Conv2d(128, 256, kernel_size=4, stride=2, padding=1),\n            nn.ReLU()\n        )\n        # Decoder\n        self.decoder = nn.Sequential(\n            nn.ConvTranspose2d(256, 128, kernel_size=4, stride=2, padding=1),\n            nn.ReLU(),\n            nn.ConvTranspose2d(128, 64, kernel_size=4, stride=2, padding=1),\n            nn.ReLU(),\n            nn.ConvTranspose2d(64, 3, kernel_size=4, stride=2, padding=1),\n            nn.Tanh()\n        )\n\n    def forward(self, x):\n        x = self.encoder(x)\n        x = self.decoder(x)\n        return x\n```\n\n##### **Discriminator Model**\n\nNext, we’ll build a discriminator that distinguishes real images from generated ones.\n\n```python\nclass Discriminator(nn.Module):\n    def __init__(self):\n        super(Discriminator, self).__init__()\n        self.model = nn.Sequential(\n            nn.Conv2d(3, 64, kernel_size=4, stride=2, padding=1),\n            nn.LeakyReLU(0.2),\n            nn.Conv2d(64, 128, kernel_size=4, stride=2, padding=1),\n            nn.BatchNorm2d(128),\n            nn.LeakyReLU(0.2),\n            nn.Conv2d(128, 256, kernel_size=4, stride=2, padding=1),\n            nn.BatchNorm2d(256),\n            nn.LeakyReLU(0.2),\n            nn.Conv2d(256, 1, kernel_size=4, stride=1, padding=1),\n            nn.Sigmoid()\n        )\n\n    def forward(self, x):\n        return self.model(x)\n```\n\n#### 4. Training the CycleGAN\n\nWe’ll train the CycleGAN by alternating between the generators and discriminators to minimize adversarial and cycle consistency losses.\n\n```python\nimport torch.optim as optim\nimport itertools\n\n# Initialize models\nG_AB = Generator()\nG_BA = Generator()\nD_A = Discriminator()\nD_B = Discriminator()\n\n# Define optimizers\noptimizer_G = optim.Adam(itertools.chain(G_AB.parameters(), G_BA.parameters()), lr=0.0002)\noptimizer_D_A = optim.Adam(D_A.parameters(), lr=0.0002)\noptimizer_D_B = optim.Adam(D_B.parameters(), lr=0.0002)\n\n# Loss functions\ncriterion_GAN = nn.BCELoss()\ncriterion_cycle = nn.L1Loss()\n\n# Training loop\nnum_epochs = 200\nfor epoch in range(num_epochs):\n    for i, (real_A, real_B) in enumerate(zip(monet_images, other_images)):\n        # Convert to torch tensors\n        real_A = real_A.unsqueeze(0)\n        real_B = real_B.unsqueeze(0)\n\n        # Generate fake images\n        fake_B = G_AB(real_A)\n        fake_A = G_BA(real_B)\n\n        # Discriminator A\n        D_A_real = D_A(real_A)\n        D_A_fake = D_A(fake_A.detach())\n        loss_D_A = (criterion_GAN(D_A_real, torch.ones_like(D_A_real)) + criterion_GAN(D_A_fake, torch.zeros_like(D_A_fake))) * 0.5\n\n        optimizer_D_A.zero_grad()\n        loss_D_A.backward()\n        optimizer_D_A.step()\n\n        # Discriminator B\n        D_B_real = D_B(real_B)\n        D_B_fake = D_B(fake_B.detach())\n        loss_D_B = (criterion_GAN(D_B_real, torch.ones_like(D_B_real)) + criterion_GAN(D_B_fake, torch.zeros_like(D_B_fake))) * 0.5\n\n        optimizer_D_B.zero_grad()\n        loss_D_B.backward()\n        optimizer_D_B.step()\n\n        # Generator AB and BA\n        D_B_fake = D_B(fake_B)\n        loss_G_AB = criterion_GAN(D_B_fake, torch.ones_like(D_B_fake))\n        D_A_fake = D_A(fake_A)\n        loss_G_BA = criterion_GAN(D_A_fake, torch.ones_like(D_A_fake))\n\n        # Cycle consistency\n        cycle_A = G_BA(fake_B)\n        cycle_B = G_AB(fake_A)\n        loss_cycle_A = criterion_cycle(cycle_A, real_A)\n        loss_cycle_B = criterion_cycle(cycle_B, real_B)\n\n        # Total loss\n        loss_G = loss_G_AB + loss_G_BA + 10 * (loss_cycle_A + loss_cycle_B)\n\n        optimizer_G.zero_grad()\n        loss_G.backward()\n        optimizer_G.step()\n\n    print(f\"Epoch [{epoch+1}/{num_epochs}] | Loss D_A: {loss_D_A.item():.4f}, Loss D_B: {loss_D_B.item():.4f}, Loss G: {loss_G.item():.4f}\")\n```\n\n#### 5. Generating and Saving Images\n\nAfter training, you can use the generator to produce Monet-style images.\n\n```python\nimport numpy as np\nimport matplotlib.pyplot as plt\n\n# Generate Monet-style images\ndef generate_image(generator, image_path):\n    image = Image.open(image_path)\n    image = transform(image).unsqueeze(0)\n    fake_image = generator(image).detach().squeeze(0)\n    fake_image = (fake_image + 1) / 2  # Rescale to [0, 1]\n    fake_image = fake_image.permute(1, 2, 0).numpy()\n    return fake_image\n\ntest_image_path = 'path_to_test_image.jpg'\nfake_monet_image = generate_image(G_AB, test_image_path)\nplt.imshow(fake_monet_image)\nplt.axis('off')\nplt.show()\n\n# Save generated images\nplt.imsave('generated_monet_image.jpg', fake_monet_image)\n```\n\n### Conclusion\n\nUsing CycleGAN for the Kaggle competition provides a robust approach to generating Monet-style images. Its ability to work with unpaired datasets and maintain cycle consistency makes it ideal for tasks involving artistic style transfer. By leveraging the power of PyTorch, you can build and train a CycleGAN model that effectively transforms ordinary images into beautiful Monet-style paintings.\n\nGood luck with your artistic adventures, and happy coding! 🎨\n\n---\n","metadata":{}}]}