{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":94183,"databundleVersionId":11224306,"sourceType":"competition"}],"dockerImageVersionId":30918,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models\nimport cv2\nfrom sklearn.cluster import KMeans\nimport matplotlib.pyplot as plt\n\n# Define constants\nIMG_SIZE = 150\nNUM_CATEGORIES = 10\nINPUT_DIR = '/kaggle/input/2025-computer-vision-homework-2/cv_data'\nTRAIN_DIR = os.path.join(INPUT_DIR, 'train')\nTEST_DIR = os.path.join(INPUT_DIR, 'test')\nVALID_DIR = os.path.join(INPUT_DIR, 'valid')\n\n# Load images function\ndef load_images(directory, max_samples=None):\n    images = []\n    filenames = []\n    \n    if not os.path.exists(directory):\n        print(f\"Directory {directory} does not exist\")\n        return np.array([]), []\n    \n    image_files = [f for f in os.listdir(directory) \n                  if f.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff'))]\n    \n    if max_samples is not None:\n        image_files = image_files[:max_samples]\n    \n    for filename in image_files:\n        img_path = os.path.join(directory, filename)\n        img = cv2.imread(img_path)\n        if img is not None:\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n            img = cv2.resize(img, (IMG_SIZE, IMG_SIZE))\n            img = img / 255.0  # Normalize\n            images.append(img)\n            filenames.append(filename)\n    \n    print(f\"Loaded {len(images)} images from {directory}\")\n    return np.array(images), filenames\n\n# Custom feature extractor\ndef extract_features_custom(images):\n    if len(images) == 0:\n        return np.array([])\n    \n    print(\"Using custom CNN feature extractor\")\n    \n    # Create a simple feature extractor - fixed to use proper input creation\n    inputs = tf.keras.Input(shape=(IMG_SIZE, IMG_SIZE, 3))\n    x = layers.Conv2D(32, (3, 3), activation='relu')(inputs)\n    x = layers.BatchNormalization()(x)\n    x = layers.MaxPooling2D((2, 2))(x)\n    \n    x = layers.Conv2D(64, (3, 3), activation='relu')(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.MaxPooling2D((2, 2))(x)\n    \n    x = layers.Conv2D(128, (3, 3), activation='relu')(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.MaxPooling2D((2, 2))(x)\n    \n    x = layers.Conv2D(256, (3, 3), activation='relu')(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.GlobalAveragePooling2D()(x)\n    \n    feature_extractor = tf.keras.Model(inputs, x)\n    \n    # Process in smaller batches to avoid memory issues\n    batch_size = 32\n    features = []\n    for i in range(0, len(images), batch_size):\n        end_idx = min(i + batch_size, len(images))\n        batch = images[i:end_idx]\n        batch_features = feature_extractor.predict(batch, verbose=0)\n        features.append(batch_features)\n        print(f\"Processed batch {i//batch_size + 1}/{(len(images)-1)//batch_size + 1}\")\n    \n    return np.vstack(features)\n\n# Clustering (K-Means)\ndef cluster_images(features, num_clusters):\n    if len(features) == 0:\n        print(\"No features to cluster\")\n        return np.array([]), None\n        \n    print(f\"Clustering {len(features)} feature vectors into {num_clusters} clusters\")\n    \n    # Use KMeans with multiple initializations for better results\n    kmeans = KMeans(\n        n_clusters=num_clusters, \n        random_state=42, \n        n_init=10,\n        max_iter=300\n    )\n    \n    clusters = kmeans.fit_predict(features)\n    \n    # Print cluster distribution\n    unique, counts = np.unique(clusters, return_counts=True)\n    distribution = dict(zip(unique, counts))\n    print(\"Cluster distribution:\", distribution)\n    \n    return clusters, kmeans\n\n# Custom callback to stop at 95% accuracy\nclass AccuracyThresholdCallback(tf.keras.callbacks.Callback):\n    def __init__(self, threshold=0.95):\n        super(AccuracyThresholdCallback, self).__init__()\n        self.threshold = threshold\n        \n    def on_epoch_end(self, epoch, logs=None):\n        logs = logs or {}\n        if logs.get('accuracy') >= self.threshold:\n            print(f'\\nReached {self.threshold*100}% accuracy, stopping training.')\n            self.model.stop_training = True\n\n# Main execution flow\nprint(\"Loading training images...\")\ntrain_images, train_filenames = load_images(TRAIN_DIR, max_samples=5000)\nprint(\"Loading validation images...\")\nvalid_images, valid_filenames = load_images(VALID_DIR, max_samples=1000)\nprint(\"Loading test images...\")\ntest_images, test_filenames = load_images(TEST_DIR)  # Load all test images\n\n# Extract features for training data\nprint(\"Extracting features from training images...\")\ntrain_features = extract_features_custom(train_images)\nprint(f\"Extracted features shape: {train_features.shape}\")\n\n# Cluster training data\nprint(\"Clustering training features...\")\ntrain_labels, kmeans_model = cluster_images(train_features, NUM_CATEGORIES)\n\n# Extract features for validation data\nprint(\"Extracting features from validation images...\")\nvalid_features = extract_features_custom(valid_images)\n\n# Use the same clustering model to predict validation labels\nprint(\"Assigning validation data to clusters...\")\nvalid_labels = kmeans_model.predict(valid_features) if len(valid_features) > 0 else np.array([])\n\n# Build CNN model to predict the clusters \nprint(\"Building CNN model...\")\ninputs = tf.keras.Input(shape=(IMG_SIZE, IMG_SIZE, 3))\nx = layers.Conv2D(32, (3, 3), activation='relu', padding='same')(inputs)\nx = layers.BatchNormalization()(x)\nx = layers.MaxPooling2D((2, 2))(x)\n\nx = layers.Conv2D(64, (3, 3), activation='relu', padding='same')(x)\nx = layers.BatchNormalization()(x)\nx = layers.MaxPooling2D((2, 2))(x)\n\nx = layers.Conv2D(128, (3, 3), activation='relu', padding='same')(x)\nx = layers.BatchNormalization()(x)\nx = layers.MaxPooling2D((2, 2))(x)\n\nx = layers.Conv2D(256, (3, 3), activation='relu', padding='same')(x)\nx = layers.BatchNormalization()(x)\nx = layers.MaxPooling2D((2, 2))(x)\n\nx = layers.Flatten()(x)\nx = layers.Dense(256, activation='relu')(x)\nx = layers.BatchNormalization()(x)\nx = layers.Dropout(0.5)(x)\nx = layers.Dense(128, activation='relu')(x)\nx = layers.BatchNormalization()(x)\nx = layers.Dropout(0.3)(x)\noutputs = layers.Dense(NUM_CATEGORIES, activation='softmax')(x)\n\nmodel = tf.keras.Model(inputs, outputs)\n\n# Compile the model\nmodel.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),\n    loss='sparse_categorical_crossentropy',\n    metrics=['accuracy']\n)\n\n# Define model checkpoint callback - fixed filepath to end with .keras\nmodel_save_path = 'kavins_unsupervised_trained_cnn.keras'\ncheckpoint_callback = tf.keras.callbacks.ModelCheckpoint(\n    filepath=model_save_path,\n    save_best_only=True,\n    monitor='val_accuracy',\n    mode='max',\n    verbose=1\n)\n\n# Define callbacks\ncallbacks = [\n    AccuracyThresholdCallback(threshold=0.95),\n    tf.keras.callbacks.EarlyStopping(\n        monitor='val_accuracy',\n        patience=5,\n        restore_best_weights=True\n    ),\n    tf.keras.callbacks.ReduceLROnPlateau(\n        monitor='val_loss',\n        factor=0.2,\n        patience=3,\n        min_lr=0.00001\n    ),\n    checkpoint_callback\n]\n\n# Train the model\nprint(\"Training CNN model...\")\nhistory = model.fit(\n    train_images, \n    train_labels,\n    epochs=20,  # Maximum of 20 epochs (was 10)\n    validation_data=(valid_images, valid_labels) if len(valid_images) > 0 and len(valid_labels) > 0 else None,\n    callbacks=callbacks,\n    batch_size=32,\n    verbose=1\n)\n\n# Save the final model (in case best model wasn't saved by checkpoint)\nmodel.save(model_save_path)\nprint(f\"Model saved to {model_save_path}\")\n\n# Save KMeans model\nimport joblib \nkmeans_save_path = 'kmeans_clustering_model.joblib'\njoblib.dump(kmeans_model, kmeans_save_path)\nprint(f\"KMeans model saved to {kmeans_save_path}\")\n\n# Load the saved model for prediction (to ensure we're using the best model)\nprint(\"Loading the saved model for prediction...\")\nmodel = tf.keras.models.load_model(model_save_path)\n\n# Generate predictions for all test images\nprint(f\"Generating predictions for {len(test_images)} test images...\")\npredictions = model.predict(test_images)\npredicted_labels = np.argmax(predictions, axis=1)\n\nids = []\nfor filename in test_filenames:\n    id_name = os.path.splitext(filename)[0]\n    ids.append(id_name)\n\nresults_df = pd.DataFrame({\n    'id': ids,\n    'label': predicted_labels\n})\n\noutput_path = 'predictions.csv'\nresults_df.to_csv(output_path, index=False)\n\nprint(f\"Prediction file saved to {output_path}\")\nprint(f\"Total predictions: {len(results_df)}\")\nprint(\"All predictions:\")\nprint(results_df) ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-03T01:50:42.880825Z","iopub.execute_input":"2025-03-03T01:50:42.881618Z","iopub.status.idle":"2025-03-03T02:34:42.320863Z","shell.execute_reply.started":"2025-03-03T01:50:42.881557Z","shell.execute_reply":"2025-03-03T02:34:42.319627Z"}},"outputs":[],"execution_count":null}]}