{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":3004,"databundleVersionId":861823,"sourceType":"competition"}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import time\nimport numpy as np\nimport random\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\n\nimport torch\nimport torchvision\nimport torch.nn as nn\nimport torch.optim as optim\nimport torchvision.transforms as transforms\nfrom torch.utils.data import Dataset, DataLoader, ConcatDataset","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:12:17.444214Z","iopub.execute_input":"2025-08-22T13:12:17.444377Z","iopub.status.idle":"2025-08-22T13:12:26.430045Z","shell.execute_reply.started":"2025-08-22T13:12:17.444361Z","shell.execute_reply":"2025-08-22T13:12:26.429428Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"seed = 114514\nrandom.seed(seed)\ntorch.manual_seed(seed)\nif torch.cuda.is_available():\n    torch.cuda.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)  \nnp.random.seed(seed)  \ntorch.backends.cudnn.benchmark = False\ntorch.backends.cudnn.deterministic = True","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:12:26.431583Z","iopub.execute_input":"2025-08-22T13:12:26.432178Z","iopub.status.idle":"2025-08-22T13:12:26.511609Z","shell.execute_reply.started":"2025-08-22T13:12:26.432156Z","shell.execute_reply":"2025-08-22T13:12:26.510979Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data preprocess","metadata":{}},{"cell_type":"code","source":"train = pd.read_csv(\"/kaggle/input/digit-recognizer/train.csv\", dtype=np.float32)\ntest = pd.read_csv(\"/kaggle/input/digit-recognizer/test.csv\", dtype=np.float32)\n\nprint(\"Train Set Shape:\", train.shape)\nprint(\"Val Set Shape:\", test.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:12:26.512215Z","iopub.execute_input":"2025-08-22T13:12:26.512400Z","iopub.status.idle":"2025-08-22T13:12:32.814639Z","shell.execute_reply.started":"2025-08-22T13:12:26.512384Z","shell.execute_reply":"2025-08-22T13:12:32.813962Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Visualise","metadata":{}},{"cell_type":"code","source":"# Label distribution\n\nlabel_dist = dict(train.label.value_counts())\nnumber = label_dist.keys()\ncount = label_dist.values()\n\nfig = plt.figure(figsize = (8, 5))\n\nplt.bar(number, count, width = 0.5)\n\nplt.xlabel(\"Label\")\nplt.ylabel(\"NO. of samples\")\nplt.title(\"Distribution of labels\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:12:32.815333Z","iopub.execute_input":"2025-08-22T13:12:32.815625Z","iopub.status.idle":"2025-08-22T13:12:33.197678Z","shell.execute_reply.started":"2025-08-22T13:12:32.815587Z","shell.execute_reply":"2025-08-22T13:12:33.196972Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# One image\n\nplt.imshow(train.iloc[0, 1:].values.reshape(28,28))  # after 1 is feature\nplt.axis(\"off\")\nplt.title(str(train.iloc[0, 0]))  # 0,0 is the label\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:12:33.199219Z","iopub.execute_input":"2025-08-22T13:12:33.199438Z","iopub.status.idle":"2025-08-22T13:12:33.283768Z","shell.execute_reply.started":"2025-08-22T13:12:33.199420Z","shell.execute_reply":"2025-08-22T13:12:33.283069Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labels = train.label.values # .values  turn to tensor\ndata = train.iloc[:, 1:].values / 255  #简单粗暴的归一化","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:12:33.284429Z","iopub.execute_input":"2025-08-22T13:12:33.284638Z","iopub.status.idle":"2025-08-22T13:12:33.329858Z","shell.execute_reply.started":"2025-08-22T13:12:33.284620Z","shell.execute_reply":"2025-08-22T13:12:33.329204Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#torch.Tensor\nlabels = torch.from_numpy(labels).type(torch.LongTensor)\ndata = torch.from_numpy(data).view(data.shape[0], 1, 28, 28)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:12:33.330658Z","iopub.execute_input":"2025-08-22T13:12:33.330938Z","iopub.status.idle":"2025-08-22T13:12:33.337096Z","shell.execute_reply.started":"2025-08-22T13:12:33.330912Z","shell.execute_reply":"2025-08-22T13:12:33.336280Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data preparation","metadata":{}},{"cell_type":"code","source":"train_data, val_data, train_labels, val_labels = train_test_split(data, labels, test_size = 0.2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:12:40.131037Z","iopub.execute_input":"2025-08-22T13:12:40.131815Z","iopub.status.idle":"2025-08-22T13:12:40.353946Z","shell.execute_reply.started":"2025-08-22T13:12:40.131788Z","shell.execute_reply":"2025-08-22T13:12:40.353289Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class MnistDataset(Dataset):\n    def __init__(self, data, labels=None, transform=None):\n        self.data = data\n        self.labels = labels\n        self.transform = transform\n\n    def __getitem__(self, index):\n        x = self.data[index]\n\n        if self.transform is not None:\n            x = self.transform(x)\n        if self.labels is not None:\n            y = self.labels[index]\n            return x, y\n        else:\n            return x\n\n    def __len__(self):\n        return self.data.size(0)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:12:41.836997Z","iopub.execute_input":"2025-08-22T13:12:41.837273Z","iopub.status.idle":"2025-08-22T13:12:41.842485Z","shell.execute_reply.started":"2025-08-22T13:12:41.837253Z","shell.execute_reply":"2025-08-22T13:12:41.841776Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"transform = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.RandomAffine(degrees=20, scale=(1.2, 1.2)),\n    transforms.RandomCrop((28, 28), padding=2, pad_if_needed=True, fill=0, padding_mode='constant'),\n    transforms.ToTensor(),\n    transforms.Normalize((0.1307,), (0.3081,))\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:12:51.596689Z","iopub.execute_input":"2025-08-22T13:12:51.596957Z","iopub.status.idle":"2025-08-22T13:12:51.601647Z","shell.execute_reply.started":"2025-08-22T13:12:51.596938Z","shell.execute_reply":"2025-08-22T13:12:51.600897Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"transform_val = transforms.Compose([\n    #transforms.ToTensor(),\n    transforms.Normalize((0.1307,), (0.3081,))\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:26:07.226339Z","iopub.execute_input":"2025-08-22T13:26:07.227105Z","iopub.status.idle":"2025-08-22T13:26:07.230961Z","shell.execute_reply.started":"2025-08-22T13:26:07.227079Z","shell.execute_reply":"2025-08-22T13:26:07.230265Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Visalise data with and without augmentation","metadata":{}},{"cell_type":"code","source":"sample = MnistDataset(train_data[:3], train_labels[:3])\nsample_aug = MnistDataset(train_data[:3], train_labels[:3], transform=transform)\n\n\nfig, axs = plt.subplots(2, 3)\n\nfor idx, item in enumerate(zip(sample, sample_aug)):\n    im = item[0][0].squeeze().numpy()\n    im_aug = item[1][0].squeeze().numpy()\n\n    axs[0, idx].imshow(im)\n    axs[0, idx].set_title(\"w/o aug\")\n    axs[0, idx].axis('off')\n    \n    axs[1, idx].imshow(im_aug)\n    axs[1, idx].set_title(\"aug\")\n    axs[1, idx].axis('off')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:26:08.511768Z","iopub.execute_input":"2025-08-22T13:26:08.512371Z","iopub.status.idle":"2025-08-22T13:26:08.795642Z","shell.execute_reply.started":"2025-08-22T13:26:08.512349Z","shell.execute_reply":"2025-08-22T13:26:08.794893Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Dataset","metadata":{}},{"cell_type":"code","source":"train_set = ConcatDataset([\n    MnistDataset(train_data, train_labels),\n    MnistDataset(train_data, train_labels, transform=transform),\n])\n\nval_set = MnistDataset(val_data, val_labels, transform=transform_val)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:26:09.311834Z","iopub.execute_input":"2025-08-22T13:26:09.312486Z","iopub.status.idle":"2025-08-22T13:26:09.316219Z","shell.execute_reply.started":"2025-08-22T13:26:09.312463Z","shell.execute_reply":"2025-08-22T13:26:09.315599Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"batch_size=64","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:26:09.340277Z","iopub.execute_input":"2025-08-22T13:26:09.340463Z","iopub.status.idle":"2025-08-22T13:26:09.343646Z","shell.execute_reply.started":"2025-08-22T13:26:09.340449Z","shell.execute_reply":"2025-08-22T13:26:09.343038Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True)\nval_loader = DataLoader(val_set, batch_size=batch_size, shuffle=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:26:09.385760Z","iopub.execute_input":"2025-08-22T13:26:09.385974Z","iopub.status.idle":"2025-08-22T13:26:09.389683Z","shell.execute_reply.started":"2025-08-22T13:26:09.385957Z","shell.execute_reply":"2025-08-22T13:26:09.389059Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model","metadata":{}},{"cell_type":"code","source":"device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\ndevice","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:26:09.600377Z","iopub.execute_input":"2025-08-22T13:26:09.601041Z","iopub.status.idle":"2025-08-22T13:26:09.605977Z","shell.execute_reply.started":"2025-08-22T13:26:09.601019Z","shell.execute_reply":"2025-08-22T13:26:09.605260Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class SimpleCNN(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.conv_block1 = nn.Sequential(\n            nn.Conv2d(1, 32, kernel_size=3, padding=1),\n            nn.BatchNorm2d(32),\n            nn.ReLU(),\n        )\n\n        self.conv_block2 = nn.Sequential(\n            nn.Conv2d(32, 64, kernel_size=3, padding=1, stride=2),\n            nn.BatchNorm2d(64),\n            nn.ReLU(),\n        )\n\n        self.conv_block3 = nn.Sequential(\n            nn.Conv2d(64, 128, kernel_size=3, padding=1, stride=2),\n            nn.BatchNorm2d(128),\n            nn.ReLU(),\n        )\n\n        self.classifier = nn.Sequential(\n            nn.Flatten(),\n            nn.Linear(128 * 7 * 7, 128),\n            nn.ReLU(),\n            nn.Dropout(0.5),\n            nn.Linear(128, 10)\n        )\n\n    def forward(self, x):\n        x = self.conv_block1(x)\n        x = self.conv_block2(x)\n        x = self.conv_block3(x)\n        x = self.classifier(x)\n\n        return x\n        ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:26:09.742056Z","iopub.execute_input":"2025-08-22T13:26:09.742334Z","iopub.status.idle":"2025-08-22T13:26:09.748754Z","shell.execute_reply.started":"2025-08-22T13:26:09.742315Z","shell.execute_reply":"2025-08-22T13:26:09.747931Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Trainer","metadata":{}},{"cell_type":"code","source":"def train_one_epoch(model, device, train_loader, optimizer, criterion):\n    model.train()\n    running_loss = 0.0\n    total_corrects = 0\n    total_samples = 0\n\n    trainer =tqdm(train_loader, desc=\"Training\")\n\n    for inputs, labels in trainer:\n        inputs, labels = inputs.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n\n        outputs = model(inputs)\n        loss = criterion(outputs, labels)\n\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item() * inputs.size(0)\n        _, preds = torch.max(outputs, 1)\n        total_corrects += torch.sum(preds == labels.data)\n        total_samples += inputs.size(0)\n\n        trainer.set_postfix(loss=loss.item(), acc=f\"{total_corrects.double() / total_samples: .4f}\")\n\n    epoch_loss = running_loss / total_samples\n    epoch_acc = total_corrects.double() / total_samples\n    \n    return epoch_loss, epoch_acc\n\n\ndef val_one_epoch(model, device, val_loader, criterion):\n    model.eval()\n    running_loss = 0\n    total_corrects = 0\n    total_samples = 0\n\n    validater = tqdm(val_loader, desc=\"Validating\")\n\n    with torch.no_grad():\n        for inputs, labels, in validater:\n            inputs, labels = inputs.to(device), labels.to(device)\n\n            outputs = model(inputs)\n\n            loss = criterion(outputs, labels)\n            running_loss += loss.item() * inputs.size(0)\n            _, preds = torch.max(outputs, 1)\n            total_corrects += torch.sum(preds == labels.data)\n            total_samples += inputs.size(0)\n\n            validater.set_postfix(loss=loss.item(), acc=f\"{total_corrects.double() / total_samples:.4f}\")\n\n        epoch_loss = running_loss / total_samples\n        epoch_acc = total_corrects.double() / total_samples\n\n        return epoch_loss ,epoch_acc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:27:06.275899Z","iopub.execute_input":"2025-08-22T13:27:06.276568Z","iopub.status.idle":"2025-08-22T13:27:06.283982Z","shell.execute_reply.started":"2025-08-22T13:27:06.276535Z","shell.execute_reply":"2025-08-22T13:27:06.283253Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"lr = 0.001\nepochs = 20\n\ndef train():\n    model = SimpleCNN().to(device)\n    criterion = nn.CrossEntropyLoss()\n    optimizer = optim.Adam(model.parameters(), lr=lr, weight_decay=1e-5)\n\n    best_acc = 0\n\n    print(\"----------Training Started ------------\")\n    start_time = time.time()\n\n    for epoch in range(1, epochs):\n        train_loss, train_acc = train_one_epoch(model, device, train_loader,  optimizer, criterion)\n\n        val_loss, val_acc = val_one_epoch(model, device, val_loader, criterion)\n\n        print(f\"\\nEpoch {epoch}/{epochs}:\")\n\n        print(f\" Train Loss:{train_loss:.4f}, Train Acc: {train_acc:.2f}%\")\n        print(f\" Val Loss:{val_loss:.4f}, Val Acc:{val_acc:.2f}%\\n\")\n\n\n        if val_acc > best_acc:\n            best_acc = val_acc\n            torch.save(model.state_dict(), \"best_model.pth\")\n            print(f\" New Best model saved with acc:{best_acc:.2f}%\")\n\n\n    end_time = time.time()\n    print(f\"--------Training Finished in {(end_time - start_time)/60:.2f}  minutes --------------\")\n    print(f\"Best val acc: {best_acc:.2f}%\")\n    ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:28:13.416581Z","iopub.execute_input":"2025-08-22T13:28:13.417293Z","iopub.status.idle":"2025-08-22T13:28:13.423019Z","shell.execute_reply.started":"2025-08-22T13:28:13.417268Z","shell.execute_reply":"2025-08-22T13:28:13.422262Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:28:18.406377Z","iopub.execute_input":"2025-08-22T13:28:18.407103Z","iopub.status.idle":"2025-08-22T13:34:36.454284Z","shell.execute_reply.started":"2025-08-22T13:28:18.407078Z","shell.execute_reply":"2025-08-22T13:34:36.453511Z"}},"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}]}