{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":14774,"databundleVersionId":875431,"sourceType":"competition"},{"sourceId":2812287,"sourceType":"datasetVersion","datasetId":1719146}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ===================================================================\n# SECTION 1: SETUP AND CONFIGURATION\n# ===================================================================\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Install necessary libraries\n!pip install -q timm albumentations scikit-plot scikit-learn opencv-python-headless coral-pytorch\n\nimport os\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport timm\nfrom torch.utils.data import Dataset, DataLoader, WeightedRandomSampler\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport torchvision.transforms as transforms\nfrom sklearn.model_selection import StratifiedKFold, train_test_split\nfrom sklearn.metrics import cohen_kappa_score, accuracy_score, confusion_matrix\nfrom coral_pytorch.dataset import levels_from_labelbatch\nimport matplotlib.pyplot as plt\nimport time\nfrom IPython.display import FileLink\nimport seaborn as sns\nimport torchvision.models as models\nfrom torch.optim.lr_scheduler import ReduceLROnPlateau\n\n# Set seeds for reproducibility\ndef set_seed(seed=42):\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    torch.backends.cudnn.deterministic = True\nset_seed()\n\n# Configuration (Enhanced for Publication Quality)\nIMG_SIZE = 384\nBATCH_SIZE = 32\nNUM_EPOCHS = 10  # Increased for thoroughness\nN_FOLDS = 3      # Increased to the gold standard for validation\nNUM_CLASSES = 5\nLR = 3e-4\nLABEL_SMOOTHING = 0.1\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {DEVICE}\")\n\n# ===================================================================\n# SECTION 2: DATASET HANDLING AND VISUALIZATION (FROM YOUR WORKING NOTEBOOK)\n# ===================================================================\n# This block is restored from your original, working notebook and error logs.\n# It correctly handles the column names and paths.\ntry:\n    print(\"Loading datasets with your original, verified paths...\")\n\n    # --- APTOS 2019 paths ---\n    aptos_df = pd.read_csv('/kaggle/input/aptos2019-blindness-detection/train.csv')\n    aptos_df['image_path'] = aptos_df['id_code'].apply(\n        lambda x: os.path.join('/kaggle/input/aptos2019-blindness-detection/train_images', f'{x}.png')\n    )\n    aptos_df = aptos_df[['image_path', 'diagnosis']]\n    aptos_df['dataset'] = 'aptos'\n\n    # --- IDRiD paths (Corrected based on your original notebook and logs) ---\n    idrid_df = pd.read_csv('/kaggle/input/idrid-dataset/idrid_labels.csv')\n    \n    # THE CORRECT LOGIC: The columns are 'id_code' and 'diagnosis' in this specific CSV.\n    idrid_df['image_path'] = idrid_df['id_code'].apply(\n        lambda x: os.path.join('/kaggle/input/idrid-dataset/Imagenes/Imagenes', f'{x}.jpg')\n    )\n    # The 'diagnosis' column is already named correctly in this file.\n    idrid_df = idrid_df[['image_path', 'diagnosis']]\n    idrid_df['dataset'] = 'idrid'\n\n    # Combine and verify paths\n    full_df = pd.concat([aptos_df, idrid_df], ignore_index=True)\n    full_df['exists'] = full_df['image_path'].apply(os.path.exists)\n\n    print(f\"Found {full_df['exists'].sum()} images out of {len(full_df)} total records.\")\n    if full_df['exists'].sum() < len(full_df):\n        print(\"Warning: Some image paths could not be found.\")\n\nexcept (FileNotFoundError, KeyError) as e:\n    print(f\"An error occurred while loading data: {e}\")\n    full_df = pd.DataFrame()\n\n\ndef visualize_dataset_statistics(df):\n    if df.empty or 'exists' not in df.columns or df['exists'].sum() == 0:\n        print(\"DataFrame is empty or no images found. Skipping visualization.\")\n        return\n        \n    df_existing = df[df['exists']].drop(columns=['exists'])\n    \n    fig, axes = plt.subplots(1, 2, figsize=(15, 6))\n    \n    class_dist = df_existing['diagnosis'].value_counts().sort_index()\n    axes[0].bar(range(5), class_dist.values, color=['#2ecc71', '#f1c40f', '#e67e22', '#e74c3c', '#8e44ad'])\n    axes[0].set_title('Class Distribution Across All Datasets', fontsize=14, fontweight='bold')\n    axes[0].set_xlabel('Diabetic Retinopathy Grade', fontweight='bold')\n    axes[0].set_ylabel('Number of Images', fontweight='bold')\n    axes[0].set_xticks(range(5))\n    axes[0].set_xticklabels(['No DR\\n(0)', 'Mild\\n(1)', 'Moderate\\n(2)', 'Severe\\n(3)', 'Proliferative\\n(4)'])\n    \n    class_weights_dict = dict(zip(range(5), 1.0 / class_dist.values))\n    weights_df = pd.DataFrame(list(class_weights_dict.items()), columns=['Grade', 'Weight'])\n    axes[1].bar(weights_df['Grade'], weights_df['Weight'], color=['#2ecc71', '#f1c40f', '#e67e22', '#e74c3c', '#8e44ad'])\n    axes[1].set_title('Class Balancing Weights', fontsize=14, fontweight='bold')\n    axes[1].set_xlabel('Diabetic Retinopathy Grade', fontweight='bold')\n    axes[1].set_ylabel('Class Weight (Inverse Frequency)', fontweight='bold')\n    axes[1].set_xticks(range(5))\n\n    plt.tight_layout()\n    plt.savefig('dataset_statistics.png', dpi=300, bbox_inches='tight')\n    plt.show()\n\nvisualize_dataset_statistics(full_df)\n\n# ===================================================================\n# SECTION 3: IMAGE PREPROCESSING AND AUGMENTATION\n# ===================================================================\ndef adaptive_circle_crop(img, tol=7):\n    if img is None: return np.zeros((IMG_SIZE, IMG_SIZE, 3), dtype=np.uint8)\n    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n    mask = gray > tol\n    if not np.any(mask): return cv2.resize(img, (IMG_SIZE, IMG_SIZE))\n    return img[np.ix_(mask.any(1), mask.any(0))]\n\ndef ben_graham_preprocess(img):\n    img = adaptive_circle_crop(img)\n    img = cv2.resize(img, (IMG_SIZE, IMG_SIZE))\n    img = cv2.addWeighted(img, 4, cv2.GaussianBlur(img, (0,0), 30), -4, 128)\n    return img\n\nclass DRDataset(Dataset):\n    def __init__(self, df, transform=None):\n        self.df = df\n        self.transform = transform\n    def __len__(self):\n        return len(self.df)\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img = cv2.imread(row['image_path'])\n        if img is None: \n            print(f\"Warning: Could not read image {row['image_path']}. Returning blank image.\")\n            return torch.zeros((3, IMG_SIZE, IMG_SIZE)), torch.tensor(0, dtype=torch.long)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        img = ben_graham_preprocess(img)\n        if self.transform:\n            img = self.transform(image=img)['image']\n        return img, torch.tensor(row['diagnosis'], dtype=torch.long)\n\ndef get_train_transforms():\n    return A.Compose([\n        A.HorizontalFlip(p=0.5),\n        A.ShiftScaleRotate(shift_limit=0.1, scale_limit=0.1, rotate_limit=30, p=0.5),\n        A.RandomBrightnessContrast(brightness_limit=0.2, contrast_limit=0.2, p=0.5),\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        ToTensorV2()\n    ])\n\ndef get_val_transforms():\n    return A.Compose([\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        ToTensorV2()\n    ])\n\n# ===================================================================\n# SECTION 4: MODEL ARCHITECTURE DEFINITIONS (REVISED AND FIXED)\n# ===================================================================\nclass MobileNetV3_CORAL(nn.Module):\n    def __init__(self, num_classes=5, pretrained=True):\n        super().__init__()\n        # 1. Create the backbone as before\n        self.backbone = timm.create_model(\n            'mobilenetv3_large_100',\n            pretrained=pretrained,\n            num_classes=0  # Removes the original classifier\n        )\n        \n        # 2. Determine the TRUE feature size with a dummy forward pass.\n        # This is the robust way to find the size, which solves the error.\n        with torch.no_grad():\n            dummy_input = torch.randn(1, 3, IMG_SIZE, IMG_SIZE)\n            # The 'in_features' will now correctly be 1280\n            in_features = self.backbone(dummy_input).shape[1]\n            \n        # 3. Create the head layer with the CORRECTLY determined size\n        self.head = nn.Linear(in_features, num_classes - 1)\n        \n    def forward(self, x):\n        # The forward pass is now simple and correct\n        return self.head(self.backbone(x))\n\nclass BaselineResNet50(nn.Module):\n    def __init__(self, num_classes=5):\n        super().__init__()\n        self.model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1)\n        self.model.fc = nn.Linear(self.model.fc.in_features, num_classes)\n    def forward(self, x):\n        return self.model(x)\n\n\n# ===================================================================\n# SECTION 5: LOSS FUNCTION AND TRAINING UTILITIES\n# ===================================================================\ndef coral_loss_with_smoothing(logits, labels, smoothing=0.0):\n    levels = levels_from_labelbatch(labels, num_classes=NUM_CLASSES).float().to(logits.device)\n    if smoothing > 0.0:\n        levels = levels * (1 - smoothing) + 0.5 * smoothing\n    return F.binary_cross_entropy_with_logits(logits, levels, reduction='mean')\n\ndef train_one_epoch(model, loader, optimizer):\n    model.train()\n    total_loss = 0\n    for images, labels in loader:\n        optimizer.zero_grad()\n        logits = model(images.to(DEVICE))\n        loss = coral_loss_with_smoothing(logits, labels.to(DEVICE), LABEL_SMOOTHING)\n        loss.backward()\n        optimizer.step()\n        total_loss += loss.item()\n    return total_loss / len(loader)\n\ndef evaluate(model, loader):\n    model.eval()\n    all_labels, all_preds = [], []\n    with torch.no_grad():\n        for images, labels in loader:\n            logits = model(images.to(DEVICE))\n            preds = torch.sum(torch.sigmoid(logits) > 0.5, dim=1)\n            all_labels.extend(labels.cpu().numpy())\n            all_preds.extend(preds.cpu().numpy())\n    return cohen_kappa_score(all_labels, all_preds, weights='quadratic'), accuracy_score(all_labels, all_preds), all_labels, all_preds\n    \ndef train_and_evaluate_baseline(train_loader, val_loader, model, epochs):\n    model.to(DEVICE)\n    criterion = nn.CrossEntropyLoss()\n    optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)\n    history = {'kappa': [], 'accuracy': []}\n    for epoch in range(epochs):\n        model.train()\n        for images, labels in train_loader:\n            optimizer.zero_grad()\n            outputs = model(images.to(DEVICE))\n            loss = criterion(outputs, labels.to(DEVICE))\n            loss.backward()\n            optimizer.step()\n        model.eval()\n        val_labels, val_preds = [], []\n        with torch.no_grad():\n            for images, labels in val_loader:\n                outputs = model(images.to(DEVICE))\n                _, predicted = torch.max(outputs.data, 1)\n                val_labels.extend(labels.cpu().numpy())\n                val_preds.extend(predicted.cpu().numpy())\n        kappa, acc = cohen_kappa_score(val_labels, val_preds, weights='quadratic'), accuracy_score(val_labels, val_preds)\n        history['kappa'].append(kappa); history['accuracy'].append(acc)\n        print(f\"Epoch {epoch+1}/{epochs} -> Val Kappa: {kappa:.4f}, Val Accuracy: {acc:.4f}\")\n    return model, {'Kappa': history['kappa'][-1], 'Accuracy': history['accuracy'][-1]}\n\n# ===================================================================\n# SECTION 6: MAIN EXPERIMENT - K-FOLD CROSS-VALIDATION\n# ===================================================================\nprint(\"\\n\" + \"=\"*80 + \"\\nRUNNING MAIN EXPERIMENT: MobileNetV3 + CORAL with 5-Fold Cross-Validation\\n\" + \"=\"*80)\nskf = StratifiedKFold(n_splits=N_FOLDS, shuffle=True, random_state=42)\ndata_df = full_df[full_df['exists']].reset_index(drop=True)\nfold_models, main_model_results_list = [], []\n\nfor fold, (train_idx, val_idx) in enumerate(skf.split(data_df, data_df['diagnosis'])):\n    print(f\"\\n----- Fold {fold+1}/{N_FOLDS} -----\")\n    train_df, val_df = data_df.iloc[train_idx], data_df.iloc[val_idx]\n    class_weights = 1. / train_df['diagnosis'].value_counts().sort_index()\n    sample_weights = train_df['diagnosis'].map(class_weights.to_dict()).values\n    train_sampler = WeightedRandomSampler(weights=sample_weights, num_samples=len(sample_weights), replacement=True)\n    train_loader = DataLoader(DRDataset(train_df, get_train_transforms()), batch_size=BATCH_SIZE, sampler=train_sampler)\n    val_loader = DataLoader(DRDataset(val_df, get_val_transforms()), batch_size=BATCH_SIZE, shuffle=False)\n    model = MobileNetV3_CORAL().to(DEVICE)\n    optimizer = torch.optim.Adam(model.parameters(), lr=LR)\n    best_kappa = -1\n    for epoch in range(NUM_EPOCHS):\n        train_loss = train_one_epoch(model, train_loader, optimizer)\n        val_kappa, val_acc, _, _ = evaluate(model, val_loader)\n        print(f\"Epoch {epoch+1}/{NUM_EPOCHS} -> Train Loss: {train_loss:.4f}, Val Kappa: {val_kappa:.4f}\")\n        if val_kappa > best_kappa:\n            best_kappa = val_kappa\n            torch.save(model.state_dict(), f'best_model_fold_{fold+1}.pth')\n    model.load_state_dict(torch.load(f'best_model_fold_{fold+1}.pth'))\n    fold_models.append(model)\n    final_kappa, final_acc, _, _ = evaluate(model, val_loader)\n    main_model_results_list.append({'Kappa': final_kappa, 'Accuracy': final_acc})\n\n# ===================================================================\n# SECTION 7: BASELINE AND ABLATION STUDIES\n# ===================================================================\n# train_df_single, val_df_single = train_test_split(data_df, test_size=0.2, random_state=42, stratify=data_df['diagnosis'])\n# baseline_loader_train = DataLoader(DRDataset(train_df_single, get_train_transforms()), batch_size=BATCH_SIZE, shuffle=True)\n# baseline_loader_val = DataLoader(DRDataset(val_df_single, get_val_transforms()), batch_size=BATCH_SIZE, shuffle=False)\n\n# print(\"\\n\" + \"=\"*80 + \"\\nRUNNING BASELINE: ResNet50 with Cross-Entropy Loss\\n\" + \"=\"*80)\n# _, baseline_results = train_and_evaluate_baseline(baseline_loader_train, baseline_loader_val, BaselineResNet50(), epochs=NUM_EPOCHS)\n\n# print(\"\\n\" + \"=\"*80 + \"\\nRUNNING ABLATION 1: MobileNetV3 without CORAL\\n\" + \"=\"*80)\n# ablation_nocoral_model = MobileNetV3_CORAL()\n# ablation_nocoral_model.head = nn.Linear(ablation_nocoral_model.backbone.num_features, NUM_CLASSES)\n# _, ablation_nocoral_results = train_and_evaluate_baseline(baseline_loader_train, baseline_loader_val, ablation_nocoral_model, epochs=NUM_EPOCHS)\n\n# print(\"\\n\" + \"=\"*80 + \"\\nRUNNING ABLATION 2: CORAL Model without Advanced Preprocessing\\n\" + \"=\"*80)\n# class DRDatasetSimple(Dataset):\n#     def __init__(self, df, transform): self.df, self.transform = df, transform\n#     def __len__(self): return len(self.df)\n#     def __getitem__(self, idx):\n#         img = cv2.imread(self.df.iloc[idx]['image_path'])\n#         if img is not None:\n#             img = self.transform(img)\n#         else:\n#             print(f\"Warning: Could not read image {self.df.iloc[idx]['image_path']}. Returning blank image.\")\n#             return torch.zeros((3, IMG_SIZE, IMG_SIZE)), torch.tensor(0, dtype=torch.long)\n#         return img, torch.tensor(self.df.iloc[idx]['diagnosis'], dtype=torch.long)\n\n# simple_transforms = transforms.Compose([transforms.ToPILImage(), transforms.Resize((IMG_SIZE, IMG_SIZE)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])\n# nopreproc_loader_train = DataLoader(DRDatasetSimple(train_df_single, simple_transforms), batch_size=BATCH_SIZE, shuffle=True)\n# nopreproc_loader_val = DataLoader(DRDatasetSimple(val_df_single, simple_transforms), batch_size=BATCH_SIZE, shuffle=False)\n# model_nopreproc = MobileNetV3_CORAL().to(DEVICE)\n# optimizer_nopreproc = torch.optim.Adam(model_nopreproc.parameters(), lr=LR)\n# for epoch in range(NUM_EPOCHS):\n#     train_one_epoch(model_nopreproc, nopreproc_loader_train, optimizer_nopreproc)\n#     val_kappa, _, _, _ = evaluate(model_nopreproc, nopreproc_loader_val)\n#     print(f\"Epoch {epoch+1}/{NUM_EPOCHS} -> Val Kappa: {val_kappa:.4f}\")\n# ablation_nopreproc_kappa, ablation_nopreproc_accuracy, _, _ = evaluate(model_nopreproc, nopreproc_loader_val)\n# ablation_nopreproc_results = {'Kappa': ablation_nopreproc_kappa, 'Accuracy': ablation_nopreproc_accuracy}\n\n# ===================================================================\n# SECTION 8: RESULTS VISUALIZATION AND ANALYSIS (FOR MAIN MODEL NOTEBOOK)\n# ===================================================================\n# Calculate and print the average performance across all folds\nmain_model_avg_kappa = np.mean([r['Kappa'] for r in main_model_results_list])\nmain_model_avg_accuracy = np.mean([r['Accuracy'] for r in main_model_results_list])\nprint(\"\\n\" + \"=\"*80)\nprint(f\"Main Model Average Performance Across {N_FOLDS} Folds:\")\nprint(f\"Average Quadratic Kappa: {main_model_avg_kappa:.4f}\")\nprint(f\"Average Accuracy: {main_model_avg_accuracy:.4f}\")\nprint(\"=\"*80 + \"\\n\")\n\n# --- Analysis of the single best model from the cross-validation ---\n\n# Find the best model from the completed folds\nbest_fold_idx = np.argmax([res['Kappa'] for res in main_model_results_list])\nbest_model = fold_models[best_fold_idx]\nprint(f\"Best model from Fold {best_fold_idx+1} with Kappa: {main_model_results_list[best_fold_idx]['Kappa']:.4f} selected for detailed analysis.\")\n\n# Create a fresh DataLoader for the validation set of the best fold\n_, best_val_idx = list(skf.split(data_df, data_df['diagnosis']))[best_fold_idx]\nbest_val_loader = DataLoader(DRDataset(data_df.iloc[best_val_idx], get_val_transforms()), batch_size=BATCH_SIZE, shuffle=False)\n_, _, final_labels, final_preds = evaluate(best_model, best_val_loader)\n\n# Plot Confusion Matrix for the best model\ncm = confusion_matrix(final_labels, final_preds)\nplt.figure(figsize=(10, 8))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=range(5), yticklabels=range(5))\nplt.title('Confusion Matrix for Best Performing Model', fontsize=16, fontweight='bold')\nplt.xlabel('Predicted Label', fontweight='bold')\nplt.ylabel('True Label', fontweight='bold')\nplt.savefig('confusion_matrix.png', dpi=300)\nplt.show()\n\n# --- In-depth Error Analysis for the best model ---\nworst_predictions = []\nwith torch.no_grad():\n    # We need to re-get the original images for visualization\n    best_val_df = data_df.iloc[best_val_idx]\n    best_val_dataset_for_viz = DRDataset(best_val_df, get_val_transforms())\n    viz_loader = DataLoader(best_val_dataset_for_viz, batch_size=BATCH_SIZE, shuffle=False)\n    \n    all_images = []\n    for images, _ in viz_loader:\n        all_images.append(images)\n    all_images = torch.cat(all_images, dim=0)\n\n    for i in range(len(final_preds)):\n        true_label, pred_label = final_labels[i], final_preds[i]\n        if true_label != pred_label:\n            worst_predictions.append((abs(true_label - pred_label), all_images[i], true_label, pred_label))\n\nworst_predictions.sort(key=lambda x: x[0], reverse=True)\nfig, axes = plt.subplots(2, 5, figsize=(20, 9))\naxes = axes.ravel()\nfor i in range(min(10, len(worst_predictions))):\n    _, image, true, pred = worst_predictions[i]\n    image = image.permute(1, 2, 0).numpy()\n    mean, std = np.array([0.485, 0.456, 0.406]), np.array([0.229, 0.224, 0.225])\n    image = std * image + mean\n    image = np.clip(image, 0, 1)\n    axes[i].imshow(image)\n    axes[i].set_title(f'True: {true}, Pred: {pred}', color='red', fontweight='bold')\n    axes[i].axis('off')\nplt.suptitle('Top 10 Worst Predictions (Best Model)', fontsize=20, fontweight='bold')\nplt.savefig('worst_predictions.png', dpi=300)\nplt.show()\n\n# ===================================================================\n# SECTION 9: MODEL CALIBRATION AND EXPORT FOR MOBILE\n# ===================================================================\nprint(\"\\n\" + \"=\"*80 + \"\\nEXPORTING BEST MODEL FOR MOBILE DEPLOYMENT\\n\" + \"=\"*80)\nbest_model.eval()\nlogits_list, labels_list = [], []\nwith torch.no_grad():\n    for images, labels in best_val_loader:\n        logits_list.append(best_model(images.to(DEVICE)))\n        labels_list.append(labels)\nlogits_all, labels_all = torch.cat(logits_list), torch.cat(labels_list)\ntemperature = nn.Parameter(torch.ones(1).to(DEVICE))\noptimizer = torch.optim.LBFGS([temperature], lr=0.01, max_iter=50)\ndef calib_eval():\n    optimizer.zero_grad()\n    loss = coral_loss_with_smoothing(logits_all / temperature, labels_all.to(DEVICE))\n    loss.backward()\n    return loss\noptimizer.step(calib_eval)\nfinal_temp = temperature.item()\nprint(f\"Optimal calibration temperature found: {final_temp:.4f}\")\n\nclass FinalMobileModel(nn.Module):\n    def __init__(self, model, temp): super().__init__(); self.model, self.temperature = model, temp\n    def forward(self, x): return torch.sum(torch.sigmoid(self.model(x) / self.temperature) > 0.5, dim=1)\n\nexportable_model = FinalMobileModel(best_model.to('cpu'), final_temp)\nexportable_model.eval()\ntraced_model = torch.jit.trace(exportable_model, torch.randn(1, 3, IMG_SIZE, IMG_SIZE))\nfrom torch.utils.mobile_optimizer import optimize_for_mobile\noptimized_model = optimize_for_mobile(traced_model)\noptimized_model._save_for_lite_interpreter(\"dr_mobilenetv3_mobile.ptl\")\nprint(\"\\nModel successfully exported to 'dr_mobilenetv3_mobile.ptl'\")\nFileLink(\"dr_mobilenetv3_mobile.ptl\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null}]}