{"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":"none","dataSources":[{"sourceId":3004,"databundleVersionId":861823,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:39:26.006838Z","iopub.execute_input":"2026-01-16T15:39:26.007098Z","iopub.status.idle":"2026-01-16T15:39:26.371173Z","shell.execute_reply.started":"2026-01-16T15:39:26.007075Z","shell.execute_reply":"2026-01-16T15:39:26.370201Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nfrom sklearn.model_selection import train_test_split\n\nimport torch\nimport torch.nn as nn\nfrom torchvision import transforms, models\n\nfrom torch.autograd import Variable\nfrom torch import nn, optim\nimport torch.nn.functional as F\nfrom torch.utils.data import DataLoader, Dataset","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:39:26.373335Z","iopub.execute_input":"2026-01-16T15:39:26.373749Z","iopub.status.idle":"2026-01-16T15:39:37.090071Z","shell.execute_reply.started":"2026-01-16T15:39:26.373724Z","shell.execute_reply":"2026-01-16T15:39:37.088948Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Load data","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)\nsample_hub = pd.read_csv('/kaggle/input/digit-recognizer/sample_submission.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:39:37.091648Z","iopub.execute_input":"2026-01-16T15:39:37.092113Z","iopub.status.idle":"2026-01-16T15:39:41.685706Z","shell.execute_reply.started":"2026-01-16T15:39:37.092088Z","shell.execute_reply":"2026-01-16T15:39:41.684626Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:39:41.686683Z","iopub.execute_input":"2026-01-16T15:39:41.686944Z","iopub.status.idle":"2026-01-16T15:39:41.732299Z","shell.execute_reply.started":"2026-01-16T15:39:41.686923Z","shell.execute_reply":"2026-01-16T15:39:41.731088Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.info()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:39:41.733590Z","iopub.execute_input":"2026-01-16T15:39:41.733932Z","iopub.status.idle":"2026-01-16T15:39:41.777438Z","shell.execute_reply.started":"2026-01-16T15:39:41.733903Z","shell.execute_reply":"2026-01-16T15:39:41.776349Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Olah DATAset\n* Pisahkan data menjadi Target dan Fitur. Lakukan normalisasi dengan membagi tiap Kolum atau Pixel dengan 255, sehingga skala pixel menjadi 0-1\n* Konversi data dari Numpy arrays menjadi Pytorch Tensors.","metadata":{}},{"cell_type":"code","source":"#pisahkan Target dan Fitur\ntargets_np = train.label.values\nfeatures_np = train.iloc[:, 1:].values/255\n\n#Split data menjadi Train dan Test set\n\nX_train, X_test, y_train, y_test = train_test_split(features_np, targets_np, test_size=0.2, random_state=42)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:39:41.778431Z","iopub.execute_input":"2026-01-16T15:39:41.778824Z","iopub.status.idle":"2026-01-16T15:39:42.090042Z","shell.execute_reply.started":"2026-01-16T15:39:41.778797Z","shell.execute_reply":"2026-01-16T15:39:42.089033Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_train.shape, X_test.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:39:42.093642Z","iopub.execute_input":"2026-01-16T15:39:42.094004Z","iopub.status.idle":"2026-01-16T15:39:42.100002Z","shell.execute_reply.started":"2026-01-16T15:39:42.093982Z","shell.execute_reply":"2026-01-16T15:39:42.099178Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Konversi dari Arrays ke Tensors\n\nclass DigitDataset(Dataset):\n    def __init__(self, X, y):\n        self.X = torch.tensor(X, dtype=torch.float32)\n        self.y = torch.tensor(y, dtype=torch.long)\n    def __len__(self):\n        return len(self.y)\n\n    def __getitem__(self, idx):\n        return self.X[idx], self.y[idx]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:39:42.100821Z","iopub.execute_input":"2026-01-16T15:39:42.101485Z","iopub.status.idle":"2026-01-16T15:39:42.119369Z","shell.execute_reply.started":"2026-01-16T15:39:42.101453Z","shell.execute_reply":"2026-01-16T15:39:42.118190Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"torch.manual_seed(1111)\ntrain_dataset = DigitDataset(X_train, y_train)\ntest_dataset = DigitDataset(X_test, y_test)\nlen(train_dataset)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:39:42.120955Z","iopub.execute_input":"2026-01-16T15:39:42.121262Z","iopub.status.idle":"2026-01-16T15:39:42.213776Z","shell.execute_reply.started":"2026-01-16T15:39:42.121225Z","shell.execute_reply":"2026-01-16T15:39:42.212828Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset.X.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:39:42.214627Z","iopub.execute_input":"2026-01-16T15:39:42.214907Z","iopub.status.idle":"2026-01-16T15:39:42.220895Z","shell.execute_reply.started":"2026-01-16T15:39:42.214882Z","shell.execute_reply":"2026-01-16T15:39:42.219876Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"batch_size = 64\ng = torch.Generator()\ng.manual_seed(1111)\ntrain_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, generator=g)\ntest_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=True, generator=g)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:39:42.221863Z","iopub.execute_input":"2026-01-16T15:39:42.222113Z","iopub.status.idle":"2026-01-16T15:39:42.242473Z","shell.execute_reply.started":"2026-01-16T15:39:42.222093Z","shell.execute_reply":"2026-01-16T15:39:42.241400Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"len(train_loader) #batch size","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:39:42.243582Z","iopub.execute_input":"2026-01-16T15:39:42.243991Z","iopub.status.idle":"2026-01-16T15:39:42.264977Z","shell.execute_reply.started":"2026-01-16T15:39:42.243959Z","shell.execute_reply":"2026-01-16T15:39:42.263576Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_loader.dataset.X.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:39:42.266030Z","iopub.execute_input":"2026-01-16T15:39:42.267146Z","iopub.status.idle":"2026-01-16T15:39:42.286418Z","shell.execute_reply.started":"2026-01-16T15:39:42.267121Z","shell.execute_reply":"2026-01-16T15:39:42.285401Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Visualisasi data","metadata":{}},{"cell_type":"code","source":"def plot_images(images):\n    n_images = len(images)\n    rows = int(np.sqrt(n_images))\n    cols = int(np.sqrt(n_images))\n\n    fig = plt.figure()\n    for i in range(rows*cols):\n        ax = fig.add_subplot(rows, cols, i+1)\n        ax.imshow(images[i].view(28,28), cmap='bone')\n        ax.axis('off')\n    #plt.title('handwritten digit')\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:39:42.287665Z","iopub.execute_input":"2026-01-16T15:39:42.288052Z","iopub.status.idle":"2026-01-16T15:39:42.306627Z","shell.execute_reply.started":"2026-01-16T15:39:42.288022Z","shell.execute_reply":"2026-01-16T15:39:42.305391Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"N_IMAGES = 25\n\nimages = [image for image, label in [train_dataset[i] for i in range(N_IMAGES)]]\n\nplot_images(images)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:39:42.308021Z","iopub.execute_input":"2026-01-16T15:39:42.308538Z","iopub.status.idle":"2026-01-16T15:39:43.154575Z","shell.execute_reply.started":"2026-01-16T15:39:42.308485Z","shell.execute_reply":"2026-01-16T15:39:43.153681Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### MLP Architecture","metadata":{}},{"cell_type":"code","source":"class MLPdigit(nn.Module):\n    def __init__(self, input_dim, output_dim):\n        super().__init__()\n        self.fc1 = nn.Linear(input_dim, 512)\n        self.fc2 = nn.Linear(512, 256)\n        self.fc3 = nn.Linear(256, 128)\n        self.fc4 = nn.Linear(128, 64)\n        self.fc5 = nn.Linear(64, 32)\n\n        # output layer\n        self.fc6 = nn.Linear(32, output_dim)\n\n        self.dropout = nn.Dropout(p=0.2)\n    def forward(self, x):\n        x = self.dropout(F.relu(self.fc1(x)))\n        x = self.dropout(F.relu(self.fc2(x)))\n        x = self.dropout(F.relu(self.fc3(x)))\n        x = self.dropout(F.relu(self.fc4(x)))\n        x = self.dropout(F.relu(self.fc5(x)))  # layer baru\n        x = self.fc6(x)\n        return x\n        ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:39:43.155971Z","iopub.execute_input":"2026-01-16T15:39:43.156277Z","iopub.status.idle":"2026-01-16T15:39:43.163951Z","shell.execute_reply.started":"2026-01-16T15:39:43.156255Z","shell.execute_reply":"2026-01-16T15:39:43.163081Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Training & Validation","metadata":{}},{"cell_type":"code","source":"input_dim = 28 * 28\noutput_dim = 10\nlr=0.001\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n\nmodel = MLPdigit(input_dim, output_dim)\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(model.parameters(), lr=lr)\n\nprint(f'This model uses {device}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:39:43.164882Z","iopub.execute_input":"2026-01-16T15:39:43.165151Z","iopub.status.idle":"2026-01-16T15:39:43.201722Z","shell.execute_reply.started":"2026-01-16T15:39:43.165128Z","shell.execute_reply":"2026-01-16T15:39:43.200796Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def cal_accuracy(y_pred, y):\n    predicted = torch.argmax(y_pred, dim=-1)\n    correct = (predicted == y).sum()\n    acc = correct.float() / y.shape[0]\n    return acc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:39:43.202381Z","iopub.execute_input":"2026-01-16T15:39:43.202681Z","iopub.status.idle":"2026-01-16T15:39:43.221608Z","shell.execute_reply.started":"2026-01-16T15:39:43.202660Z","shell.execute_reply":"2026-01-16T15:39:43.220622Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train(model, train_loader, optimizer, criterion, device):\n    epoch_loss = 0.0\n    epoch_acc = 0.0\n\n    model.train()\n\n    for X, y in train_loader:\n        y_pred = model(X).to(device)        \n        \n        loss = criterion(y_pred, y)\n\n        acc = cal_accuracy(y_pred, y)\n\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n\n        epoch_loss += loss.item()\n        epoch_acc += acc.item()\n\n    return epoch_loss/len(train_loader), epoch_acc/len(train_loader)        ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:39:43.222697Z","iopub.execute_input":"2026-01-16T15:39:43.223039Z","iopub.status.idle":"2026-01-16T15:39:43.242677Z","shell.execute_reply.started":"2026-01-16T15:39:43.223007Z","shell.execute_reply":"2026-01-16T15:39:43.241380Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def evaluate(model, test_loader, criterion, device):\n    epoch_loss = 0\n    epoch_acc = 0\n\n    model.eval()\n\n    with torch.no_grad():\n        for X, y in test_loader:\n            y_pred = model(X).to(device)\n            loss = criterion(y_pred, y)\n            acc = cal_accuracy(y_pred, y)\n\n            epoch_loss += loss.item()\n            epoch_acc += acc.item()\n\n    return epoch_loss/len(test_loader), epoch_acc/len(test_loader)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:39:43.243705Z","iopub.execute_input":"2026-01-16T15:39:43.244016Z","iopub.status.idle":"2026-01-16T15:39:43.262702Z","shell.execute_reply.started":"2026-01-16T15:39:43.243994Z","shell.execute_reply":"2026-01-16T15:39:43.261580Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"best_valid_loss = 0\nEPOCHS = 50\ntrain_losses, test_losses, train_accs, valid_accs = [], [], [], []\n\nfor epoch in range(EPOCHS):\n    train_loss, train_acc = train(model, train_loader, optimizer, criterion, device)\n    valid_loss, valid_acc = evaluate(model, test_loader, criterion, device)\n    train_losses.append(train_loss)\n    test_losses.append(valid_loss)\n    train_accs.append(train_acc)\n    valid_accs.append(valid_acc)\n\n    if valid_loss < best_valid_loss:\n        best_valid_loss = valid_loss\n        torch.save(model.state_dict(), 'digit-model.pt')\n\n    print(f'Epoch: {epoch}')\n    print(f'\\t Train loss: {train_loss:.3f} | Train acc: {train_acc:.3f}')\n    print(f'\\t  Val  loss: {valid_loss:.3f} |   Val acc: {valid_acc:.3f}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:39:43.263792Z","iopub.execute_input":"2026-01-16T15:39:43.264082Z","iopub.status.idle":"2026-01-16T15:44:23.989019Z","shell.execute_reply.started":"2026-01-16T15:39:43.264061Z","shell.execute_reply":"2026-01-16T15:44:23.987921Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Learning Curve","metadata":{}},{"cell_type":"code","source":"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))\n\n# --- Plot 1: Losses ---\nax1.plot(train_losses, label='Training Loss')\nax1.plot(test_losses, label='Validation Loss')\nax1.set_title('Loss vs. Epochs')\nax1.set_xlabel('Epoch')\nax1.set_ylabel('Loss')\nax1.legend()\nax1.grid(True)\n\n# --- Plot 2: Accuracies ---\nax2.plot(train_accs, label='Training Accuracy', color='green')\nax2.plot(valid_accs, label='Validation Accuracy', color='orange')\nax2.set_title('Accuracy vs. Epochs')\nax2.set_xlabel('Epoch')\nax2.set_ylabel('Accuracy')\nax2.legend()\nax2.grid(True)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:44:23.992652Z","iopub.execute_input":"2026-01-16T15:44:23.992930Z","iopub.status.idle":"2026-01-16T15:44:24.469631Z","shell.execute_reply.started":"2026-01-16T15:44:23.992909Z","shell.execute_reply":"2026-01-16T15:44:24.468712Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Confusion Matrix","metadata":{}},{"cell_type":"code","source":"X","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T15:44:24.470985Z","iopub.execute_input":"2026-01-16T15:44:24.471339Z","iopub.status.idle":"2026-01-16T15:44:24.499071Z","shell.execute_reply.started":"2026-01-16T15:44:24.471309Z","shell.execute_reply":"2026-01-16T15:44:24.497754Z"}},"outputs":[],"execution_count":null}]}