{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":21755,"databundleVersionId":1475600,"sourceType":"competition"},{"sourceId":13660077,"sourceType":"datasetVersion","datasetId":8684913}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Import Libraries & Setup Env","metadata":{}},{"cell_type":"code","source":"!pip install protobuf==3.20.*\n\n# ==============================\n# 1. IMPORT & CONFIG\n# ==============================\nimport os\nimport numpy as np\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nimport matplotlib.pyplot as plt\nfrom glob import glob\nimport random\nfrom datetime import datetime\nimport json\n\n# --- Config ---\nclass Config:\n    SEED = 42\n    IMG_SIZE = 256\n    BATCH_SIZE = 4\n    EPOCHS = 10\n    BUFFER_SIZE = 1000\n    LR = 2e-4\n    BETA_1 = 0.5\n    LAMBDA_CYCLE = 10.0\n    LAMBDA_ID = 0.5\n    SAMPLE_EVERY = 5  # Visualisasi setiap 5 epoch\n    EVAL_EVERY = 5   # Hitung FID setiap 5 epoch\n\ncfg = Config()\n\n# Seed\ntf.random.set_seed(cfg.SEED)\nnp.random.seed(cfg.SEED)\nrandom.seed(cfg.SEED)\n\nprint(\"TensorFlow:\", tf.__version__)\nprint(\"GPU:\", len(tf.config.list_physical_devices('GPU')) > 0)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-09T18:02:40.301779Z","iopub.execute_input":"2025-11-09T18:02:40.302086Z","iopub.status.idle":"2025-11-09T18:02:43.780414Z","shell.execute_reply.started":"2025-11-09T18:02:40.302064Z","shell.execute_reply":"2025-11-09T18:02:43.779392Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data Loading & Preprocessing","metadata":{}},{"cell_type":"code","source":"# ==============================\n# 2. DATA LOADING (Kaggle Dataset - No ZIP)\n# ==============================\nBASE = '/kaggle/input/gan-getting-started'  # Path di Kaggle / Colab\nMONET_JPG = f'{BASE}/monet_jpg'\nPHOTO_JPG = f'{BASE}/photo_jpg'\n\ndef load_image(path):\n    img = tf.io.read_file(path)\n    img = tf.image.decode_jpeg(img, channels=3)\n    img = tf.image.resize(img, [cfg.IMG_SIZE, cfg.IMG_SIZE])\n    img = (img / 127.5) - 1.0  # [-1, 1]\n    return img\n\ndef augment(img):\n    img = tf.image.random_flip_left_right(img)\n    img = tf.image.random_brightness(img, 0.1)\n    img = tf.image.random_contrast(img, 0.8, 1.2)\n    return img\n\n# Load paths\nmonet_paths = sorted(glob(f\"{MONET_JPG}/*.jpg\"))\nphoto_paths = sorted(glob(f\"{PHOTO_JPG}/*.jpg\"))\n\nprint(f\"Monet: {len(monet_paths)} images\")\nprint(f\"Photo: {len(photo_paths)} images\")\n\n# Datasets\nmonet_ds = tf.data.Dataset.from_tensor_slices(monet_paths)\nmonet_ds = monet_ds.map(load_image, num_parallel_calls=tf.data.AUTOTUNE)\nmonet_ds = monet_ds.map(augment, num_parallel_calls=tf.data.AUTOTUNE)\nmonet_ds = monet_ds.shuffle(cfg.BUFFER_SIZE).batch(cfg.BATCH_SIZE).prefetch(tf.data.AUTOTUNE)\n\nphoto_ds = tf.data.Dataset.from_tensor_slices(photo_paths)\nphoto_ds = photo_ds.map(load_image, num_parallel_calls=tf.data.AUTOTUNE)\nphoto_ds = photo_ds.map(augment, num_parallel_calls=tf.data.AUTOTUNE)\nphoto_ds = photo_ds.shuffle(cfg.BUFFER_SIZE).batch(cfg.BATCH_SIZE).prefetch(tf.data.AUTOTUNE)\n\n# Sample untuk visualisasi\nsample_photo = next(iter(photo_ds.take(1)))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-09T18:02:43.786731Z","iopub.execute_input":"2025-11-09T18:02:43.786978Z","iopub.status.idle":"2025-11-09T18:02:45.240830Z","shell.execute_reply.started":"2025-11-09T18:02:43.786954Z","shell.execute_reply":"2025-11-09T18:02:45.240194Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model Architectures (Generator & Discriminator)","metadata":{}},{"cell_type":"code","source":"# ==============================\n# 3. MODEL: CycleGAN\n# ==============================\ndef Generator():\n    inputs = layers.Input(shape=[256, 256, 3], name='input_image')\n\n    # Downsampling\n    def downsample(filters, size=4, apply_norm=True):\n        initializer = tf.random_normal_initializer(0., 0.02)\n        layer = layers.Conv2D(filters, size, strides=2, padding='same',\n                              kernel_initializer=initializer, use_bias=False)\n        x = layer(inputs if layer == layers.Conv2D else previous)\n        if apply_norm:\n            x = layers.BatchNormalization()(x)\n        x = layers.LeakyReLU()(x)\n        return x\n\n    # Manual downsampling\n    x = inputs\n\n    # C7s1-64\n    x = layers.Conv2D(64, 7, strides=1, padding='same',\n                      kernel_initializer=tf.random_normal_initializer(0., 0.02),\n                      use_bias=False)(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.ReLU()(x)\n\n    # d128\n    x = layers.Conv2D(128, 4, strides=2, padding='same',\n                      kernel_initializer=tf.random_normal_initializer(0., 0.02),\n                      use_bias=False)(x)\n    x = layers.BatchNormalization()(x)\n    x1 = layers.LeakyReLU()(x)\n\n    # d256\n    x = layers.Conv2D(256, 4, strides=2, padding='same',\n                      kernel_initializer=tf.random_normal_initializer(0., 0.02),\n                      use_bias=False)(x1)\n    x = layers.BatchNormalization()(x)\n    x2 = layers.LeakyReLU()(x)\n\n    # Residual blocks\n    for _ in range(6):\n        residual = x2\n        x = layers.Conv2D(256, 3, padding='same')(x2)\n        x = layers.BatchNormalization()(x)\n        x = layers.ReLU()(x)\n        x = layers.Conv2D(256, 3, padding='same')(x)\n        x = layers.BatchNormalization()(x)\n        x2 = layers.Add()([x, residual])\n\n    # u128\n    x = layers.Conv2DTranspose(128, 4, strides=2, padding='same',\n                               kernel_initializer=tf.random_normal_initializer(0., 0.02),\n                               use_bias=False)(x2)\n    x = layers.BatchNormalization()(x)\n    x = layers.ReLU()(x)\n    x = layers.Concatenate()([x, x1])\n\n    # u64\n    x = layers.Conv2DTranspose(64, 4, strides=2, padding='same',\n                               kernel_initializer=tf.random_normal_initializer(0., 0.02),\n                               use_bias=False)(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.ReLU()(x)\n\n    # Output\n    outputs = layers.Conv2D(3, 7, strides=1, padding='same',\n                            kernel_initializer=tf.random_normal_initializer(0., 0.02),\n                            activation='tanh')(x)\n\n    return keras.Model(inputs=inputs, outputs=outputs, name='Generator')\n\ndef Discriminator():\n    initializer = tf.random_normal_initializer(0., 0.02)\n\n    inp = layers.Input(shape=[256, 256, 3], name='input_image')\n\n    x = layers.Conv2D(64, 4, strides=2, padding='same',\n                      kernel_initializer=initializer, use_bias=False)(inp)\n    x = layers.LeakyReLU()(x)\n\n    x = layers.Conv2D(128, 4, strides=2, padding='same',\n                      kernel_initializer=initializer, use_bias=False)(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.LeakyReLU()(x)\n\n    x = layers.Conv2D(256, 4, strides=2, padding='same',\n                      kernel_initializer=initializer, use_bias=False)(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.LeakyReLU()(x)\n\n    x = layers.Conv2D(512, 4, strides=1, padding='same',\n                      kernel_initializer=initializer, use_bias=False)(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.LeakyReLU()(x)\n\n    outputs = layers.Conv2D(1, 4, strides=1, padding='same',\n                            kernel_initializer=initializer)(x)\n\n    return keras.Model(inputs=inp, outputs=outputs, name='Discriminator')\n\n# Initialize\nG = Generator()  # Photo → Monet\nF = Generator()  # Monet → Photo\nDx = Discriminator()\nDy = Discriminator()\n\n# Loss\nloss_fn = keras.losses.BinaryCrossentropy(from_logits=True)\n\ndef d_loss(real, fake):\n    return loss_fn(tf.ones_like(real), real) + loss_fn(tf.zeros_like(fake), fake)\n\ndef g_loss(fake):\n    return loss_fn(tf.ones_like(fake), fake)\n\ndef cycle_loss(real, cycled):\n    return cfg.LAMBDA_CYCLE * tf.reduce_mean(tf.abs(real - cycled))\n\ndef identity_loss(real, same):\n    return cfg.LAMBDA_ID * cfg.LAMBDA_CYCLE * tf.reduce_mean(tf.abs(real - same))\n\n# Optimizers\ng_opt = keras.optimizers.Adam(cfg.LR, cfg.BETA_1)\nf_opt = keras.optimizers.Adam(cfg.LR, cfg.BETA_1)\ndx_opt = keras.optimizers.Adam(cfg.LR, cfg.BETA_1)\ndy_opt = keras.optimizers.Adam(cfg.LR, cfg.BETA_1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-09T18:02:45.253404Z","iopub.execute_input":"2025-11-09T18:02:45.253684Z","iopub.status.idle":"2025-11-09T18:02:45.890311Z","shell.execute_reply.started":"2025-11-09T18:02:45.253666Z","shell.execute_reply":"2025-11-09T18:02:45.889741Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Evaluation","metadata":{}},{"cell_type":"code","source":"# ==============================\n# 4. EVALUATION: FID\n# ==============================\ninception = keras.applications.InceptionV3(include_top=False, pooling='avg', input_shape=(299,299,3))\ninception.trainable = False\n\ndef preprocess(img):\n    img = tf.image.resize(img, [299,299])\n    img = (img + 1) * 127.5\n    return img\n\ndef get_features(imgs):\n    imgs = tf.image.resize(imgs, [299, 299])\n    imgs = (imgs + 1) * 127.5  # [-1,1] → [0,255]\n    imgs = tf.clip_by_value(imgs, 0, 255)\n    return inception(imgs, training=False)\n\ndef calculate_fid(real_imgs_ds, fake_imgs_ds, batch_size=32):\n    act1_list = []\n    act2_list = []\n\n    # Real images\n    for batch in real_imgs_ds:\n        feats = get_features(batch)\n        act1_list.append(feats)\n    act1 = tf.concat(act1_list, axis=0)\n\n    # Fake images\n    for batch in fake_imgs_ds:\n        fake = G(batch, training=False)\n        feats = get_features(fake)\n        act2_list.append(feats)\n    act2 = tf.concat(act2_list, axis=0)\n\n    # Statistik\n    mu1, sigma1 = tf.reduce_mean(act1, axis=0), tf_cov(act1)\n    mu2, sigma2 = tf.reduce_mean(act2, axis=0), tf_cov(act2)\n    diff = mu1 - mu2\n\n    # sqrtm\n    covmean = tf.linalg.sqrtm(tf.cast(sigma1 @ sigma2, tf.complex64))\n    \n    # Perbaikan: Gunakan tf.abs(tf.imag(...)) > 1e-6 untuk cek kompleks\n    if tf.reduce_any(tf.abs(tf.imag(covmean)) > 1e-6):\n        covmean = tf.cast(tf.math.real(covmean), tf.float32)\n    else:\n        covmean = tf.cast(covmean, tf.float32)\n\n    # FID\n    fid = tf.reduce_sum(diff ** 2) + tf.linalg.trace(sigma1 + sigma2 - 2 * covmean)\n    return float(fid.numpy())  # <-- konversi ke Python float\n\ndef tf_cov(m):\n    m = m - tf.reduce_mean(m, axis=0)\n    fact = tf.cast(tf.shape(m)[0] - 1, tf.float32)\n    return tf.matmul(m, m, transpose_a=True) / fact","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-09T18:02:45.891704Z","iopub.execute_input":"2025-11-09T18:02:45.892006Z","iopub.status.idle":"2025-11-09T18:02:47.595755Z","shell.execute_reply.started":"2025-11-09T18:02:45.891989Z","shell.execute_reply":"2025-11-09T18:02:47.594951Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Training Loop","metadata":{}},{"cell_type":"code","source":"# ==============================\n# 5. TRAINING LOOP + VISUALIZATION\n# ==============================\n@tf.function\ndef train_step(real_photo, real_monet):\n    with tf.GradientTape(persistent=True) as tape:\n        fake_monet = G(real_photo, training=True)\n        cycled_photo = F(fake_monet, training=True)\n        fake_photo = F(real_monet, training=True)\n        cycled_monet = G(fake_photo, training=True)\n        same_photo = F(real_photo, training=True)\n        same_monet = G(real_monet, training=True)\n        \n        disc_real_photo = Dx(real_photo, training=True)\n        disc_fake_photo = Dx(fake_photo, training=True)\n        disc_real_monet = Dy(real_monet, training=True)\n        disc_fake_monet = Dy(fake_monet, training=True)\n        \n        g_total = g_loss(disc_fake_monet) + cycle_loss(real_photo, cycled_photo) + identity_loss(real_monet, same_monet)\n        f_total = g_loss(disc_fake_photo) + cycle_loss(real_monet, cycled_monet) + identity_loss(real_photo, same_photo)\n        dx_total = d_loss(disc_real_photo, disc_fake_photo)\n        dy_total = d_loss(disc_real_monet, disc_fake_monet)\n    \n    g_opt.apply_gradients(zip(tape.gradient(g_total, G.trainable_variables), G.trainable_variables))\n    f_opt.apply_gradients(zip(tape.gradient(f_total, F.trainable_variables), F.trainable_variables))\n    dx_opt.apply_gradients(zip(tape.gradient(dx_total, Dx.trainable_variables), Dx.trainable_variables))\n    dy_opt.apply_gradients(zip(tape.gradient(dy_total, Dy.trainable_variables), Dy.trainable_variables))\n    \n    return g_total, f_total, dx_total, dy_total\n\n# History\nhistory = {\"g_loss\": [], \"f_loss\": [], \"dx_loss\": [], \"dy_loss\": [], \"fid\": []}\nos.makedirs(\"results\", exist_ok=True)\n\n# Training\nfor epoch in range(1, cfg.EPOCHS + 1):\n    start = datetime.now()\n    losses = []\n    \n    for real_photo, real_monet in tf.data.Dataset.zip((photo_ds, monet_ds)):\n        g_l, f_l, dx_l, dy_l = train_step(real_photo, real_monet)\n        losses.append([g_l.numpy(), f_l.numpy(), dx_l.numpy(), dy_l.numpy()])\n    \n    # Average loss\n    avg = np.mean(losses, axis=0)\n    history[\"g_loss\"].append(avg[0])\n    history[\"f_loss\"].append(avg[1])\n    history[\"dx_loss\"].append(avg[2])\n    history[\"dy_loss\"].append(avg[3])\n    \n    print(f\"Epoch {epoch}/{cfg.EPOCHS} | G: {avg[0]:.3f} | F: {avg[1]:.3f} | Dx: {avg[2]:.3f} | Dy: {avg[3]:.3f} | Time: {datetime.now()-start}\")\n\n    # Visualisasi\n    if epoch % cfg.SAMPLE_EVERY == 0:\n        pred = G(sample_photo, training=False)\n        pred = (pred[0].numpy() + 1) / 2\n        orig = (sample_photo[0].numpy() + 1) / 2\n        \n        plt.figure(figsize=(10,5))\n        plt.subplot(1,2,1); plt.imshow(orig); plt.title(\"Original Photo\"); plt.axis('off')\n        plt.subplot(1,2,2); plt.imshow(pred); plt.title(f\"Monet Style (Epoch {epoch})\"); plt.axis('off')\n        plt.savefig(f\"results/epoch_{epoch}.png\")\n        plt.close()\n    \n    # FID\n    if epoch % cfg.EVAL_EVERY == 0:\n        print(\"  Calculating FID (batch-wise)...\")\n        \n        real_monet_batches = monet_ds.take(25)\n        photo_batches = photo_ds.take(25)\n    \n        try:\n            fid = calculate_fid(real_monet_batches, photo_batches)\n            history[\"fid\"].append(fid)\n            print(f\"  FID: {fid:.2f}\")\n        except Exception as e:\n            print(f\"  FID failed: {e}\")\n            history[\"fid\"].append(None)\n        \n# Save history\nhistory_clean = {\n    k: [float(x) if x is not None else None for x in v]\n    for k, v in history.items()\n}\n\nwith open(\"results/history.json\", \"w\") as f:\n    json.dump(history_clean, f, indent=2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-09T18:02:47.596622Z","iopub.execute_input":"2025-11-09T18:02:47.596861Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Final Result","metadata":{}},{"cell_type":"code","source":"# ==============================\n# 6. FINAL RESULTS & ANALYSIS\n# ==============================\nplt.figure(figsize=(15, 10))\n\n# Loss curves\nplt.subplot(2, 2, 1)\nplt.plot(history[\"g_loss\"], label=\"G (Photo→Monet)\")\nplt.plot(history[\"f_loss\"], label=\"F (Monet→Photo)\")\nplt.title(\"Generator Loss\")\nplt.legend()\n\nplt.subplot(2, 2, 2)\nplt.plot(history[\"dx_loss\"], label=\"Dx\")\nplt.plot(history[\"dy_loss\"], label=\"Dy\")\nplt.title(\"Discriminator Loss\")\nplt.legend()\n\n# FID\nplt.subplot(2, 2, 3)\nplt.plot(range(cfg.EVAL_EVERY, cfg.EPOCHS+1, cfg.EVAL_EVERY), history[\"fid\"], 'o-')\nplt.title(\"FID Score (Lower = Better)\")\nplt.xlabel(\"Epoch\")\n\n# Sample final result\nplt.subplot(2, 2, 4)\nfinal_pred = G(sample_photo, training=False)[0]\nfinal_pred = (final_pred.numpy() + 1) / 2\norig = (sample_photo[0].numpy() + 1) / 2\nplt.imshow(np.hstack([orig, final_pred]))\nplt.title(\"Left: Original | Right: Final Monet Style\")\nplt.axis('off')\n\nplt.tight_layout()\nplt.savefig(\"results/summary.png\")\nplt.show()\n\nprint(f\"\\nFinal FID: {history['fid'][-1]:.2f}\")\nprint(\"Results saved in 'results/' folder\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}