{"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":[],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Introduction","metadata":{}},{"cell_type":"markdown","source":"Notebook is part of my coursework at Austin Community College for my Machine Learning course focused on Deep Learning.","metadata":{}},{"cell_type":"markdown","source":"## Project Details","metadata":{}},{"cell_type":"markdown","source":"* Implement baseline CNN, visualize results\n* Implement GAN\n* Use GAN for Augmentation\n* Produce submission csv","metadata":{}},{"cell_type":"markdown","source":"## Dataset","metadata":{}},{"cell_type":"markdown","source":"Using the CIFAR-10 dataset, as it seemed like the most straightforward choice out of the options supplied","metadata":{}},{"cell_type":"markdown","source":"## README","metadata":{}},{"cell_type":"markdown","source":"AI Usage disclosure:\n* Tool used: ChatGPT\n* Generated template of GAN alternating training components\n* Evaluation of code for g_loss trapped at 0.000, determined improper syntax for ADAM learning rate\n* Evaluation of custom training loop for suggestions to include tracking loss for GAN\n* General checking of ValueErrors","metadata":{}},{"cell_type":"markdown","source":"# Environment Setup","metadata":{}},{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-24T02:52:07.336592Z","iopub.execute_input":"2025-11-24T02:52:07.337331Z","iopub.status.idle":"2025-11-24T02:52:07.607018Z","shell.execute_reply.started":"2025-11-24T02:52:07.337303Z","shell.execute_reply":"2025-11-24T02:52:07.606424Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras import layers, models\nfrom tensorflow.keras.models import Sequential\nfrom functools import partial\nfrom tensorflow.keras import Input\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Dropout\nfrom tensorflow.keras.optimizers import Adam\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay\nfrom tensorflow.keras.preprocessing.image import array_to_img, load_img, img_to_array\nimport matplotlib.pyplot as plt\nplt.rc('font', size=14)\nplt.rc('axes', labelsize=14, titlesize=14)\nplt.rc('legend', fontsize=14)\nplt.rc('xtick', labelsize=10)\nplt.rc('ytick', labelsize=10)\n\n\nimport random\n\ntf.random.set_seed(72)\nnp.random.seed(72)\nrandom.seed(72)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T02:52:07.608079Z","iopub.execute_input":"2025-11-24T02:52:07.608459Z","iopub.status.idle":"2025-11-24T02:52:25.832931Z","shell.execute_reply.started":"2025-11-24T02:52:07.608440Z","shell.execute_reply":"2025-11-24T02:52:25.832108Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"os.makedirs(\"outputs/samples\", exist_ok=True)\nos.makedirs(\"synthetic\", exist_ok=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T02:52:25.833795Z","iopub.execute_input":"2025-11-24T02:52:25.834473Z","iopub.status.idle":"2025-11-24T02:52:25.838785Z","shell.execute_reply.started":"2025-11-24T02:52:25.834446Z","shell.execute_reply":"2025-11-24T02:52:25.837819Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Output folders for saved samples requirement","metadata":{}},{"cell_type":"markdown","source":"# Data Import","metadata":{}},{"cell_type":"code","source":"(x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T02:52:25.840754Z","iopub.execute_input":"2025-11-24T02:52:25.841018Z","iopub.status.idle":"2025-11-24T02:52:36.807194Z","shell.execute_reply.started":"2025-11-24T02:52:25.841000Z","shell.execute_reply":"2025-11-24T02:52:36.806561Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data Exploration","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(6,6))\nfor i in range(25):\n    plt.subplot(5,5,i+1)\n    plt.imshow(x_train[i])\n    plt.axis(\"off\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T02:52:36.807899Z","iopub.execute_input":"2025-11-24T02:52:36.808107Z","iopub.status.idle":"2025-11-24T02:52:37.403739Z","shell.execute_reply.started":"2025-11-24T02:52:36.808093Z","shell.execute_reply":"2025-11-24T02:52:37.402864Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img = x_train[0]\n\nprint(\"Shape:\", img.shape)\nprint(\"Data type:\", img.dtype)\nprint(\"Min pixel value:\", img.min())\nprint(\"Max pixel value:\", img.max())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T02:52:37.404549Z","iopub.execute_input":"2025-11-24T02:52:37.404805Z","iopub.status.idle":"2025-11-24T02:52:37.409880Z","shell.execute_reply.started":"2025-11-24T02:52:37.404777Z","shell.execute_reply":"2025-11-24T02:52:37.409056Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.imshow(img)\nplt.title(f\"Label: {y_train[0]}\")\nplt.axis(\"off\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T02:52:37.410787Z","iopub.execute_input":"2025-11-24T02:52:37.411126Z","iopub.status.idle":"2025-11-24T02:52:37.495008Z","shell.execute_reply.started":"2025-11-24T02:52:37.411102Z","shell.execute_reply":"2025-11-24T02:52:37.494441Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Images are 32 by 32 pixels and RGB, which means my input layer shape will be 32, 32, 3","metadata":{}},{"cell_type":"code","source":"x_train_norm = (x_train.astype(\"float32\") - 127.5) / 127.5\nx_test_norm = x_test.astype(\"float32\") / 255.0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T02:52:37.495678Z","iopub.execute_input":"2025-11-24T02:52:37.495977Z","iopub.status.idle":"2025-11-24T02:52:37.910407Z","shell.execute_reply.started":"2025-11-24T02:52:37.495956Z","shell.execute_reply":"2025-11-24T02:52:37.909283Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_images, val_images, train_labels, val_labels = train_test_split(\n    x_train_norm, y_train, test_size=0.2, random_state=72\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T02:52:37.911637Z","iopub.execute_input":"2025-11-24T02:52:37.912289Z","iopub.status.idle":"2025-11-24T02:52:38.109274Z","shell.execute_reply.started":"2025-11-24T02:52:37.912251Z","shell.execute_reply":"2025-11-24T02:52:38.108366Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Splitting test and train set before moving forward, nothing remarkably different than usual approach","metadata":{}},{"cell_type":"markdown","source":"# Model Building","metadata":{}},{"cell_type":"markdown","source":"In this section going to\n\n1. Build and train a baseline CNN\n2. Build and train a GAN\n3. Use the GAN to create synthetic images\n4. Combine synthetic and original images\n5. Clone and train CNN on combined data","metadata":{}},{"cell_type":"markdown","source":"## CNN Model Building","metadata":{}},{"cell_type":"code","source":"DefaultConv2D = partial(tf.keras.layers.Conv2D, kernel_size=3, padding=\"same\",\n                        activation=\"relu\", kernel_initializer=\"he_normal\")\n\ncnn_model = tf.keras.Sequential([\n    Input(shape=(32, 32, 3)),\n    DefaultConv2D(filters=64, kernel_size=7,),\n    tf.keras.layers.MaxPool2D(),\n    DefaultConv2D(filters=256),\n    DefaultConv2D(filters=256),\n    tf.keras.layers.MaxPool2D(),\n    DefaultConv2D(filters=512),\n    DefaultConv2D(filters=512),\n    tf.keras.layers.MaxPool2D(),\n    tf.keras.layers.Flatten(),\n    tf.keras.layers.Dense(units=256, activation=\"relu\",\n                          kernel_initializer=\"he_normal\"),\n    tf.keras.layers.Dropout(0.5),\n    tf.keras.layers.Dense(units=128, activation=\"relu\",\n                          kernel_initializer=\"he_normal\"),\n    tf.keras.layers.Dropout(0.5),\n    tf.keras.layers.Dense(units=64, activation=\"relu\",\n                          kernel_initializer=\"he_normal\"),\n    tf.keras.layers.Dropout(0.5),\n    tf.keras.layers.Dense(units=10, activation=\"softmax\")\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T03:30:01.004734Z","iopub.execute_input":"2025-11-24T03:30:01.005231Z","iopub.status.idle":"2025-11-24T03:30:01.081094Z","shell.execute_reply.started":"2025-11-24T03:30:01.005206Z","shell.execute_reply":"2025-11-24T03:30:01.080285Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cnn_model.compile(\n    optimizer=\"adam\",\n    loss=\"sparse_categorical_crossentropy\",\n    metrics=[\"accuracy\"]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T03:30:10.502606Z","iopub.execute_input":"2025-11-24T03:30:10.502951Z","iopub.status.idle":"2025-11-24T03:30:10.511134Z","shell.execute_reply.started":"2025-11-24T03:30:10.502925Z","shell.execute_reply":"2025-11-24T03:30:10.510410Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cnn_model.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T03:30:12.974529Z","iopub.execute_input":"2025-11-24T03:30:12.975113Z","iopub.status.idle":"2025-11-24T03:30:12.995003Z","shell.execute_reply.started":"2025-11-24T03:30:12.975087Z","shell.execute_reply":"2025-11-24T03:30:12.994292Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## CNN Model Training","metadata":{}},{"cell_type":"code","source":"history_cnn= cnn_model.fit(\n    train_images, train_labels,\n    validation_data=(val_images, val_labels),\n    epochs=30,\n    batch_size=64\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T03:30:17.016070Z","iopub.execute_input":"2025-11-24T03:30:17.016651Z","iopub.status.idle":"2025-11-24T03:36:49.775048Z","shell.execute_reply.started":"2025-11-24T03:30:17.016625Z","shell.execute_reply":"2025-11-24T03:36:49.774406Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Project details asked for few epochs for the baseline, but I want to give an even playing field so i'm increasing the epochs for the baseline to 30.","metadata":{}},{"cell_type":"markdown","source":"## CNN Evaluation","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"preds = cnn_model.predict(val_images)\npred_labels = np.argmax(preds, axis=1)\ncm = confusion_matrix(val_labels, pred_labels)\ndisp = ConfusionMatrixDisplay(confusion_matrix=cm)\n\nplt.figure(figsize=(6, 6))\ndisp.plot(cmap=\"Blues\", values_format=\"d\")\nplt.title(\"Confusion Matrix\")\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Rather large outlier on class 5 but predicted 3. Not ideal, but unsure at this time how to identify what is actually going on.","metadata":{}},{"cell_type":"markdown","source":"## GAN Model Building","metadata":{}},{"cell_type":"markdown","source":"### Making The Generator","metadata":{}},{"cell_type":"code","source":"def make_gen(latent_dim=100):\n    model = models.Sequential()\n    model.add(layers.Dense(8*8*256, use_bias=False, input_shape=(latent_dim,)))\n    model.add(layers.Reshape((8,8,256)))\n    model.add(layers.Conv2DTranspose(128, 4, strides=2, padding=\"same\", use_bias=False))\n    model.add(layers.ReLU())\n    model.add(layers.Conv2DTranspose(64, 4, strides=2, padding=\"same\", use_bias=False))\n    model.add(layers.ReLU())\n    model.add(layers.Conv2DTranspose(3, 3, activation=\"tanh\", padding=\"same\"))\n    return model\n\ngenerator = make_gen()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T02:53:48.360550Z","iopub.execute_input":"2025-11-24T02:53:48.361068Z","iopub.status.idle":"2025-11-24T02:53:48.414426Z","shell.execute_reply.started":"2025-11-24T02:53:48.361042Z","shell.execute_reply":"2025-11-24T02:53:48.413678Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Making The Discriminator","metadata":{}},{"cell_type":"code","source":"def make_disc():\n    model = models.Sequential()\n    model.add(layers.Conv2D(64, 4, strides=2, padding=\"same\", input_shape=(32,32,3)))\n    model.add(layers.LeakyReLU(0.2))\n    model.add(layers.Conv2D(128, 4, strides=2, padding=\"same\"))\n    model.add(layers.LeakyReLU(0.2))\n    model.add(layers.Flatten())\n    model.add(layers.Dense(1, activation=\"sigmoid\"))\n    return model\n\ndiscriminator = make_disc()\ndiscriminator.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=2e-4, beta_1=0.5),\n                      loss=\"binary_crossentropy\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T02:53:48.415196Z","iopub.execute_input":"2025-11-24T02:53:48.415461Z","iopub.status.idle":"2025-11-24T02:53:48.461951Z","shell.execute_reply.started":"2025-11-24T02:53:48.415442Z","shell.execute_reply":"2025-11-24T02:53:48.461234Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Putting It All Together","metadata":{}},{"cell_type":"code","source":"latent_dim = 100\ngan_input = layers.Input(shape=(latent_dim,))\ndiscriminator.trainable = False\n\nfake_img = generator(gan_input)\ngan_output = discriminator(fake_img)\ngan = models.Model(gan_input, gan_output)\ngan.compile(optimizer=tf.keras.optimizers.Adam(2e-4, 0.5),\n            loss=\"binary_crossentropy\")\n\ngan.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T02:53:48.462742Z","iopub.execute_input":"2025-11-24T02:53:48.463087Z","iopub.status.idle":"2025-11-24T02:53:48.483021Z","shell.execute_reply.started":"2025-11-24T02:53:48.463061Z","shell.execute_reply":"2025-11-24T02:53:48.482321Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## GAN Model Training","metadata":{}},{"cell_type":"code","source":"epochs = 30\nbatch_size = 64\nsteps_per_epoch = 600 \n\nd_losses = []\ng_losses = []\n\nfor epoch in range(epochs):\n    print(f\"\\n=== Epoch {epoch+1}/{epochs} ===\")\n\n    for step in range(steps_per_epoch):\n\n        # 1. Train Discriminator\n        discriminator.trainable = True\n        generator.trainable = False\n\n        idx = np.random.randint(0, x_train_norm.shape[0], batch_size)\n        real_imgs = x_train_norm[idx]\n\n        noise = tf.convert_to_tensor(\n            np.random.normal(0, 1, (batch_size, latent_dim)),\n            dtype=tf.float32\n        )\n        fake_imgs = generator(noise, training=False)\n\n        real_labels = np.random.uniform(0.7, 1.2, (batch_size, 1))\n        fake_labels = np.random.uniform(0.0, 0.3, (batch_size, 1))\n\n        d_loss_real = discriminator.train_on_batch(real_imgs, real_labels)\n        d_loss_fake = discriminator.train_on_batch(fake_imgs, fake_labels)\n        d_loss = (d_loss_real + d_loss_fake) / 2\n\n        # 2. Train Generator\n        discriminator.trainable = False\n        generator.trainable = True\n\n        noise = tf.convert_to_tensor(\n            np.random.normal(0, 1, (batch_size, latent_dim)),\n            dtype=tf.float32\n        )\n        valid_y = np.ones((batch_size, 1))\n\n        g_loss = gan.train_on_batch(noise, valid_y)\n\n        # 3. Record history\n        d_losses.append(d_loss)\n        g_losses.append(g_loss)\n\n        if (step+1) % 10 == 0:\n            print(\n                f\"Step {step+1}/{steps_per_epoch} - d_loss: {d_loss:.4f} - g_loss: {g_loss:.4f}\",\n                end=\"\\r\"\n            )\n\n    # sample and plot images each epoch\n    noise = tf.convert_to_tensor(np.random.normal(0, 1, (25, latent_dim)), dtype=tf.float32)\n    generated_samples = generator(noise, training=False)\n\n    fig = plt.figure(figsize=(5,5))\n    for i in range(25):\n        plt.subplot(5,5,i+1)\n        plt.imshow((generated_samples[i].numpy() + 1)/2)\n        plt.axis(\"off\")\n    plt.savefig(f\"outputs/samples/epoch_{epoch+1}.png\")\n    if (epoch+1) % 5 == 0:\n        plt.show()\n    plt.close()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T02:53:48.483788Z","iopub.execute_input":"2025-11-24T02:53:48.484091Z","iopub.status.idle":"2025-11-24T02:59:55.819153Z","shell.execute_reply.started":"2025-11-24T02:53:48.484073Z","shell.execute_reply":"2025-11-24T02:59:55.818214Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"I don't fully understand what happened at epoch 30, but the images generated at that point are distorted in a way I cannot describe. Clearly I did something wrong here, but i'm not sure I understand GANs enough to fix it.","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(10,5))\nplt.plot(d_losses, label=\"Discriminator Loss\")\nplt.plot(g_losses, label=\"Generator Loss\")\nplt.title(\"Baseline GAN Training Loss\")\nplt.xlabel(\"Training step\")\nplt.ylabel(\"Loss\")\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T02:59:55.820263Z","iopub.execute_input":"2025-11-24T02:59:55.820617Z","iopub.status.idle":"2025-11-24T02:59:56.047969Z","shell.execute_reply.started":"2025-11-24T02:59:55.820585Z","shell.execute_reply":"2025-11-24T02:59:56.047118Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Misunderstood assignment requirements, this visualization is not required, but it correlates to how the loss relation was described in textbook so it stays.","metadata":{}},{"cell_type":"markdown","source":"## GAN for Augmentation","metadata":{}},{"cell_type":"markdown","source":"Now to create synthetic images to mix with the original data","metadata":{}},{"cell_type":"markdown","source":"### Generating the Images and Saving to Folder","metadata":{}},{"cell_type":"code","source":"num_classes = 10          \nimages_per_class = 500    \n\n\n\nos.makedirs(\"synthetic\", exist_ok=True)\nfor c in range(num_classes):\n    os.makedirs(f\"synthetic/class_{c}\", exist_ok=True)\n\n# Generate synthetic images per class\nfor c in range(num_classes):\n    for i in range(images_per_class):\n        noise = np.random.normal(0, 1, (1, latent_dim))\n        generated_img = generator.predict(noise, verbose=0)\n       \n        generated_img = (generated_img[0] + 1) / 2.0\n       \n        img = array_to_img(generated_img)\n        img.save(f\"synthetic/class_{c}/img_{i}.png\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T02:59:56.048801Z","iopub.execute_input":"2025-11-24T02:59:56.049068Z","iopub.status.idle":"2025-11-24T03:05:23.411027Z","shell.execute_reply.started":"2025-11-24T02:59:56.049042Z","shell.execute_reply":"2025-11-24T03:05:23.410418Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Reading from Folder","metadata":{}},{"cell_type":"code","source":"synthetic_images = []\nsynthetic_labels = []\nimg_size = train_images.shape[1:3]\n\nfor c in range(num_classes):\n    folder = f\"synthetic/class_{c}\"\n    for filename in os.listdir(folder):\n        path = os.path.join(folder, filename)\n        img = load_img(path, target_size=img_size)     \n        img_array = img_to_array(img) / 255.0           \n        synthetic_images.append(img_array)\n        synthetic_labels.append(c)\n\nsynthetic_images = np.array(synthetic_images)\nsynthetic_labels = np.array(synthetic_labels)\nsynthetic_labels = synthetic_labels.reshape(-1, 1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T03:05:23.411896Z","iopub.execute_input":"2025-11-24T03:05:23.412609Z","iopub.status.idle":"2025-11-24T03:05:24.323489Z","shell.execute_reply.started":"2025-11-24T03:05:23.412573Z","shell.execute_reply":"2025-11-24T03:05:24.322893Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"block reads the generated images into a list, transforms it into a numpy array, and then makes the shape match the regular data","metadata":{}},{"cell_type":"markdown","source":"### Combining Original Data with Synthetic Data","metadata":{}},{"cell_type":"code","source":"augmented_images = np.concatenate([train_images, synthetic_images], axis=0)\naugmented_labels = np.concatenate([train_labels, synthetic_labels], axis=0)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T03:05:24.324337Z","iopub.execute_input":"2025-11-24T03:05:24.324583Z","iopub.status.idle":"2025-11-24T03:05:24.696762Z","shell.execute_reply.started":"2025-11-24T03:05:24.324565Z","shell.execute_reply":"2025-11-24T03:05:24.695924Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"## Building CNN to use Augmented Data","metadata":{}},{"cell_type":"code","source":"aug_model = tf.keras.models.clone_model(cnn_model)\naug_model.compile(optimizer=\"adam\",\n                  loss=\"sparse_categorical_crossentropy\",\n                  metrics=[\"accuracy\"])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T03:39:27.623433Z","iopub.execute_input":"2025-11-24T03:39:27.623980Z","iopub.status.idle":"2025-11-24T03:39:27.685993Z","shell.execute_reply.started":"2025-11-24T03:39:27.623957Z","shell.execute_reply":"2025-11-24T03:39:27.685410Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Cloning the model this way lets me keep the structure without the weights, letting me effectively start training again from scratch.","metadata":{}},{"cell_type":"code","source":"history_aug = aug_model.fit(\n    augmented_images, augmented_labels,\n    validation_data=(val_images, val_labels),\n    epochs=30,\n    batch_size=64\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T03:39:30.939370Z","iopub.execute_input":"2025-11-24T03:39:30.939941Z","iopub.status.idle":"2025-11-24T03:46:43.049125Z","shell.execute_reply.started":"2025-11-24T03:39:30.939922Z","shell.execute_reply":"2025-11-24T03:46:43.048290Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"aug_preds = aug_model.predict(val_images)\naug_pred_labels = np.argmax(aug_preds, axis=1)\ncm = confusion_matrix(val_labels, aug_pred_labels)\ndisp = ConfusionMatrixDisplay(confusion_matrix=cm)\n\nplt.figure(figsize=(6, 6))\ndisp.plot(cmap=\"Blues\", values_format=\"d\")\nplt.title(\"Confusion Matrix\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T03:47:02.980869Z","iopub.execute_input":"2025-11-24T03:47:02.981143Z","iopub.status.idle":"2025-11-24T03:47:06.131433Z","shell.execute_reply.started":"2025-11-24T03:47:02.981124Z","shell.execute_reply":"2025-11-24T03:47:06.130802Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Confusion matrix is remarkably similar to baseline, this is promising.","metadata":{}},{"cell_type":"markdown","source":"# Evaluation","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(10,5))\nplt.plot(history_cnn.history[\"val_accuracy\"], label=\"Baseline Val Accuracy\")\nplt.plot(history_aug.history[\"val_accuracy\"], label=\"Augmented Val Accuracy\")\nplt.title(\"Validation Accuracy Comparison\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Accuracy\")\nplt.legend()\nplt.grid(True)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T03:47:10.389962Z","iopub.execute_input":"2025-11-24T03:47:10.390227Z","iopub.status.idle":"2025-11-24T03:47:10.571823Z","shell.execute_reply.started":"2025-11-24T03:47:10.390209Z","shell.execute_reply":"2025-11-24T03:47:10.571184Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Accuracy appears to be roughly consistent after around 15 epochs, after which both appear to be roughly equal","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(10,5))\nplt.plot(history_cnn.history[\"val_loss\"], label=\"Baseline Val Loss\")\nplt.plot(history_aug.history[\"val_loss\"], label=\"Augmented Val Loss\")\nplt.title(\"Validation Loss Comparison\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.legend()\nplt.grid(True)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T03:47:14.910010Z","iopub.execute_input":"2025-11-24T03:47:14.910693Z","iopub.status.idle":"2025-11-24T03:47:15.081134Z","shell.execute_reply.started":"2025-11-24T03:47:14.910669Z","shell.execute_reply":"2025-11-24T03:47:15.080309Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Validation loss improves up until ~ epoch 15, after which the growth suggests overfitting. Future versions should include additional dropouts to counteract this effect","metadata":{}},{"cell_type":"code","source":"baseline_val_acc  = history_cnn.history[\"val_accuracy\"][-1]\nbaseline_val_loss = history_cnn.history[\"val_loss\"][-1]\n\n\naug_val_acc  = history_aug.history[\"val_accuracy\"][-1]\naug_val_loss = history_aug.history[\"val_loss\"][-1]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T04:11:15.182467Z","iopub.execute_input":"2025-11-24T04:11:15.183095Z","iopub.status.idle":"2025-11-24T04:11:15.186709Z","shell.execute_reply.started":"2025-11-24T04:11:15.183075Z","shell.execute_reply":"2025-11-24T04:11:15.186004Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Code just pulls the final value out of the history for each model for the category for tabular display in next cell","metadata":{}},{"cell_type":"code","source":"comparison_df = pd.DataFrame({\n    \"Model\": [\"Baseline CNN\", \"GAN-Augmented CNN\"],\n    \"Validation Accuracy\": [baseline_val_acc, aug_val_acc],\n    \"Validation Loss\": [baseline_val_loss, aug_val_loss]\n})\n\nprint(comparison_df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T04:11:42.932954Z","iopub.execute_input":"2025-11-24T04:11:42.933219Z","iopub.status.idle":"2025-11-24T04:11:42.952216Z","shell.execute_reply.started":"2025-11-24T04:11:42.933200Z","shell.execute_reply":"2025-11-24T04:11:42.951618Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Both models perform remarkably similar, with slightly higher accuracy in the baseline, and slightly less loss in the augmented. This makes me think the GAN is underperforming in terms of creating quality images, as more raw data should suggest that the GAN-Augmented model would perform better. ","metadata":{}},{"cell_type":"markdown","source":"# Mock Submission","metadata":{}},{"cell_type":"code","source":"test_preds = np.argmax(aug_model.predict(x_test_norm), axis=1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T03:47:27.949431Z","iopub.execute_input":"2025-11-24T03:47:27.950123Z","iopub.status.idle":"2025-11-24T03:47:29.668642Z","shell.execute_reply.started":"2025-11-24T03:47:27.950091Z","shell.execute_reply":"2025-11-24T03:47:29.668026Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission = pd.DataFrame({\n    \"id\": np.arange(len(test_preds)),\n    \"label\": test_preds\n})\n\nsubmission.to_csv(\"submission.csv\", index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-24T03:47:34.319367Z","iopub.execute_input":"2025-11-24T03:47:34.319658Z","iopub.status.idle":"2025-11-24T03:47:34.332093Z","shell.execute_reply.started":"2025-11-24T03:47:34.319638Z","shell.execute_reply":"2025-11-24T03:47:34.331347Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"No competition for this dataset, but treating my csv file as though it was for a competition. Most just have the ID# for the test data and the output class# rather than the human readable information, so i'm sticking with that format here.","metadata":{}},{"cell_type":"markdown","source":"# Reflection","metadata":{}},{"cell_type":"markdown","source":"1. First time importing a dataset like this, think it went fairly well.\n2. First time using a GAN, I think I understand it well enough to use one, but lack a deep enough understanding to see how to fix issues in one.\n3. Second CNN had a decent amount of additional data, should have performed better.\n4. Overfitting took place around epoch 15, need to either adjust the learning rate leading up to it, or introduce additional dropout layers.\n5. Need to try this again on more detailed images, think the distorted features from scaled down images might be contributing to my issues","metadata":{}}]}