{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","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":31234,"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-16T17:17:36.627454Z","iopub.execute_input":"2026-01-16T17:17:36.627871Z","iopub.status.idle":"2026-01-16T17:17:38.533833Z","shell.execute_reply.started":"2026-01-16T17:17:36.627839Z","shell.execute_reply":"2026-01-16T17:17:38.532612Z"}},"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-16T17:17:38.534953Z","iopub.execute_input":"2026-01-16T17:17:38.535464Z","iopub.status.idle":"2026-01-16T17:17:52.872778Z","shell.execute_reply.started":"2026-01-16T17:17:38.535435Z","shell.execute_reply":"2026-01-16T17:17:52.870812Z"}},"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')\ntest  = pd.read_csv('/kaggle/input/digit-recognizer/test.csv')\nsample_sub = pd.read_csv('/kaggle/input/digit-recognizer/sample_submission.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T17:17:52.875571Z","iopub.execute_input":"2026-01-16T17:17:52.876162Z","iopub.status.idle":"2026-01-16T17:17:57.863091Z","shell.execute_reply.started":"2026-01-16T17:17:52.876129Z","shell.execute_reply":"2026-01-16T17:17:57.861450Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T17:17:57.864897Z","iopub.execute_input":"2026-01-16T17:17:57.865786Z","iopub.status.idle":"2026-01-16T17:17:57.911266Z","shell.execute_reply.started":"2026-01-16T17:17:57.865611Z","shell.execute_reply":"2026-01-16T17:17:57.910121Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.info()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T17:17:57.912700Z","iopub.execute_input":"2026-01-16T17:17:57.913145Z","iopub.status.idle":"2026-01-16T17:17:57.964558Z","shell.execute_reply.started":"2026-01-16T17:17:57.913094Z","shell.execute_reply":"2026-01-16T17:17:57.963264Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Olah DATAset\n\n-Pisahkan data menjadi Target dan Fitur. Lakukan normalisasi dengan membagi tiap Kolum atau Pixel dengan 255, sehingga skala pixel menjadi 0-1\n\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)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T17:17:57.966465Z","iopub.execute_input":"2026-01-16T17:17:57.967298Z","iopub.status.idle":"2026-01-16T17:17:58.633011Z","shell.execute_reply.started":"2026-01-16T17:17:57.967267Z","shell.execute_reply":"2026-01-16T17:17:58.632083Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_train.shape, X_test.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T17:17:58.634223Z","iopub.execute_input":"2026-01-16T17:17:58.634671Z","iopub.status.idle":"2026-01-16T17:17:58.642492Z","shell.execute_reply.started":"2026-01-16T17:17:58.634604Z","shell.execute_reply":"2026-01-16T17:17:58.641255Z"}},"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-16T17:17:58.643844Z","iopub.execute_input":"2026-01-16T17:17:58.644256Z","iopub.status.idle":"2026-01-16T17:17:58.662084Z","shell.execute_reply.started":"2026-01-16T17:17:58.644226Z","shell.execute_reply":"2026-01-16T17:17:58.660761Z"}},"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-16T17:17:58.665809Z","iopub.execute_input":"2026-01-16T17:17:58.666245Z","iopub.status.idle":"2026-01-16T17:17:58.785131Z","shell.execute_reply.started":"2026-01-16T17:17:58.666214Z","shell.execute_reply":"2026-01-16T17:17:58.784022Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset.X.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T17:17:58.786808Z","iopub.execute_input":"2026-01-16T17:17:58.787286Z","iopub.status.idle":"2026-01-16T17:17:58.794353Z","shell.execute_reply.started":"2026-01-16T17:17:58.787256Z","shell.execute_reply":"2026-01-16T17:17:58.793304Z"}},"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-16T17:17:58.795704Z","iopub.execute_input":"2026-01-16T17:17:58.796120Z","iopub.status.idle":"2026-01-16T17:17:58.815022Z","shell.execute_reply.started":"2026-01-16T17:17:58.796087Z","shell.execute_reply":"2026-01-16T17:17:58.813816Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"len(train_loader) #batch size","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T17:17:58.816458Z","iopub.execute_input":"2026-01-16T17:17:58.816815Z","iopub.status.idle":"2026-01-16T17:17:58.837968Z","shell.execute_reply.started":"2026-01-16T17:17:58.816785Z","shell.execute_reply":"2026-01-16T17:17:58.836780Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_loader.dataset.X.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T17:17:58.839134Z","iopub.execute_input":"2026-01-16T17:17:58.839731Z","iopub.status.idle":"2026-01-16T17:17:58.859810Z","shell.execute_reply.started":"2026-01-16T17:17:58.839683Z","shell.execute_reply":"2026-01-16T17:17:58.858595Z"}},"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-16T17:17:58.861183Z","iopub.execute_input":"2026-01-16T17:17:58.862095Z","iopub.status.idle":"2026-01-16T17:17:58.882032Z","shell.execute_reply.started":"2026-01-16T17:17:58.862058Z","shell.execute_reply":"2026-01-16T17:17:58.880707Z"}},"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-16T17:17:58.883312Z","iopub.execute_input":"2026-01-16T17:17:58.883604Z","iopub.status.idle":"2026-01-16T17:17:59.580948Z","shell.execute_reply.started":"2026-01-16T17:17:58.883573Z","shell.execute_reply":"2026-01-16T17:17:59.579940Z"}},"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, output_dim)\n\n        self.dropout = nn.Dropout(p=0.2)\n        #self.sftmax = F.softmax\n\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.fc5(x)\n\n        return x","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-16T17:17:59.582172Z","iopub.execute_input":"2026-01-16T17:17:59.582623Z","iopub.status.idle":"2026-01-16T17:17:59.590814Z","shell.execute_reply.started":"2026-01-16T17:17:59.582576Z","shell.execute_reply":"2026-01-16T17:17:59.589435Z"}},"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-16T17:17:59.592036Z","iopub.execute_input":"2026-01-16T17:17:59.592401Z","iopub.status.idle":"2026-01-16T17:17:59.638047Z","shell.execute_reply.started":"2026-01-16T17:17:59.592371Z","shell.execute_reply":"2026-01-16T17:17:59.637086Z"}},"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-16T17:17:59.640029Z","iopub.execute_input":"2026-01-16T17:17:59.640426Z","iopub.status.idle":"2026-01-16T17:17:59.651803Z","shell.execute_reply.started":"2026-01-16T17:17:59.640395Z","shell.execute_reply":"2026-01-16T17:17:59.650564Z"}},"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-16T17:17:59.653198Z","iopub.execute_input":"2026-01-16T17:17:59.653853Z","iopub.status.idle":"2026-01-16T17:17:59.678935Z","shell.execute_reply.started":"2026-01-16T17:17:59.653817Z","shell.execute_reply":"2026-01-16T17:17:59.677467Z"}},"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-16T17:17:59.681089Z","iopub.execute_input":"2026-01-16T17:17:59.681855Z","iopub.status.idle":"2026-01-16T17:17:59.700619Z","shell.execute_reply.started":"2026-01-16T17:17:59.681817Z","shell.execute_reply":"2026-01-16T17:17:59.697549Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"best_valid_loss = 0\nEPOCHS = 20\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-16T17:20:15.004390Z","iopub.execute_input":"2026-01-16T17:20:15.004783Z","iopub.status.idle":"2026-01-16T17:22:20.444171Z","shell.execute_reply.started":"2026-01-16T17:20:15.004751Z","shell.execute_reply":"2026-01-16T17:22:20.442770Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Berikut adalah kesimpulan dari proses pelatihan (training) model yang telah saya lakukan:\n\n- Kualitas Model: Sangat Baik\nSecara umum, arsitektur model yang kamu gunakan sangat efektif untuk dataset ini.\n\nAkurasi Validasi Tertinggi: Mencapai ~97.9% (Epoch 16 & 19).\n\nAkurasi Training: Mencapai 99.6%, yang menunjukkan model memiliki kapasitas yang cukup untuk mempelajari fitur pada data.\n\n- Dinamika Training: Terindikasi Overfitting\nTerdapat tanda-tanda overfitting mulai dari pertengahan proses (sekitar Epoch 7 ke atas):\n\nTrain Loss terus menurun secara konsisten (semakin \"pintar\" pada data latih).\n\nValidation Loss justru berhenti turun dan mulai fluktuatif/naik (dari 0.109 menjadi 0.131).\n\nArtinya: Melatih hingga 20 epoch sebenarnya berlebihan. Titik optimal modelmu sebenarnya berada di Epoch 7 atau 8, di mana loss validasi paling rendah.\n\n- Masalah Teknis (PENTING)\nTerdapat kegagalan logika pada kode penyimpanan model. Karena best_valid_loss diinisialisasi dengan angka 0, syarat if valid_loss < best_valid_loss tidak pernah terpenuhi. Akibatnya, file digit-model.pt tidak pernah tersimpan atau tidak ter-update dengan bobot terbaik selama proses training.","metadata":{}},{"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-16T17:23:02.706358Z","iopub.execute_input":"2026-01-16T17:23:02.707021Z","iopub.status.idle":"2026-01-16T17:23:03.122259Z","shell.execute_reply.started":"2026-01-16T17:23:02.706977Z","shell.execute_reply":"2026-01-16T17:23:03.120161Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Berikut adalah analisisnya berdasarkan grafik tersebut:\n\n- Analisis Grafik Kiri: Loss vs. Epochs (Indikator Utama)\nIni adalah grafik yang paling \"jujur\" menceritakan kondisi modelmu.\n\nGaris Biru (Training Loss): Turun dengan mulus dan terus menerus. Ini artinya model semakin lama semakin hebat menghafal soal-soal latihan (data train).\n\nGaris Oranye (Validation Loss): Perhatikan perilakunya. Ia turun sedikit di awal (Epoch 0-2), tapi setelah itu trennya justru naik ke atas dan sangat fluktuatif (naik-turun tajam).\n\nThe Gap (Jarak): Semakin ke kanan (semakin banyak epoch), jarak antara garis biru dan oranye semakin lebar.\n\nArtinya: Model kamu sudah tidak lagi \"belajar\" untuk memahami pola umum, melainkan mulai menghafal noise atau detail spesifik pada data training yang tidak berguna untuk data validasi.\n\n- Analisis Grafik Kanan: Accuracy vs. Epochs\nGaris Hijau (Training Acc): Naik terus hingga hampir menyentuh 1.0 (100%).\n\nGaris Kuning (Validation Acc): Stuck (stagnan) di angka sekitar 0.975 - 0.979. Meskipun training accuracy naik, validation accuracy tidak ikut naik. Ini menunjukkan bahwa perbaikan yang dilakukan model pada data training tidak berdampak positif pada kemampuannya menebak data baru.","metadata":{}}]}