{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"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":"gpu","dataSources":[{"sourceType":"competition","sourceId":16017,"databundleVersionId":708189,"isSourceIdPinned":false}],"dockerImageVersionId":31328,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 🧠 Kannada MNIST - 深度學習解題\n\n**競賽連結**: [Kaggle - Kannada MNIST](https://www.kaggle.com/c/Kannada-MNIST)\n\n## 競賽說明\nKannada MNIST 是一個針對卡納達語（印度南部語言）數字 0-9 的手寫辨識競賽，\n類似於經典的 MNIST，但使用的是卡納達數字字符。\n\n## 解題流程\n1. 資料載入與探索 (EDA)\n2. 資料預處理\n3. 建立深度學習模型 (CNN)\n4. 訓練與評估\n5. 預測與提交","metadata":{}},{"cell_type":"markdown","source":"## 1. 安裝與匯入套件","metadata":{}},{"cell_type":"code","source":"# 安裝必要套件（Kaggle 環境通常已預裝）\n# !pip install tensorflow keras numpy pandas matplotlib seaborn scikit-learn\n\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport matplotlib.gridspec as gridspec\nimport seaborn as sns\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Deep Learning\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers, models, callbacks, regularizers\nfrom tensorflow.keras.utils import to_categorical\n\n# Sklearn\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import confusion_matrix, classification_report\n\n# 設定隨機種子確保可重現性\nSEED = 42\nnp.random.seed(SEED)\ntf.random.set_seed(SEED)\n\n# ── 設定 Matplotlib 中文字型 ──\nimport matplotlib.font_manager as fm\nimport subprocess, os\n\n# Kaggle / Linux 環境：安裝 Noto Sans CJK\nif not any('Noto' in f.name for f in fm.fontManager.ttflist):\n    subprocess.run(['apt-get', 'install', '-y', '-q', 'fonts-noto-cjk'], \n                   capture_output=True)\n    fm.fontManager.__init__()  # 重新載入字型快取\n\n# 依優先順序挑選可用的中文字型\n_cjk_candidates = [\n    'Noto Sans CJK TC', 'Noto Sans CJK SC',\n    'WenQuanYi Micro Hei', 'AR PL UMing CN',\n    'Microsoft JhengHei', 'Microsoft YaHei',\n    'SimHei', 'PingFang TC',\n]\n_available = {f.name for f in fm.fontManager.ttflist}\n_chosen = next((f for f in _cjk_candidates if f in _available), None)\n\nif _chosen:\n    plt.rcParams['font.family'] = _chosen\n    print(f'中文字型已設定：{_chosen}')\nelse:\n    # 找不到預設 CJK 字型時，直接抓第一個含 CJK/Noto/Wen 的字型檔\n    _fallback = next(\n        (f.fname for f in fm.fontManager.ttflist\n         if any(k in f.name for k in ['CJK','Noto','Wen','Hei','Ming'])), None)\n    if _fallback:\n        plt.rcParams['font.family'] = fm.FontProperties(fname=_fallback).get_name()\n        print(f'使用備用字型：{_fallback}')\n    else:\n        print('警告：未找到中文字型，圖表中文可能顯示為方塊')\n\nplt.rcParams['axes.unicode_minus'] = False  # 修正負號顯示問題\n\nprint(f'TensorFlow 版本: {tf.__version__}')\nprint(f'Keras 版本: {keras.__version__}')\nprint(f'GPU 可用: {len(tf.config.list_physical_devices(\"GPU\")) > 0}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-07T05:59:32.610004Z","iopub.execute_input":"2026-04-07T05:59:32.610800Z","iopub.status.idle":"2026-04-07T05:59:32.621065Z","shell.execute_reply.started":"2026-04-07T05:59:32.610764Z","shell.execute_reply":"2026-04-07T05:59:32.620230Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. 資料載入","metadata":{}},{"cell_type":"code","source":"# 載入資料（Kaggle 環境路徑）\ntrain_df = pd.read_csv('/kaggle/input/competitions/Kannada-MNIST/train.csv')\ntest_df  = pd.read_csv('/kaggle/input/competitions/Kannada-MNIST/test.csv')\ndig_mnist = pd.read_csv('/kaggle/input/competitions/Kannada-MNIST/Dig-MNIST.csv')  # 額外測試集\nsample_sub = pd.read_csv('/kaggle/input/competitions/Kannada-MNIST/sample_submission.csv')\n\nprint('Train shape:', train_df.shape)\nprint('Test shape :', test_df.shape)\nprint('Dig-MNIST  :', dig_mnist.shape)\nprint()\ntrain_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-07T05:59:37.216179Z","iopub.execute_input":"2026-04-07T05:59:37.216490Z","iopub.status.idle":"2026-04-07T05:59:41.560745Z","shell.execute_reply.started":"2026-04-07T05:59:37.216463Z","shell.execute_reply":"2026-04-07T05:59:41.559989Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. 探索性資料分析 (EDA)","metadata":{}},{"cell_type":"code","source":"# 類別分布\nfig, axes = plt.subplots(1, 2, figsize=(14, 5))\n\ntrain_df['label'].value_counts().sort_index().plot(\n    kind='bar', ax=axes[0], color=sns.color_palette('viridis', 10)\n)\naxes[0].set_title('Train 標籤分布', fontsize=14)\naxes[0].set_xlabel('數字類別')\naxes[0].set_ylabel('樣本數量')\n\ndig_mnist['label'].value_counts().sort_index().plot(\n    kind='bar', ax=axes[1], color=sns.color_palette('magma', 10)\n)\naxes[1].set_title('Dig-MNIST 標籤分布', fontsize=14)\naxes[1].set_xlabel('數字類別')\n\nplt.tight_layout()\nplt.show()\n\nprint('Train 類別分布:\\n', train_df['label'].value_counts().sort_index())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-07T03:31:04.004422Z","iopub.execute_input":"2026-04-07T03:31:04.004909Z","iopub.status.idle":"2026-04-07T03:31:04.341937Z","shell.execute_reply.started":"2026-04-07T03:31:04.004881Z","shell.execute_reply":"2026-04-07T03:31:04.340997Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 視覺化樣本圖片\ndef show_samples(df, title='Sample Images', n_per_class=5):\n    fig, axes = plt.subplots(10, n_per_class, figsize=(n_per_class * 2, 22))\n    fig.suptitle(title, fontsize=16, y=1.01)\n    \n    for digit in range(10):\n        samples = df[df['label'] == digit].head(n_per_class)\n        for j, (_, row) in enumerate(samples.iterrows()):\n            img = row.drop('label').values.reshape(28, 28)\n            axes[digit, j].imshow(img, cmap='gray_r')\n            axes[digit, j].axis('off')\n            if j == 0:\n                axes[digit, j].set_ylabel(f'Digit {digit}', fontsize=12, rotation=0, \n                                           labelpad=40, va='center')\n    plt.tight_layout()\n    plt.show()\n\nshow_samples(train_df, title='Kannada MNIST - 訓練樣本')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-07T03:31:04.344330Z","iopub.execute_input":"2026-04-07T03:31:04.344665Z","iopub.status.idle":"2026-04-07T03:31:05.826775Z","shell.execute_reply.started":"2026-04-07T03:31:04.344636Z","shell.execute_reply":"2026-04-07T03:31:05.825834Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. 資料預處理","metadata":{}},{"cell_type":"code","source":"# 分離特徵與標籤\nX_train_full = train_df.drop('label', axis=1).values\ny_train_full = train_df['label'].values\n\nX_test  = test_df.drop('id', axis=1).values\ntest_ids = test_df['id'].values\n\n# 正規化 [0, 255] -> [0, 1]\nX_train_full = X_train_full.astype('float32') / 255.0\nX_test       = X_test.astype('float32') / 255.0\n\n# Reshape 為 (N, 28, 28, 1) - CNN 輸入格式\nX_train_full = X_train_full.reshape(-1, 28, 28, 1)\nX_test       = X_test.reshape(-1, 28, 28, 1)\n\n# One-hot encoding\ny_train_ohe = to_categorical(y_train_full, num_classes=10)\n\n# 訓練 / 驗證集切分 (80% / 20%)\nX_train, X_val, y_train, y_val = train_test_split(\n    X_train_full, y_train_ohe,\n    test_size=0.15, random_state=SEED, stratify=y_train_full\n)\n\nprint(f'X_train shape: {X_train.shape}')\nprint(f'X_val   shape: {X_val.shape}')\nprint(f'X_test  shape: {X_test.shape}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-07T03:31:05.828062Z","iopub.execute_input":"2026-04-07T03:31:05.828432Z","iopub.status.idle":"2026-04-07T03:31:06.736479Z","shell.execute_reply.started":"2026-04-07T03:31:05.828392Z","shell.execute_reply":"2026-04-07T03:31:06.735623Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5. 建立 CNN 模型","metadata":{}},{"cell_type":"code","source":"def build_cnn_model(input_shape=(28, 28, 1), num_classes=10):\n    \"\"\"\n    深度 CNN 模型架構：\n    - 多個 Conv Block（Conv2D + BN + ReLU + MaxPool + Dropout）\n    - Global Average Pooling\n    - Dense + Dropout 分類頭\n    \"\"\"\n    model = models.Sequential([\n        # ── Block 1 ──\n        layers.Conv2D(32, (3, 3), padding='same', input_shape=input_shape),\n        layers.BatchNormalization(),\n        layers.Activation('relu'),\n        layers.Conv2D(32, (3, 3), padding='same'),\n        layers.BatchNormalization(),\n        layers.Activation('relu'),\n        layers.MaxPooling2D((2, 2)),\n        layers.Dropout(0.25),\n\n        # ── Block 2 ──\n        layers.Conv2D(64, (3, 3), padding='same'),\n        layers.BatchNormalization(),\n        layers.Activation('relu'),\n        layers.Conv2D(64, (3, 3), padding='same'),\n        layers.BatchNormalization(),\n        layers.Activation('relu'),\n        layers.MaxPooling2D((2, 2)),\n        layers.Dropout(0.25),\n\n        # ── Block 3 ──\n        layers.Conv2D(128, (3, 3), padding='same'),\n        layers.BatchNormalization(),\n        layers.Activation('relu'),\n        layers.Conv2D(128, (3, 3), padding='same'),\n        layers.BatchNormalization(),\n        layers.Activation('relu'),\n        layers.MaxPooling2D((2, 2)),\n        layers.Dropout(0.4),\n\n        # ── 分類頭 ──\n        layers.GlobalAveragePooling2D(),\n        layers.Dense(256, activation='relu',\n                     kernel_regularizer=regularizers.l2(1e-4)),\n        layers.BatchNormalization(),\n        layers.Dropout(0.5),\n        layers.Dense(num_classes, activation='softmax')\n    ], name='KannadaMNIST_CNN')\n\n    return model\n\n\nmodel = build_cnn_model()\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-07T03:31:06.737966Z","iopub.execute_input":"2026-04-07T03:31:06.738633Z","iopub.status.idle":"2026-04-07T03:31:07.012274Z","shell.execute_reply.started":"2026-04-07T03:31:06.738594Z","shell.execute_reply":"2026-04-07T03:31:07.011558Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 視覺化模型架構（需安裝 pydot / graphviz，可選）\ntry:\n    tf.keras.utils.plot_model(\n        model, \n        to_file='model_architecture.png',\n        show_shapes=True, \n        show_layer_names=True,\n        dpi=80\n    )\n    from IPython.display import Image\n    Image('model_architecture.png')\nexcept Exception as e:\n    print(f'無法繪製架構圖（需安裝 graphviz）: {e}')\n    print('模型已建立完成，繼續訓練...')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-07T03:31:07.013414Z","iopub.execute_input":"2026-04-07T03:31:07.013812Z","iopub.status.idle":"2026-04-07T03:31:07.356815Z","shell.execute_reply.started":"2026-04-07T03:31:07.013781Z","shell.execute_reply":"2026-04-07T03:31:07.356023Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 6. 編譯與訓練模型","metadata":{}},{"cell_type":"code","source":"# 編譯模型\nmodel.compile(\n    optimizer=keras.optimizers.Adam(learning_rate=1e-3),\n    loss='categorical_crossentropy',\n    metrics=['accuracy']\n)\n\n# 回調函數\ncb_list = [\n    # 動態學習率：驗證 loss 連續 5 epoch 未改善則降低 LR\n    callbacks.ReduceLROnPlateau(\n        monitor='val_loss', factor=0.5, patience=5,\n        min_lr=1e-6, verbose=1\n    ),\n    # 早停：驗證 accuracy 連續 10 epoch 未改善則停止\n    callbacks.EarlyStopping(\n        monitor='val_accuracy', patience=10,\n        restore_best_weights=True, verbose=1\n    ),\n    # 儲存最佳模型\n    callbacks.ModelCheckpoint(\n        'best_model.h5', monitor='val_accuracy',\n        save_best_only=True, verbose=1\n    )\n]\n\n# 訓練參數\nBATCH_SIZE = 128\nEPOCHS     = 50\n\nprint(f'開始訓練... Batch Size={BATCH_SIZE}, Max Epochs={EPOCHS}')\n\nhistory = model.fit(\n    X_train, y_train,\n    batch_size=BATCH_SIZE,\n    epochs=EPOCHS,\n    validation_data=(X_val, y_val),\n    callbacks=cb_list,\n    verbose=1\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-07T03:31:07.358126Z","iopub.execute_input":"2026-04-07T03:31:07.358573Z","iopub.status.idle":"2026-04-07T05:18:27.575128Z","shell.execute_reply.started":"2026-04-07T03:31:07.358495Z","shell.execute_reply":"2026-04-07T05:18:27.574348Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 7. 訓練結果視覺化","metadata":{}},{"cell_type":"code","source":"def plot_training_history(history):\n    fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n    \n    # Accuracy\n    axes[0].plot(history.history['accuracy'],     label='Train Acc', lw=2, color='steelblue')\n    axes[0].plot(history.history['val_accuracy'], label='Val Acc',   lw=2, color='coral', linestyle='--')\n    axes[0].set_title('模型準確率', fontsize=14)\n    axes[0].set_xlabel('Epoch')\n    axes[0].set_ylabel('Accuracy')\n    axes[0].legend()\n    axes[0].grid(True, alpha=0.3)\n    \n    # Loss\n    axes[1].plot(history.history['loss'],     label='Train Loss', lw=2, color='steelblue')\n    axes[1].plot(history.history['val_loss'], label='Val Loss',   lw=2, color='coral', linestyle='--')\n    axes[1].set_title('模型損失', fontsize=14)\n    axes[1].set_xlabel('Epoch')\n    axes[1].set_ylabel('Loss')\n    axes[1].legend()\n    axes[1].grid(True, alpha=0.3)\n    \n    plt.tight_layout()\n    plt.show()\n    \n    best_epoch = np.argmax(history.history['val_accuracy']) + 1\n    best_acc   = max(history.history['val_accuracy'])\n    print(f'\\n最佳 Epoch: {best_epoch}')\n    print(f'最佳驗證準確率: {best_acc:.4f} ({best_acc*100:.2f}%)')\n\nplot_training_history(history)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-07T05:18:27.576616Z","iopub.execute_input":"2026-04-07T05:18:27.576895Z","iopub.status.idle":"2026-04-07T05:18:28.013071Z","shell.execute_reply.started":"2026-04-07T05:18:27.576867Z","shell.execute_reply":"2026-04-07T05:18:28.012250Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 8. 模型評估","metadata":{}},{"cell_type":"code","source":"# 在驗證集上評估\nval_loss, val_acc = model.evaluate(X_val, y_val, verbose=0)\nprint(f'驗證集 Loss    : {val_loss:.4f}')\nprint(f'驗證集 Accuracy: {val_acc:.4f} ({val_acc*100:.2f}%)')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-07T05:18:28.015715Z","iopub.execute_input":"2026-04-07T05:18:28.016009Z","iopub.status.idle":"2026-04-07T05:18:35.530909Z","shell.execute_reply.started":"2026-04-07T05:18:28.015983Z","shell.execute_reply":"2026-04-07T05:18:35.529954Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 混淆矩陣\ny_val_pred  = model.predict(X_val, verbose=0)\ny_val_pred_classes  = np.argmax(y_val_pred, axis=1)\ny_val_true_classes  = np.argmax(y_val, axis=1)\n\ncm = confusion_matrix(y_val_true_classes, y_val_pred_classes)\n\nplt.figure(figsize=(10, 8))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n            xticklabels=range(10), yticklabels=range(10))\nplt.title('混淆矩陣（驗證集）', fontsize=14)\nplt.xlabel('預測類別')\nplt.ylabel('真實類別')\nplt.show()\n\nprint('\\n分類報告：')\nprint(classification_report(y_val_true_classes, y_val_pred_classes,\n                             target_names=[str(i) for i in range(10)]))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-07T05:18:35.532194Z","iopub.execute_input":"2026-04-07T05:18:35.532629Z","iopub.status.idle":"2026-04-07T05:18:43.909454Z","shell.execute_reply.started":"2026-04-07T05:18:35.532587Z","shell.execute_reply":"2026-04-07T05:18:43.908601Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 錯誤預測視覺化\nwrong_idx = np.where(y_val_pred_classes != y_val_true_classes)[0]\nprint(f'錯誤預測數量: {len(wrong_idx)} / {len(y_val_true_classes)}')\n\nfig, axes = plt.subplots(3, 8, figsize=(16, 6))\naxes = axes.flatten()\n\nfor i, idx in enumerate(wrong_idx[:24]):\n    axes[i].imshow(X_val[idx, :, :, 0], cmap='gray_r')\n    true_label = y_val_true_classes[idx]\n    pred_label = y_val_pred_classes[idx]\n    conf       = y_val_pred[idx][pred_label]\n    axes[i].set_title(f'T:{true_label} P:{pred_label}\\n{conf:.2f}', fontsize=8, color='red')\n    axes[i].axis('off')\n\nplt.suptitle('預測錯誤的樣本（T=真實, P=預測）', fontsize=12)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-07T05:18:43.910586Z","iopub.execute_input":"2026-04-07T05:18:43.910895Z","iopub.status.idle":"2026-04-07T05:18:45.352782Z","shell.execute_reply.started":"2026-04-07T05:18:43.910867Z","shell.execute_reply":"2026-04-07T05:18:45.351923Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 9. 在 Dig-MNIST 上測試","metadata":{}},{"cell_type":"code","source":"# Dig-MNIST 評估\nX_dig = dig_mnist.drop('label', axis=1).values.astype('float32') / 255.0\ny_dig = dig_mnist['label'].values\nX_dig = X_dig.reshape(-1, 28, 28, 1)\ny_dig_ohe = to_categorical(y_dig, 10)\n\ndig_loss, dig_acc = model.evaluate(X_dig, y_dig_ohe, verbose=0)\nprint(f'Dig-MNIST Loss    : {dig_loss:.4f}')\nprint(f'Dig-MNIST Accuracy: {dig_acc:.4f} ({dig_acc*100:.2f}%)')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-07T05:18:45.354000Z","iopub.execute_input":"2026-04-07T05:18:45.354246Z","iopub.status.idle":"2026-04-07T05:18:53.991648Z","shell.execute_reply.started":"2026-04-07T05:18:45.354221Z","shell.execute_reply":"2026-04-07T05:18:53.990829Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 11. 生成提交檔案","metadata":{}},{"cell_type":"code","source":"# 預測測試集\ntest_preds = model.predict(X_test, verbose=0)\ntest_labels = np.argmax(test_preds, axis=1)\n\n# 生成提交檔案\nsubmission = pd.DataFrame({\n    'id': test_ids,\n    'label': test_labels\n})\n\nsubmission.to_csv('/kaggle/working/submission.csv', index=False)\n\nprint('提交檔案已儲存！')\nprint(submission.head(10))\nprint(f'\\n總預測數: {len(submission)}')\nprint(f'預測類別分布:\\n{submission[\"label\"].value_counts().sort_index()}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-07T05:18:53.992797Z","iopub.execute_input":"2026-04-07T05:18:53.993148Z","iopub.status.idle":"2026-04-07T05:18:58.235350Z","shell.execute_reply.started":"2026-04-07T05:18:53.993109Z","shell.execute_reply":"2026-04-07T05:18:58.234555Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 視覺化測試集預測結果\nfig, axes = plt.subplots(2, 10, figsize=(20, 4))\n\nfor digit in range(10):\n    idx = np.where(test_labels == digit)[0][0]\n    conf = test_preds[idx][digit]\n    \n    axes[0, digit].imshow(X_test[idx, :, :, 0], cmap='gray_r')\n    axes[0, digit].set_title(f'預測: {digit}\\n信心: {conf:.2f}', fontsize=9)\n    axes[0, digit].axis('off')\n    \n    # 信心條形圖\n    axes[1, digit].bar(range(10), test_preds[idx], color='steelblue')\n    axes[1, digit].bar(digit, test_preds[idx][digit], color='coral')\n    axes[1, digit].set_xticks(range(10))\n    axes[1, digit].set_xticklabels(range(10), fontsize=7)\n    axes[1, digit].set_ylim(0, 1)\n\nplt.suptitle('各類別測試預測範例 + 信心分數', fontsize=13)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-07T05:18:58.236309Z","iopub.execute_input":"2026-04-07T05:18:58.236625Z","iopub.status.idle":"2026-04-07T05:18:59.640389Z","shell.execute_reply.started":"2026-04-07T05:18:58.236595Z","shell.execute_reply":"2026-04-07T05:18:59.639606Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 12. 結果總結","metadata":{}},{"cell_type":"code","source":"print('=' * 50)\nprint('         Kannada MNIST 模型訓練結果總結')\nprint('=' * 50)\nprint(f'模型架構   : 深度 CNN（3 個 Conv Block）')\nprint(f'訓練樣本數  : {len(X_train):,}')\nprint(f'驗證樣本數  : {len(X_val):,}')\nprint(f'測試樣本數  : {len(X_test):,}')\nprint(f'驗證集準確率: {val_acc*100:.2f}%')\nprint(f'Dig-MNIST  : {dig_acc*100:.2f}%')\nprint(f'使用 TTA   : 否')\nprint('=' * 50)\nprint('提交檔案已儲存至 submission.csv')\n\nprint(f'\\n模型總參數量: {model.count_params():,}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-07T05:18:59.641453Z","iopub.execute_input":"2026-04-07T05:18:59.641734Z","iopub.status.idle":"2026-04-07T05:18:59.649256Z","shell.execute_reply.started":"2026-04-07T05:18:59.641708Z","shell.execute_reply":"2026-04-07T05:18:59.648256Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 📌 進一步提升成績的方向\n\n| 技巧 | 預期提升 | 說明 |\n|------|---------|------|\n| **EfficientNet / ResNet** | +0.5~1% | 使用預訓練骨幹 |\n| **Ensemble (多模型投票)** | +0.3~0.5% | 訓練多個不同架構模型 |\n| **更多 TTA 次數** | +0.1~0.3% | n_aug=20~50 |\n| **混合 Dig-MNIST 訓練** | +0.2~0.5% | 加入額外訓練資料 |\n| **Cutmix / Mixup** | +0.2~0.5% | 進階資料增強 |\n| **Label Smoothing** | +0.1~0.2% | 防止過擬合 |\n","metadata":{}}]}