{"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":"nvidiaTeslaT4","dataSources":[],"dockerImageVersionId":31154,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.metrics import accuracy_score, classification_report\nimport time\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Set random seed for reproducibility\nnp.random.seed(42)\n\nprint(\"=\"*60)\nprint(\"✓ Basic libraries imported successfully\")\nprint(\"=\"*60)\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-04T13:47:48.350034Z","iopub.execute_input":"2025-11-04T13:47:48.350621Z","iopub.status.idle":"2025-11-04T13:47:51.903759Z","shell.execute_reply.started":"2025-11-04T13:47:48.350591Z","shell.execute_reply":"2025-11-04T13:47:51.902691Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow import keras\n\nprint(\"=\"*60)\nprint(\"TENSORFLOW SETUP\")\nprint(\"=\"*60)\nprint(f\"TensorFlow version: {tf.__version__}\")\n\n# Set TensorFlow random seed\ntf.random.set_seed(42)\n\nprint(\"✓ TensorFlow imported successfully\")\nprint(\"=\"*60)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-04T13:47:59.102616Z","iopub.execute_input":"2025-11-04T13:47:59.102900Z","iopub.status.idle":"2025-11-04T13:48:12.300248Z","shell.execute_reply.started":"2025-11-04T13:47:59.102878Z","shell.execute_reply":"2025-11-04T13:48:12.299442Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"=\"*60)\nprint(\"DETECTING GPU DEVICES\")\nprint(\"=\"*60)\n\n# Detect GPUs\ngpus = tf.config.list_physical_devices('GPU')\nprint(f\"Number of GPUs detected: {len(gpus)}\")\n\nif len(gpus) > 0:\n    for i, gpu in enumerate(gpus):\n        print(f\"  GPU {i}: {gpu}\")\n    \n    # Enable memory growth to avoid OOM\n    for gpu in gpus:\n        try:\n            tf.config.experimental.set_memory_growth(gpu, True)\n            print(f\"  ✓ Memory growth enabled for {gpu.name}\")\n        except RuntimeError as e:\n            print(f\"  ! Could not enable memory growth: {e}\")\n    \n    print(f\"\\n✓ {len(gpus)} GPU(s) available and configured\")\nelse:\n    print(\"⚠ No GPU detected - will use CPU (slower)\")\n\nprint(\"=\"*60)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-04T13:48:18.552874Z","iopub.execute_input":"2025-11-04T13:48:18.553407Z","iopub.status.idle":"2025-11-04T13:48:19.291826Z","shell.execute_reply.started":"2025-11-04T13:48:18.553382Z","shell.execute_reply":"2025-11-04T13:48:19.291103Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n\" + \"=\"*60)\nprint(\"LOADING MNIST DATASET\")\nprint(\"=\"*60)\n\n# Load MNIST from Keras\n(X_train_full, y_train_full), (X_test, y_test) = keras.datasets.mnist.load_data()\n\n# Flatten images from 28x28 to 784 features and normalize\nX_train_full = X_train_full.reshape(-1, 784).astype('float32') / 255.0\nX_test = X_test.reshape(-1, 784).astype('float32') / 255.0\n\nprint(f\"✓ Dataset loaded successfully\")\nprint(f\"  Training samples: {X_train_full.shape[0]}\")\nprint(f\"  Test samples: {X_test.shape[0]}\")\nprint(f\"  Features per image: {X_train_full.shape[1]}\")\nprint(f\"  Classes (digits 0-9): {len(np.unique(y_train_full))}\")\nprint(\"=\"*60)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-04T13:48:25.877858Z","iopub.execute_input":"2025-11-04T13:48:25.878112Z","iopub.status.idle":"2025-11-04T13:48:26.518701Z","shell.execute_reply.started":"2025-11-04T13:48:25.878091Z","shell.execute_reply":"2025-11-04T13:48:26.517901Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n\" + \"=\"*60)\nprint(\"CREATING LABELED AND UNLABELED POOLS\")\nprint(\"=\"*60)\n\n# Randomly shuffle dataset\nindices = np.random.permutation(len(X_train_full))\nX_train_full = X_train_full[indices]\ny_train_full = y_train_full[indices]\n\n# Configuration\nn_initial = 500      # Initial labeled set (0.83% of total)\nn_query = 50        # Samples to query per iteration\nn_iterations = 40    # Number of active learning iterations\n\n# Create labeled pool (what model can see)\nX_labeled = X_train_full[:n_initial].copy()\ny_labeled = y_train_full[:n_initial].copy()\n\n# Create unlabeled pool (labels are hidden from model)\nX_unlabeled_pool = X_train_full[n_initial:].copy()\ny_unlabeled_pool = y_train_full[n_initial:].copy()  # We have labels but hide from model\n\nprint(f\"✓ Pools created successfully\")\nprint(f\"  Initial labeled set: {X_labeled.shape[0]} samples ({(n_initial/len(X_train_full)*100):.2f}%)\")\nprint(f\"  Unlabeled pool: {X_unlabeled_pool.shape[0]} samples\")\nprint(f\"  Query size per iteration: {n_query} samples\")\nprint(f\"  Total iterations: {n_iterations}\")\nprint(f\"  Final labeled set: {n_initial + (n_query * n_iterations)} samples\")\nprint(\"=\"*60)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-04T13:48:31.942831Z","iopub.execute_input":"2025-11-04T13:48:31.943322Z","iopub.status.idle":"2025-11-04T13:48:32.081753Z","shell.execute_reply.started":"2025-11-04T13:48:31.943300Z","shell.execute_reply":"2025-11-04T13:48:32.081087Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n\" + \"=\"*60)\nprint(\"DEFINING QUERY STRATEGIES\")\nprint(\"=\"*60)\n\ndef random_query(X_unlabeled, n_query):\n    \"\"\"\n    Random Sampling (Baseline)\n    Randomly selects samples - no intelligence\n    \"\"\"\n    indices = np.random.choice(len(X_unlabeled), n_query, replace=False)\n    return indices\n\ndef uncertainty_sampling(model, X_unlabeled, n_query):\n    \"\"\"\n    Uncertainty Sampling\n    Selects samples where model has lowest confidence\n    Formula: uncertainty = 1 - max(P(y|x))\n    \"\"\"\n    probs = model.predict_proba(X_unlabeled)\n    uncertainty = 1 - np.max(probs, axis=1)\n    indices = np.argsort(uncertainty)[-n_query:]\n    return indices\n\ndef margin_sampling(model, X_unlabeled, n_query):\n    \"\"\"\n    Margin Sampling\n    Selects samples where top 2 predicted classes are closest\n    Formula: margin = P(y1|x) - P(y2|x)\n    \"\"\"\n    probs = model.predict_proba(X_unlabeled)\n    probs_sorted = np.sort(probs, axis=1)\n    margin = probs_sorted[:, -1] - probs_sorted[:, -2]\n    indices = np.argsort(margin)[:n_query]\n    return indices\n\nprint(\"✓ Strategy 1: Random Sampling\")\nprint(\"  - Baseline with no intelligence\")\nprint()\nprint(\"✓ Strategy 2: Uncertainty Sampling\")\nprint(\"  - Selects samples with lowest confidence\")\nprint()\nprint(\"✓ Strategy 3: Margin Sampling\")\nprint(\"  - Selects samples where top 2 classes are closest\")\nprint(\"=\"*60)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-04T13:48:38.968289Z","iopub.execute_input":"2025-11-04T13:48:38.969112Z","iopub.status.idle":"2025-11-04T13:48:38.976336Z","shell.execute_reply.started":"2025-11-04T13:48:38.969077Z","shell.execute_reply":"2025-11-04T13:48:38.975596Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n\" + \"=\"*60)\nprint(\"INITIALIZING RESULTS STORAGE\")\nprint(\"=\"*60)\n\n# Dictionary to store all results\nresults = {\n    'Random': {\n        'n_samples': [n_initial],\n        'accuracy': [],\n        'time_per_iter': []\n    },\n    'Uncertainty': {\n        'n_samples': [n_initial],\n        'accuracy': [],\n        'time_per_iter': []\n    },\n    'Margin': {\n        'n_samples': [n_initial],\n        'accuracy': [],\n        'time_per_iter': []\n    }\n}\n\nprint(\"✓ Results dictionary created\")\nprint(\"  Will track: accuracy, sample count, training time\")\nprint(\"=\"*60)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-04T13:48:47.904079Z","iopub.execute_input":"2025-11-04T13:48:47.904544Z","iopub.status.idle":"2025-11-04T13:48:47.909638Z","shell.execute_reply.started":"2025-11-04T13:48:47.904521Z","shell.execute_reply":"2025-11-04T13:48:47.908882Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n\" + \"=\"*60)\nprint(\"RUNNING STRATEGY 1: RANDOM SAMPLING\")\nprint(\"=\"*60)\nprint(\"Starting training...\\n\")\n\n# Create copies of pools for this strategy\nX_lab_random = X_labeled.copy()\ny_lab_random = y_labeled.copy()\nX_unlab_random = X_unlabeled_pool.copy()\ny_unlab_random = y_unlabeled_pool.copy()\n\n# Active learning loop\nfor iteration in range(n_iterations):\n    start_time = time.time()\n    \n    # Train Logistic Regression (uses all CPU cores with n_jobs=-1)\n    model_random = LogisticRegression(max_iter=100, solver='saga', n_jobs=-1, verbose=0)\n    model_random.fit(X_lab_random, y_lab_random)\n    \n    # Evaluate on test set\n    y_pred = model_random.predict(X_test)\n    acc = accuracy_score(y_test, y_pred)\n    \n    # Store results\n    results['Random']['accuracy'].append(acc)\n    iter_time = time.time() - start_time\n    results['Random']['time_per_iter'].append(iter_time)\n    \n    # Print progress\n    print(f\"Iter {iteration+1:2d}/{n_iterations} | Samples: {len(y_lab_random):4d} | Acc: {acc:.4f} | Time: {iter_time:.2f}s\")\n    \n    # Query strategy: random selection\n    query_idx = random_query(X_unlab_random, n_query)\n    \n    # Add queried samples to labeled set\n    X_lab_random = np.vstack([X_lab_random, X_unlab_random[query_idx]])\n    y_lab_random = np.hstack([y_lab_random, y_unlab_random[query_idx]])\n    results['Random']['n_samples'].append(len(y_lab_random))\n    \n    # Remove queried samples from unlabeled pool\n    X_unlab_random = np.delete(X_unlab_random, query_idx, axis=0)\n    y_unlab_random = np.delete(y_unlab_random, query_idx)\n\nprint(f\"\\n{'='*60}\")\nprint(f\"✓ RANDOM SAMPLING COMPLETE\")\nprint(f\"  Final accuracy: {results['Random']['accuracy'][-1]:.4f}\")\nprint(f\"  Total time: {sum(results['Random']['time_per_iter']):.2f}s\")\nprint(f\"{'='*60}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-04T13:49:05.377003Z","iopub.execute_input":"2025-11-04T13:49:05.377275Z","iopub.status.idle":"2025-11-04T13:54:14.396545Z","shell.execute_reply.started":"2025-11-04T13:49:05.377253Z","shell.execute_reply":"2025-11-04T13:54:14.395752Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n\" + \"=\"*60)\nprint(\"RUNNING STRATEGY 2: UNCERTAINTY SAMPLING\")\nprint(\"=\"*60)\nprint(\"Starting training...\\n\")\n\n# Create copies of pools\nX_lab_unc = X_labeled.copy()\ny_lab_unc = y_labeled.copy()\nX_unlab_unc = X_unlabeled_pool.copy()\ny_unlab_unc = y_unlabeled_pool.copy()\n\n# Active learning loop\nfor iteration in range(n_iterations):\n    start_time = time.time()\n    \n    # Train model\n    model_unc = LogisticRegression(max_iter=100, solver='saga', n_jobs=-1, verbose=0)\n    model_unc.fit(X_lab_unc, y_lab_unc)\n    \n    # Evaluate\n    y_pred = model_unc.predict(X_test)\n    acc = accuracy_score(y_test, y_pred)\n    \n    # Store results\n    results['Uncertainty']['accuracy'].append(acc)\n    iter_time = time.time() - start_time\n    results['Uncertainty']['time_per_iter'].append(iter_time)\n    \n    print(f\"Iter {iteration+1:2d}/{n_iterations} | Samples: {len(y_lab_unc):4d} | Acc: {acc:.4f} | Time: {iter_time:.2f}s\")\n    \n    # Query strategy: uncertainty sampling\n    query_idx = uncertainty_sampling(model_unc, X_unlab_unc, n_query)\n    \n    # Update pools\n    X_lab_unc = np.vstack([X_lab_unc, X_unlab_unc[query_idx]])\n    y_lab_unc = np.hstack([y_lab_unc, y_unlab_unc[query_idx]])\n    results['Uncertainty']['n_samples'].append(len(y_lab_unc))\n    \n    X_unlab_unc = np.delete(X_unlab_unc, query_idx, axis=0)\n    y_unlab_unc = np.delete(y_unlab_unc, query_idx)\n\nprint(f\"\\n{'='*60}\")\nprint(f\"✓ UNCERTAINTY SAMPLING COMPLETE\")\nprint(f\"  Final accuracy: {results['Uncertainty']['accuracy'][-1]:.4f}\")\nprint(f\"  Total time: {sum(results['Uncertainty']['time_per_iter']):.2f}s\")\nprint(f\"{'='*60}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-04T13:54:44.413926Z","iopub.execute_input":"2025-11-04T13:54:44.414623Z","iopub.status.idle":"2025-11-04T13:59:53.949143Z","shell.execute_reply.started":"2025-11-04T13:54:44.414598Z","shell.execute_reply":"2025-11-04T13:59:53.948327Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n\" + \"=\"*60)\nprint(\"RUNNING STRATEGY 3: MARGIN SAMPLING\")\nprint(\"=\"*60)\nprint(\"Starting training...\\n\")\n\n# Create copies\nX_lab_margin = X_labeled.copy()\ny_lab_margin = y_labeled.copy()\nX_unlab_margin = X_unlabeled_pool.copy()\ny_unlab_margin = y_unlabeled_pool.copy()\n\n# Active learning loop\nfor iteration in range(n_iterations):\n    start_time = time.time()\n    \n    # Train model\n    model_margin = LogisticRegression(max_iter=100, solver='saga', n_jobs=-1, verbose=0)\n    model_margin.fit(X_lab_margin, y_lab_margin)\n    \n    # Evaluate\n    y_pred = model_margin.predict(X_test)\n    acc = accuracy_score(y_test, y_pred)\n    \n    # Store results\n    results['Margin']['accuracy'].append(acc)\n    iter_time = time.time() - start_time\n    results['Margin']['time_per_iter'].append(iter_time)\n    \n    print(f\"Iter {iteration+1:2d}/{n_iterations} | Samples: {len(y_lab_margin):4d} | Acc: {acc:.4f} | Time: {iter_time:.2f}s\")\n    \n    # Query strategy: margin sampling\n    query_idx = margin_sampling(model_margin, X_unlab_margin, n_query)\n    \n    # Update pools\n    X_lab_margin = np.vstack([X_lab_margin, X_unlab_margin[query_idx]])\n    y_lab_margin = np.hstack([y_lab_margin, y_unlab_margin[query_idx]])\n    results['Margin']['n_samples'].append(len(y_lab_margin))\n    \n    X_unlab_margin = np.delete(X_unlab_margin, query_idx, axis=0)\n    y_unlab_margin = np.delete(y_unlab_margin, query_idx)\n\nprint(f\"\\n{'='*60}\")\nprint(f\"✓ MARGIN SAMPLING COMPLETE\")\nprint(f\"  Final accuracy: {results['Margin']['accuracy'][-1]:.4f}\")\nprint(f\"  Total time: {sum(results['Margin']['time_per_iter']):.2f}s\")\nprint(f\"{'='*60}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-04T14:01:30.750896Z","iopub.execute_input":"2025-11-04T14:01:30.751174Z","iopub.status.idle":"2025-11-04T14:06:40.306597Z","shell.execute_reply.started":"2025-11-04T14:01:30.751151Z","shell.execute_reply":"2025-11-04T14:06:40.305775Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n\" + \"=\"*60)\nprint(\"CREATING MAIN COMPARISON PLOT\")\nprint(\"=\"*60)\n\n# Define colors\ncolors = {'Random': '#d62728', 'Uncertainty': '#2ca02c', 'Margin': '#1f77b4'}\nmarkers = {'Random': 'o', 'Uncertainty': 's', 'Margin': '^'}\n\n# Create figure\nfig, ax = plt.subplots(figsize=(14, 8))\n\n# Plot learning curves for all strategies\nfor strategy in ['Random', 'Uncertainty', 'Margin']:\n    n_samples = results[strategy]['n_samples'][:-1]\n    accuracies = results[strategy]['accuracy']\n    \n    ax.plot(n_samples, accuracies, \n            marker=markers[strategy], \n            linewidth=2.5, \n            markersize=7, \n            label=strategy, \n            color=colors[strategy], \n            alpha=0.8)\n\n# Add reference lines\nax.axhline(y=0.85, color='gray', linestyle='--', alpha=0.5, linewidth=1.5, label='85% Target')\nax.axhline(y=0.90, color='gray', linestyle=':', alpha=0.5, linewidth=1.5, label='90% Target')\n\n# Formatting\nax.set_xlabel('Number of Labeled Samples', fontsize=13, fontweight='bold')\nax.set_ylabel('Test Accuracy', fontsize=13, fontweight='bold')\nax.set_title('Active Learning: Comparison of Query Strategies on MNIST\\nLogistic Regression with T4 GPU Acceleration', \n             fontsize=14, fontweight='bold', pad=20)\nax.legend(fontsize=11, loc='lower right', framealpha=0.95, shadow=True)\nax.grid(True, alpha=0.3, linestyle='--')\nax.set_ylim([0.65, 0.98])\n\n# Styling\nax.set_facecolor('#f8f9fa')\nfig.patch.set_facecolor('white')\n\nplt.tight_layout()\nplt.savefig('learning_curves.png', dpi=300, bbox_inches='tight', facecolor='white')\nprint(\"✓ Saved: learning_curves.png\")\nplt.show()\n\nprint(\"=\"*60)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-04T14:07:07.969385Z","iopub.execute_input":"2025-11-04T14:07:07.969799Z","iopub.status.idle":"2025-11-04T14:07:09.030228Z","shell.execute_reply.started":"2025-11-04T14:07:07.969776Z","shell.execute_reply":"2025-11-04T14:07:09.029501Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def find_samples_for_accuracy(n_samples, accuracies, target_acc):\n    \"\"\"\n    Find minimum number of samples needed to reach target accuracy\n    \"\"\"\n    for samples, acc in zip(n_samples, accuracies):\n        if acc >= target_acc:\n            return samples\n    # If target not reached, return last value\n    return n_samples[-1] if len(n_samples) > 0 else 'N/A'\n\nprint(\"✓ Helper function defined for calculating sample efficiency\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-04T14:07:14.154917Z","iopub.execute_input":"2025-11-04T14:07:14.155382Z","iopub.status.idle":"2025-11-04T14:07:14.160093Z","shell.execute_reply.started":"2025-11-04T14:07:14.155359Z","shell.execute_reply":"2025-11-04T14:07:14.159278Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n\" + \"=\"*80)\nprint(\"TABLE 1: SAMPLE EFFICIENCY COMPARISON\")\nprint(\"=\"*80)\n\ncomparison_data = []\nrandom_samples_85 = None\nrandom_samples_90 = None\n\nfor strategy in ['Random', 'Uncertainty', 'Margin']:\n    n_samples = results[strategy]['n_samples'][:-1]\n    accuracies = results[strategy]['accuracy']\n    \n    samples_85 = find_samples_for_accuracy(n_samples, accuracies, 0.85)\n    samples_90 = find_samples_for_accuracy(n_samples, accuracies, 0.90)\n    final_acc = accuracies[-1]\n    \n    if strategy == 'Random':\n        random_samples_85 = samples_85\n        random_samples_90 = samples_90\n        improvement_85 = '-'\n        improvement_90 = '-'\n    else:\n        if isinstance(samples_85, int) and isinstance(random_samples_85, int):\n            improvement_85 = f\"{((random_samples_85 - samples_85) / random_samples_85 * 100):.1f}%\"\n        else:\n            improvement_85 = 'N/A'\n            \n        if isinstance(samples_90, int) and isinstance(random_samples_90, int):\n            improvement_90 = f\"{((random_samples_90 - samples_90) / random_samples_90 * 100):.1f}%\"\n        else:\n            improvement_90 = 'N/A'\n    \n    comparison_data.append({\n        'Strategy': strategy,\n        'Samples for 85%': samples_85,\n        'Reduction vs Random (85%)': improvement_85,\n        'Samples for 90%': samples_90,\n        'Reduction vs Random (90%)': improvement_90,\n        'Final Accuracy': f\"{final_acc:.4f}\"\n    })\n\ndf_comparison = pd.DataFrame(comparison_data)\nprint(\"\\n\" + df_comparison.to_string(index=False))\nprint(\"\\n\" + \"=\"*80)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-04T14:07:16.336796Z","iopub.execute_input":"2025-11-04T14:07:16.337038Z","iopub.status.idle":"2025-11-04T14:07:16.364487Z","shell.execute_reply.started":"2025-11-04T14:07:16.337023Z","shell.execute_reply":"2025-11-04T14:07:16.363833Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n\" + \"=\"*80)\nprint(\"TABLE 2: COST ANALYSIS (Labeling Cost = $1 per Sample)\")\nprint(\"=\"*80)\n\ncost_data = []\nrandom_cost_90 = None\n\nfor strategy in ['Random', 'Uncertainty', 'Margin']:\n    n_samples = results[strategy]['n_samples'][:-1]\n    accuracies = results[strategy]['accuracy']\n    \n    samples_90 = find_samples_for_accuracy(n_samples, accuracies, 0.90)\n    \n    if isinstance(samples_90, int):\n        cost = samples_90 * 1  # $1 per sample\n        \n        if strategy == 'Random':\n            random_cost_90 = cost\n            savings = '-'\n            percent_savings = '-'\n        else:\n            savings = f\"${random_cost_90 - cost:,.0f}\"\n            percent_savings = f\"{((random_cost_90 - cost) / random_cost_90 * 100):.1f}%\"\n    else:\n        cost = 'N/A'\n        savings = 'N/A'\n        percent_savings = 'N/A'\n    \n    cost_data.append({\n        'Strategy': strategy,\n        'Samples for 90%': samples_90,\n        'Cost @ $1/sample': f\"${cost:,.0f}\" if isinstance(cost, int) else cost,\n        'Savings vs Random': savings,\n        'Cost Reduction': percent_savings\n    })\n\ndf_cost = pd.DataFrame(cost_data)\nprint(\"\\n\" + df_cost.to_string(index=False))\nprint(\"\\n\" + \"=\"*80)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-04T14:07:28.501293Z","iopub.execute_input":"2025-11-04T14:07:28.502023Z","iopub.status.idle":"2025-11-04T14:07:28.510507Z","shell.execute_reply.started":"2025-11-04T14:07:28.501998Z","shell.execute_reply":"2025-11-04T14:07:28.509773Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n\" + \"=\"*80)\nprint(\"TABLE 3: COMPUTATIONAL EFFICIENCY\")\nprint(\"=\"*80)\n\ntiming_data = []\n\nfor strategy in ['Random', 'Uncertainty', 'Margin']:\n    total_time = sum(results[strategy]['time_per_iter'])\n    avg_time = np.mean(results[strategy]['time_per_iter'])\n    num_iters = len(results[strategy]['time_per_iter'])\n    \n    timing_data.append({\n        'Strategy': strategy,\n        'Total Training Time': f\"{total_time:.2f}s ({total_time/60:.1f} min)\",\n        'Avg Time per Iteration': f\"{avg_time:.2f}s\",\n        'Total Iterations': num_iters\n    })\n\ndf_timing = pd.DataFrame(timing_data)\nprint(\"\\n\" + df_timing.to_string(index=False))\nprint(\"\\n\" + \"=\"*80)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-04T14:07:31.964899Z","iopub.execute_input":"2025-11-04T14:07:31.965155Z","iopub.status.idle":"2025-11-04T14:07:31.972620Z","shell.execute_reply.started":"2025-11-04T14:07:31.965139Z","shell.execute_reply":"2025-11-04T14:07:31.971643Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\nCreating Sample Efficiency Bar Chart...\")\n\n# Create bar chart\nfig, ax = plt.subplots(figsize=(10, 7))\n\nstrategies = ['Random', 'Uncertainty', 'Margin']\nsamples_90_list = []\n\nfor strategy in strategies:\n    n_samples = results[strategy]['n_samples'][:-1]\n    accuracies = results[strategy]['accuracy']\n    samples = find_samples_for_accuracy(n_samples, accuracies, 0.90)\n    samples_90_list.append(samples if isinstance(samples, int) else 0)\n\ncolors_bar = ['#d62728', '#2ca02c', '#1f77b4']\nbars = ax.bar(strategies, samples_90_list, color=colors_bar, alpha=0.7, edgecolor='black', linewidth=2)\n\n# Add value labels on bars\nfor bar, samples in zip(bars, samples_90_list):\n    if samples > 0:\n        height = bar.get_height()\n        ax.text(bar.get_x() + bar.get_width()/2., height,\n                f'{int(samples)}\\nsamples',\n                ha='center', va='bottom', fontsize=11, fontweight='bold')\n\n# Add savings percentage\nrandom_samples = samples_90_list[0]\nfor i, (bar, samples) in enumerate(zip(bars, samples_90_list)):\n    if i > 0 and samples > 0 and random_samples > 0:\n        savings = ((random_samples - samples) / random_samples * 100)\n        ax.text(bar.get_x() + bar.get_width()/2., samples/2,\n                f'{savings:.0f}%\\nsavings',\n                ha='center', va='center', fontsize=10, fontweight='bold', color='white',\n                bbox=dict(boxstyle='round', facecolor='black', alpha=0.5))\n\nax.set_ylabel('Samples Needed for 90% Accuracy', fontsize=12, fontweight='bold')\nax.set_title('Active Learning: Sample Efficiency Comparison\\n(Fewer samples = Better)', \n             fontsize=13, fontweight='bold')\nax.set_ylim([0, max(samples_90_list) * 1.15])\nax.set_facecolor('#f8f9fa')\nfig.patch.set_facecolor('white')\nplt.grid(axis='y', alpha=0.3, linestyle='--')\nplt.tight_layout()\n\nplt.savefig('sample_efficiency_comparison.png', dpi=300, bbox_inches='tight', facecolor='white')\nprint(\"✓ Saved: sample_efficiency_comparison.png\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-04T14:07:36.922317Z","iopub.execute_input":"2025-11-04T14:07:36.923168Z","iopub.status.idle":"2025-11-04T14:07:37.600497Z","shell.execute_reply.started":"2025-11-04T14:07:36.923140Z","shell.execute_reply":"2025-11-04T14:07:37.599747Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\nExporting detailed results to CSV...\")\n\n# Create detailed results dataframe\nresults_export = {\n    'Iteration': list(range(1, n_iterations + 1)),\n    'Random_Samples': results['Random']['n_samples'][:-1],\n    'Random_Accuracy': [f\"{acc:.4f}\" for acc in results['Random']['accuracy']],\n    'Random_Time': [f\"{t:.2f}\" for t in results['Random']['time_per_iter']],\n    'Uncertainty_Samples': results['Uncertainty']['n_samples'][:-1],\n    'Uncertainty_Accuracy': [f\"{acc:.4f}\" for acc in results['Uncertainty']['accuracy']],\n    'Uncertainty_Time': [f\"{t:.2f}\" for t in results['Uncertainty']['time_per_iter']],\n    'Margin_Samples': results['Margin']['n_samples'][:-1],\n    'Margin_Accuracy': [f\"{acc:.4f}\" for acc in results['Margin']['accuracy']],\n    'Margin_Time': [f\"{t:.2f}\" for t in results['Margin']['time_per_iter']]\n}\n\ndf_export = pd.DataFrame(results_export)\ndf_export.to_csv('active_learning_results.csv', index=False)\n\nprint(\"✓ Saved: active_learning_results.csv\")\nprint(\"\\n 40 rows:\")\nprint(df_export.head(40).to_string(index=False))\nprint(f\"\\n... {len(df_export)} total rows\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-04T14:08:36.005741Z","iopub.execute_input":"2025-11-04T14:08:36.006331Z","iopub.status.idle":"2025-11-04T14:08:36.018941Z","shell.execute_reply.started":"2025-11-04T14:08:36.006307Z","shell.execute_reply":"2025-11-04T14:08:36.018299Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n\" + \"=\"*80)\nprint(\"EXPERIMENT COMPLETED SUCCESSFULLY!\")\nprint(\"=\"*80)\n\nprint(\"\\n📊 GENERATED FILES:\")\nprint(\"   1. learning_curves.png - Main comparison plot\")\nprint(\"   2. sample_efficiency_comparison.png - Bar chart showing savings\")\nprint(\"   3. active_learning_results.csv - Detailed numerical results\")\n\nprint(\"\\n📈 KEY FINDINGS:\")\n\nfor strategy in ['Random', 'Uncertainty', 'Margin']:\n    n_samples = results[strategy]['n_samples'][:-1]\n    accuracies = results[strategy]['accuracy']\n    \n    samples_85 = find_samples_for_accuracy(n_samples, accuracies, 0.85)\n    samples_90 = find_samples_for_accuracy(n_samples, accuracies, 0.90)\n    final_acc = accuracies[-1]\n    total_time = sum(results[strategy]['time_per_iter'])\n    \n    print(f\"\\n   {strategy.upper()}:\")\n    print(f\"      • Initial accuracy (500 samples): {accuracies[0]:.4f}\")\n    print(f\"      • Samples for 85% accuracy: {samples_85}\")\n    print(f\"      • Samples for 90% accuracy: {samples_90}\")\n    print(f\"      • Final accuracy: {final_acc:.4f}\")\n    print(f\"      • Total training time: {total_time:.1f}s ({total_time/60:.1f} min)\")\n\n# Calculate savings\nrandom_90 = find_samples_for_accuracy(results['Random']['n_samples'][:-1], results['Random']['accuracy'], 0.90)\nunc_90 = find_samples_for_accuracy(results['Uncertainty']['n_samples'][:-1], results['Uncertainty']['accuracy'], 0.90)\nmargin_90 = find_samples_for_accuracy(results['Margin']['n_samples'][:-1], results['Margin']['accuracy'], 0.90)\n\nif isinstance(random_90, int) and isinstance(unc_90, int) and isinstance(margin_90, int):\n    print(f\"\\n💰 COST SAVINGS (@ $1/sample):\")\n    print(f\"   • Random: ${random_90:,.0f}\")\n    print(f\"   • Uncertainty: ${unc_90:,.0f} (Saves {((random_90 - unc_90) / random_90 * 100):.1f}%)\")\n    print(f\"   • Margin: ${margin_90:,.0f} (Saves {((random_90 - margin_90) / random_90 * 100):.1f}%)\")\n\nprint(\"\\n✅ NEXT STEPS:\")\nprint(\"   1. Download all PNG and CSV files from Output tab\")\nprint(\"   2. Share results screenshots\")\nprint(\"   3. Use these results to write your 12-page paper\")\n\nprint(\"\\n\" + \"=\"*80)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-04T14:08:40.939765Z","iopub.execute_input":"2025-11-04T14:08:40.940384Z","iopub.status.idle":"2025-11-04T14:08:40.948813Z","shell.execute_reply.started":"2025-11-04T14:08:40.940360Z","shell.execute_reply":"2025-11-04T14:08:40.948173Z"}},"outputs":[],"execution_count":null}]}