{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.10.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"0540462d-fb05-4168-8853-64d7af0ab09f","cell_type":"markdown","source":"# Bài 7: Vision Transformer\n**Trường Đại học Công nghệ Kỹ thuật TP. Hồ Chí Minh**  \nKhoa Điện - Điện tử | Bộ môn Điện tử - Thông tin  \nThực hành Học máy và Trí tuệ nhân tạo","metadata":{}},{"id":"b80bf7f3-94c3-46e5-a6f2-1062beb67d1f","cell_type":"code","source":"# ============================================================\n# CÀI ĐẶT THƯ VIỆN (chạy 1 lần)\n# ============================================================\nimport subprocess, sys\nsubprocess.check_call([sys.executable, '-m', 'pip', 'install',\n                       'torch', 'torchvision', 'matplotlib', '--quiet'])","metadata":{},"outputs":[],"execution_count":null},{"id":"9c78a416-3b97-4a16-8fe1-711f7d41c7d0","cell_type":"code","source":"# ============================================================\n# IMPORT CHUNG - dùng cho toàn bộ notebook\n# ============================================================\nimport torch\nimport torch.nn as nn\nimport torchvision\nimport torchvision.transforms as transforms\nimport torchvision.datasets as datasets\nimport torchvision.models as models\nfrom torch.utils.data import DataLoader\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nDEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint('Device:', DEVICE)","metadata":{},"outputs":[],"execution_count":null},{"id":"6135aa7c-f8f5-45ee-84f4-ba97a1636c70","cell_type":"markdown","source":"---\n## Bài tập 1: Tải dataset CIFAR-10","metadata":{}},{"id":"588ae625-3d71-4214-a95b-77b2e0b507be","cell_type":"markdown","source":"### Ý 1 – Sử dụng thư viện `torchvision.datasets` để tải CIFAR-10","metadata":{}},{"id":"d5647e97-471b-405a-ac07-a66f38b0a647","cell_type":"code","source":"# Ý 1: Sử dụng torchvision.datasets để tải CIFAR-10\n\nimport torchvision.datasets as datasets\n\n# Chỉ dùng ToTensor() để tải thô, chưa áp transform phức tạp\nraw_train_dataset = datasets.CIFAR10(\n    root='./data',\n    train=True,\n    download=True,\n    transform=transforms.ToTensor()\n)\n\nraw_test_dataset = datasets.CIFAR10(\n    root='./data',\n    train=False,\n    download=True,\n    transform=transforms.ToTensor()\n)\n\nprint('Số lượng ảnh train:', len(raw_train_dataset))\nprint('Số lượng ảnh test :', len(raw_test_dataset))\nprint('Các lớp:', raw_train_dataset.classes)","metadata":{},"outputs":[],"execution_count":null},{"id":"cfec96b6-6290-47a1-ac77-64c206023bd6","cell_type":"markdown","source":"### Ý 2 – Áp dụng các phép biến đổi cơ bản bằng `torchvision.transforms`","metadata":{}},{"id":"726f4025-033b-4a46-8ffa-3665928a455e","cell_type":"code","source":"# Ý 2: Áp dụng các phép biến đổi cơ bản bằng torchvision.transforms\n\nimport torchvision.transforms as transforms\n\ntransform_basic = transforms.Compose([\n    transforms.Resize(224),           # resize về 224x224 cho ViT\n    transforms.ToTensor(),            # chuyển PIL Image → Tensor [0,1]\n    transforms.Normalize(             # chuẩn hóa theo mean/std của ImageNet\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    )\n])\n\nprint('Transform pipeline:')\nprint(transform_basic)\nprint()\n\n# Thử lên một ảnh mẫu để kiểm tra shape\nsample_img, sample_label = raw_train_dataset[0]\nprint('Shape ảnh gốc (ToTensor)  :', sample_img.shape)  # [3, 32, 32]\n\n# Tải lại dataset với transform đầy đủ\ntrain_dataset_b1 = datasets.CIFAR10(\n    root='./data', train=True, download=False, transform=transform_basic\n)\ntransformed_img, _ = train_dataset_b1[0]\nprint('Shape ảnh sau transform    :', transformed_img.shape)  # [3, 224, 224]","metadata":{},"outputs":[],"execution_count":null},{"id":"f47a3b57-f7f8-420c-b879-9c654ef48bca","cell_type":"markdown","source":"### Ý 3 – Ví dụ đầy đủ: tải train và test dataset theo chuẩn bài thực hành","metadata":{}},{"id":"600052d4-bcdf-45f7-9272-142f54f2153c","cell_type":"code","source":"# Ý 3: Ví dụ đầy đủ tải train_dataset và test_dataset theo chuẩn bài TH\n\nimport torchvision.datasets as datasets\nimport torchvision.transforms as transforms\n\ntransform = transforms.Compose([\n    transforms.Resize(224),\n    transforms.ToTensor(),\n    transforms.Normalize(              # chuẩn hóa theo mean/std ImageNet\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    )\n])\n\ntrain_dataset = datasets.CIFAR10(\n    root='./data',\n    train=True,\n    download=True,\n    transform=transform\n)\n\ntest_dataset = datasets.CIFAR10(\n    root='./data',\n    train=False,\n    download=True,\n    transform=transform\n)\n\nCLASSES = train_dataset.classes\n\nprint('=== Dataset CIFAR-10 đã sẵn sàng ===')\nprint(f'Train size : {len(train_dataset):,} ảnh')\nprint(f'Test  size : {len(test_dataset):,} ảnh')\nprint(f'Số lớp     : {len(CLASSES)}')\nprint(f'Tên lớp    : {CLASSES}')","metadata":{},"outputs":[],"execution_count":null},{"id":"d9a9d74d-0832-4c95-a221-efce0649bd02","cell_type":"markdown","source":"---\n## Bài tập 2: Hiển thị một số ảnh trong dataset bằng matplotlib","metadata":{}},{"id":"b0e9629f-2417-4f3b-8e5d-eaf7bb54f67f","cell_type":"markdown","source":"### Ý 1 – Lấy một batch từ DataLoader","metadata":{}},{"id":"30570638-fa15-46b1-b27e-0f3eb716fd6e","cell_type":"code","source":"# Ý 1: Lấy một batch từ DataLoader\n\nfrom torch.utils.data import DataLoader\n\ndisplay_loader = DataLoader(train_dataset, batch_size=16, shuffle=True)\n\n# Lấy đúng 1 batch\nimages_batch, labels_batch = next(iter(display_loader))\n\nprint('Shape của batch ảnh  :', images_batch.shape)   # [16, 3, 224, 224]\nprint('Shape của batch nhãn :', labels_batch.shape)   # [16]\nprint('Nhãn (index) trong batch:', labels_batch.tolist())\nprint('Nhãn (tên)   trong batch:', [CLASSES[l] for l in labels_batch.tolist()])","metadata":{},"outputs":[],"execution_count":null},{"id":"0ea4e1ac-7ea7-4cac-b659-ba9027fa7db7","cell_type":"markdown","source":"### Ý 2 – Sử dụng `plt.imshow()` để hiển thị ảnh","metadata":{}},{"id":"fab2ab78-a093-478b-a63a-d13bdac3db7b","cell_type":"code","source":"# Ý 2: Sử dụng plt.imshow() hiển thị ảnh\n\nimport matplotlib.pyplot as plt\n\n# Hiển thị ảnh đầu tiên trong batch\nimg   = images_batch[0]           # Tensor [3, 224, 224]\nlabel = labels_batch[0].item()\n\n# Chuyển sang HWC để imshow\nimg_hwc = img.permute(1, 2, 0).numpy()\nimg_hwc = img_hwc.clip(0, 1)     # đảm bảo giá trị trong [0,1]\n\nplt.figure(figsize=(3, 3))\nplt.imshow(img_hwc)\nplt.title(f'Label: {CLASSES[label]}')\nplt.axis('off')\nplt.tight_layout()\nplt.show()\n\nprint('Đã hiển thị ảnh với nhãn:', CLASSES[label])","metadata":{},"outputs":[],"execution_count":null},{"id":"6b904ced-d89b-4269-b9c3-fa257fc41d5b","cell_type":"markdown","source":"### Ý 3 – Chuyển tensor sang định dạng HWC trước khi hiển thị (grid nhiều ảnh)","metadata":{}},{"id":"c278d737-3c0a-49c5-b365-f5dd30d340c3","cell_type":"code","source":"# Ý 3: Chuyển tensor sang HWC và hiển thị grid 16 ảnh\n\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nfig, axes = plt.subplots(4, 4, figsize=(10, 10))\nfig.suptitle('Mẫu ảnh CIFAR-10 từ DataLoader', fontsize=14)\n\nfor i, ax in enumerate(axes.flatten()):\n    # Bước chuyển: Tensor CHW → numpy HWC\n    img_hwc = images_batch[i].permute(1, 2, 0).numpy()\n    img_hwc = img_hwc.clip(0, 1)\n\n    ax.imshow(img_hwc)\n    ax.set_title(CLASSES[labels_batch[i].item()], fontsize=9)\n    ax.axis('off')\n\nplt.tight_layout()\nplt.show()\nprint('Hiển thị xong 16 ảnh mẫu.')","metadata":{},"outputs":[],"execution_count":null},{"id":"c33bc76e-650c-40b5-ba24-84210367f21c","cell_type":"markdown","source":"---\n## Bài tập 3: Huấn luyện Vision Transformer trong 5 epoch","metadata":{}},{"id":"09b4af2f-06e6-4337-bf7e-118f4d1e601e","cell_type":"markdown","source":"### Ý 1 – Sử dụng mô hình `vit_b_16` từ torchvision","metadata":{}},{"id":"8cb914c9-c0da-4f05-98e0-770136ac834d","cell_type":"code","source":"# Ý 1: Tải mô hình vit_b_16 pretrained\n\nimport torchvision.models as models\n\n# weights='DEFAULT' tương đương pretrained=True (API mới của torchvision)\nvit_model = models.vit_b_16(weights=models.ViT_B_16_Weights.DEFAULT)\n\nprint('Kiến trúc lớp cuối (trước khi thay):')\nprint(vit_model.heads)\nprint()\ntotal_params = sum(p.numel() for p in vit_model.parameters())\nprint(f'Tổng số tham số: {total_params:,}')","metadata":{},"outputs":[],"execution_count":null},{"id":"53e11510-6fb2-4e98-bda4-804cd3c4f09f","cell_type":"markdown","source":"### Ý 2 – Thay đổi lớp phân loại cuối để phù hợp với 10 lớp CIFAR-10","metadata":{}},{"id":"78f57c6f-8e49-4bd1-8974-1b2577cca901","cell_type":"code","source":"# Ý 2: Thay lớp head cuối thành 10 lớp (CIFAR-10)\n\nimport torch.nn as nn\n\nin_features = vit_model.heads.head.in_features   # 768 với vit_b_16\nvit_model.heads.head = nn.Linear(in_features, 10)\n\nprint('Kiến trúc lớp cuối (sau khi thay):')\nprint(vit_model.heads)\nprint()\nprint(f'in_features : {in_features}')\nprint(f'out_features: 10  (tương ứng 10 lớp CIFAR-10)')","metadata":{},"outputs":[],"execution_count":null},{"id":"e57e81ec-623b-44d8-8827-302aa0e4fae7","cell_type":"markdown","source":"### Ý 3 – Sử dụng optimizer Adam và huấn luyện 5 epoch","metadata":{}},{"id":"363385ba-fb0d-4ad0-b0b3-74c9594dde88","cell_type":"code","source":"# Ý 3: Huấn luyện 5 epoch với Adam optimizer\n\nfrom torch.utils.data import DataLoader\n\n# DataLoader cho training (batch_size=32 theo bài TH)\ntrain_loader_b3 = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=2)\n\nvit_model = vit_model.to(DEVICE)\ncriterion  = nn.CrossEntropyLoss()\noptimizer  = torch.optim.Adam(vit_model.parameters(), lr=0.0001)\n\nEPOCHS = 5\nepoch_losses = []\n\nfor epoch in range(EPOCHS):\n    vit_model.train()\n    running_loss = 0.0\n\n    for batch_idx, (images, labels) in enumerate(train_loader_b3):\n        images = images.to(DEVICE)\n        labels = labels.to(DEVICE)\n\n        outputs = vit_model(images)\n        loss    = criterion(outputs, labels)\n\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item()\n\n        if (batch_idx + 1) % 100 == 0:\n            print(f'  Epoch [{epoch+1}/{EPOCHS}] | Batch [{batch_idx+1}/{len(train_loader_b3)}] | Loss: {loss.item():.4f}')\n\n    avg_loss = running_loss / len(train_loader_b3)\n    epoch_losses.append(avg_loss)\n    print(f'>>> Epoch [{epoch+1}/{EPOCHS}] - Avg Loss: {avg_loss:.4f}')\n\nprint('\\nHuấn luyện hoàn tất!')\nprint('Loss qua các epoch:', [f'{l:.4f}' for l in epoch_losses])","metadata":{},"outputs":[],"execution_count":null},{"id":"a19e077d-b9a8-4255-b574-29b4894e1bf7","cell_type":"markdown","source":"---\n## Bài tập 4: Tính accuracy trên tập test","metadata":{}},{"id":"cbe47554-cfd0-40cf-9412-2ae1ee2ad058","cell_type":"markdown","source":"### Ý 1 – Chuyển mô hình sang chế độ evaluation bằng `model.eval()`","metadata":{}},{"id":"14f58d94-52d4-4bc6-ac22-dc08311ecc8d","cell_type":"code","source":"# Ý 1: Chuyển mô hình sang evaluation mode\n\nvit_model.eval()\n\nprint('Trạng thái training của model:', vit_model.training)\nprint('=> model.training = False: đã chuyển sang eval mode ✓')\nprint('   (Dropout và BatchNorm sẽ hoạt động ở chế độ inference)')","metadata":{},"outputs":[],"execution_count":null},{"id":"0c531a1b-3e08-4bb7-9d5f-3f0f8c15f6b4","cell_type":"markdown","source":"### Ý 2 – Tắt gradient bằng `torch.no_grad()`","metadata":{}},{"id":"07cd432d-4ccd-4deb-a590-f07c0204e070","cell_type":"code","source":"# Ý 2: Tắt gradient với torch.no_grad() để tiết kiệm bộ nhớ khi inference\n\ntest_loader_b4 = DataLoader(test_dataset, batch_size=32, num_workers=2)\n\nall_preds  = []\nall_labels = []\n\nwith torch.no_grad():          # <-- tắt gradient\n    for images, labels in test_loader_b4:\n        images = images.to(DEVICE)\n        labels = labels.to(DEVICE)\n\n        outputs   = vit_model(images)\n        _, preds  = torch.max(outputs, 1)\n\n        all_preds.append(preds.cpu())\n        all_labels.append(labels.cpu())\n\nall_preds  = torch.cat(all_preds)\nall_labels = torch.cat(all_labels)\n\nprint('Tổng số mẫu test:', len(all_labels))\nprint('Đã thu thập toàn bộ prediction (gradient=OFF) ✓')","metadata":{},"outputs":[],"execution_count":null},{"id":"a440c04c-2609-4492-afd5-86b02d1fcdb8","cell_type":"markdown","source":"### Ý 3 – So sánh nhãn dự đoán với nhãn thật, in accuracy","metadata":{}},{"id":"226a248e-9200-4ce9-8fbd-2a2b942c7445","cell_type":"code","source":"# Ý 3: So sánh predicted == labels và tính accuracy\n\ncorrect  = (all_preds == all_labels).sum().item()\ntotal    = len(all_labels)\naccuracy = 100.0 * correct / total\n\nprint('=== KẾT QUẢ ĐÁNH GIÁ TRÊN TẬP TEST ===')\nprint(f'Số dự đoán đúng : {correct:,} / {total:,}')\nprint(f'Accuracy         : {accuracy:.2f}%')\n\n# Accuracy per class\nprint('\\nAccuracy theo từng lớp:')\nfor i, cls in enumerate(CLASSES):\n    mask     = (all_labels == i)\n    cls_acc  = 100.0 * (all_preds[mask] == i).sum().item() / mask.sum().item()\n    print(f'  {cls:12s}: {cls_acc:.2f}%')","metadata":{},"outputs":[],"execution_count":null},{"id":"b534d541-8632-4028-9c44-d18031b58230","cell_type":"markdown","source":"---\n## Bài tập 5: Thay đổi learning rate và quan sát accuracy","metadata":{}},{"id":"ede3b3e0-8949-48f2-93a7-bc3267687059","cell_type":"markdown","source":"### Ý 1 – Thử learning rate = 0.0001","metadata":{}},{"id":"ef90d68d-c2a6-4cf1-ad8e-fe0b5dbbd52d","cell_type":"code","source":"# Ý 1: Huấn luyện và đánh giá với lr = 0.0001\n\ndef train_and_eval(lr, epochs=3, batch_size=32):\n    \"\"\"Hàm tiện ích: huấn luyện ViT với lr cho trước, trả về accuracy test.\"\"\"\n    m = models.vit_b_16(weights=models.ViT_B_16_Weights.DEFAULT)\n    m.heads.head = nn.Linear(m.heads.head.in_features, 10)\n    m = m.to(DEVICE)\n\n    loader   = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=2)\n    t_loader = DataLoader(test_dataset,  batch_size=batch_size, num_workers=2)\n    crit     = nn.CrossEntropyLoss()\n    opt      = torch.optim.Adam(m.parameters(), lr=lr)\n\n    for ep in range(epochs):\n        m.train()\n        run_loss = 0.0\n        for imgs, lbls in loader:\n            imgs, lbls = imgs.to(DEVICE), lbls.to(DEVICE)\n            loss = crit(m(imgs), lbls)\n            opt.zero_grad(); loss.backward(); opt.step()\n            run_loss += loss.item()\n        print(f'  [lr={lr}] Epoch {ep+1}/{epochs} - Loss: {run_loss/len(loader):.4f}')\n\n    m.eval()\n    correct = total = 0\n    with torch.no_grad():\n        for imgs, lbls in t_loader:\n            imgs, lbls = imgs.to(DEVICE), lbls.to(DEVICE)\n            _, pred = torch.max(m(imgs), 1)\n            total   += lbls.size(0)\n            correct += (pred == lbls).sum().item()\n    return 100.0 * correct / total\n\nlr1 = 0.0001\nacc_lr1 = train_and_eval(lr=lr1, epochs=3)\nprint(f'\\n>>> Accuracy với lr={lr1}: {acc_lr1:.2f}%')","metadata":{},"outputs":[],"execution_count":null},{"id":"8cc63433-814e-4ba5-82e7-3759b5136e6d","cell_type":"markdown","source":"### Ý 2 – Thử learning rate = 0.0003","metadata":{}},{"id":"1ef1843d-35df-40e2-8bb2-f1163d939ed3","cell_type":"code","source":"# Ý 2: Huấn luyện và đánh giá với lr = 0.0003\n\nlr2 = 0.0003\nacc_lr2 = train_and_eval(lr=lr2, epochs=3)\nprint(f'\\n>>> Accuracy với lr={lr2}: {acc_lr2:.2f}%')","metadata":{},"outputs":[],"execution_count":null},{"id":"e6fb0897-19ed-4ffe-97ec-2ae01f7ce38c","cell_type":"markdown","source":"### Ý 3 – Thử learning rate = 0.001 và so sánh kết quả","metadata":{}},{"id":"0b9f1df3-3f42-4216-9eef-11a11a86fde6","cell_type":"code","source":"# Ý 3: Huấn luyện với lr=0.001 và in bảng so sánh 3 learning rates\n\nlr3 = 0.001\nacc_lr3 = train_and_eval(lr=lr3, epochs=3)\nprint(f'\\n>>> Accuracy với lr={lr3}: {acc_lr3:.2f}%')\n\n# Bảng so sánh\nprint('\\n' + '='*38)\nprint(f'{\"Learning Rate\":<15} | {\"Accuracy (%)\":>12}')\nprint('-'*38)\nfor lr_val, acc_val in [(lr1, acc_lr1), (lr2, acc_lr2), (lr3, acc_lr3)]:\n    print(f'{str(lr_val):<15} | {acc_val:>11.2f}%')\nprint('='*38)","metadata":{},"outputs":[],"execution_count":null},{"id":"81b193cf-6cd6-4df2-8d10-15addf7179dc","cell_type":"markdown","source":"---\n## Bài tập 6: Thử batch size khác nhau (16, 32, 64)","metadata":{}},{"id":"4ae00738-415f-4177-b091-b97ad531a307","cell_type":"markdown","source":"### Ý 1 – Thử batch_size = 16","metadata":{}},{"id":"2fd5b25b-9199-4407-89d9-b390a7ba6762","cell_type":"code","source":"# Ý 1: batch_size = 16 — quan sát tốc độ và loss\n\nimport time\n\ndef train_one_epoch_with_bs(batch_size, lr=0.0001):\n    \"\"\"Huấn luyện 1 epoch, trả về (avg_loss, thời gian, accuracy).\"\"\"\n    m = models.vit_b_16(weights=models.ViT_B_16_Weights.DEFAULT)\n    m.heads.head = nn.Linear(m.heads.head.in_features, 10)\n    m = m.to(DEVICE)\n\n    loader   = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=2)\n    t_loader = DataLoader(test_dataset,  batch_size=batch_size, num_workers=2)\n    crit     = nn.CrossEntropyLoss()\n    opt      = torch.optim.Adam(m.parameters(), lr=lr)\n\n    m.train()\n    run_loss = 0.0\n    t0 = time.time()\n    for imgs, lbls in loader:\n        imgs, lbls = imgs.to(DEVICE), lbls.to(DEVICE)\n        loss = crit(m(imgs), lbls)\n        opt.zero_grad(); loss.backward(); opt.step()\n        run_loss += loss.item()\n    elapsed = time.time() - t0\n    avg_loss = run_loss / len(loader)\n\n    m.eval()\n    correct = total = 0\n    with torch.no_grad():\n        for imgs, lbls in t_loader:\n            imgs, lbls = imgs.to(DEVICE), lbls.to(DEVICE)\n            _, pred = torch.max(m(imgs), 1)\n            total   += lbls.size(0)\n            correct += (pred == lbls).sum().item()\n    acc = 100.0 * correct / total\n    return avg_loss, elapsed, acc\n\nbs = 16\nloss_16, time_16, acc_16 = train_one_epoch_with_bs(batch_size=bs)\nprint(f'batch_size={bs}: Loss={loss_16:.4f} | Thời gian={time_16:.1f}s | Accuracy={acc_16:.2f}%')","metadata":{},"outputs":[],"execution_count":null},{"id":"e4c30110-548b-464a-8eb0-f71ad035580b","cell_type":"markdown","source":"### Ý 2 – Thử batch_size = 32","metadata":{}},{"id":"23cbb40a-d43b-487f-bec3-9941e5c36a52","cell_type":"code","source":"# Ý 2: batch_size = 32\n\nbs = 32\nloss_32, time_32, acc_32 = train_one_epoch_with_bs(batch_size=bs)\nprint(f'batch_size={bs}: Loss={loss_32:.4f} | Thời gian={time_32:.1f}s | Accuracy={acc_32:.2f}%')","metadata":{},"outputs":[],"execution_count":null},{"id":"be7e8b0c-a1db-45b4-8251-4be3e657a903","cell_type":"markdown","source":"### Ý 3 – Thử batch_size = 64 và so sánh 3 batch size","metadata":{}},{"id":"07be45fb-a4a7-4fa6-89b9-0f3aab930009","cell_type":"code","source":"# Ý 3: batch_size = 64 và bảng so sánh tốc độ, loss, accuracy\n\nbs = 64\nloss_64, time_64, acc_64 = train_one_epoch_with_bs(batch_size=bs)\nprint(f'batch_size={bs}: Loss={loss_64:.4f} | Thời gian={time_64:.1f}s | Accuracy={acc_64:.2f}%')\n\nprint()\nprint('='*60)\nprint(f'{\"Batch Size\":<12} | {\"Avg Loss\":<10} | {\"Thời gian (s)\":<15} | {\"Accuracy\":<10}')\nprint('-'*60)\nfor bs_v, l, t, a in [(16, loss_16, time_16, acc_16),\n                       (32, loss_32, time_32, acc_32),\n                       (64, loss_64, time_64, acc_64)]:\n    print(f'{str(bs_v):<12} | {l:<10.4f} | {t:<15.1f} | {a:<10.2f}%')\nprint('='*60)","metadata":{},"outputs":[],"execution_count":null},{"id":"471c599e-1f2d-48f8-9a67-567cc201f84b","cell_type":"markdown","source":"---\n## Bài tập 7: So sánh Vision Transformer và CNN đơn giản","metadata":{}},{"id":"507f35d3-c6ee-4c3e-9455-2b155cb4e0c9","cell_type":"markdown","source":"### Ý 1 – Xây dựng SimpleCNN với vài lớp convolution","metadata":{}},{"id":"554d4b99-f5eb-40ac-890c-804b6fe2b04f","cell_type":"code","source":"# Ý 1: Định nghĩa kiến trúc SimpleCNN\n\nimport torch.nn as nn\n\nclass SimpleCNN(nn.Module):\n    def __init__(self, num_classes=10):\n        super().__init__()\n        self.conv = nn.Sequential(\n            nn.Conv2d(3, 32, 3, padding=1),   # [B, 32, 224, 224]\n            nn.ReLU(),\n            nn.MaxPool2d(2),                   # [B, 32, 112, 112]\n            nn.Conv2d(32, 64, 3, padding=1),   # [B, 64, 112, 112]\n            nn.ReLU(),\n            nn.MaxPool2d(2),                   # [B, 64, 56, 56]\n            nn.Conv2d(64, 128, 3, padding=1),  # [B, 128, 56, 56]\n            nn.ReLU(),\n            nn.MaxPool2d(2)                    # [B, 128, 28, 28]\n        )\n        self.fc = nn.Sequential(\n            nn.Flatten(),\n            nn.Linear(128 * 28 * 28, 256),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(256, num_classes)\n        )\n\n    def forward(self, x):\n        x = self.conv(x)\n        x = self.fc(x)\n        return x\n\ncnn_model = SimpleCNN(num_classes=10)\nprint('Kiến trúc SimpleCNN:')\nprint(cnn_model)\nprint()\ncnn_params = sum(p.numel() for p in cnn_model.parameters())\nprint(f'Số tham số SimpleCNN: {cnn_params:,}')","metadata":{},"outputs":[],"execution_count":null},{"id":"1187247f-a94c-44b4-8295-973599a757cd","cell_type":"markdown","source":"### Ý 2 – Huấn luyện SimpleCNN trên CIFAR-10","metadata":{}},{"id":"5745ae96-4eb9-4d37-985d-aea1492d069d","cell_type":"code","source":"# Ý 2: Huấn luyện SimpleCNN 5 epoch\n\nimport time\n\ncnn_model = cnn_model.to(DEVICE)\ntrain_loader_b7 = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=2)\ntest_loader_b7  = DataLoader(test_dataset,  batch_size=64, num_workers=2)\n\ncrit_cnn = nn.CrossEntropyLoss()\nopt_cnn  = torch.optim.Adam(cnn_model.parameters(), lr=0.001)\n\ncnn_losses = []\nt_cnn_start = time.time()\n\nfor epoch in range(5):\n    cnn_model.train()\n    run = 0.0\n    for imgs, lbls in train_loader_b7:\n        imgs, lbls = imgs.to(DEVICE), lbls.to(DEVICE)\n        loss = crit_cnn(cnn_model(imgs), lbls)\n        opt_cnn.zero_grad(); loss.backward(); opt_cnn.step()\n        run += loss.item()\n    avg = run / len(train_loader_b7)\n    cnn_losses.append(avg)\n    print(f'[CNN] Epoch {epoch+1}/5 - Loss: {avg:.4f}')\n\ncnn_train_time = time.time() - t_cnn_start\nprint(f'\\nTổng thời gian huấn luyện CNN: {cnn_train_time:.1f}s')","metadata":{},"outputs":[],"execution_count":null},{"id":"f5d10445-c29d-482f-8730-0c8f0fa985d4","cell_type":"markdown","source":"### Ý 3 – So sánh accuracy và số tham số giữa ViT và CNN","metadata":{}},{"id":"8ca14726-d14b-41aa-a510-8b62d11178b6","cell_type":"code","source":"# Ý 3: Đánh giá CNN và in bảng so sánh ViT vs CNN\n\n# Tính lại accuracy ViT (đảm bảo không phụ thuộc cell bài 4)\nvit_model.eval()\ncorrect_vit = total_vit = 0\nwith torch.no_grad():\n    for imgs, lbls in DataLoader(test_dataset, batch_size=64, num_workers=2):\n        imgs, lbls = imgs.to(DEVICE), lbls.to(DEVICE)\n        _, pred = torch.max(vit_model(imgs), 1)\n        total_vit   += lbls.size(0)\n        correct_vit += (pred == lbls).sum().item()\naccuracy = 100.0 * correct_vit / total_vit   # ghi đè/khởi tạo biến an toàn\n\n\ncnn_model.eval()\ncorrect_cnn = total_cnn = 0\nwith torch.no_grad():\n    for imgs, lbls in test_loader_b7:\n        imgs, lbls = imgs.to(DEVICE), lbls.to(DEVICE)\n        _, pred = torch.max(cnn_model(imgs), 1)\n        total_cnn   += lbls.size(0)\n        correct_cnn += (pred == lbls).sum().item()\nacc_cnn = 100.0 * correct_cnn / total_cnn\n\n# Số tham số\nvit_params = sum(p.numel() for p in vit_model.parameters())\n\nprint('='*65)\nprint(f'{\"Tiêu chí\":<25} | {\"SimpleCNN\":>15} | {\"ViT-B/16\":>15}')\nprint('-'*65)\nprint(f'{\"Accuracy (test)\":<25} | {acc_cnn:>14.2f}% | {accuracy:>14.2f}%')\nprint(f'{\"Số tham số\":<25} | {cnn_params:>15,} | {vit_params:>15,}')\nprint(f'{\"Thời gian train (s)\":<25} | {cnn_train_time:>14.1f}s | {\"(5 epoch trước)\":>15}')\nprint(f'{\"Kiến trúc\":<25} | {\"Convolution\":>15} | {\"Self-Attention\":>15}')\nprint(f'{\"Dữ liệu cần thiết\":<25} | {\"Ít hơn\":>15} | {\"Nhiều hơn\":>15}')\nprint('='*65)","metadata":{},"outputs":[],"execution_count":null},{"id":"f833006c-8acc-40ca-8ea4-f3195d76c76c","cell_type":"markdown","source":"---\n## Bài tập 8: Thử dataset CIFAR-100","metadata":{}},{"id":"f403bad1-a7c1-4446-939c-df46b7a67bb1","cell_type":"markdown","source":"### Ý 1 – Tải CIFAR-100 bằng `torchvision.datasets.CIFAR100`","metadata":{}},{"id":"a7775925-5a8e-48ac-911e-5577ba991c49","cell_type":"code","source":"# Ý 1: Tải CIFAR-100 (100 lớp)\n\ntransform_c100 = transforms.Compose([\n    transforms.Resize(224),\n    transforms.ToTensor(),\n    transforms.Normalize([0.5071, 0.4867, 0.4408],\n                         [0.2675, 0.2565, 0.2761])\n])\n\ntrain_c100 = datasets.CIFAR100(\n    root='./data', train=True,  download=True, transform=transform_c100\n)\ntest_c100  = datasets.CIFAR100(\n    root='./data', train=False, download=True, transform=transform_c100\n)\n\nprint('Train size :', len(train_c100))\nprint('Test  size :', len(test_c100))\nprint('Số lớp     :', len(train_c100.classes))\nprint('10 lớp đầu :', train_c100.classes[:10])","metadata":{},"outputs":[],"execution_count":null},{"id":"dfb73460-8b80-49e4-8da6-003e5eb1f003","cell_type":"markdown","source":"### Ý 2 – Thay đổi số lớp đầu ra của ViT thành 100","metadata":{}},{"id":"35c4599f-4206-4e24-8850-9c6f89b6be4b","cell_type":"code","source":"# Ý 2: Tải ViT và thay head thành 100 lớp\n\nvit_c100 = models.vit_b_16(weights=models.ViT_B_16_Weights.DEFAULT)\nvit_c100.heads.head = nn.Linear(vit_c100.heads.head.in_features, 100)\nvit_c100 = vit_c100.to(DEVICE)\n\nprint('Lớp head đã được thay đổi:')\nprint(vit_c100.heads)\nprint()\nprint('in_features  :', vit_c100.heads.head.in_features)\nprint('out_features :', vit_c100.heads.head.out_features, '← 100 lớp CIFAR-100 ✓')","metadata":{},"outputs":[],"execution_count":null},{"id":"64e0ee0d-225d-4aa4-b1a9-28a741a57e9f","cell_type":"markdown","source":"### Ý 3 – Huấn luyện và đánh giá ViT trên CIFAR-100","metadata":{}},{"id":"a9e28477-5de8-4882-b011-98dcb37f8312","cell_type":"code","source":"# Ý 3: Huấn luyện ViT 3 epoch trên CIFAR-100 và in accuracy\n\nloader_c100_train = DataLoader(train_c100, batch_size=32, shuffle=True, num_workers=2)\nloader_c100_test  = DataLoader(test_c100,  batch_size=32, num_workers=2)\n\ncrit_c100 = nn.CrossEntropyLoss()\nopt_c100  = torch.optim.Adam(vit_c100.parameters(), lr=0.0001)\n\nfor epoch in range(3):\n    vit_c100.train()\n    run = 0.0\n    for imgs, lbls in loader_c100_train:\n        imgs, lbls = imgs.to(DEVICE), lbls.to(DEVICE)\n        loss = crit_c100(vit_c100(imgs), lbls)\n        opt_c100.zero_grad(); loss.backward(); opt_c100.step()\n        run += loss.item()\n    print(f'[CIFAR-100] Epoch {epoch+1}/3 - Loss: {run/len(loader_c100_train):.4f}')\n\nvit_c100.eval()\ncorrect = total = 0\nwith torch.no_grad():\n    for imgs, lbls in loader_c100_test:\n        imgs, lbls = imgs.to(DEVICE), lbls.to(DEVICE)\n        _, pred = torch.max(vit_c100(imgs), 1)\n        total   += lbls.size(0)\n        correct += (pred == lbls).sum().item()\nacc_c100 = 100.0 * correct / total\n\nprint(f'\\n=== Accuracy ViT-B/16 trên CIFAR-100: {acc_c100:.2f}% ===')","metadata":{},"outputs":[],"execution_count":null},{"id":"096d4e25-73b9-4023-b2e9-8df280c2209c","cell_type":"markdown","source":"---\n## Bài tập 9: Thử mô hình ViT khác – `vit_b_32`","metadata":{}},{"id":"13fc0082-4c1d-4cfb-bc0a-f6b29e67f009","cell_type":"markdown","source":"### Ý 1 – Tải mô hình `vit_b_32` và thay head","metadata":{}},{"id":"4c3b4b0d-0a53-483d-bca3-c54245e6b1fb","cell_type":"code","source":"# Ý 1: Tải vit_b_32 pretrained và thay head thành 10 lớp\n\nvit_b32 = models.vit_b_32(weights=models.ViT_B_32_Weights.DEFAULT)\nvit_b32.heads.head = nn.Linear(vit_b32.heads.head.in_features, 10)\nvit_b32 = vit_b32.to(DEVICE)\n\nb32_params = sum(p.numel() for p in vit_b32.parameters())\nb16_params = sum(p.numel() for p in vit_model.parameters())\n\nprint('Mô hình vit_b_32 đã được tải:')\nprint(vit_b32.heads)\nprint()\nprint(f'Patch size vit_b_16: 16x16  | Tham số: {b16_params:,}')\nprint(f'Patch size vit_b_32: 32x32  | Tham số: {b32_params:,}')\nprint(f'vit_b_32 ít tham số hơn vì số patch ít hơn: (224/32)²={int((224/32)**2)} thay vì (224/16)²={int((224/16)**2)}')","metadata":{},"outputs":[],"execution_count":null},{"id":"e906d047-2779-4b6c-9e64-edcbbb285c8e","cell_type":"markdown","source":"### Ý 2 – Huấn luyện `vit_b_32` trên CIFAR-10","metadata":{}},{"id":"62c7d634-611c-4cfc-bda8-ed4ad80ab3dd","cell_type":"code","source":"# Ý 2: Huấn luyện vit_b_32 trên CIFAR-10 (3 epoch)\n\nloader_b9 = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=2)\ncrit_b9   = nn.CrossEntropyLoss()\nopt_b9    = torch.optim.Adam(vit_b32.parameters(), lr=0.0001)\n\nfor epoch in range(3):\n    vit_b32.train()\n    run = 0.0\n    for imgs, lbls in loader_b9:\n        imgs, lbls = imgs.to(DEVICE), lbls.to(DEVICE)\n        loss = crit_b9(vit_b32(imgs), lbls)\n        opt_b9.zero_grad(); loss.backward(); opt_b9.step()\n        run += loss.item()\n    print(f'[vit_b_32] Epoch {epoch+1}/3 - Loss: {run/len(loader_b9):.4f}')","metadata":{},"outputs":[],"execution_count":null},{"id":"11959e3e-92ff-42d7-9d91-34a3b536487b","cell_type":"markdown","source":"### Ý 3 – So sánh accuracy giữa `vit_b_16` và `vit_b_32`","metadata":{}},{"id":"fc0b9c68-3461-446f-892f-bacc773be867","cell_type":"code","source":"# Ý 3: Đánh giá vit_b_32 và in bảng so sánh với vit_b_16\n\n# Tính lại accuracy vit_b_16 (đảm bảo không phụ thuộc cell bài 4)\nvit_model.eval()\ncorrect_b16 = total_b16 = 0\nwith torch.no_grad():\n    for imgs, lbls in DataLoader(test_dataset, batch_size=32, num_workers=2):\n        imgs, lbls = imgs.to(DEVICE), lbls.to(DEVICE)\n        _, pred = torch.max(vit_model(imgs), 1)\n        total_b16   += lbls.size(0)\n        correct_b16 += (pred == lbls).sum().item()\naccuracy = 100.0 * correct_b16 / total_b16   # cập nhật biến accuracy\n\n\ntest_loader_b9 = DataLoader(test_dataset, batch_size=32, num_workers=2)\n\nvit_b32.eval()\ncorrect_b32 = total_b32 = 0\nwith torch.no_grad():\n    for imgs, lbls in test_loader_b9:\n        imgs, lbls = imgs.to(DEVICE), lbls.to(DEVICE)\n        _, pred = torch.max(vit_b32(imgs), 1)\n        total_b32   += lbls.size(0)\n        correct_b32 += (pred == lbls).sum().item()\nacc_b32 = 100.0 * correct_b32 / total_b32\n\nprint('='*58)\nprint(f'{\"Tiêu chí\":<20} | {\"ViT-B/16\":>16} | {\"ViT-B/32\":>16}')\nprint('-'*58)\nprint(f'{\"Patch size\":<20} | {\"16x16\":>16} | {\"32x32\":>16}')\nprint(f'{\"Số patch (224²)\":<20} | {\"196\":>16} | {\"49\":>16}')\nprint(f'{\"Số tham số\":<20} | {b16_params:>16,} | {b32_params:>16,}')\nprint(f'{\"Accuracy (test)\":<20} | {accuracy:>15.2f}% | {acc_b32:>15.2f}%')\nprint('='*58)","metadata":{},"outputs":[],"execution_count":null},{"id":"60fa1829-b9a6-4b37-8ed4-c702a8631d8d","cell_type":"markdown","source":"---\n## Bài tập 10: Phân tích các trường hợp mô hình dự đoán sai","metadata":{}},{"id":"dc707238-cd68-47da-8f15-4b6630927f56","cell_type":"markdown","source":"### Ý 1 – Lưu lại các ảnh mà mô hình dự đoán sai","metadata":{}},{"id":"e30f3123-7886-4b37-aaa0-6dcde45de96a","cell_type":"code","source":"# Ý 1: Thu thập các ảnh bị dự đoán sai từ test set\n\nfrom torch.utils.data import DataLoader\n\n# Dùng lại vit_model (đã train ở bài 3) và test_dataset\ntest_loader_b10 = DataLoader(test_dataset, batch_size=32, num_workers=2)\n\nwrong_images  = []   # tensor ảnh\nwrong_labels  = []   # nhãn thật\nwrong_preds   = []   # nhãn dự đoán sai\n\nvit_model.eval()\nwith torch.no_grad():\n    for imgs, lbls in test_loader_b10:\n        imgs_d, lbls_d = imgs.to(DEVICE), lbls.to(DEVICE)\n        outputs = vit_model(imgs_d)\n        _, preds = torch.max(outputs, 1)\n\n        mask = (preds != lbls_d)          # chỉ lấy các mẫu sai\n        if mask.any():\n            wrong_images.append(imgs[mask.cpu()])     # giữ trên CPU\n            wrong_labels.append(lbls_d[mask].cpu())\n            wrong_preds.append(preds[mask].cpu())\n\n        if len(wrong_images) > 0 and sum(len(w) for w in wrong_images) >= 16:\n            break   # đủ 16 ảnh để hiển thị\n\nwrong_images = torch.cat(wrong_images)[:16]\nwrong_labels = torch.cat(wrong_labels)[:16]\nwrong_preds  = torch.cat(wrong_preds)[:16]\n\nprint(f'Thu thập được {len(wrong_images)} ảnh dự đoán sai.')\nprint('Nhãn thật  :', [CLASSES[l] for l in wrong_labels.tolist()])\nprint('Nhãn sai   :', [CLASSES[p] for p in wrong_preds.tolist()])","metadata":{},"outputs":[],"execution_count":null},{"id":"655c50af-f9fa-414b-a730-0d5967713d74","cell_type":"markdown","source":"### Ý 2 – Hiển thị ảnh cùng với nhãn thật và nhãn dự đoán","metadata":{}},{"id":"999742e9-1f8d-4b09-a6ef-c44099192aa1","cell_type":"code","source":"# Ý 2: Visualize ảnh sai — nhãn thật (xanh) vs nhãn dự đoán (đỏ)\n\nimport matplotlib.pyplot as plt\n\nfig, axes = plt.subplots(4, 4, figsize=(14, 14))\nfig.suptitle('Các ảnh dự đoán SAI  (Xanh = Nhãn thật | Đỏ = Nhãn dự đoán sai)', fontsize=13)\n\nfor i, ax in enumerate(axes.flatten()):\n    img = wrong_images[i].permute(1, 2, 0).numpy().clip(0, 1)\n    true_cls = CLASSES[wrong_labels[i].item()]\n    pred_cls = CLASSES[wrong_preds[i].item()]\n\n    ax.imshow(img)\n    ax.set_title(f'✔ Thật: {true_cls}\\n✘ Đoán: {pred_cls}',\n                 fontsize=8, color='red')   # luôn đỏ vì đây là ảnh sai\n    ax.axis('off')\n\nplt.tight_layout()\nplt.show()\nprint('Đã hiển thị 16 ảnh dự đoán sai.')","metadata":{},"outputs":[],"execution_count":null},{"id":"37e8ae28-44d5-4807-a63e-ce25e0046778","cell_type":"markdown","source":"### Ý 3 – Thảo luận tại sao mô hình có thể nhầm lẫn","metadata":{}},{"id":"1f600312-feec-493e-b5bb-4b7ae76a5d35","cell_type":"code","source":"# Ý 3: Phân tích thống kê các cặp nhầm lẫn phổ biến nhất\n\nfrom collections import Counter\nimport matplotlib.pyplot as plt\n\n# Thu thập TẤT CẢ ảnh sai (không giới hạn 16)\nall_wrong_true = []\nall_wrong_pred = []\n\nvit_model.eval()\nwith torch.no_grad():\n    for imgs, lbls in DataLoader(test_dataset, batch_size=64, num_workers=2):\n        imgs_d, lbls_d = imgs.to(DEVICE), lbls.to(DEVICE)\n        _, preds = torch.max(vit_model(imgs_d), 1)\n        mask = (preds != lbls_d)\n        all_wrong_true.extend(lbls_d[mask].cpu().tolist())\n        all_wrong_pred.extend(preds[mask].cpu().tolist())\n\n# Cặp (true_class, pred_class) phổ biến nhất\npairs = Counter(zip(\n    [CLASSES[t] for t in all_wrong_true],\n    [CLASSES[p] for p in all_wrong_pred]\n))\ntop10_pairs = pairs.most_common(10)\n\nprint('=== Top 10 cặp nhầm lẫn phổ biến nhất ===')\nprint(f'{\"Thật → Dự đoán\":<25} | {\"Số lần nhầm\"}')\nprint('-'*40)\nfor (true_c, pred_c), cnt in top10_pairs:\n    print(f'{true_c:>10} → {pred_c:<12} | {cnt}')\n\n# Bar chart\nlabels_chart = [f'{t}→{p}' for (t, p), _ in top10_pairs]\ncounts_chart = [c for _, c in top10_pairs]\n\nplt.figure(figsize=(10, 4))\nplt.barh(labels_chart[::-1], counts_chart[::-1], color='salmon')\nplt.xlabel('Số lần nhầm lẫn')\nplt.title('Top 10 cặp nhầm lẫn phổ biến (ViT-B/16 trên CIFAR-10)')\nplt.tight_layout()\nplt.show()\n\nprint()\nprint('=== THẢO LUẬN ===')\nprint('1. Các cặp dễ nhầm thường có ngoại hình tương tự (cat↔dog, deer↔horse).')\nprint('2. ViT cần nhiều dữ liệu hơn CNN để học feature cục bộ tốt.')\nprint('3. CIFAR-10 chỉ 32x32 → resize lên 224 gây mất nét, tăng lỗi.')\nprint('4. 5 epoch còn ít; fine-tuning lâu hơn sẽ giảm nhầm lẫn.')","metadata":{},"outputs":[],"execution_count":null}]}