{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":6799,"databundleVersionId":4225553,"sourceType":"competition"},{"sourceId":1462296,"sourceType":"datasetVersion","datasetId":857191},{"sourceId":12464516,"sourceType":"datasetVersion","datasetId":7609993},{"sourceId":508713,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":403514,"modelId":421446}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Data Preprocessing","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport shutil\nimport os\nfrom PIL import Image\nfrom pycocotools.coco import COCO\nimport random\n\n# Đường dẫn dataset\nimagenet_dir = '/kaggle/input/imagenet-object-localization-challenge'\ncoco_dir = '/kaggle/input/coco-2017-dataset/coco2017'\noutput_dir = '/kaggle/working/dataset'\n\n# Tạo thư mục output\nos.makedirs(os.path.join(output_dir, 'images'), exist_ok=True)\nos.makedirs(os.path.join(output_dir, 'labels'), exist_ok=True)\n\n# Mapping classes\n# YOLO class IDs: 0=person, 1=phone, 2=reflex_camera, 3=polaroid_camera\nimagenet_classes = {\n    'n02992529': 1,  # mobile phone -> phone\n    'n04069434': 2,  # reflex camera\n    'n03976467': 3   # Polaroid camera\n}\n\ncoco_classes = {\n    1: 0,   # person (COCO ID: 1) -> YOLO ID: 0\n    77: 1   # cell phone (COCO ID: 77, không phải 68!) -> YOLO ID: 1\n}\n\n# Counter để theo dõi số lượng mẫu\nclass_counts = {0: 0, 1: 0, 2: 0, 3: 0}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T01:13:27.213604Z","iopub.execute_input":"2025-08-06T01:13:27.213863Z","iopub.status.idle":"2025-08-06T01:13:27.529983Z","shell.execute_reply.started":"2025-08-06T01:13:27.213843Z","shell.execute_reply":"2025-08-06T01:13:27.529221Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"=== Xử lý ImageNet ===\")\n# Xử lý ImageNet\ntry:\n    annotations_file = os.path.join(imagenet_dir, 'LOC_train_solution.csv')\n    annotations = pd.read_csv(annotations_file)\n    \n    # Lọc annotations cho các class cần thiết\n    desired_classes = list(imagenet_classes.keys())\n    filtered_annotations = annotations[annotations['PredictionString'].str.contains('|'.join(desired_classes))]\n    \n    print(f\"Tìm thấy {len(filtered_annotations)} annotations từ ImageNet\")\n    \n    for idx, row in filtered_annotations.iterrows():\n        image_id = row['ImageId']\n        predictions = row['PredictionString'].split()\n        \n        # Xử lý từng bounding box trong prediction\n        i = 0\n        while i < len(predictions):\n            if predictions[i] in desired_classes:\n                class_id = predictions[i]\n                xmin, ymin, xmax, ymax = map(float, predictions[i+1:i+5])\n                \n                # Đường dẫn hình ảnh\n                image_path = os.path.join(imagenet_dir, 'ILSVRC/Data/CLS-LOC/train', class_id, f'{image_id}.JPEG')\n                if not os.path.exists(image_path):\n                    i += 5\n                    continue\n                \n                # Sao chép hình ảnh (chỉ sao chép 1 lần)\n                output_image_path = os.path.join(output_dir, 'images', f'imagenet_{image_id}.jpg')\n                if not os.path.exists(output_image_path):\n                    shutil.copy(image_path, output_image_path)\n                \n                # Lấy kích thước hình ảnh\n                with Image.open(image_path) as img:\n                    img_width, img_height = img.size\n                \n                # Chuyển đổi sang format YOLO\n                x_center = (xmin + xmax) / 2 / img_width\n                y_center = (ymin + ymax) / 2 / img_height\n                width_norm = (xmax - xmin) / img_width\n                height_norm = (ymax - ymin) / img_height\n                \n                yolo_class_id = imagenet_classes[class_id]\n                label = f\"{yolo_class_id} {x_center:.6f} {y_center:.6f} {width_norm:.6f} {height_norm:.6f}\\n\"\n                \n                # Lưu hoặc append label\n                label_path = os.path.join(output_dir, 'labels', f'imagenet_{image_id}.txt')\n                mode = 'a' if os.path.exists(label_path) else 'w'\n                with open(label_path, mode) as f:\n                    f.write(label)\n                \n                class_counts[yolo_class_id] += 1\n                i += 5\n            else:\n                i += 1\n                \nexcept Exception as e:\n    print(f\"Lỗi khi xử lý ImageNet: {e}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T01:13:30.132082Z","iopub.execute_input":"2025-08-06T01:13:30.13278Z","iopub.status.idle":"2025-08-06T01:13:33.135041Z","shell.execute_reply.started":"2025-08-06T01:13:30.132756Z","shell.execute_reply":"2025-08-06T01:13:33.134449Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n=== Xử lý COCO ===\")\n# Xử lý COCO\ntry:\n    # Load COCO annotations\n    coco = COCO(os.path.join(coco_dir, 'annotations/instances_train2017.json'))\n    \n    # Lấy danh sách image IDs cho mỗi category\n    person_img_ids = coco.getImgIds(catIds=[1])  # person\n    phone_img_ids = coco.getImgIds(catIds=[77])  # cell phone (ID chính xác là 77!)\n    \n    # Để cân bằng dataset, giới hạn số lượng ảnh person\n    max_person_images = 2000  # Giới hạn để tránh mất cân bằng\n    person_img_ids = random.sample(person_img_ids, min(len(person_img_ids), max_person_images))\n    \n    # Combine tất cả image IDs (loại bỏ duplicates)\n    all_img_ids = list(set(person_img_ids + phone_img_ids))\n    \n    print(f\"Tìm thấy {len(person_img_ids)} ảnh có person\")\n    print(f\"Tìm thấy {len(phone_img_ids)} ảnh có cell phone\")\n    print(f\"Tổng cộng {len(all_img_ids)} ảnh unique từ COCO\")\n    \n    processed_count = 0\n    for img_id in all_img_ids:\n        img_info = coco.loadImgs(img_id)[0]\n        \n        # Lấy tất cả annotations cho image này (cả person và cell phone)\n        ann_ids = coco.getAnnIds(imgIds=img_id, catIds=[1, 77])\n        anns = coco.loadAnns(ann_ids)\n        \n        if not anns:\n            continue\n            \n        # Sao chép hình ảnh\n        image_path = os.path.join(coco_dir, 'train2017', img_info['file_name'])\n        if not os.path.exists(image_path):\n            continue\n            \n        output_image_path = os.path.join(output_dir, 'images', f'coco_{img_info[\"file_name\"]}')\n        shutil.copy(image_path, output_image_path)\n        \n        # Tạo label file\n        label_path = os.path.join(output_dir, 'labels', f'coco_{os.path.splitext(img_info[\"file_name\"])[0]}.txt')\n        \n        with open(label_path, 'w') as f:\n            for ann in anns:\n                if ann['category_id'] in coco_classes:\n                    # Chuyển đổi COCO bbox sang YOLO format\n                    bbox = ann['bbox']  # [x, y, width, height]\n                    x, y, w, h = bbox\n                    \n                    # Kiểm tra bbox hợp lệ\n                    if w <= 0 or h <= 0:\n                        continue\n                        \n                    x_center = (x + w/2) / img_info['width']\n                    y_center = (y + h/2) / img_info['height']\n                    width_norm = w / img_info['width']\n                    height_norm = h / img_info['height']\n                    \n                    # Đảm bảo giá trị nằm trong [0, 1]\n                    x_center = max(0, min(1, x_center))\n                    y_center = max(0, min(1, y_center))\n                    width_norm = max(0, min(1, width_norm))\n                    height_norm = max(0, min(1, height_norm))\n                    \n                    yolo_class_id = coco_classes[ann['category_id']]\n                    f.write(f\"{yolo_class_id} {x_center:.6f} {y_center:.6f} {width_norm:.6f} {height_norm:.6f}\\n\")\n                    class_counts[yolo_class_id] += 1\n        \n        processed_count += 1\n        if processed_count % 500 == 0:\n            print(f\"Đã xử lý {processed_count} ảnh từ COCO...\")\n            \nexcept Exception as e:\n    print(f\"Lỗi khi xử lý COCO: {e}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T01:13:33.69926Z","iopub.execute_input":"2025-08-06T01:13:33.699519Z","iopub.status.idle":"2025-08-06T01:14:14.943075Z","shell.execute_reply.started":"2025-08-06T01:13:33.699501Z","shell.execute_reply":"2025-08-06T01:14:14.942406Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\nprint(\"\\n=== Thống kê dataset ===\")\nfor class_id, count in class_counts.items():\n    class_names = ['person', 'phone', 'reflex_camera', 'polaroid_camera']\n    print(f\"{class_names[class_id]}: {count} instances\")\n\n# Tạo train và validation split\nall_images = [f for f in os.listdir(os.path.join(output_dir, 'images')) if f.endswith(('.jpg', '.jpeg', '.png'))]\nrandom.shuffle(all_images)\n\n# 80% train, 20% val\nsplit_idx = int(0.8 * len(all_images))\ntrain_images = all_images[:split_idx]\nval_images = all_images[split_idx:]\n\nprint(f\"\\nTổng số ảnh: {len(all_images)}\")\nprint(f\"Train: {len(train_images)}, Val: {len(val_images)}\")\n\n# Tạo train.txt và val.txt\nwith open(os.path.join(output_dir, 'train.txt'), 'w') as f:\n    for img in train_images:\n        f.write(os.path.join(output_dir, 'images', img) + '\\n')\n\nwith open(os.path.join(output_dir, 'val.txt'), 'w') as f:\n    for img in val_images:\n        f.write(os.path.join(output_dir, 'images', img) + '\\n')\n\n# Tạo data.yaml\nyaml_content = f\"\"\"\ntrain: {os.path.join(output_dir, 'train.txt')}\nval: {os.path.join(output_dir, 'val.txt')}\n\nnc: 4\nnames: ['person', 'phone', 'reflex_camera', 'polaroid_camera']\n\n# Augmentation parameters để tăng cường nhận diện person\nmosaic: 1.0\nmixup: 0.5\ncopy_paste: 0.1\n\"\"\"\n\nwith open(os.path.join(output_dir, 'data.yaml'), 'w') as f:\n    f.write(yaml_content)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T01:14:20.563318Z","iopub.execute_input":"2025-08-06T01:14:20.563608Z","iopub.status.idle":"2025-08-06T01:14:20.59775Z","shell.execute_reply.started":"2025-08-06T01:14:20.563586Z","shell.execute_reply":"2025-08-06T01:14:20.596958Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Knowledge Distillation","metadata":{}},{"cell_type":"code","source":"# Knowledge Distillation cho YOLOv11 với Dataset từ ImageNet + COCO\n# Fixed version - xử lý lỗi batch collation\n\nimport pandas as pd\nimport shutil\nimport os\nfrom PIL import Image\nfrom pycocotools.coco import COCO\nimport random\nimport yaml\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import DataLoader, Dataset\nimport numpy as np\nimport cv2\nimport time\nimport matplotlib.pyplot as plt\nfrom pathlib import Path\nfrom tqdm import tqdm\nfrom datetime import datetime\n\n# Install ultralytics\n!pip install ultralytics -q\n\nfrom ultralytics import YOLO\nfrom ultralytics.utils.plotting import Annotator, colors\n\n# Check GPU\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Using device: {device}\")\nif torch.cuda.is_available():\n    print(f\"GPU: {torch.cuda.get_device_name(0)}\")\n\n# ====================== CUSTOM COLLATE FUNCTION ======================\n\ndef custom_collate_fn(batch):\n    \"\"\"Custom collate function to handle variable-sized labels\"\"\"\n    images = []\n    labels_list = []\n    paths = []\n    \n    for img, labels, path in batch:\n        images.append(img)\n        labels_list.append(labels)\n        paths.append(path)\n    \n    # Stack images normally\n    images = torch.stack(images, 0)\n    \n    # For labels, we keep them as a list since they have different sizes\n    # Each label tensor has shape [num_boxes, 5] where 5 = [class, x, y, w, h]\n    \n    return images, labels_list, paths\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T01:14:25.361886Z","iopub.execute_input":"2025-08-06T01:14:25.362232Z","iopub.status.idle":"2025-08-06T01:14:30.653417Z","shell.execute_reply.started":"2025-08-06T01:14:25.362177Z","shell.execute_reply":"2025-08-06T01:14:30.652446Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Dataset class","metadata":{}},{"cell_type":"code","source":"# ====================== DATASET CLASS ======================\n\nclass ScreenPhotoDataset(Dataset):\n    \"\"\"Custom dataset for screen photography detection\"\"\"\n    \n    def __init__(self, data_yaml_path, mode='train', img_size=640, augment=True):\n        self.img_size = img_size\n        self.mode = mode\n        self.augment = augment and mode == 'train'\n        \n        # Load data configuration\n        with open(data_yaml_path, 'r') as f:\n            self.data_config = yaml.safe_load(f)\n        \n        # Get image paths\n        txt_file = self.data_config['train'] if mode == 'train' else self.data_config['val']\n        with open(txt_file, 'r') as f:\n            self.img_paths = [line.strip() for line in f.readlines()]\n        \n        # Class names\n        self.class_names = self.data_config.get('names', ['person', 'phone', 'reflex_camera', 'polaroid_camera'])\n        \n        print(f\"Loaded {len(self.img_paths)} {mode} images\")\n    \n    def __len__(self):\n        return len(self.img_paths)\n    \n    def letterbox(self, img, new_shape=(640, 640), color=(114, 114, 114)):\n        \"\"\"Resize and pad image while maintaining aspect ratio\"\"\"\n        shape = img.shape[:2]  # current shape [height, width]\n        \n        # Scale ratio (new / old)\n        r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])\n        \n        # Compute padding\n        new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))\n        dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]  # wh padding\n        \n        dw /= 2  # divide padding into 2 sides\n        dh /= 2\n        \n        if shape[::-1] != new_unpad:  # resize\n            img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)\n        \n        top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))\n        left, right = int(round(dw - 0.1)), int(round(dw + 0.1))\n        img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)\n        \n        return img, r, (dw, dh)\n    \n    def __getitem__(self, idx):\n        # Load image\n        img_path = self.img_paths[idx]\n        img = cv2.imread(img_path)\n        if img is None:\n            print(f\"Warning: Could not load image {img_path}\")\n            return self.__getitem__((idx + 1) % len(self))\n        \n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        h0, w0 = img.shape[:2]  # original hw\n        \n        # Letterbox\n        img, ratio, pad = self.letterbox(img, new_shape=(self.img_size, self.img_size))\n        \n        # Load labels\n        label_path = img_path.replace('images', 'labels').replace('.jpg', '.txt').replace('.png', '.txt')\n        \n        labels = []\n        if os.path.exists(label_path):\n            with open(label_path, 'r') as f:\n                for line in f:\n                    parts = line.strip().split()\n                    if len(parts) == 5:\n                        cls, x, y, w, h = map(float, parts)\n                        labels.append([cls, x, y, w, h])\n        \n        labels = np.array(labels).reshape(-1, 5) if labels else np.zeros((0, 5))\n        \n        # Convert normalized to pixel coordinates\n        if len(labels):\n            labels[:, 1] = w0 * labels[:, 1] + pad[0]  # x padding\n            labels[:, 2] = h0 * labels[:, 2] + pad[1]  # y padding\n            labels[:, 3] *= w0  # width\n            labels[:, 4] *= h0  # height\n            \n            # Convert pixel to normalized with padding\n            labels[:, 1] /= self.img_size\n            labels[:, 2] /= self.img_size\n            labels[:, 3] /= self.img_size\n            labels[:, 4] /= self.img_size\n        \n        # Apply augmentations if training\n        if self.augment:\n            # Random horizontal flip\n            if random.random() < 0.5:\n                img = np.fliplr(img)\n                if len(labels):\n                    labels[:, 1] = 1 - labels[:, 1]\n            \n            # Random HSV augmentation\n            if random.random() < 0.5:\n                img_hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV).astype(np.float32)\n                h_gain = random.uniform(-0.015, 0.015)\n                s_gain = random.uniform(0.7, 1.3)\n                v_gain = random.uniform(0.4, 1.6)\n                \n                img_hsv[..., 0] = (img_hsv[..., 0] + h_gain * 180) % 180\n                img_hsv[..., 1] = np.clip(img_hsv[..., 1] * s_gain, 0, 255)\n                img_hsv[..., 2] = np.clip(img_hsv[..., 2] * v_gain, 0, 255)\n                \n                img = cv2.cvtColor(img_hsv.astype(np.uint8), cv2.COLOR_HSV2RGB)\n        \n        # Normalize and convert to tensor\n        img = img.astype(np.float32) / 255.0\n        img = torch.from_numpy(img).permute(2, 0, 1).contiguous()\n        \n        labels = torch.from_numpy(labels).float()\n        \n        return img, labels, img_path","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T01:14:35.847354Z","iopub.execute_input":"2025-08-06T01:14:35.848004Z","iopub.status.idle":"2025-08-06T01:14:35.867494Z","shell.execute_reply.started":"2025-08-06T01:14:35.847975Z","shell.execute_reply":"2025-08-06T01:14:35.866753Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Distilation Class","metadata":{}},{"cell_type":"code","source":"# ====================== DISTILLATION TRAINER ======================\n\nclass YOLODistillationTrainer:\n    \"\"\"Trainer for knowledge distillation\"\"\"\n    \n    def __init__(self, teacher_path, student_type='n', output_dir='/kaggle/working/distillation'):\n        self.teacher_path = teacher_path\n        self.student_type = student_type\n        self.output_dir = output_dir\n        os.makedirs(output_dir, exist_ok=True)\n        \n        # Load teacher\n        print(\"Loading teacher model...\")\n        self.teacher = YOLO(teacher_path)\n        self.teacher.model.eval()\n        \n        # Create student\n        print(f\"Creating student model ({student_type})...\")\n        student_models = {\n            'n': 'yolo11n.pt',\n            's': 'yolo11s.pt', \n            'm': 'yolo11m.pt'\n        }\n        self.student = YOLO(student_models[student_type])\n        \n        # Print model comparison\n        self._print_model_comparison()\n        \n    def _print_model_comparison(self):\n        \"\"\"Print teacher vs student comparison\"\"\"\n        teacher_params = sum(p.numel() for p in self.teacher.model.parameters())\n        student_params = sum(p.numel() for p in self.student.model.parameters())\n        \n        print(\"\\n\" + \"=\"*50)\n        print(\"MODEL COMPARISON\")\n        print(\"=\"*50)\n        print(f\"Teacher parameters: {teacher_params/1e6:.2f}M\")\n        print(f\"Student parameters: {student_params/1e6:.2f}M\")\n        print(f\"Compression ratio: {teacher_params/student_params:.1f}x\")\n        print(\"=\"*50 + \"\\n\")\n    \n    def distillation_loss(self, student_pred, teacher_pred, labels_list, alpha=0.7, temperature=4.0):\n        \"\"\"Calculate combined distillation and task loss\"\"\"\n        device = student_pred[0].device\n        batch_size = len(labels_list)\n        \n        # Task loss (student vs ground truth)\n        task_loss = 0\n        \n        # For YOLO, we need to calculate the loss differently\n        # Using MSE loss as a simple approximation\n        for i in range(len(student_pred)):\n            s_feat = student_pred[i]\n            t_feat = teacher_pred[i]\n            \n            # Feature-level distillation\n            feat_loss = F.mse_loss(s_feat, t_feat)\n            task_loss += feat_loss\n        \n        # Normalize by number of layers\n        task_loss /= len(student_pred)\n        \n        # Distillation loss (student vs teacher)\n        distill_loss = 0\n        \n        # Softmax with temperature on classification outputs\n        if len(student_pred) > 0:\n            # Get the last layer features (usually contains class predictions)\n            s_out = student_pred[-1]\n            t_out = teacher_pred[-1]\n            \n            # Apply temperature scaling and calculate KL divergence\n            if s_out.shape[-1] > 5:  # Has class predictions\n                s_cls = s_out[..., 5:] / temperature\n                t_cls = t_out[..., 5:] / temperature\n                \n                distill_loss = F.kl_div(\n                    F.log_softmax(s_cls.view(-1, s_cls.shape[-1]), dim=-1),\n                    F.softmax(t_cls.view(-1, t_cls.shape[-1]), dim=-1),\n                    reduction='batchmean'\n                ) * (temperature ** 2)\n        \n        # Combined loss\n        total_loss = alpha * task_loss + (1 - alpha) * distill_loss\n        \n        return total_loss\n    \n    def train_epoch_standard(self, train_loader, optimizer, epoch, epochs, alpha=0.7, temperature=4.0):\n        \"\"\"Train one epoch using standard distillation\"\"\"\n        self.student.model.train()\n        epoch_loss = 0\n        \n        pbar = tqdm(train_loader, desc=f'Epoch {epoch+1}/{epochs}')\n        \n        for batch_idx, (images, labels_list, paths) in enumerate(pbar):\n            images = images.to(device)\n            \n            optimizer.zero_grad()\n            \n            # Get predictions\n            with torch.no_grad():\n                teacher_outputs = self.teacher.model(images)\n            \n            student_outputs = self.student.model(images)\n            \n            # Calculate distillation loss\n            loss = self.distillation_loss(\n                student_outputs, teacher_outputs, labels_list,\n                alpha=alpha, temperature=temperature\n            )\n            \n            loss.backward()\n            \n            # Gradient clipping\n            torch.nn.utils.clip_grad_norm_(self.student.model.parameters(), max_norm=10.0)\n            \n            optimizer.step()\n            \n            epoch_loss += loss.item()\n            \n            # Update progress bar\n            pbar.set_postfix({'loss': f'{loss.item():.4f}'})\n        \n        return epoch_loss / len(train_loader)\n    \n    def train(self, data_yaml_path, epochs=30, batch_size=16, alpha=0.7, temperature=4.0):\n        \"\"\"Train student with distillation using Ultralytics trainer\"\"\"\n        \n        print(f\"Starting distillation training...\")\n        print(f\"Epochs: {epochs}, Batch size: {batch_size}\")\n        print(f\"Alpha: {alpha}, Temperature: {temperature}\")\n        \n        # Option 1: Use Ultralytics built-in training (recommended)\n        results = self.student.train(\n            data=data_yaml_path,\n            epochs=epochs,\n            batch=batch_size,\n            imgsz=640,\n            patience=10,\n            save=True,\n            device=device,\n            workers=4,\n            project=self.output_dir,\n            name='student_distilled',\n            exist_ok=True,\n            lr0=0.001,\n            lrf=0.01,\n            momentum=0.937,\n            weight_decay=0.0005,\n            warmup_epochs=3,\n            close_mosaic=10,\n            # Augmentation\n            hsv_h=0.015,\n            hsv_s=0.7,\n            hsv_v=0.4,\n            degrees=0.0,\n            translate=0.1,\n            scale=0.5,\n            shear=0.0,\n            perspective=0.0,\n            flipud=0.0,\n            fliplr=0.5,\n            mosaic=1.0,\n            mixup=0.0,\n            copy_paste=0.0\n        )\n        \n        # Save final model\n        final_path = os.path.join(self.output_dir, 'student_final.pt')\n        shutil.copy(\n            os.path.join(self.output_dir, 'student_distilled/weights/best.pt'),\n            final_path\n        )\n        print(f\"\\nTraining completed! Model saved to: {final_path}\")\n        \n        return results\n    \n    def train_custom(self, data_yaml_path, epochs=30, batch_size=16, alpha=0.7, temperature=4.0):\n        \"\"\"Alternative: Custom training loop with manual distillation\"\"\"\n        \n        print(f\"Starting custom distillation training...\")\n        \n        # Create datasets with custom collate function\n        train_dataset = ScreenPhotoDataset(data_yaml_path, mode='train', augment=True)\n        val_dataset = ScreenPhotoDataset(data_yaml_path, mode='val', augment=False)\n        \n        train_loader = DataLoader(\n            train_dataset, \n            batch_size=batch_size,\n            shuffle=True, \n            num_workers=4, \n            pin_memory=True,\n            collate_fn=custom_collate_fn  # Use custom collate function\n        )\n        \n        # Optimizer\n        optimizer = torch.optim.Adam(self.student.model.parameters(), lr=0.001)\n        scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)\n        \n        # Move models to device\n        self.teacher.model.to(device)\n        self.student.model.to(device)\n        \n        # Training history\n        history = {'train_loss': [], 'val_map': []}\n        \n        # Training loop\n        for epoch in range(epochs):\n            # Train one epoch\n            avg_loss = self.train_epoch_standard(\n                train_loader, optimizer, epoch, epochs, alpha, temperature\n            )\n            \n            history['train_loss'].append(avg_loss)\n            scheduler.step()\n            \n            print(f\"Epoch {epoch+1}/{epochs} - Loss: {avg_loss:.4f} - LR: {scheduler.get_last_lr()[0]:.6f}\")\n            \n            # Validation every 5 epochs\n            if (epoch + 1) % 5 == 0:\n                print(\"Validating...\")\n                metrics = self.student.val(data=data_yaml_path, verbose=False)\n                val_map = metrics.box.map\n                history['val_map'].append(val_map)\n                print(f\"Validation mAP@0.5:0.95: {val_map:.4f}\")\n                \n                # Save checkpoint\n                checkpoint_path = os.path.join(self.output_dir, f'student_epoch_{epoch+1}.pt')\n                self.student.save(checkpoint_path)\n        \n        # Save final model\n        final_path = os.path.join(self.output_dir, 'student_final.pt')\n        self.student.save(final_path)\n        print(f\"\\nTraining completed! Model saved to: {final_path}\")\n        \n        return history","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T01:14:40.671465Z","iopub.execute_input":"2025-08-06T01:14:40.67177Z","iopub.status.idle":"2025-08-06T01:14:40.690576Z","shell.execute_reply.started":"2025-08-06T01:14:40.671748Z","shell.execute_reply":"2025-08-06T01:14:40.689693Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## EVALUATION & COMPARISON","metadata":{}},{"cell_type":"code","source":"# ====================== EVALUATION & COMPARISON ======================\n\ndef evaluate_and_compare(teacher_path, student_path, data_yaml_path):\n    \"\"\"Evaluate and compare models\"\"\"\n    \n    print(\"\\n\" + \"=\"*60)\n    print(\"MODEL EVALUATION & COMPARISON\")\n    print(\"=\"*60)\n    \n    # Load models\n    teacher = YOLO(teacher_path)\n    student = YOLO(student_path)\n    \n    # 1. Model Size Comparison\n    teacher_size = os.path.getsize(teacher_path) / (1024*1024)  # MB\n    student_size = os.path.getsize(student_path) / (1024*1024)  # MB\n    \n    print(f\"\\n📦 Model Size:\")\n    print(f\"   Teacher: {teacher_size:.1f} MB\")\n    print(f\"   Student: {student_size:.1f} MB\")\n    print(f\"   Reduction: {teacher_size/student_size:.1f}x smaller\")\n    \n    # 2. Inference Speed Comparison\n    print(f\"\\n⚡ Inference Speed (on {device}):\")\n    \n    # Create dummy input\n    dummy_img = np.random.randint(0, 255, (640, 640, 3), dtype=np.uint8)\n    \n    # Warmup\n    for _ in range(10):\n        _ = teacher(dummy_img, verbose=False)\n        _ = student(dummy_img, verbose=False)\n    \n    # Teacher timing\n    teacher_times = []\n    for _ in range(100):\n        start = time.time()\n        _ = teacher(dummy_img, verbose=False)\n        teacher_times.append((time.time() - start) * 1000)\n    \n    # Student timing  \n    student_times = []\n    for _ in range(100):\n        start = time.time()\n        _ = student(dummy_img, verbose=False)\n        student_times.append((time.time() - start) * 1000)\n    \n    teacher_avg = np.mean(teacher_times)\n    student_avg = np.mean(student_times)\n    \n    print(f\"   Teacher: {teacher_avg:.2f} ms/image\")\n    print(f\"   Student: {student_avg:.2f} ms/image\")\n    print(f\"   Speedup: {teacher_avg/student_avg:.2f}x faster\")\n    \n    # 3. Accuracy Comparison\n    print(f\"\\n🎯 Accuracy Comparison:\")\n    \n    print(\"   Evaluating teacher...\")\n    teacher_metrics = teacher.val(data=data_yaml_path, verbose=False)\n    \n    print(\"   Evaluating student...\")\n    student_metrics = student.val(data=data_yaml_path, verbose=False)\n    \n    print(f\"\\n   Overall Metrics:\")\n    print(f\"   {'Metric':<15} {'Teacher':<10} {'Student':<10} {'Retention':<10}\")\n    print(f\"   {'-'*45}\")\n    \n    metrics_list = [\n        ('mAP@0.5', teacher_metrics.box.map50, student_metrics.box.map50),\n        ('mAP@0.5:0.95', teacher_metrics.box.map, student_metrics.box.map),\n        ('Precision', teacher_metrics.box.mp, student_metrics.box.mp),\n        ('Recall', teacher_metrics.box.mr, student_metrics.box.mr)\n    ]\n    \n    for metric_name, teacher_val, student_val in metrics_list:\n        retention = (student_val/teacher_val)*100 if teacher_val > 0 else 0\n        print(f\"   {metric_name:<15} {teacher_val:.4f}     {student_val:.4f}     {retention:.1f}%\")\n    \n    return {\n        'size_reduction': teacher_size/student_size,\n        'speed_improvement': teacher_avg/student_avg,\n        'map_retention': (student_metrics.box.map/teacher_metrics.box.map)*100 if teacher_metrics.box.map > 0 else 0\n    }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T01:14:47.637722Z","iopub.execute_input":"2025-08-06T01:14:47.63802Z","iopub.status.idle":"2025-08-06T01:14:47.648715Z","shell.execute_reply.started":"2025-08-06T01:14:47.637997Z","shell.execute_reply":"2025-08-06T01:14:47.64799Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## VISUALIZE RESULTS","metadata":{}},{"cell_type":"code","source":"\n# ====================== VISUALIZE RESULTS ======================\n\ndef visualize_detections(teacher_path, student_path, data_yaml_path, num_samples=5):\n    \"\"\"Visualize detection results comparison\"\"\"\n    \n    teacher = YOLO(teacher_path)\n    student = YOLO(student_path)\n    \n    # Load validation images\n    with open(data_yaml_path, 'r') as f:\n        data_config = yaml.safe_load(f)\n    \n    val_file = data_config['val']\n    with open(val_file, 'r') as f:\n        val_images = [line.strip() for line in f.readlines()]\n    \n    # Random sample\n    samples = random.sample(val_images, min(num_samples, len(val_images)))\n    \n    # Create visualization\n    fig, axes = plt.subplots(num_samples, 3, figsize=(15, 5*num_samples))\n    if num_samples == 1:\n        axes = axes.reshape(1, -1)\n    \n    for idx, img_path in enumerate(samples):\n        # Load original image\n        img = cv2.imread(img_path)\n        img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        \n        # Teacher prediction\n        teacher_results = teacher(img_path, verbose=False)[0]\n        teacher_img = teacher_results.plot()\n        \n        # Student prediction\n        student_results = student(img_path, verbose=False)[0]\n        student_img = student_results.plot()\n        \n        # Original\n        axes[idx, 0].imshow(img_rgb)\n        axes[idx, 0].set_title('Original', fontsize=12, fontweight='bold')\n        axes[idx, 0].axis('off')\n        \n        # Teacher\n        axes[idx, 1].imshow(teacher_img)\n        axes[idx, 1].set_title('Teacher Model', fontsize=12, fontweight='bold')\n        axes[idx, 1].axis('off')\n        \n        # Student\n        axes[idx, 2].imshow(student_img)\n        axes[idx, 2].set_title('Student Model (Distilled)', fontsize=12, fontweight='bold')\n        axes[idx, 2].axis('off')\n        \n        # Add detection info\n        if teacher_results.boxes is not None:\n            teacher_count = len(teacher_results.boxes)\n            axes[idx, 1].text(10, 30, f'Detections: {teacher_count}', \n                             color='white', fontsize=10, \n                             bbox=dict(boxstyle=\"round,pad=0.3\", facecolor='black', alpha=0.5))\n        \n        if student_results.boxes is not None:\n            student_count = len(student_results.boxes)\n            axes[idx, 2].text(10, 30, f'Detections: {student_count}', \n                             color='white', fontsize=10,\n                             bbox=dict(boxstyle=\"round,pad=0.3\", facecolor='black', alpha=0.5))\n    \n    plt.tight_layout()\n    plt.savefig('/kaggle/working/detection_comparison.png', dpi=150, bbox_inches='tight')\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T01:14:54.191806Z","iopub.execute_input":"2025-08-06T01:14:54.192354Z","iopub.status.idle":"2025-08-06T01:14:54.201693Z","shell.execute_reply.started":"2025-08-06T01:14:54.192327Z","shell.execute_reply":"2025-08-06T01:14:54.200939Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\n\n# ====================== MAIN EXECUTION ======================\n\ndef main():\n    \"\"\"Main execution function\"\"\"\n    \n    print(\"🚀 YOLOv11 Knowledge Distillation for Screen Photography Detection\")\n    print(\"=\"*70)\n    \n    # Paths\n    data_yaml_path = '/kaggle/working/dataset/data.yaml'\n    teacher_path = '/kaggle/input/testimage/teacher_best.pt'\n    output_dir = '/kaggle/working/distillation'\n    \n    # Step 1: Check dataset\n    if not os.path.exists(data_yaml_path):\n        print(\"❌ Dataset not found! Please run dataset preparation code first.\")\n        return\n    else:\n        print(\"✅ Dataset found at:\", data_yaml_path)\n    \n    # Step 2: Check teacher model\n    if not os.path.exists(teacher_path):\n        print(\"❌ Teacher model not found! Please train or provide teacher model.\")\n        return\n    else:\n        print(\"✅ Teacher model found at:\", teacher_path)\n    \n    # Step 3: Knowledge Distillation\n    print(\"\\n🎓 Starting Knowledge Distillation...\")\n    \n    # Configure distillation\n    config = {\n        'student_type': 'n',  # nano model for edge deployment\n        'epochs': 30,\n        'batch_size': 16,\n        'alpha': 0.7,\n        'temperature': 4.0\n    }\n    \n    # Create trainer\n    trainer = YOLODistillationTrainer(\n        teacher_path=teacher_path,\n        student_type=config['student_type'],\n        output_dir=output_dir\n    )\n    \n    # Train student using Ultralytics trainer (recommended)\n    # This uses YOLO's built-in training which handles data loading properly\n    results = trainer.train(\n        data_yaml_path=data_yaml_path,\n        epochs=config['epochs'],\n        batch_size=config['batch_size'],\n        alpha=config['alpha'],\n        temperature=config['temperature']\n    )\n    \n    # Alternative: Use custom training loop with manual distillation\n    # Uncomment below to use custom training instead:\n    # history = trainer.train_custom(\n    #     data_yaml_path=data_yaml_path,\n    #     epochs=config['epochs'],\n    #     batch_size=config['batch_size'],\n    #     alpha=config['alpha'],\n    #     temperature=config['temperature']\n    # )\n    \n    # Step 4: Evaluation\n    print(\"\\n📊 Evaluating models...\")\n    student_path = os.path.join(output_dir, 'student_final.pt')\n    \n    if os.path.exists(student_path):\n        results = evaluate_and_compare(teacher_path, student_path, data_yaml_path)\n        \n        # Step 5: Visualization\n        print(\"\\n🎨 Generating visualizations...\")\n        visualize_detections(teacher_path, student_path, data_yaml_path, num_samples=5)\n        \n        # Step 6: Export for deployment\n        print(\"\\n📱 Exporting models for deployment...\")\n        \n        student_model = YOLO(student_path)\n        \n        # Export to different formats\n        print(\"   Exporting to ONNX...\")\n        student_model.export(format='onnx', simplify=True)\n        \n        print(\"   Exporting to TensorFlow Lite...\")\n        student_model.export(format='tflite')\n        \n        # Final summary\n        print(\"\\n\" + \"=\"*70)\n        print(\"✅ DISTILLATION COMPLETED SUCCESSFULLY!\")\n        print(\"=\"*70)\n        print(f\"📦 Size reduction: {results['size_reduction']:.1f}x smaller\")\n        print(f\"⚡ Speed improvement: {results['speed_improvement']:.1f}x faster\") \n        print(f\"🎯 Accuracy retention: {results['map_retention']:.1f}%\")\n        print(f\"💾 Student model saved to: {student_path}\")\n        print(\"=\"*70)\n    else:\n        print(\"❌ Student model training failed!\")\n\n# Run the main function\nif __name__ == \"__main__\":\n    main()","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-07-14T02:32:50.741198Z","iopub.execute_input":"2025-07-14T02:32:50.741775Z","iopub.status.idle":"2025-07-14T03:24:11.398675Z","shell.execute_reply.started":"2025-07-14T02:32:50.741749Z","shell.execute_reply":"2025-07-14T03:24:11.39707Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"teacher_path = '/kaggle/input/finetuneyoloteacher/pytorch/default/yolo_bestx.pt'\nstudent_path = '/kaggle/input/testimage/student_final.pt'\ndata_yaml_path = '/kaggle/working/dataset/data.yaml'\nvisualize_detections(teacher_path, student_path, data_yaml_path, num_samples=5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-06T01:22:52.304935Z","iopub.execute_input":"2025-08-06T01:22:52.305293Z","iopub.status.idle":"2025-08-06T01:22:52.499319Z","shell.execute_reply.started":"2025-08-06T01:22:52.305262Z","shell.execute_reply":"2025-08-06T01:22:52.498228Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Optimize Student","metadata":{}},{"cell_type":"code","source":"!pip install ultralytics","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T09:12:55.426166Z","iopub.execute_input":"2025-07-23T09:12:55.426647Z","iopub.status.idle":"2025-07-23T09:14:10.230269Z","shell.execute_reply.started":"2025-07-23T09:12:55.42662Z","shell.execute_reply":"2025-07-23T09:14:10.229462Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport os\nimport shutil\nfrom ultralytics import YOLO\nimport numpy as np\n\ndef actually_reduce_yolo_size(\n    input_model_path='/kaggle/input/testimage/student_final.pt',\n    working_dir='/kaggle/working/optimization_workspace'\n):\n    \"\"\"\n    Code thực sự giảm kích thước model YOLO\n    \"\"\"\n    \n    print(\"🚀 YOLO Size Reduction - Working Version\")\n    print(\"=\"*60)\n    \n    # Create working directory\n    os.makedirs(working_dir, exist_ok=True)\n    \n    # STEP 1: Copy model to writable location\n    model_path = os.path.join(working_dir, 'student_final.pt')\n    shutil.copy(input_model_path, model_path)\n    \n    original_size = os.path.getsize(model_path) / (1024*1024)\n    print(f\"📦 Original model size: {original_size:.2f} MB\")\n    \n    results = {}\n    \n    # ========== METHOD 1: Use Existing OpenVINO Model ==========\n    print(\"\\n1️⃣ Using Existing OpenVINO INT8 Model...\")\n    \n    # From your previous run, OpenVINO already succeeded!\n    openvino_path = '/kaggle/working/distillation/student_final_int8_openvino_model'\n    \n    if os.path.exists(openvino_path):\n        total_size = 0\n        for root, dirs, files in os.walk(openvino_path):\n            for file in files:\n                total_size += os.path.getsize(os.path.join(root, file))\n        \n        openvino_size_mb = total_size / (1024*1024)\n        print(f\"   ✅ Found existing OpenVINO model: {openvino_size_mb:.2f} MB\")\n        \n        results['openvino_int8'] = {\n            'path': openvino_path,\n            'size_mb': openvino_size_mb,\n            'reduction': ((original_size - openvino_size_mb) / original_size) * 100\n        }\n    \n    # ========== METHOD 2: ONNX Export + Quantization ==========\n    print(\"\\n2️⃣ ONNX Export + INT8 Quantization...\")\n    \n    try:\n        # Load model from writable location\n        model = YOLO(model_path)\n        \n        # Export to ONNX (this will work now)\n        onnx_path = model.export(\n            format='onnx',\n            imgsz=320,\n            simplify=True,\n            dynamic=False\n        )\n        \n        print(f\"   ONNX exported to: {onnx_path}\")\n        onnx_size = os.path.getsize(onnx_path) / (1024*1024)\n        print(f\"   ONNX FP32 size: {onnx_size:.2f} MB\")\n        \n        # Quantize to INT8\n        from onnxruntime.quantization import quantize_dynamic, QuantType\n        \n        quantized_path = os.path.join(working_dir, 'model_int8.onnx')\n        \n        quantize_dynamic(\n            model_input=onnx_path,\n            model_output=quantized_path,\n            weight_type=QuantType.QUInt8,\n            per_channel=True,\n            reduce_range=True\n        )\n        \n        quantized_size = os.path.getsize(quantized_path) / (1024*1024)\n        print(f\"   ✅ ONNX INT8 size: {quantized_size:.2f} MB\")\n        \n        results['onnx_int8'] = {\n            'path': quantized_path,\n            'size_mb': quantized_size,\n            'reduction': ((original_size - quantized_size) / original_size) * 100\n        }\n        \n    except Exception as e:\n        print(f\"   ❌ ONNX failed: {e}\")\n    \n    # ========== METHOD 3: Half Precision (FP16) ==========\n    print(\"\\n3️⃣ Half Precision (FP16) Model...\")\n    \n    try:\n        # Load model\n        model = YOLO(model_path)\n        \n        # Convert to half precision\n        model.model = model.model.half()\n        \n        # Save FP16 model\n        fp16_path = os.path.join(working_dir, 'model_fp16.pt')\n        \n        # Save only the model state\n        torch.save({\n            'model': model.model.state_dict(),\n            'nc': model.model.nc,  # number of classes\n            'names': model.names,  # class names\n        }, fp16_path)\n        \n        fp16_size = os.path.getsize(fp16_path) / (1024*1024)\n        print(f\"   ✅ FP16 model size: {fp16_size:.2f} MB\")\n        \n        results['fp16'] = {\n            'path': fp16_path,\n            'size_mb': fp16_size,\n            'reduction': ((original_size - fp16_size) / original_size) * 100\n        }\n        \n    except Exception as e:\n        print(f\"   ❌ FP16 failed: {e}\")\n    \n    # ========== METHOD 4: Simple INT8 Weights ==========\n    print(\"\\n4️⃣ Simple INT8 Weight Quantization...\")\n    \n    try:\n        # Load model with weights_only=False\n        checkpoint = torch.load(model_path, map_location='cpu', weights_only=False)\n        \n        # Get model state dict\n        if hasattr(checkpoint, 'state_dict'):\n            state_dict = checkpoint.state_dict()\n        elif isinstance(checkpoint, dict) and 'model' in checkpoint:\n            state_dict = checkpoint['model'].state_dict() if hasattr(checkpoint['model'], 'state_dict') else checkpoint['model']\n        else:\n            state_dict = checkpoint\n        \n        # Quantize weights\n        quantized_weights = {}\n        metadata = {}\n        \n        for name, param in state_dict.items():\n            if param.dtype == torch.float32 and param.numel() > 100:  # Only quantize larger tensors\n                # Calculate scale and zero point\n                min_val = param.min().item()\n                max_val = param.max().item()\n                scale = (max_val - min_val) / 255.0\n                zero_point = round(-min_val / scale)\n                \n                # Quantize to uint8\n                quantized = torch.clamp(\n                    torch.round(param / scale + zero_point), \n                    0, 255\n                ).to(torch.uint8)\n                \n                quantized_weights[name] = quantized\n                metadata[name] = {'scale': scale, 'zero_point': zero_point}\n            else:\n                # Keep small tensors as is\n                quantized_weights[name] = param\n        \n        # Save quantized model\n        int8_path = os.path.join(working_dir, 'model_int8_simple.pth')\n        torch.save({\n            'weights': quantized_weights,\n            'metadata': metadata,\n            'model_info': {\n                'nc': 4,  # number of classes\n                'names': ['person', 'phone', 'reflex_camera', 'polaroid_camera']\n            }\n        }, int8_path, pickle_protocol=4)\n        \n        int8_size = os.path.getsize(int8_path) / (1024*1024)\n        print(f\"   ✅ INT8 weights size: {int8_size:.2f} MB\")\n        \n        results['int8_weights'] = {\n            'path': int8_path,\n            'size_mb': int8_size,\n            'reduction': ((original_size - int8_size) / original_size) * 100\n        }\n        \n    except Exception as e:\n        print(f\"   ❌ INT8 weights failed: {e}\")\n    \n    # ========== METHOD 5: Export for Specific Platform ==========\n    print(\"\\n5️⃣ Platform-Specific Exports...\")\n    \n    # CoreML for iOS (usually smaller)\n    try:\n        model = YOLO(model_path)\n        coreml_path = model.export(format='coreml', imgsz=320, nms=True)\n        if os.path.exists(coreml_path):\n            coreml_size = os.path.getsize(coreml_path) / (1024*1024)\n            print(f\"   ✅ CoreML size: {coreml_size:.2f} MB\")\n            results['coreml'] = {\n                'path': coreml_path,\n                'size_mb': coreml_size,\n                'reduction': ((original_size - coreml_size) / original_size) * 100\n            }\n    except:\n        pass\n    \n    # ========== SUMMARY ==========\n    print(\"\\n\" + \"=\"*60)\n    print(\"📊 OPTIMIZATION RESULTS\")\n    print(\"=\"*60)\n    \n    print(f\"\\nOriginal size: {original_size:.2f} MB\\n\")\n    \n    # Sort by size\n    sorted_results = sorted(\n        [(k, v) for k, v in results.items()], \n        key=lambda x: x[1]['size_mb']\n    )\n    \n    print(f\"{'Method':<20} {'Size (MB)':<10} {'Reduction':<15} {'Status'}\")\n    print(\"-\"*60)\n    \n    for name, info in sorted_results:\n        reduction = info['reduction']\n        status = \"✅ Success\" if reduction > 0 else \"❌ Larger\"\n        print(f\"{name:<20} {info['size_mb']:<10.2f} {reduction:>6.1f}%        {status}\")\n    \n    # Best option\n    best_options = [r for r in sorted_results if r[1]['reduction'] > 0]\n    if best_options:\n        best_name, best_info = best_options[0]\n        print(f\"\\n🎯 BEST OPTION: {best_name}\")\n        print(f\"   Size: {best_info['size_mb']:.2f} MB (↓{best_info['reduction']:.1f}%)\")\n        print(f\"   Path: {best_info['path']}\")\n        \n        # Deployment code\n        if 'onnx' in best_name:\n            print(\"\\n💻 DEPLOYMENT CODE:\")\n            print(\"-\"*40)\n            print(\"\"\"\n# For Raspberry Pi / Jetson\nimport onnxruntime as ort\nimport numpy as np\nimport cv2\n\n# Load INT8 model\nsession = ort.InferenceSession('model_int8.onnx')\n\n# Inference function\ndef detect(image_path):\n    # Preprocess\n    img = cv2.imread(image_path)\n    img = cv2.resize(img, (320, 320))\n    img = img.astype(np.float32) / 255.0\n    img = np.transpose(img, (2, 0, 1))\n    img = np.expand_dims(img, 0)\n    \n    # Run inference\n    outputs = session.run(None, {session.get_inputs()[0].name: img})\n    return outputs\n\n# Use it\nresults = detect('test.jpg')\n\"\"\")\n        \n        elif 'openvino' in best_name:\n            print(\"\\n💻 For Intel devices:\")\n            print(f\"Model ready at: {best_info['path']}\")\n            print(\"Use OpenVINO Runtime for inference\")\n    \n    else:\n        print(\"\\n⚠️ No size reduction achieved. Try different methods.\")\n    \n    return results\n\n# ========== QUICK FIX: Use Existing Models ==========\ndef use_existing_optimized_models():\n    \"\"\"\n    Sử dụng các model đã tối ưu từ lần chạy trước\n    \"\"\"\n    print(\"\\n🔍 Checking for existing optimized models...\")\n    \n    models = {\n        'OpenVINO INT8': '/kaggle/working/distillation/student_final_int8_openvino_model',\n        'ONNX': '/kaggle/working/distillation/student_final.onnx',\n    }\n    \n    found = []\n    for name, path in models.items():\n        if os.path.exists(path):\n            if os.path.isdir(path):\n                size = sum(os.path.getsize(os.path.join(root, f)) \n                          for root, dirs, files in os.walk(path) \n                          for f in files) / (1024*1024)\n            else:\n                size = os.path.getsize(path) / (1024*1024)\n            \n            found.append((name, path, size))\n            print(f\"✅ Found {name}: {size:.2f} MB at {path}\")\n    \n    if found:\n        best = min(found, key=lambda x: x[2])\n        print(f\"\\n🎯 Best existing model: {best[0]} ({best[2]:.2f} MB)\")\n        print(f\"Path: {best[1]}\")\n        \n        return best[1]\n    \n    return None\n\n# ========== MAIN EXECUTION ==========\nif __name__ == \"__main__\":\n    # First check existing models\n    existing = use_existing_optimized_models()\n    \n    if existing:\n        print(\"\\n✅ You already have optimized models!\")\n        print(\"Use the OpenVINO model at:\")\n        print(\"/kaggle/working/distillation/student_final_int8_openvino_model\")\n        print(\"Size: ~3.2 MB (38% smaller than original)\")\n    \n    # Then try new optimization\n    print(\"\\n\" + \"=\"*60)\n    print(\"Running new optimization...\")\n    \n    results = actually_reduce_yolo_size()\n    \n    print(\"\\n✅ Done! Your best options for IoT deployment are above.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T09:14:17.689408Z","iopub.execute_input":"2025-07-23T09:14:17.690265Z","iopub.status.idle":"2025-07-23T09:15:13.826283Z","shell.execute_reply.started":"2025-07-23T09:14:17.69023Z","shell.execute_reply":"2025-07-23T09:15:13.825652Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Using Model","metadata":{}},{"cell_type":"code","source":"import os\nimport random\nfrom PIL import Image, ImageDraw, ImageFont\nimport matplotlib.pyplot as plt\nfrom ultralytics import YOLO\nimport numpy as np\n\n# --- Load YOLOv8 model ---\n# Dùng mô hình YOLOv8 đã được huấn luyện trước với dữ liệu COCO\nmodel = YOLO('/kaggle/working/optimization_workspace/model_int8.onnx')  # Sử dụng model YOLOv8 medium pre-trained\n\n# --- Load ảnh ---\n# Chọn một ảnh ngẫu nhiên từ bộ validation của COCO\nimg_path = '/kaggle/input/testimage/WIN_20250607_19_34_01_Pro.jpg'  # Cập nhật đúng đường dẫn ảnh nếu cần\nimg = Image.open(img_path).convert('RGB')\n\n# --- Chạy inference với YOLOv8 ---\nresults = model(img_path)[0]  # Sử dụng mô hình đã tải để chạy inference\nboxes = results.boxes.xyxy.cpu().numpy()  # Lấy toạ độ bounding boxes (x1, y1, x2, y2)\nclasses = results.boxes.cls.cpu().numpy().astype(int)  # Lấy lớp của các object\nconfidences = results.boxes.conf.cpu().numpy()  # Lấy độ tự tin của các prediction\n\n# --- Vẽ kết quả và đánh giá hành động ---\ndraw = ImageDraw.Draw(img)\nfont = ImageFont.load_default()\n\n# Tập hợp các box theo class\nby_cls = {}\nfor box, cls, conf in zip(boxes, classes, confidences):\n    by_cls.setdefault(cls, []).append((box, conf))\n\n# Kiểm tra hành động \"chụp ảnh màn hình\"\npersons = by_cls.get(0, [])  # Lớp person (ID=0)\nphones = by_cls.get(1, [])  # Lớp cell phone (ID=67)\n\ndetected = False\nfor p_box, _ in persons:\n    for ph_box, _ in phones:\n        # Kiểm tra xem điện thoại có gần màn hình (có thể bạn cần thêm \"screen\" nếu muốn)\n        cx, cy = (ph_box[0] + ph_box[2]) / 2, (ph_box[1] + ph_box[3]) / 2  # Tọa độ trung tâm của điện thoại\n        px, py = (p_box[0] + p_box[2]) / 2, (p_box[1] + p_box[3]) / 2  # Tọa độ trung tâm của người\n\n        # Kiểm tra xem điện thoại có nằm trong vùng tầm tay của người hay không\n        if p_box[0] < cx < p_box[2] and p_box[1] < cy < p_box[3]:\n            # Vẽ các bounding boxes\n            draw.rectangle(p_box.tolist(), outline='blue', width=2)\n            draw.rectangle(ph_box.tolist(), outline='green', width=2)\n            draw.text((ph_box[0], ph_box[1]-10), \"likely taking photo\", fill='green', font=font)\n            detected = True\n            break\n    if detected: break\n\nif detected:\n    print(\"Detected taking-photo action based on rule-based heuristic.\")\nelse:\n    print(\"No taking-photo action detected.\")\n\n# --- Hiển thị kết quả ---\nplt.figure(figsize=(8, 8))\nplt.imshow(img)\nplt.axis('off')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-14T08:55:35.823672Z","iopub.execute_input":"2025-07-14T08:55:35.824008Z","iopub.status.idle":"2025-07-14T08:55:36.249341Z","shell.execute_reply.started":"2025-07-14T08:55:35.823984Z","shell.execute_reply":"2025-07-14T08:55:36.24866Z"}},"outputs":[],"execution_count":null}]}