{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3"},"language_info":{"name":"python"},"colab":{"provenance":[],"gpuType":"T4","toc_visible":true},"accelerator":"GPU","kaggle":{"accelerator":"none","dataSources":[{"sourceType":"competition","sourceId":3175,"databundleVersionId":44352,"isSourceIdPinned":false}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Phase 1: Architecture Implementation & Validation\nIn this section, we define and train our custom Convolutional Neural Network (CNN) from scratch.\n* **Architecture:** 4 Convolutional Blocks (32, 64, 128, 256 filters) designed for $64 \\times 64$ inputs.\n* **Methodology:** The labeled dataset (61,578 images) is split into **80% Training** and **20% Validation**.\n* **Goal:** To establish a baseline performance (RMSE and Accuracy) and verify the model learns effectively without overfitting.","metadata":{"id":"t1fQYOPHE71K"}},{"cell_type":"code","source":"import os\nimport time\nimport zipfile\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nimport matplotlib.pyplot as plt\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout, BatchNormalization\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.callbacks import ModelCheckpoint, ReduceLROnPlateau, EarlyStopping, Callback\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom sklearn.model_selection import train_test_split\n\n\n#  SETUP ENVIRONMENT (Kaggle)\ndef setup_kaggle_data():\n    if not os.path.exists('kaggle.json'):\n        print(\"ERROR: Please upload kaggle.json\")\n        return False\n\n    os.makedirs('/root/.kaggle', exist_ok=True)\n    os.system('cp kaggle.json /root/.kaggle/')\n    os.system('chmod 600 /root/.kaggle/kaggle.json')\n\n    if not os.path.exists('images_training_rev1'):\n        print(\"Downloading Data...\")\n        os.system('kaggle competitions download -c galaxy-zoo-the-galaxy-challenge')\n        with zipfile.ZipFile('galaxy-zoo-the-galaxy-challenge.zip', 'r') as z: z.extractall('.')\n        with zipfile.ZipFile('images_training_rev1.zip', 'r') as z: z.extractall('.')\n        with zipfile.ZipFile('training_solutions_rev1.zip', 'r') as z: z.extractall('.')\n\n    print(\"Environment Ready.\")\n    return True\n\n\n# MODULE 1: INDEPENDENT DATA LOADER (WRAPPER)\nclass GalaxyDataLoader:\n\n    def __init__(self, csv_path, img_dir, img_height=64, img_width=64, batch_size=64):\n        self.csv_path = csv_path\n        self.img_dir = img_dir\n        self.img_height = img_height\n        self.img_width = img_width\n        self.batch_size = batch_size\n        self.classes = None\n\n        self.df = pd.read_csv(csv_path)\n        self.df['GalaxyID'] = self.df['GalaxyID'].astype(str) + '.jpg'\n        self.classes = self.df.columns[1:] # All columns except ID\n\n    def get_generators(self, split_size=0.2):\n\n        train_df, val_df = train_test_split(self.df, test_size=split_size, random_state=42)\n\n        # We use the standard ImageDataGenerator as the internal engine\n        # But this class wraps it, so the Main Code doesn't need to know the details\n        datagen = ImageDataGenerator(rescale=1./255)\n\n        train_gen = datagen.flow_from_dataframe(\n            dataframe=train_df,\n            directory=self.img_dir,\n            x_col='GalaxyID',\n            y_col=self.classes,\n            target_size=(self.img_height, self.img_width),\n            batch_size=self.batch_size,\n            class_mode='raw',\n            shuffle=True\n        )\n\n        val_gen = datagen.flow_from_dataframe(\n            dataframe=val_df,\n            directory=self.img_dir,\n            x_col='GalaxyID',\n            y_col=self.classes,\n            target_size=(self.img_height, self.img_width),\n            batch_size=self.batch_size,\n            class_mode='raw',\n            shuffle=False\n        )\n\n        return train_gen, val_gen, len(self.classes)\n\n\n# MODULE 2: NEURAL NETWORK MANAGER\nclass GalaxyNetwork:\n    \"\"\"\n    It is agnostic to where the data comes from, as long as it fits the shape.\n    \"\"\"\n    def __init__(self, input_shape, num_classes):\n        self.input_shape = input_shape\n        self.num_classes = num_classes\n        self.model = self._build_architecture()\n        self.history = None\n\n    def _build_architecture(self):\n        model = Sequential([\n            Conv2D(32, (3, 3), padding='same', activation='relu', input_shape=self.input_shape),\n            BatchNormalization(),\n            MaxPooling2D((2, 2)),\n\n            Conv2D(64, (3, 3), padding='same', activation='relu'),\n            BatchNormalization(),\n            MaxPooling2D((2, 2)),\n\n            Conv2D(128, (3, 3), padding='same', activation='relu'),\n            BatchNormalization(),\n            MaxPooling2D((2, 2)),\n\n            Conv2D(256, (3, 3), padding='same', activation='relu'),\n            BatchNormalization(),\n            MaxPooling2D((2, 2)),\n\n            Flatten(),\n            Dense(512, activation='relu'),\n            Dropout(0.5),\n            Dense(256, activation='relu'),\n            Dropout(0.25),\n            Dense(self.num_classes, activation='sigmoid')\n        ])\n\n        optimizer = Adam(learning_rate=1e-3)\n        model.compile(optimizer=optimizer, loss='mean_squared_error', metrics=['root_mean_squared_error'])\n        return model\n\n    def train(self, train_gen, val_gen, epochs=25):\n        reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=3, min_lr=1e-6, verbose=1)\n        early_stop = EarlyStopping(monitor='val_loss', patience=6, restore_best_weights=True)\n\n        class TimeHistory(Callback):\n            def on_train_begin(self, logs={}): self.times = []\n            def on_epoch_begin(self, batch, logs={}): self.epoch_time_start = time.time()\n            def on_epoch_end(self, batch, logs={}): self.times.append(time.time() - self.epoch_time_start)\n\n        timer = TimeHistory()\n\n        print(\"Starting Training...\")\n        start_time = time.time()\n\n        self.history = self.model.fit(\n            train_gen,\n            epochs=epochs,\n            validation_data=val_gen,\n            callbacks=[reduce_lr, early_stop, timer]\n        )\n\n        total_time = time.time() - start_time\n        avg_epoch_time = np.mean(timer.times)\n        return total_time, avg_epoch_time\n\n    def evaluate_performance(self, val_gen):\n        val_loss, val_rmse = self.model.evaluate(val_gen, verbose=0)\n\n        # 2. Custom Classification Accuracy\n        # We need to predict the whole validation set to check accuracy\n        print(\"Generating Predictions for Accuracy Calculation...\")\n        preds = self.model.predict(val_gen)\n        labels = val_gen.labels\n\n        # Compare argmax of first 3 classes (Smooth, Features, Artifact)\n        pred_q1 = np.argmax(preds[:, 0:3], axis=1)\n        true_q1 = np.argmax(labels[:, 0:3], axis=1)\n        accuracy = np.sum(pred_q1 == true_q1) / len(labels) * 100\n\n        print(\"Testing Inference Speed...\")\n        start = time.time()\n        _ = self.model.predict(val_gen, steps=10)\n        inf_time_per_batch = (time.time() - start) / 10\n\n        return val_rmse, accuracy, inf_time_per_batch\n\n\nif __name__ == \"__main__\":\n\n    if setup_kaggle_data():\n\n        print(\"\\n--- Phase 1: Data Preparation ---\")\n        data_loader = GalaxyDataLoader(\n            csv_path='training_solutions_rev1.csv',\n            img_dir='images_training_rev1',\n            img_height=64, img_width=64, batch_size=64\n        )\n\n        train_gen, val_gen, num_classes = data_loader.get_generators()\n\n        print(\"\\n--- Phase 2: Model Initialization ---\")\n        net = GalaxyNetwork(input_shape=(64, 64, 3), num_classes=num_classes)\n        print(net.model.summary())\n\n        print(\"\\n--- Phase 3: Training ---\")\n        total_time, avg_epoch = net.train(train_gen, val_gen, epochs=25)\n\n        print(\"\\n--- Phase 4: Evaluation ---\")\n        rmse, acc, inf_speed = net.evaluate_performance(val_gen)\n\n        print(\"\\n\" + \"=\"*30)\n        print(\"FINAL REPORT RESULTS\")\n        print(\"=\"*30)\n        print(f\"Validation RMSE: {rmse:.5f}\")\n        print(f\"Classification Accuracy (Q1): {acc:.2f}%\")\n        print(f\"Total Training Time: {total_time:.2f} s\")\n        print(f\"Avg Time per Epoch: {avg_epoch:.2f} s\")\n        print(f\"Inference Time (per batch of 64): {inf_speed*1000:.2f} ms\")\n        print(\"=\"*30)\n\n        plt.figure(figsize=(10, 4))\n        plt.subplot(1, 2, 1)\n        plt.plot(net.history.history['loss'], label='Train')\n        plt.plot(net.history.history['val_loss'], label='Val')\n        plt.title('Loss (MSE)')\n        plt.legend()\n\n        plt.subplot(1, 2, 2)\n        plt.plot(net.history.history['root_mean_squared_error'], label='Train')\n        plt.plot(net.history.history['val_root_mean_squared_error'], label='Val')\n        plt.title('RMSE')\n        plt.legend()\n        plt.show()","metadata":{"id":"cp2zbTLD-xpR","outputId":"64f70e9e-456f-49fc-e484-9ab4e2193965"},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Phase 2a: Hyperparameter Sensitivity Analysis\nIn this section, we optimize the training algorithm by performing a grid search sensitivity analysis on **Learning Rate (LR)** and **Batch Size**.\n\n* **Experiment:** We train the model for **3 epochs** across a grid of hyperparameters:\n  * **Learning Rates:** $10^{-2}, 10^{-3}, 10^{-4}$\n  * **Batch Sizes:** $32, 64$\n* **Goal:** To identify the configuration that yields the lowest validation RMSE and fastest convergence stability before committing to full training.","metadata":{"id":"OQbVMFs8FE5P"}},{"cell_type":"code","source":"import time\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom tensorflow.keras.optimizers import Adam\n# We reuse the classes we defined in the Main Block (GalaxyDataLoader, GalaxyNetwork)\n\nprint(\"--- STARTING SENSITIVITY ANALYSIS ---\")\n\nlearning_rates = [0.01, 0.001, 0.0001]\nbatch_sizes = [32, 64]\n\nresults = []\n\ndata_loader = GalaxyDataLoader(\n    csv_path='training_solutions_rev1.csv',\n    img_dir='images_training_rev1',\n    img_height=64, img_width=64, batch_size=64\n)\n\nfull_df = data_loader.df\ntrain_df, val_df = train_test_split(full_df, test_size=0.2, random_state=42)\n\nfor lr in learning_rates:\n    for bs in batch_sizes:\n        print(f\"\\nTesting: LR={lr}, Batch={bs}...\")\n\n        datagen = ImageDataGenerator(rescale=1./255)\n        train_gen_sens = datagen.flow_from_dataframe(\n            dataframe=train_df, # Use subset to speed up if needed, but we use full here\n            directory='images_training_rev1',\n            x_col='GalaxyID',\n            y_col=data_loader.classes,\n            target_size=(64, 64),\n            batch_size=bs,\n            class_mode='raw',\n            shuffle=True,\n            verbose=0\n        )\n\n        val_gen_sens = datagen.flow_from_dataframe(\n            dataframe=val_df,\n            directory='images_training_rev1',\n            x_col='GalaxyID',\n            y_col=data_loader.classes,\n            target_size=(64, 64),\n            batch_size=bs,\n            class_mode='raw',\n            shuffle=False,\n            verbose=0\n        )\n\n        # 2. Build a fresh model\n        net = GalaxyNetwork(input_shape=(64, 64, 3), num_classes=37)\n        # Re-compile with the specific LR\n        net.model.compile(optimizer=Adam(learning_rate=lr), loss='mean_squared_error', metrics=['root_mean_squared_error'])\n\n        # 3. Train for just 3 Epochs\n        start_time = time.time()\n        hist = net.model.fit(train_gen_sens, validation_data=val_gen_sens, epochs=3, verbose=1)\n        end_time = time.time()\n\n\n        final_loss = hist.history['val_loss'][-1]\n        final_rmse = hist.history['val_root_mean_squared_error'][-1]\n\n        results.append({\n            'LR': lr,\n            'Batch_Size': bs,\n            'Val_RMSE': final_rmse,\n            'Val_Loss': final_loss,\n            'Time_3_Epochs': end_time - start_time\n        })\n\nprint(\"\\n--- SENSITIVITY RESULTS TABLE ---\")\nprint(f\"{'LR':<10} | {'Batch':<10} | {'RMSE (3 ep)':<15} | {'Loss':<15}\")\nprint(\"-\" * 60)\nfor r in results:\n    print(f\"{r['LR']:<10} | {r['Batch_Size']:<10} | {r['Val_RMSE']:.5f}         | {r['Val_Loss']:.5f}\")\n\nlrs = [r['LR'] for r in results]\nrmses = [r['Val_RMSE'] for r in results]\nbatches = [r['Batch_Size'] for r in results]\n\nplt.figure(figsize=(10, 6))\nfor bs in batch_sizes:\n    subset = [r for r in results if r['Batch_Size'] == bs]\n    plt.plot([s['LR'] for s in subset], [s['Val_RMSE'] for s in subset], marker='o', label=f'Batch Size {bs}')\n\nplt.xscale('log')\nplt.xlabel('Learning Rate (log scale)')\nplt.ylabel('Validation RMSE (after 3 epochs)')\nplt.title('Sensitivity Analysis: Learning Rate vs Batch Size')\nplt.legend()\nplt.grid(True)\nplt.show()","metadata":{"id":"fFjOrp3_IlN1","outputId":"394d7d3d-d04f-4f89-fb76-8ab57bee0ed2"},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Phase 2b: Hyperparameter Sensitivity Analysis (Dropout Rate)\n**Task:** Optimize regularization to prevent overfitting.\n\nIn this section, we conduct a controlled experiment to determine the optimal **Dropout Rate**.\n* **Variable:** Dropout probability in the first Dense layer (after Flatten).\n* **Values Tested:** `0.2`, `0.5`, `0.8`.\n* **Method:** We instantiate the full 4-block CNN architecture for each value and train for **3 epochs**.\n* **Goal:** To find the rate that balances model complexity (low underfitting) with generalization (low overfitting).","metadata":{"id":"4dB-fqYdFtZe"}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models, optimizers\n\nprint(\"Preparing Data for Sensitivity Analysis...\")\nloader = GalaxyDataLoader(\n    csv_path='training_solutions_rev1.csv',\n    img_dir='images_training_rev1',\n    img_height=64, img_width=64, batch_size=64\n)\ntrain_gen, val_gen, num_classes = loader.get_generators()\n\n# Define the Builder Function (MATCHING BLOCK 1 ARCHITECTURE)\ndef build_model_dynamic(dropout_rate=0.4):\n    model = models.Sequential([\n        layers.Input(shape=(64, 64, 3)),\n\n        # Block 1\n        layers.Conv2D(32, (3, 3), padding='same', activation='relu'),\n        layers.BatchNormalization(),\n        layers.MaxPooling2D((2, 2)),\n\n        # Block 2\n        layers.Conv2D(64, (3, 3), padding='same', activation='relu'),\n        layers.BatchNormalization(),\n        layers.MaxPooling2D((2, 2)),\n\n        # Block 3\n        layers.Conv2D(128, (3, 3), padding='same', activation='relu'),\n        layers.BatchNormalization(),\n        layers.MaxPooling2D((2, 2)),\n\n        # Block 4\n        layers.Conv2D(256, (3, 3), padding='same', activation='relu'),\n        layers.BatchNormalization(),\n        layers.MaxPooling2D((2, 2)),\n\n        # Flatten & Dense\n        layers.Flatten(),\n        layers.Dense(512, activation='relu'),\n        layers.Dropout(dropout_rate),  # Testing this primary dropout\n        layers.Dense(256, activation='relu'),\n        layers.Dense(37, activation='sigmoid')\n    ])\n\n    model.compile(optimizer='adam', loss='mean_squared_error', metrics=['root_mean_squared_error'])\n    return model\n\n# Run the Micro-Experiment\ndropout_values = [0.2, 0.5, 0.8]\nhistories = {}\n\nprint(\"Starting Fast Sensitivity Analysis on Dropout...\")\n\nfor d_rate in dropout_values:\n    print(f\"\\n--- Testing Dropout Rate: {d_rate} ---\")\n    temp_model = build_model_dynamic(dropout_rate=d_rate)\n\n    # Train for 3 epochs\n    history = temp_model.fit(train_gen, validation_data=val_gen, epochs=3, verbose=1)\n\n    histories[d_rate] = history.history['val_root_mean_squared_error']\n\n# Plot Results\nplt.figure(figsize=(10, 6))\nfor d_rate, val_rmse in histories.items():\n    plt.plot(range(1, 4), val_rmse, marker='o', label=f'Dropout {d_rate}')\n\nplt.title('Sensitivity Analysis: Dropout Rate')\nplt.xlabel('Epoch')\nplt.ylabel('Validation RMSE')\nplt.legend()\nplt.grid(True)\nplt.savefig('dropout_sensitivity.png')\nprint(\"\\nSUCCESS: Graph saved as 'dropout_sensitivity.png'.\")\nplt.show()","metadata":{"id":"onvjy-DWFqpI","outputId":"7980bc0e-9e9a-4f42-e103-85b90caa3196"},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Phase 2c: Inference Time Analysis\n**Task:** Measure the response time (Inference Latency) of the network.\n\nTo verify the system's efficiency, we measure the time required to process a single image.\n* **Method:** We isolate a single image, perform a GPU \"warm-up\" run, and then average the prediction time over 100 iterations.\n* **Metric:** Milliseconds per Image (ms/img).","metadata":{"id":"sKrd2k6jTj6S"}},{"cell_type":"code","source":"import time\nimport numpy as np\n\nprint(\"--- Starting Inference Time Measurement ---\")\n\n#  Prepare a Single Sample\n# We take one batch from the validation generator and extract the first image\ntest_batch, _ = next(iter(val_gen))\ntest_image = test_batch[0:1]  # Shape becomes (1, 64, 64, 3)\n\nprint(f\"Input Shape: {test_image.shape}\")\n\n# GPUs need a 'compilation' run before they reach peak speed.\n# We run one prediction that we don't count.\nprint(\"Warming up GPU...\")\n_ = temp_model.predict(test_image, verbose=0)\n\n\nprint(\"Running 100 iterations to get stable average...\")\nlatencies = []\n\nfor _ in range(100):\n    start_time = time.time()\n    _ = temp_model.predict(test_image, verbose=0)\n    end_time = time.time()\n\n    latencies.append(end_time - start_time)\n\n# 4. Calculate Statistics\navg_time_sec = np.mean(latencies)\navg_time_ms = avg_time_sec * 1000\nfps = 1.0 / avg_time_sec\n\nprint(f\"\\n\" + \"=\"*40)\nprint(f\"RESULTS (Inference Latency)\")\nprint(f\"=\"*40)\nprint(f\"Average Time per Image : {avg_time_ms:.2f} ms\")\nprint(f\"Throughput             : {fps:.2f} images/sec\")\nprint(f\"=\"*40)","metadata":{"id":"Cmp42YkSTmXU","outputId":"22be7bc2-8db0-4c1a-c9ab-5330122776ef"},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Phase 3: Scalability Analysis: Time vs. Data Size\nThis experiment measures the computational cost of the model as the dataset grows.\n* **Method:** We verify training time on subsets of the data (20%, 40%, 60%, 80%, 100%).\n* **Goal:** To demonstrate that the custom architecture scales linearly and is efficient enough for real-time training on academic hardware.","metadata":{"id":"1sDz9xNqFJ5L"}},{"cell_type":"code","source":"print(\"\\n--- STARTING SCALABILITY ANALYSIS ---\")\n\ndata_fractions = [0.2, 0.4, 0.6, 0.8, 1.0]\ntime_results = []\n\nfull_df = pd.read_csv('training_solutions_rev1.csv')\nfull_df['GalaxyID'] = full_df['GalaxyID'].astype(str) + '.jpg'\nclasses = full_df.columns[1:]\n\ndatagen = ImageDataGenerator(rescale=1./255)\n\nfor frac in data_fractions:\n    print(f\"\\nTesting with {frac*100}% of data...\")\n\n    if frac < 1.0:\n        partial_df = full_df.sample(frac=frac, random_state=42)\n    else:\n        partial_df = full_df\n\n    print(f\"Number of images: {len(partial_df)}\")\n\n    train_gen_scale = datagen.flow_from_dataframe(\n        dataframe=partial_df,\n        directory='images_training_rev1',\n        x_col='GalaxyID',\n        y_col=classes,\n        target_size=(64, 64),\n        batch_size=64,\n        class_mode='raw',\n        shuffle=True,\n        verbose=0\n    )\n\n    net = GalaxyNetwork(input_shape=(64, 64, 3), num_classes=37)\n\n    # Measure time for 1 Epoch\n    start_time = time.time()\n    net.model.fit(train_gen_scale, epochs=1, verbose=1)\n    end_time = time.time()\n\n    duration = end_time - start_time\n    time_results.append({'Fraction': frac, 'Images': len(partial_df), 'Time': duration})\n\nprint(\"\\n--- SCALABILITY RESULTS TABLE ---\")\nprint(f\"{'Fraction':<10} | {'Images':<10} | {'Time (1 Epoch)':<15}\")\nprint(\"-\" * 50)\nfor t in time_results:\n    print(f\"{t['Fraction']:<10} | {t['Images']:<10} | {t['Time']:.2f} s\")\n\nplt.figure(figsize=(8, 6))\nplt.plot([t['Images'] for t in time_results], [t['Time'] for t in time_results], marker='o', color='green')\nplt.xlabel('Number of Input Images')\nplt.ylabel('Training Time per Epoch (seconds)')\nplt.title('Network Scalability: Time vs Data Size')\nplt.grid(True)\nplt.show()","metadata":{"id":"m3zIrDlMOgKt","outputId":"9188a19f-7f68-4f50-c73b-90fa625d6950"},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Phase 4: Final Deployment (Full Dataset Training)\nHaving validated the architecture and hyperparameters, we proceed to the \"Production\" phase.\n* **Training:** The model is retrained on **100% of the training images** (no validation split) to maximize knowledge.\n* **Inference:** The trained network predicts probabilities for the **79,975 unlabelled test images**.\n* **Output:** Generates `galaxy_zoo_submission.csv` for official evaluation on the Kaggle Leaderboard.","metadata":{"id":"m5KGBybcF_OK"}},{"cell_type":"code","source":"import os\nimport zipfile\nimport time\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout, BatchNormalization\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\n\n\nprint(\"--- STEP 1: CHECKING DATA ---\")\n\n# Setup Kaggle credentials if needed\nif not os.path.exists('/root/.kaggle/kaggle.json'):\n    if os.path.exists('kaggle.json'):\n        print(\"Setting up Kaggle API...\")\n        os.makedirs('/root/.kaggle', exist_ok=True)\n        os.system('cp kaggle.json /root/.kaggle/')\n        os.system('chmod 600 /root/.kaggle/kaggle.json')\n    else:\n        print(\"ERROR: Please upload kaggle.json to the files area!\")\n\n# Download Data (if not already there)\nif not os.path.exists('images_training_rev1') or not os.path.exists('images_test_rev1'):\n    print(\"Downloading Competition Data... (This takes a few minutes)\")\n    os.system('kaggle competitions download -c galaxy-zoo-the-galaxy-challenge')\n\n    print(\"Unzipping Data...\")\n    with zipfile.ZipFile('galaxy-zoo-the-galaxy-challenge.zip', 'r') as z: z.extractall('.')\n\n    if not os.path.exists('images_training_rev1'):\n        with zipfile.ZipFile('images_training_rev1.zip', 'r') as z: z.extractall('.')\n\n    if not os.path.exists('training_solutions_rev1.csv'):\n        with zipfile.ZipFile('training_solutions_rev1.zip', 'r') as z: z.extractall('.')\n\n    if not os.path.exists('images_test_rev1'):\n        print(\"Unzipping Test Images (79,000+ files)...\")\n        with zipfile.ZipFile('images_test_rev1.zip', 'r') as z: z.extractall('.')\n\nprint(\"Data Ready.\")\n\n\n\nprint(\"\\n--- STEP 2: LOADING 100% TRAINING DATA ---\")\n\ndf = pd.read_csv('training_solutions_rev1.csv')\ndf['GalaxyID'] = df['GalaxyID'].astype(str) + '.jpg'\nclasses = df.columns[1:]\n\ndatagen = ImageDataGenerator(rescale=1./255)\n\nfull_train_gen = datagen.flow_from_dataframe(\n    dataframe=df,\n    directory='images_training_rev1',\n    x_col='GalaxyID',\n    y_col=classes,\n    target_size=(64, 64),\n    batch_size=64,\n    class_mode='raw',\n    shuffle=True\n)\n\nmodel = Sequential([\n    Conv2D(32, (3, 3), padding='same', activation='relu', input_shape=(64, 64, 3)),\n    BatchNormalization(),\n    MaxPooling2D((2, 2)),\n\n    Conv2D(64, (3, 3), padding='same', activation='relu'),\n    BatchNormalization(),\n    MaxPooling2D((2, 2)),\n\n    Conv2D(128, (3, 3), padding='same', activation='relu'),\n    BatchNormalization(),\n    MaxPooling2D((2, 2)),\n\n    Conv2D(256, (3, 3), padding='same', activation='relu'),\n    BatchNormalization(),\n    MaxPooling2D((2, 2)),\n\n    Flatten(),\n    Dense(512, activation='relu'),\n    Dropout(0.5),\n    Dense(256, activation='relu'),\n    Dropout(0.25),\n    Dense(37, activation='sigmoid')\n])\n\nmodel.compile(optimizer=Adam(learning_rate=1e-3), loss='mean_squared_error', metrics=['root_mean_squared_error'])\n\nprint(\"\\n--- STEP 3: TRAINING ON FULL DATASET (BLIND) ---\")\n# Train for 18 epochs (Safe limit to avoid overfitting since we can't see validation loss)\nhistory = model.fit(full_train_gen, epochs=18, verbose=1)\n\nprint(\"\\n--- STEP 4: PREPARING TEST DATA ---\")\n\ntest_files = [f for f in os.listdir('images_test_rev1') if f.endswith('.jpg')]\ntest_df = pd.DataFrame({'GalaxyID': test_files})\nprint(f\"Found {len(test_df)} test images.\")\n\ntest_gen = datagen.flow_from_dataframe(\n    dataframe=test_df,\n    directory='images_test_rev1',\n    x_col='GalaxyID',\n    y_col=None,\n    target_size=(64, 64),\n    batch_size=64,\n    class_mode=None,\n    shuffle=False\n)\n\nprint(\"\\n--- STEP 5: RUNNING INFERENCE (This takes ~5-10 mins) ---\")\npredictions = model.predict(test_gen, verbose=1)\n\nprint(\"\\n--- STEP 6: SAVING SUBMISSION FILE ---\")\n\nsubmission = pd.DataFrame(predictions, columns=classes)\n\nsubmission.insert(0, 'GalaxyID', test_df['GalaxyID'].str.replace('.jpg', ''))\n\nsubmission.to_csv('galaxy_zoo_submission.csv', index=False)\n\nprint(\"SUCCESS! File 'galaxy_zoo_submission.csv' created.\")\n","metadata":{"id":"KM4qbZKsR2Br","outputId":"795b0410-ca8d-45db-d705-d3a9ad73659b"},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Bonus 5: Visualization: The 37 Galaxy Archetypes\nIn this final section, we visualize the images to understand exactly what the model is learning.\n\n* **Objective:** Display the \"perfect archetype\" for every morphological category in the Galaxy Zoo decision tree.\n* **Method:**\n    1.  Iterate through all 37 target classes (e.g., `Class1.1`, `Class7.3`).\n    2.  Scan the training labels (`training_solutions_rev1.csv`) to find the single image with the **maximum probability** for that specific class.\n    3.  Map the abstract Class ID to a **Human-Readable Name** (e.g., *Class7.1* $\\rightarrow$ *\"Completely Round\"*).\n* **Result:** A $5 \\times 8$ grid showing the definitive examples of distinct galaxy features, from \"Smooth Ellipticals\" to \"Spiral Arms\" and \"Edge-on Disks\".","metadata":{"id":"VauBePoXGq-x"}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\nimport pandas as pd\nimport numpy as np\nimport os\n\nprint(\"--- PREPARING VISUALIZATION FOR ALL 37 CLASSES ---\")\n\n# --- MAPPING DICTIONARY (Based on Galaxy Zoo Decision Tree) ---\nCOLUMN_TO_NAME = {\n    'Class1.1': 'Smooth',\n    'Class1.2': 'Features/Disk',\n    'Class1.3': 'Star/Artifact',\n    'Class2.1': 'Edge-on: Yes',\n    'Class2.2': 'Edge-on: No',\n    'Class3.1': 'Bar: Yes',\n    'Class3.2': 'Bar: No',\n    'Class4.1': 'Spiral: Yes',\n    'Class4.2': 'Spiral: No',\n    'Class5.1': 'No Bulge',\n    'Class5.2': 'Noticeable Bulge',\n    'Class5.3': 'Obvious Bulge',\n    'Class5.4': 'Dominant Bulge',\n    'Class6.1': 'Odd: Yes',\n    'Class6.2': 'Odd: No',\n    'Class7.1': 'Completely Round',\n    'Class7.2': 'In-between',\n    'Class7.3': 'Cigar Shaped',\n    'Class8.1': 'Odd: Ring',\n    'Class8.2': 'Odd: Lens/Arc',\n    'Class8.3': 'Odd: Disturbed',\n    'Class8.4': 'Odd: Irregular',\n    'Class8.5': 'Odd: Other',\n    'Class8.6': 'Odd: Merger',\n    'Class8.7': 'Odd: Dust Lane',\n    'Class9.1': 'Bulge: Rounded',\n    'Class9.2': 'Bulge: Boxy',\n    'Class9.3': 'Bulge: None',\n    'Class10.1': 'Arms: Tight',\n    'Class10.2': 'Arms: Medium',\n    'Class10.3': 'Arms: Loose',\n    'Class11.1': '1 Arm',\n    'Class11.2': '2 Arms',\n    'Class11.3': '3 Arms',\n    'Class11.4': '4 Arms',\n    'Class11.5': '5+ Arms',\n    'Class11.6': 'Arms: Cannot Tell'\n}\n\ncsv_path = 'training_solutions_rev1.csv'\nimage_dir = 'images_training_rev1'\n\nif not os.path.exists(csv_path) or not os.path.exists(image_dir):\n    print(\" ERROR: Data missing. Please run the Data Restore block first.\")\nelse:\n    df = pd.read_csv(csv_path)\n\n    class_columns = df.columns[1:]\n    num_classes = len(class_columns)\n    print(f\"Found {num_classes} distinct classes to visualize.\")\n\n    best_examples = {}\n    print(\"Searching for the most representative galaxy for each class...\")\n\n    for col_name in class_columns:\n        best_idx = df[col_name].idxmax()\n\n        best_id = int(df.loc[best_idx, 'GalaxyID'])\n        best_prob = df.loc[best_idx, col_name]\n\n        best_examples[col_name] = (best_id, best_prob)\n\n    rows = 5\n    cols = 8\n    fig, axes = plt.subplots(rows, cols, figsize=(24, 18))\n    axes_flat = axes.flatten()\n\n    print(\"Generating plot grid...\")\n\n    for i, ax in enumerate(axes_flat):\n        if i < num_classes:\n            class_code = class_columns[i]\n            gal_id, prob = best_examples[class_code]\n\n\n            human_name = COLUMN_TO_NAME.get(class_code, \"Unknown\")\n\n            img_path = os.path.join(image_dir, f\"{gal_id}.jpg\")\n\n            if os.path.exists(img_path):\n                img = mpimg.imread(img_path)\n                ax.imshow(img)\n\n                title_text = f\"{class_code}\\n{human_name}\\n(Prob: {prob:.2f})\"\n                ax.set_title(title_text, fontsize=10, fontweight='bold')\n            else:\n                ax.text(0.5, 0.5, \"Image Not Found\", ha='center', va='center')\n\n            ax.axis('off')\n        else:\n            ax.axis('off')\n\n    plt.tight_layout()\n    fig.suptitle(\"Representative Samples for Galaxy Zoo Classes\\n\", fontsize=18, y=1.02)\n\n    plt.show()\n    save_path = 'all_37_classes_labeled.png'\n    fig.savefig(save_path, dpi=300, bbox_inches='tight')\n    print(f\"SUCCESS: Visualization saved as '{save_path}'\")","metadata":{"id":"i62fPr9zEoUN","outputId":"0bbd74af-5373-4be9-ab0a-22b2c97e8411"},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Bonus 5b. Model Inspection: Learned Filters\nIn this final step, we open the \"Black Box\" to verify the model has actually learned useful features.\n\n* **Model Loading:** We load the pre-trained weights from `my_galaxy_model_backup.keras` to ensure we are inspecting the exact model intended for deployment.\n* **Summary:** We print the full parameter count to verify the model size (~2.6M trainable params).\n* **Filter Visualization:** We extract the weights from the **First Convolutional Layer** and plot them as images. This allows us to visually confirm that the network is detecting meaningful geometric features (such as edges and curves) rather than random noise.","metadata":{"id":"IN2VNQXGOECe"}},{"cell_type":"code","source":"import os\nimport tensorflow as tf\nfrom tensorflow.keras.models import load_model\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport math\n\nfilename = 'my_galaxy_model_backup.keras'\ntarget_model = None\n\nprint(f\"Checking for model file: {filename}...\")\n\nif os.path.exists(filename):\n    print(f\"File found! Loading '{filename}'...\")\n    try:\n        target_model = load_model(filename)\n        print(\"SUCCESS: Model loaded from disk.\")\n    except Exception as e:\n        print(f\"Error loading file: {e}\")\nelse:\n    print(f\"ERROR: Could not find '{filename}' on disk AND no model in memory.\")\n    print(\"Please drag and drop your .keras file into the 'Files' sidebar.\")\n\nif target_model:\n    print(\"\\n--- MODEL SUMMARY ---\")\n    target_model.summary()\n\n    print(\"\\nVisualizing first layer filters...\")\n\n    conv_layers = [layer for layer in target_model.layers if 'conv' in layer.name]\n\n    if not conv_layers:\n        print(\"No Convolutional layers found to visualize.\")\n    else:\n        # Get the weights\n        first_layer = conv_layers[0]\n        filters, biases = first_layer.get_weights()\n\n        # Normalize filter values to 0-1 for display\n        f_min, f_max = filters.min(), filters.max()\n        filters = (filters - f_min) / (f_max - f_min)\n\n        # Plot the first 32 filters\n        n_filters = min(32, filters.shape[3])\n        cols = 8\n        rows = math.ceil(n_filters / cols)\n\n        plt.figure(figsize=(16, rows * 2))\n\n        for i in range(n_filters):\n            f = filters[:, :, :, i]\n            ax = plt.subplot(rows, cols, i+1)\n            ax.set_xticks([])\n            ax.set_yticks([])\n\n            # Plot average across channels (RGB)\n            plt.imshow(np.mean(f, axis=2), cmap='viridis')\n            plt.title(f'Filter {i+1}', fontsize=8)\n\n        plt.suptitle(f\"Learned Filters - Layer: {first_layer.name}\", fontsize=16)\n        plt.tight_layout()\n\n        # Display only (No file saving)\n        plt.show()","metadata":{"id":"p7m37Cr-KZFl","outputId":"1d49b3a5-9a3e-42e3-9585-f14bc2a51429"},"outputs":[],"execution_count":null}]}