{"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":[{"sourceType":"competition","sourceId":3004,"databundleVersionId":861823}],"dockerImageVersionId":31286,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import torch\nimport pandas as pd\nfrom torch.utils.data import Dataset, DataLoader\n\nclass MNISTDataset(Dataset):\n    def __init__(self, csv_file, is_train=True):\n        self.data = pd.read_csv(csv_file)\n        self.is_train = is_train\n        if self.is_train:\n            self.y = self.data['label'].values\n            self.X = self.data.drop('label', axis=1).values / 255.0\n        else:\n            self.X = self.data.values / 255.0\n\n    def __len__(self):\n        return len(self.X)\n\n    def __getitem__(self, idx):\n        if self.is_train:\n            label = int(self.y[idx])\n            y_one_hot = torch.zeros(10)\n            y_one_hot[label] = 1.0\n            return torch.tensor(self.X[idx], dtype=torch.float32), y_one_hot\n        return torch.tensor(self.X[idx], dtype=torch.float32)\n\ndef flatten_and_transpose(X: torch.Tensor):\n    if len(X.shape) > 2:\n        X = torch.reshape(X, (X.shape[0], -1))\n    return X.T\n\ndef init_params(n_features, layers_size):\n    params = {}\n    for i in range(len(layers_size)):\n        if i == 0:\n            width = n_features\n        else:\n            width = layers_size[i - 1]\n        height = layers_size[i]\n        w = torch.rand(size=(height, width), dtype=torch.float) * torch.sqrt(torch.tensor(2.0 / width))\n        b = torch.zeros(size=(height, 1), dtype=torch.float)\n        params[f'W{i + 1}'] = w\n        params[f'b{i + 1}'] = b\n    return params\n\ndef forward_prop(input_data, params):\n    n_layers = len(params) // 2\n    z = input_data\n    cache = {}\n    cache['A0'] = input_data\n    for i in range(1, n_layers + 1):\n        if i != n_layers:\n            tmp_z = torch.matmul(params[f'W{i}'], z) + params[f'b{i}']\n            tmp_a = torch.mul(tmp_z, (tmp_z > 0).to(torch.float))\n            z = tmp_a\n            cache[f'Z{i}'] = tmp_z\n            cache[f'A{i}'] = tmp_a\n        else:\n            tmp_z = torch.matmul(params[f'W{i}'], z) + params[f'b{i}']\n            tmp_z_stable = tmp_z - torch.max(tmp_z, dim=0, keepdim=True).values\n            tmp_a = torch.exp(tmp_z_stable) / torch.exp(tmp_z_stable).sum(dim=0)\n            z = tmp_a\n            cache[f'Z{i}'] = tmp_z_stable\n            cache[f'A{i}'] = tmp_a\n    return z, cache\n\ndef backward_prop(A, Y, params, cache):\n    grads = {}\n    L = len(params) // 2\n    m = A.size()[1]\n    grads[f'dZ{L}'] = A - Y\n    grads[f'dW{L}'] = torch.matmul(A - Y, cache[f'A{L - 1}'].T) / m\n    grads[f'db{L}'] = (A - Y).sum(axis=1, keepdim=True) / m\n\n    for i in range(L - 1, 0, -1):\n        grads[f'dZ{i}'] = torch.mul(\n            torch.matmul(params[f'W{i + 1}'].T, grads[f'dZ{i + 1}']),\n            (cache[f'Z{i}'] > 0).to(torch.float)\n        )\n        grads[f'dW{i}'] = torch.matmul(grads[f'dZ{i}'], cache[f'A{i - 1}'].T) / m\n        grads[f'db{i}'] = grads[f'dZ{i}'].sum(axis=1, keepdim=True) / m\n\n    return grads\n\ndef update_params(params, grads, v, m, t=1, beta1=0.9, beta2=0.999, eps=1e-7, lr=1e-3):\n    L = len(params) // 2\n    for i in range(1, L + 1):\n        dW = grads[f'dW{i}']\n        db = grads[f'db{i}']\n        tmp_v_dW = beta1 * v[f'dW{i}'] + (1 - beta1) * dW \n        tmp_v_db = beta1 * v[f'db{i}'] + (1 - beta1) * db \n        tmp_m_dW = beta2 * m[f'dW{i}'] + (1 - beta2) * torch.square(dW) \n        tmp_m_db = beta2 * m[f'db{i}'] + (1 - beta2) * torch.square(db)\n\n        v_dW_corrected = tmp_v_dW / (1 - beta1**t)\n        v_db_corrected = tmp_v_db / (1 - beta1**t)\n        m_dW_corrected = tmp_m_dW / (1 - beta2**t)\n        m_db_corrected = tmp_m_db / (1 - beta2**t)\n\n        params[f'W{i}'] -= lr * v_dW_corrected / (torch.sqrt(m_dW_corrected) + eps)\n        params[f'b{i}'] -= lr * v_db_corrected / (torch.sqrt(m_db_corrected) + eps)\n        v[f'dW{i}'] = tmp_v_dW\n        v[f'db{i}'] = tmp_v_db\n        m[f'dW{i}'] = tmp_m_dW\n        m[f'db{i}'] = tmp_m_db\n    return params, v, m\n\ndef train(train_csv_path, n_features=28 * 28, epochs=10, lr=0.01, batch_size=64):\n    train_dataset = MNISTDataset(train_csv_path, is_train=True)\n    layers_size = [32, 64, 32, 10]\n    cost_per_epochs = []\n    params = init_params(n_features, layers_size)\n    L = len(params) // 2\n    v = {}\n    m = {}\n    for i in range(1, L + 1):\n        v[f'dW{i}'] = torch.zeros_like(params[f'W{i}'])\n        v[f'db{i}'] = torch.zeros_like(params[f'b{i}'])\n        m[f'dW{i}'] = torch.zeros_like(params[f'W{i}'])\n        m[f'db{i}'] = torch.zeros_like(params[f'b{i}'])\n\n    t = 0\n    for e in range(1, epochs + 1):\n        train_data_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\n        n_batch = len(train_data_loader)\n        cost = 0\n        \n        for _, (X, y) in enumerate(train_data_loader):\n            t += 1\n            ft_X = flatten_and_transpose(X)\n            A, cache = forward_prop(ft_X, params)\n            grads = backward_prop(A, y.T, params, cache)\n            params, v, m = update_params(params, grads, v, m, lr=lr, t=t)\n            cost += -torch.sum(torch.mul(y.T, torch.log(A + 1e-9))) / n_batch\n        \n        cost_per_epochs.append(cost.item())\n        print(f'Epoch [{e}/{epochs}]: cost = {cost.item()}')\n    print('Done.')\n\n    return params, cost_per_epochs\n\ndef predict(params, X_test):\n    predict_probs, _ = forward_prop(X_test, params)\n    label = torch.argmax(predict_probs, dim=0)\n    return label\n\ndef submit(params, test_csv_path, output_path='submission.csv'):\n    test_dataset = MNISTDataset(test_csv_path, is_train=False)\n    test_data_loader = DataLoader(test_dataset, batch_size=256, shuffle=False)\n    \n    predictions = []\n    for X in test_data_loader:\n        ft_X = flatten_and_transpose(X)\n        batch_preds = predict(params, ft_X)\n        predictions.extend(batch_preds.tolist())\n        \n    submission = pd.DataFrame({\n        'ImageId': range(1, len(predictions) + 1),\n        'Label': predictions\n    })\n    submission.to_csv(output_path, index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-11T03:28:16.307506Z","iopub.execute_input":"2026-03-11T03:28:16.307865Z","iopub.status.idle":"2026-03-11T03:28:19.531257Z","shell.execute_reply.started":"2026-03-11T03:28:16.307836Z","shell.execute_reply":"2026-03-11T03:28:19.530094Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_path = '/kaggle/input/competitions/digit-recognizer/train.csv'\ntest_path = '/kaggle/input/competitions/digit-recognizer/test.csv'\nsubmission_path = '/kaggle/working/submission.csv'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-11T03:30:16.264958Z","iopub.execute_input":"2026-03-11T03:30:16.265782Z","iopub.status.idle":"2026-03-11T03:30:16.270521Z","shell.execute_reply.started":"2026-03-11T03:30:16.265739Z","shell.execute_reply":"2026-03-11T03:30:16.269241Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"params, cost_per_epochs = train(\n    train_csv_path=train_path,\n    n_features=28 * 28,\n    epochs=30,\n    lr=0.001,\n    batch_size=64\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-11T03:43:59.368988Z","iopub.execute_input":"2026-03-11T03:43:59.369350Z","iopub.status.idle":"2026-03-11T03:45:24.369487Z","shell.execute_reply.started":"2026-03-11T03:43:59.369317Z","shell.execute_reply":"2026-03-11T03:45:24.368481Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submit(\n    params=params,\n    test_csv_path=test_path,\n    output_path=submission_path\n)\nprint('Submit successfully.')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-11T03:45:28.571141Z","iopub.execute_input":"2026-03-11T03:45:28.572239Z","iopub.status.idle":"2026-03-11T03:45:30.347057Z","shell.execute_reply.started":"2026-03-11T03:45:28.572203Z","shell.execute_reply":"2026-03-11T03:45:30.345984Z"}},"outputs":[],"execution_count":null}]}