{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":5048,"databundleVersionId":868335}],"dockerImageVersionId":31328,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Thiết lập môi trường và chia dữ liệu","metadata":{}},{"cell_type":"code","source":"import os\nimport time\nimport copy\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import models, transforms\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import classification_report, confusion_matrix","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-15T02:48:06.824562Z","iopub.execute_input":"2026-05-15T02:48:06.825225Z","iopub.status.idle":"2026-05-15T02:48:16.364934Z","shell.execute_reply.started":"2026-05-15T02:48:06.825195Z","shell.execute_reply":"2026-05-15T02:48:16.364210Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# 1. Đường dẫn dữ liệu trên Kaggle\nBASE_PATH = '/kaggle/input/competitions/state-farm-distracted-driver-detection'\ndf = pd.read_csv(os.path.join(BASE_PATH, 'driver_imgs_list.csv'))\n\n# 2. Chia theo Subject ID (Tài xế) để tránh Overfitting\ndrivers = df['subject'].unique()\ntrain_drivers, val_drivers = train_test_split(drivers, test_size=0.2, random_state=42)\n\n# 3. Tạo danh sách file cho tập Train và Val\ntrain_df = df[df['subject'].isin(train_drivers)].reset_index(drop=True)\nval_df = df[df['subject'].isin(val_drivers)].reset_index(drop=True)\n\n# 4. Xuất ra file CSV dùng chung\ntrain_df.to_csv('train_list.csv', index=False)\nval_df.to_csv('val_list.csv', index=False)\n\nprint(f\" Train: {len(train_df)} ảnh, Val: {len(val_df)} ảnh.\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-05-15T02:48:39.344875Z","iopub.execute_input":"2026-05-15T02:48:39.345420Z","iopub.status.idle":"2026-05-15T02:48:39.441615Z","shell.execute_reply.started":"2026-05-15T02:48:39.345389Z","shell.execute_reply":"2026-05-15T02:48:39.440969Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install ultralytics -q\nfrom ultralytics import YOLO\nimport cv2\nimport os\nfrom tqdm import tqdm\n\nBASE_PATH = '/kaggle/input/competitions/state-farm-distracted-driver-detection'\nTRAIN_DIR = os.path.join(BASE_PATH, 'imgs', 'train')\n# 1. Khởi tạo mô hình YOLOv8 nano (rất nhanh)\nyolo_detector = YOLO('yolov8n.pt')\n\n# 2. Tạo thư mục chứa ảnh đã cắt\nCROP_DIR = '/kaggle/working/cropped_images'\nos.makedirs(CROP_DIR, exist_ok=True)\nfor i in range(10):\n    os.makedirs(os.path.join(CROP_DIR, f'c{i}'), exist_ok=True)\n\ndef preprocess_dataset(dataframe, source_dir, target_dir):\n    print(\"🚀 Đang tiến hành cắt ảnh bằng YOLOv8...\")\n    for idx, row in tqdm(dataframe.iterrows(), total=len(dataframe)):\n        img_path = os.path.join(source_dir, row['classname'], row['img'])\n        save_path = os.path.join(target_dir, row['classname'], row['img'])\n        \n        # Chạy YOLO phát hiện người (class 0)\n        results = yolo_detector(img_path, verbose=False)\n        img = cv2.imread(img_path)\n        \n        found = False\n        for r in results:\n            for box in r.boxes:\n                if int(box.cls) == 0: # Nếu là người\n                    x1, y1, x2, y2 = map(int, box.xyxy[0])\n                    # Cắt và lưu\n                    crop_img = img[y1:y2, x1:x2]\n                    if crop_img.size > 0:\n                        cv2.imwrite(save_path, crop_img)\n                        found = True\n                    break\n            if found: break\n            \n        # Nếu không tìm thấy người, copy ảnh gốc sang để không mất dữ liệu\n        if not found:\n            cv2.imwrite(save_path, img)\n\n# Chạy tiền xử lý cho toàn bộ ảnh (đã chia ở Cell 2)\n# Lưu ý: Chỉ cần chạy 1 lần. \n# Nếu muốn nhanh hơn để test, bạn có thể chỉ chạy trên val_df trước.\npreprocess_dataset(df, TRAIN_DIR, CROP_DIR)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-15T02:48:43.384077Z","iopub.execute_input":"2026-05-15T02:48:43.384597Z","iopub.status.idle":"2026-05-15T02:56:46.608188Z","shell.execute_reply.started":"2026-05-15T02:48:43.384566Z","shell.execute_reply":"2026-05-15T02:56:46.607351Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Xây dựng bộ nạp dữ liệu","metadata":{}},{"cell_type":"code","source":"from torch.utils.data import DataLoader, Dataset\nfrom torchvision import transforms\nfrom PIL import Image\nimport os\n\ntrain_transforms = transforms.Compose([\n    # Quan trọng: Cắt ngẫu nhiên giúp mô hình không học thuộc vị trí\n    transforms.RandomResizedCrop(224, scale=(0.7, 1.0)), \n    transforms.RandomHorizontalFlip(p=0.5), \n    transforms.ColorJitter(brightness=0.2, contrast=0.2),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\nval_transforms = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\n# 2. Custom Dataset (Đảm bảo đọc đúng từ CROP_DIR của YOLO)\nclass CroppedDriverDataset(Dataset):\n    def __init__(self, dataframe, root_dir, transform=None):\n        self.dataframe = dataframe\n        self.root_dir = root_dir\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.dataframe)\n\n    def __getitem__(self, idx):\n        row = self.dataframe.iloc[idx]\n        img_path = os.path.join(self.root_dir, row['classname'], row['img'])\n        image = Image.open(img_path).convert(\"RGB\")\n        if self.transform:\n            image = self.transform(image)\n        \n        # Chuyển classname (c0, c1...) thành số (0, 1...)\n        label = int(row['classname'][1:])\n        return image, label\n\n# 3. Khởi tạo Dataset & DataLoader\n# LƯU Ý: Đặt num_workers=0 để dứt điểm lỗi AssertionError\ntrain_dataset = CroppedDriverDataset(train_df, CROP_DIR, transform=train_transforms)\nval_dataset = CroppedDriverDataset(val_df, CROP_DIR, transform=val_transforms)\n\ntrain_loader = DataLoader(\n    train_dataset, \n    batch_size=32, \n    shuffle=True, \n    num_workers=0,  # Sửa lỗi: Không dùng đa luồng để tránh xung đột\n    pin_memory=False\n)\n\nval_loader = DataLoader(\n    val_dataset, \n    batch_size=32, \n    shuffle=False, \n    num_workers=0, \n    pin_memory=False\n)\n\nprint(f\"✅ Đã tải {len(train_dataset)} ảnh train và {len(val_dataset)} ảnh val.\")\nprint(\"🚀 Lỗi Multiprocessing đã được khắc phục bằng cách đặt num_workers=0.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-15T02:56:56.486540Z","iopub.execute_input":"2026-05-15T02:56:56.487398Z","iopub.status.idle":"2026-05-15T02:56:56.498682Z","shell.execute_reply.started":"2026-05-15T02:56:56.487357Z","shell.execute_reply":"2026-05-15T02:56:56.497879Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## MobileNetV3","metadata":{}},{"cell_type":"code","source":"# CẬP NHẬT CELL 4\nimport torch\nimport torch.nn as nn\nfrom torchvision import models\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = models.mobilenet_v3_large(weights='IMAGENET1K_V1')\n\n# Lấy số feature đầu vào của classifier\nnum_ftrs = model.classifier[0].in_features \n\n# Thay thế toàn bộ classifier để có sức mạnh tính toán tốt hơn\nmodel.classifier = nn.Sequential(\n    nn.Linear(num_ftrs, 1024),\n    nn.Hardswish(),\n    nn.Dropout(p=0.6), # Tăng cường độ Dropout\n    nn.Linear(1024, 10)\n)\nmodel = model.to(device)\nprint(\"✅ Đã nâng cấp Classifier với 1024 neurons!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-15T02:57:01.280173Z","iopub.execute_input":"2026-05-15T02:57:01.280605Z","iopub.status.idle":"2026-05-15T02:57:01.695391Z","shell.execute_reply.started":"2026-05-15T02:57:01.280572Z","shell.execute_reply":"2026-05-15T02:57:01.694722Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\n\n# 1. Thiết lập trọng số lớp (Tăng ưu tiên cho c0 và c9)\n# c9 được nhân 4 lần để bù đắp việc khó nhận diện\nweights = torch.tensor([2.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 4.0]).to(device)\n\n# 2. Loss Function với Label Smoothing để chống Overfitting\ncriterion = nn.CrossEntropyLoss(weight=weights, label_smoothing=0.1)\n\n# 3. Optimizer AdamW\noptimizer = optim.AdamW(model.parameters(), lr=1e-4, weight_decay=0.05)\n\n# 4. OneCycleLR Scheduler (Đã sửa lỗi khởi tạo LR)\nnum_epochs = 15\nsteps_per_epoch = len(train_loader)\n\nscheduler = optim.lr_scheduler.OneCycleLR(\n    optimizer, \n    max_lr=1e-4, \n    epochs=num_epochs,\n    steps_per_epoch=steps_per_epoch,\n    pct_start=0.3,       # 30% thời gian đầu tăng LR lên max\n    div_factor=10,       # LR khởi đầu = max_lr / 10\n    final_div_factor=100, # LR kết thúc = max_lr / 100\n    anneal_strategy='cos'\n)\n\nprint(f\"✅ Cell 5 hoàn tất: Đã sẵn sàng 15 Epochs với {steps_per_epoch} steps mỗi Epoch.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-15T02:57:08.687253Z","iopub.execute_input":"2026-05-15T02:57:08.687584Z","iopub.status.idle":"2026-05-15T02:57:08.695730Z","shell.execute_reply.started":"2026-05-15T02:57:08.687556Z","shell.execute_reply":"2026-05-15T02:57:08.694828Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport torch\n\n# --- Hàm hỗ trợ Mixup ---\ndef mixup_data(x, y, alpha=0.2):\n    if alpha > 0:\n        lam = np.random.beta(alpha, alpha)\n    else:\n        lam = 1\n    batch_size = x.size(0)\n    index = torch.randperm(batch_size).to(device)\n    mixed_x = lam * x + (1 - lam) * x[index, :]\n    y_a, y_b = y, y[index]\n    return mixed_x, y_a, y_b, lam\n\ndef mixup_criterion(criterion, pred, y_a, y_b, lam):\n    return lam * criterion(pred, y_a) + (1 - lam) * criterion(pred, y_b)\n\n# --- Bắt đầu Training ---\nbest_val_acc = 0.0\ntotal_steps = num_epochs * steps_per_epoch\n\nprint(f\"🚀 Bắt đầu huấn luyện: Mixup + OneCycleLR\")\n\nfor epoch in range(num_epochs):\n    model.train()\n    running_loss, correct_train, total_train = 0.0, 0, 0\n    \n    for images, labels in train_loader:\n        images, labels = images.to(device), labels.to(device)\n        optimizer.zero_grad()\n        \n        # Áp dụng Mixup ngẫu nhiên 50% số batch\n        if np.random.random() > 0.5:\n            mixed_images, labels_a, labels_b, lam = mixup_data(images, labels, alpha=0.2)\n            outputs = model(mixed_images)\n            loss = mixup_criterion(criterion, outputs, labels_a, labels_b, lam)\n        else:\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n        \n        loss.backward()\n        optimizer.step()\n        \n        # CẬP NHẬT LR THEO TỪNG BATCH (Quan trọng)\n        scheduler.step()\n        \n        running_loss += loss.item()\n        _, predicted = torch.max(outputs, 1)\n        total_train += labels.size(0)\n        correct_train += (predicted == labels).sum().item()\n\n    # --- Validation ---\n    model.eval()\n    correct_val, total_val = 0, 0\n    val_loss = 0.0\n    with torch.no_grad():\n        for images, labels in val_loader:\n            images, labels = images.to(device), labels.to(device)\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            val_loss += loss.item()\n            _, predicted = torch.max(outputs, 1)\n            total_val += labels.size(0)\n            correct_val += (predicted == labels).sum().item()\n    \n    # Chỉ số cuối mỗi Epoch\n    train_acc = 100 * correct_train / total_train\n    val_acc = 100 * correct_val / total_val\n    # Lấy LR hiện tại chính xác từ optimizer\n    current_lr = optimizer.param_groups[0]['lr']\n    \n    print(f\"Epoch [{epoch+1}/{num_epochs}]\")\n    print(f\"  > Train Acc: {train_acc:.2f}% | Val Acc: {val_acc:.2f}%\")\n    print(f\"  > LR: {current_lr:.8f} | Loss: {running_loss/steps_per_epoch:.4f}\")\n    \n    if val_acc > best_val_acc:\n        best_val_acc = val_acc\n        torch.save(model.state_dict(), 'best_driver_model_final.pth')\n        print(f\"  🌟 Đã lưu mô hình mới: {best_val_acc:.2f}%\")\n    print(\"-\" * 30)\n\nprint(f\"✅ HOÀN TẤT! Độ chính xác cao nhất: {best_val_acc:.2f}%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-15T02:57:11.961233Z","iopub.execute_input":"2026-05-15T02:57:11.961967Z","iopub.status.idle":"2026-05-15T03:40:01.905006Z","shell.execute_reply.started":"2026-05-15T02:57:11.961930Z","shell.execute_reply":"2026-05-15T03:40:01.904098Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.metrics import classification_report, confusion_matrix\n# Tải lại trọng số tốt nhất trước khi đánh giá\nmodel.load_state_dict(torch.load('best_driver_model_final.pth'))\nmodel.eval()\n\ny_true, y_pred = [], []\n\nwith torch.no_grad():\n    for images, labels in val_loader:\n        images = images.to(device)\n        outputs = model(images)\n        _, predicted = torch.max(outputs, 1)\n        y_true.extend(labels.numpy())\n        y_pred.extend(predicted.cpu().numpy())\n\n# 1. Xuất báo cáo chi tiết\n# class_names đã được định nghĩa ở Cell 1\nprint(\"\\n📊 BÁO CÁO CHI TIẾT HIỆU SUẤT MÔ HÌNH:\")\nprint(classification_report(y_true, y_pred, target_names=[f\"c{i}\" for i in range(10)]))\n\n# 2. Vẽ Ma trận nhầm lẫn (Confusion Matrix) bằng Seaborn cho chuyên nghiệp\ncm = confusion_matrix(y_true, y_pred)\nplt.figure(figsize=(12, 9))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues', \n            xticklabels=[f\"c{i}\" for i in range(10)], \n            yticklabels=[f\"c{i}\" for i in range(10)])\nplt.title('Ma trận nhầm lẫn (Confusion Matrix)')\nplt.xlabel('Dự đoán')\nplt.ylabel('Thực tế')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-15T03:41:49.063009Z","iopub.execute_input":"2026-05-15T03:41:49.063588Z","iopub.status.idle":"2026-05-15T03:42:18.224808Z","shell.execute_reply.started":"2026-05-15T03:41:49.063552Z","shell.execute_reply":"2026-05-15T03:42:18.223921Z"}},"outputs":[],"execution_count":null}]}