{"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":"bea7a930-a7eb-4119-9221-a26db0fd5638","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":"2c5a8132-4b01-4962-afa4-1d6da395cc47","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":"f77185dc-d610-4a96-9120-c8f0f9094910","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":"2c315b5a-96f0-4a81-8ad6-5671313f83dc","cell_type":"markdown","source":"---\n## Bài tập 1: Tải dataset CIFAR-10","metadata":{}},{"id":"d88f6251-fab8-4d2f-9ed3-2697e2cb6b36","cell_type":"markdown","source":"### Ý 1 – Sử dụng thư viện `torchvision.datasets` để tải CIFAR-10","metadata":{}},{"id":"3094a7e1-e30b-459a-ae10-da0024c355d9","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":"8d3bf37d-7d1d-44a7-84fd-3a99010f519e","cell_type":"markdown","source":"### Ý 2 – Áp dụng các phép biến đổi cơ bản bằng `torchvision.transforms`","metadata":{}},{"id":"3df1fa0d-a6dc-4c7e-b7ac-11ed70dff19d","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":"380e1c36-203c-47a8-befc-836715c14cf2","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":"2c5ee450-3020-47f1-b7a9-55ebb7290b58","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":"dfb2168e-55f2-487e-86cc-5eaea2c75f81","cell_type":"markdown","source":"---\n## Bài tập 2: Hiển thị một số ảnh trong dataset bằng matplotlib","metadata":{}},{"id":"f36c034c-acc9-47b9-9e66-c30740235bad","cell_type":"markdown","source":"### Ý 1 – Lấy một batch từ DataLoader","metadata":{}},{"id":"584c5472-e6cd-4512-b551-7855f1093d17","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":"a99a27c1-63e4-47da-8f05-2ea3ac30bc78","cell_type":"markdown","source":"### Ý 2 – Sử dụng `plt.imshow()` để hiển thị ảnh","metadata":{}},{"id":"dd7f5a49-dce0-4251-9cae-b0f15d5f097c","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":"0d9704c3-cea1-478a-b5cd-99cbf95de14b","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":"ff90cc8d-f334-427d-b24e-6a752d6abd32","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":"d5cac870-223d-4f2c-8ba7-72130b0a2aae","cell_type":"markdown","source":"---\n## Bài tập 3: Huấn luyện Vision Transformer trong 5 epoch","metadata":{}},{"id":"badd3880-7a49-4f3b-a5a1-eb7834829def","cell_type":"markdown","source":"### Ý 1 – Sử dụng mô hình `vit_b_16` từ torchvision","metadata":{}},{"id":"1db81ac4-a404-4e69-85cf-d822543b2ecc","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":"a5237f0d-85e9-4061-ae8b-b43775322052","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":"93f8abb2-120c-46da-9213-171965522b97","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":"19c284d0-e6f7-4379-a695-f62230d9b359","cell_type":"markdown","source":"### Ý 3 – Sử dụng optimizer Adam và huấn luyện 5 epoch","metadata":{}},{"id":"31a9b487-967e-4de4-9215-6476dca16d08","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":"833f5fbe-307a-46ea-9eff-b9cbc10b168a","cell_type":"markdown","source":"---\n## Bài tập 4: Tính accuracy trên tập test","metadata":{}},{"id":"d4e75d71-85a4-4a5e-9b6d-a8557eaa0a99","cell_type":"markdown","source":"### Ý 1 – Chuyển mô hình sang chế độ evaluation bằng `model.eval()`","metadata":{}},{"id":"878cef26-3a7d-4fff-bae1-dc531b57aa69","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":"468c1c90-89ab-457d-937a-35087fe88531","cell_type":"markdown","source":"### Ý 2 – Tắt gradient bằng `torch.no_grad()`","metadata":{}},{"id":"8d067dd2-9c73-4e3b-b82d-1fd982555001","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":"de7e4938-74cd-4b51-8783-fccf7f5ebfd8","cell_type":"markdown","source":"### Ý 3 – So sánh nhãn dự đoán với nhãn thật, in accuracy","metadata":{}},{"id":"814a26a6-add2-4fcb-b322-59becbde5a6b","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":"42a60daf-f847-47d4-b5e8-934d5d689c87","cell_type":"markdown","source":"---\n## Bài tập 5: Thay đổi learning rate và quan sát accuracy","metadata":{}},{"id":"314303ee-5808-4b44-9d72-dc012d14093e","cell_type":"markdown","source":"### Ý 1 – Thử learning rate = 0.0001","metadata":{}},{"id":"94400bc4-e449-4f11-bb68-396beeb294b7","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":"6c3bb90f-5806-4a0a-9299-c657f6c05097","cell_type":"markdown","source":"### Ý 2 – Thử learning rate = 0.0003","metadata":{}},{"id":"bef0f04e-3690-45ec-9efe-b6bede6aa638","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":"c6ca5cdb-2b2b-49f6-976c-8d4c56e836ac","cell_type":"markdown","source":"### Ý 3 – Thử learning rate = 0.001 và so sánh kết quả","metadata":{}},{"id":"ab18bcab-41de-4c32-842c-6f560ed5cad4","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":"165465c5-7c3e-45a2-96af-d46d438ca4e3","cell_type":"markdown","source":"---\n## Bài tập 6: Thử batch size khác nhau (16, 32, 64)","metadata":{}},{"id":"ed50a029-9c15-4692-b277-ef4dd797ba31","cell_type":"markdown","source":"### Ý 1 – Thử batch_size = 16","metadata":{}},{"id":"c4713551-1bc2-40c0-8d07-8e9041e6e697","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":"d0269feb-48f3-4759-aaab-915ab392b18a","cell_type":"markdown","source":"### Ý 2 – Thử batch_size = 32","metadata":{}},{"id":"7dcfff6f-fec8-45a9-8a6a-64ccbb0f108d","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":"f679e203-2518-4c17-baa1-0745b129f479","cell_type":"markdown","source":"### Ý 3 – Thử batch_size = 64 và so sánh 3 batch size","metadata":{}},{"id":"6272058b-a0c5-4ad9-b30e-1c83ee68bed4","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":"82ca7c41-4948-48d4-b17f-3cbdae6ace32","cell_type":"markdown","source":"---\n## Bài tập 7: So sánh Vision Transformer và CNN đơn giản","metadata":{}},{"id":"f460565e-7dcb-467e-ba8c-aee3d0136b35","cell_type":"markdown","source":"### Ý 1 – Xây dựng SimpleCNN với vài lớp convolution","metadata":{}},{"id":"9f999f31-1d54-4e3c-a5b8-fd7b67ead4e1","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":"a33f4baf-b675-4a2b-8f7c-f5462e9d816f","cell_type":"markdown","source":"### Ý 2 – Huấn luyện SimpleCNN trên CIFAR-10","metadata":{}},{"id":"504571a9-2f1a-4425-bcec-53d93a47f970","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":"e97e3ee3-2302-4f82-888a-20758c0f6aaa","cell_type":"markdown","source":"### Ý 3 – So sánh accuracy và số tham số giữa ViT và CNN","metadata":{}},{"id":"527937f8-a480-4d5c-8296-d119f9381bee","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":"4a3c43a5-7ce0-46d9-b7df-b6057fb98cd6","cell_type":"markdown","source":"---\n## Bài tập 8: Thử dataset CIFAR-100","metadata":{}},{"id":"78165738-cd95-4d8f-8d15-bf876c37a784","cell_type":"markdown","source":"### Ý 1 – Tải CIFAR-100 bằng `torchvision.datasets.CIFAR100`","metadata":{}},{"id":"9eb3a856-e9c7-467d-8cc1-767e1dc8c16e","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":"d795b9b0-60c4-4206-9eff-01b268475912","cell_type":"markdown","source":"### Ý 2 – Thay đổi số lớp đầu ra của ViT thành 100","metadata":{}},{"id":"30841005-efba-4185-ae73-468e2e7d04b3","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":"847a13d7-c6f0-43e8-a112-511703e5a1cf","cell_type":"markdown","source":"### Ý 3 – Huấn luyện và đánh giá ViT trên CIFAR-100","metadata":{}},{"id":"c774c1a3-e990-4759-8e58-191591cc4efb","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":"e2041f8f-9e60-493d-820a-a64ef44eb5c4","cell_type":"markdown","source":"---\n## Bài tập 9: Thử mô hình ViT khác – `vit_b_32`","metadata":{}},{"id":"e57e2b18-600a-4909-a2ec-824f4bf4519a","cell_type":"markdown","source":"### Ý 1 – Tải mô hình `vit_b_32` và thay head","metadata":{}},{"id":"27f8ddc2-8d33-4bd7-8e67-7b45b16af07a","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":"10130aeb-b897-491d-a8ab-870ba02f58f5","cell_type":"markdown","source":"### Ý 2 – Huấn luyện `vit_b_32` trên CIFAR-10","metadata":{}},{"id":"a2b5c2f3-91c6-43b9-a05e-8a52f13101ef","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":"b09de7cf-2c5b-45c6-a7eb-39efed1c8eef","cell_type":"markdown","source":"### Ý 3 – So sánh accuracy giữa `vit_b_16` và `vit_b_32`","metadata":{}},{"id":"f4eced07-799f-42c4-8d5c-847d023ee9cd","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":"1551acb4-513c-48be-9a18-cc9d0000e676","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":"31598cfd-a5b7-4497-8da9-afd02b7e78c7","cell_type":"markdown","source":"### Ý 1 – Lưu lại các ảnh mà mô hình dự đoán sai","metadata":{}},{"id":"a6f6254d-226a-4f32-b0f5-48c869626fa2","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":"2068912c-d23e-42fc-9945-0894655515a9","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":"d75ffd9a-76ca-447b-b1de-68b3fea651ce","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":"86c9371a-534e-460c-9cad-3bb4c839d11a","cell_type":"markdown","source":"### Ý 3 – Thảo luận tại sao mô hình có thể nhầm lẫn","metadata":{}},{"id":"3495f100-3d77-47c2-9fcb-e71a28009f69","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}]}