{"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 numpy as np\nimport pandas as pd\n\ndef flatten_and_transpose(X):\n    if len(X.shape) > 2:\n        X = np.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 = np.random.rand(height, width) * np.sqrt(2.0 / width)\n        b = np.zeros((height, 1))\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 = np.dot(params[f'W{i}'], z) + params[f'b{i}']\n            tmp_a = tmp_z * (tmp_z > 0).astype(float)\n            z = tmp_a\n            cache[f'Z{i}'] = tmp_z\n            cache[f'A{i}'] = tmp_a\n        else:\n            tmp_z = np.dot(params[f'W{i}'], z) + params[f'b{i}']\n            tmp_z_stable = tmp_z - np.max(tmp_z, axis=0, keepdims=True)\n            tmp_a = np.exp(tmp_z_stable) / np.sum(np.exp(tmp_z_stable), axis=0, keepdims=True)\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.shape[1]\n    grads[f'dZ{L}'] = A - Y\n    grads[f'dW{L}'] = np.dot(A - Y, cache[f'A{L - 1}'].T) / m\n    grads[f'db{L}'] = np.sum(A - Y, axis=1, keepdims=True) / m\n\n    for i in range(L - 1, 0, -1):\n        grads[f'dZ{i}'] = np.dot(params[f'W{i + 1}'].T, grads[f'dZ{i + 1}']) * (cache[f'Z{i}'] > 0).astype(float)\n        grads[f'dW{i}'] = np.dot(grads[f'dZ{i}'], cache[f'A{i - 1}'].T) / m\n        grads[f'db{i}'] = np.sum(grads[f'dZ{i}'], axis=1, keepdims=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) * np.square(dW) \n        tmp_m_db = beta2 * m[f'db{i}'] + (1 - beta2) * np.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 / (np.sqrt(m_dW_corrected) + eps)\n        params[f'b{i}'] -= lr * v_db_corrected / (np.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    df_train = pd.read_csv(train_csv_path)\n    Y_raw = df_train['label'].values\n    X_raw = df_train.drop('label', axis=1).values / 255.0\n    \n    n_samples = len(X_raw)\n    Y_oh = np.zeros((n_samples, 10))\n    Y_oh[np.arange(n_samples), Y_raw] = 1.0\n\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_adam = {}\n    for i in range(1, L + 1):\n        v[f'dW{i}'] = np.zeros_like(params[f'W{i}'])\n        v[f'db{i}'] = np.zeros_like(params[f'b{i}'])\n        m_adam[f'dW{i}'] = np.zeros_like(params[f'W{i}'])\n        m_adam[f'db{i}'] = np.zeros_like(params[f'b{i}'])\n\n    t = 0\n    for e in range(1, epochs + 1):\n        indices = np.arange(n_samples)\n        np.random.shuffle(indices)\n        X_shuffled = X_raw[indices]\n        Y_shuffled = Y_oh[indices]\n        \n        n_batch = int(np.ceil(n_samples / batch_size))\n        cost = 0\n        \n        for i in range(0, n_samples, batch_size):\n            X_batch = X_shuffled[i:i + batch_size]\n            Y_batch = Y_shuffled[i:i + batch_size]\n            \n            t += 1\n            ft_X = flatten_and_transpose(X_batch)\n            A, cache = forward_prop(ft_X, params)\n            grads = backward_prop(A, Y_batch.T, params, cache)\n            params, v, m_adam = update_params(params, grads, v, m_adam, lr=lr, t=t)\n            cost += -np.sum(Y_batch.T * np.log(A + 1e-9)) / n_batch\n            \n        cost_per_epochs.append(cost)\n        print(f'Epoch [{e}/{epochs}]: cost = {cost}')\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 = np.argmax(predict_probs, axis=0)\n    return label\n\ndef submit(params, test_csv_path, output_path='submission.csv'):\n    df_test = pd.read_csv(test_csv_path)\n    X_test = df_test.values / 255.0\n    n_samples = len(X_test)\n    batch_size = 256\n    \n    predictions = []\n    for i in range(0, n_samples, batch_size):\n        X_batch = X_test[i:i + batch_size]\n        ft_X = flatten_and_transpose(X_batch)\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":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-03-11T03:40:22.696916Z","iopub.execute_input":"2026-03-11T03:40:22.697100Z","iopub.status.idle":"2026-03-11T03:40:24.380366Z","shell.execute_reply.started":"2026-03-11T03:40:22.697079Z","shell.execute_reply":"2026-03-11T03:40:24.379216Z"}},"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:40:24.382082Z","iopub.execute_input":"2026-03-11T03:40:24.382445Z","iopub.status.idle":"2026-03-11T03:40:24.386656Z","shell.execute_reply.started":"2026-03-11T03:40:24.382419Z","shell.execute_reply":"2026-03-11T03:40:24.385905Z"}},"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=20,\n    lr=0.001,\n    batch_size=64\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-11T03:41:00.045726Z","iopub.execute_input":"2026-03-11T03:41:00.046053Z","iopub.status.idle":"2026-03-11T03:41:23.514365Z","shell.execute_reply.started":"2026-03-11T03:41:00.046029Z","shell.execute_reply":"2026-03-11T03:41:23.513159Z"}},"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(f'Submit successfully.')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-11T03:41:44.789957Z","iopub.execute_input":"2026-03-11T03:41:44.790252Z","iopub.status.idle":"2026-03-11T03:41:46.135534Z","shell.execute_reply.started":"2026-03-11T03:41:44.790232Z","shell.execute_reply":"2026-03-11T03:41:46.135004Z"}},"outputs":[],"execution_count":null}]}