{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.7.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":3004,"databundleVersionId":861823,"sourceType":"competition"}],"dockerImageVersionId":30396,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as transforms\nfrom sklearn.model_selection import train_test_split\nfrom tqdm import tqdm\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\ndevice","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-17T16:15:32.939475Z","iopub.execute_input":"2025-10-17T16:15:32.940502Z","iopub.status.idle":"2025-10-17T16:15:32.948063Z","shell.execute_reply.started":"2025-10-17T16:15:32.940466Z","shell.execute_reply":"2025-10-17T16:15:32.946942Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"##  Dataset","metadata":{}},{"cell_type":"code","source":"class MNISTDataset(Dataset):\n    def __init__(self, data, labels=None, transform=None):\n        self.data = data.astype(np.float32) / 255.0  # Normalize to [0,1]\n        self.data = self.data.reshape(-1, 1, 28, 28)  # kaggle flatten image thành vector -> phải reshape lại size ảnh gốc (28x28)\n        self.labels = labels\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.data)\n\n    def __getitem__(self, idx):\n        img = self.data[idx]\n        if self.transform:\n            img = self.transform(torch.from_numpy(img))\n            \n        if self.labels is not None:\n            return img, self.labels[idx]\n            \n        return img","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-17T16:15:33.266175Z","iopub.execute_input":"2025-10-17T16:15:33.266540Z","iopub.status.idle":"2025-10-17T16:15:33.273119Z","shell.execute_reply.started":"2025-10-17T16:15:33.266510Z","shell.execute_reply":"2025-10-17T16:15:33.272241Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load mnist từ kaggle\ntrain_df = pd.read_csv('/kaggle/input/digit-recognizer/train.csv')\ntest_df = pd.read_csv('/kaggle/input/digit-recognizer/test.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-17T16:15:33.425918Z","iopub.execute_input":"2025-10-17T16:15:33.426606Z","iopub.status.idle":"2025-10-17T16:15:36.190511Z","shell.execute_reply.started":"2025-10-17T16:15:33.426575Z","shell.execute_reply":"2025-10-17T16:15:36.189529Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Tách label và feature\nlabels = train_df['label'].values\ntrain_data = train_df.drop('label', axis=1).values\ntest_data = test_df.values\n\n# Chia train, val\ntrain_data, val_data, train_labels, val_labels = train_test_split(\n    train_data, labels, test_size=0.1, random_state=42, stratify=labels\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-17T16:15:36.192308Z","iopub.execute_input":"2025-10-17T16:15:36.192951Z","iopub.status.idle":"2025-10-17T16:15:36.681943Z","shell.execute_reply.started":"2025-10-17T16:15:36.192912Z","shell.execute_reply":"2025-10-17T16:15:36.681211Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Nếu output là ảnh (bài toán generation) thì normalize (-1, 1), (0, 1) hay z-score không khác là mấy\n# Tuy nhiên nếu bài toán là phân loại thì nên dùng z-score (z-score có tính chất resolution-invariant)\nmean = 0.1307 # Mean của tập MNIST\nstd = 0.3081 # Std của tập MNIST\n\n# Augment cho train -> tăng tính đa dạng -> học khó hơn\ntrain_transform = transforms.Compose([\n    transforms.RandomAffine(degrees=5, translate=(0.05, 0.05), scale=(0.95, 1.05), shear=5), # Biến đổi hình học\n    transforms.ColorJitter(brightness=0.1, contrast=0.1), # Biến đổi màu sắc\n    transforms.Normalize(mean=(mean,), std=(std,)), # z-score\n    transforms.RandomErasing(p=0.3, scale=(0.02, 0.2), ratio=(0.3, 3.3), value=0) # Xóa ngẫu nhiên\n])\n\n# Augment cho val và test (chỉ chuẩn hóa thôi để đánh giá khách quan trên dữ liệu thực)\nval_test_transform = transforms.Compose([\n    transforms.Normalize(mean=(mean,), std=(std,))\n])\n\n# Datasets\ntrain_dataset = MNISTDataset(train_data, train_labels, transform=train_transform)\nval_dataset = MNISTDataset(val_data, val_labels, transform=val_test_transform)\ntest_dataset = MNISTDataset(test_data, transform=val_test_transform)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-17T16:15:36.683062Z","iopub.execute_input":"2025-10-17T16:15:36.683351Z","iopub.status.idle":"2025-10-17T16:15:36.801781Z","shell.execute_reply.started":"2025-10-17T16:15:36.683327Z","shell.execute_reply":"2025-10-17T16:15:36.800673Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# tạo DataLoaders\nbatch_size = 256\ntrain_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=2)\nval_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False, num_workers=2)\ntest_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, num_workers=2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-17T16:15:36.804148Z","iopub.execute_input":"2025-10-17T16:15:36.804489Z","iopub.status.idle":"2025-10-17T16:15:36.810325Z","shell.execute_reply.started":"2025-10-17T16:15:36.804460Z","shell.execute_reply":"2025-10-17T16:15:36.809126Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model","metadata":{}},{"cell_type":"code","source":"# Model Complexity: Data đơn giản (28x28, gray-scale) -> thiết kế 1 mạng CNN đơn giản\n# Pipeline: Extract feature -> Classification\n# Activation GELU: Smooth alternative to ReLU, better gradient flow","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CNN(nn.Module):\n    def __init__(self):\n        super(CNN, self).__init__()\n    \n        # Feature Extractor\n        self.feature_extractor = nn.Sequential(\n            nn.Conv2d(1, 32, kernel_size=3, padding=1),\n            nn.BatchNorm2d(32),\n            nn.GELU(),\n            nn.MaxPool2d(2, 2),  # 14x14x32\n            \n            nn.Conv2d(32, 64, kernel_size=3, padding=1),\n            nn.BatchNorm2d(64),\n            nn.GELU(), # 14x14x64\n            \n            nn.Conv2d(64, 128, kernel_size=3, padding=1),\n            nn.BatchNorm2d(128),\n            nn.GELU(),\n            nn.MaxPool2d(2, 2),  # 7x7x128\n        )\n        \n        # Classification Head\n        self.classifier = nn.Sequential(\n            nn.Flatten(),  # Flatten từ 7x7x128 -> 6272\n            nn.Linear(128 * 7 * 7, 512),\n            nn.BatchNorm1d(512),\n            nn.GELU(),\n            nn.Dropout(0.5),\n            nn.Linear(512, 10)\n        )\n        \n        # Khởi tạo trọng số\n        self.apply(self._init_weights)\n\n    def _init_weights(self, m):\n        # GeLu tương tự ReLU -> khởi tạo theo pp Kaiming Normal\n        if isinstance(m, nn.Conv2d) or isinstance(m, nn.Linear):\n            nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')\n            if m.bias is not None:\n                nn.init.constant_(m.bias, 0)\n        elif isinstance(m, (nn.BatchNorm2d, nn.BatchNorm1d)):\n            nn.init.constant_(m.weight, 1)\n            nn.init.constant_(m.bias, 0)\n\n    def forward(self, x):\n        features = self.feature_extractor(x)\n        output = self.classifier(features)\n        return output","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-17T16:15:36.811632Z","iopub.execute_input":"2025-10-17T16:15:36.811962Z","iopub.status.idle":"2025-10-17T16:15:36.822534Z","shell.execute_reply.started":"2025-10-17T16:15:36.811900Z","shell.execute_reply":"2025-10-17T16:15:36.821674Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = CNN().to(device)\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(model.parameters(), lr=0.001)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-17T16:15:36.823677Z","iopub.execute_input":"2025-10-17T16:15:36.823997Z","iopub.status.idle":"2025-10-17T16:15:39.492798Z","shell.execute_reply.started":"2025-10-17T16:15:36.823956Z","shell.execute_reply":"2025-10-17T16:15:39.491717Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training","metadata":{}},{"cell_type":"code","source":"epochs = 20\nbest_acc = 0.0\n\nfor epoch in range(epochs):\n    # Training\n    model.train()\n    train_loss = 0.0\n    correct = 0\n    total = 0\n    \n    for images, labels in tqdm(train_loader):\n        images, labels = images.to(device), labels.to(device)\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward() # tính gradient\n        optimizer.step() # cập nhập\n        \n        train_loss += loss.item()\n        _, predicted = outputs.max(1)\n        total += labels.size(0)\n        correct += predicted.eq(labels).sum().item()\n    \n    train_loss /= len(train_loader)\n    train_acc = correct / total\n    \n    # Evaluation\n    model.eval()\n    val_loss = 0.0\n    correct = 0\n    total = 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        \n            val_loss += loss.item()\n            _, predicted = outputs.max(1)\n            total += labels.size(0)\n            correct += predicted.eq(labels).sum().item()\n    \n    val_loss /= len(val_loader)\n    val_acc = correct / total\n    \n    print(f'Epoch [{epoch+1}/{epochs}] Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f}, Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.4f}')\n    \n    if val_acc > best_acc:\n        best_acc = val_acc\n        torch.save(model.state_dict(), 'best_model.pth')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-17T16:15:39.494603Z","iopub.execute_input":"2025-10-17T16:15:39.494973Z","iopub.status.idle":"2025-10-17T16:20:52.148756Z","shell.execute_reply.started":"2025-10-17T16:15:39.494938Z","shell.execute_reply":"2025-10-17T16:20:52.147668Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.load_state_dict(torch.load('best_model.pth'))\nmodel.eval()\n\n# Predict on test set\npredictions = []\nwith torch.no_grad():\n    for images in test_loader:\n        images = images.to(device)\n        outputs = model(images)\n        _, predicted = outputs.max(1)\n        predictions.extend(predicted.cpu().numpy())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-17T16:21:58.502319Z","iopub.execute_input":"2025-10-17T16:21:58.503306Z","iopub.status.idle":"2025-10-17T16:21:59.757038Z","shell.execute_reply.started":"2025-10-17T16:21:58.503255Z","shell.execute_reply":"2025-10-17T16:21:59.755808Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create submission\nsubmission = pd.DataFrame({\n    'ImageId': range(1, len(predictions) + 1),\n    'Label': predictions\n})\nsubmission.to_csv('submission.csv', index=False)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}