{ "cells": [ { "cell_type": "markdown", "id": "4d5dec2a", "metadata": { "id": "4d5dec2a" }, "source": [ "# **自定義訓練流程(Custom training)**\n", "此份程式碼會介紹如何建立自定義的 dataset, model, losses 以及透過 tf.GradinetType 去訓練模型。\n", "\n", "## 本章節內容大綱\n", "* ### [建立資料集](#CreateDataset)\n", "* ### [建構模型](#BuildModel)\n", "* ### [訓練模型](#TrainModel)\n", "* ### [評估模型](#EvaluateModel)\n", "---" ] }, { "cell_type": "markdown", "id": "bfbb847d", "metadata": { "id": "bfbb847d" }, "source": [ "## 匯入套件" ] }, { "cell_type": "code", "execution_count": null, "id": "14f5dc49", "metadata": { "id": "14f5dc49" }, "outputs": [], "source": [ "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "# Tensorflow 相關套件\n", "import tensorflow as tf\n", "from tensorflow import keras\n", "from tensorflow.keras import layers" ] }, { "cell_type": "markdown", "id": "21b588b9", "metadata": { "id": "21b588b9" }, "source": [ "\n", "## 建立資料集" ] }, { "cell_type": "code", "execution_count": null, "id": "6061b022", "metadata": { "id": "6061b022" }, "outputs": [], "source": [ "# 上傳資料\n", "!wget -q https://github.com/TA-aiacademy/course_3.0/releases/download/DL/Data_part4.zip\n", "!unzip -q Data_part4.zip" ] }, { "cell_type": "code", "execution_count": null, "id": "2b20bf1f", "metadata": { "id": "2b20bf1f" }, "outputs": [], "source": [ "df = pd.read_csv('./Data/bodyperformance.csv')\n", "df.head()" ] }, { "cell_type": "markdown", "id": "1cc38e16", "metadata": { "id": "1cc38e16" }, "source": [ "* #### 身體素質資料集\n", "共 13393 筆,11 種身體體能表現相關特徵,類別共 4 種,0 等為最優依序排列至 3 等。" ] }, { "cell_type": "code", "execution_count": null, "id": "c693819f", "metadata": { "id": "c693819f" }, "outputs": [], "source": [ "X = df.iloc[:, :-1].values\n", "y = df['class'].values" ] }, { "cell_type": "code", "execution_count": null, "id": "4b0b73ac", "metadata": { "id": "4b0b73ac" }, "outputs": [], "source": [ "y_onehot = keras.utils.to_categorical(y)" ] }, { "cell_type": "code", "execution_count": null, "id": "39743ca1", "metadata": { "id": "39743ca1" }, "outputs": [], "source": [ "from sklearn.model_selection import train_test_split\n", "X_train, X_valid, y_train, y_valid = train_test_split(X, y_onehot,\n", " test_size=0.2,\n", " random_state=17,\n", " stratify=y)" ] }, { "cell_type": "code", "execution_count": null, "id": "8b88aab4", "metadata": { "id": "8b88aab4" }, "outputs": [], "source": [ "print(f'X_train shape: {X_train.shape}')\n", "print(f'X_valid shape: {X_valid.shape}')\n", "print(f'y_train shape: {y_train.shape}')\n", "print(f'y_valid shape: {y_valid.shape}')" ] }, { "cell_type": "code", "execution_count": null, "id": "fbe7379a", "metadata": { "id": "fbe7379a" }, "outputs": [], "source": [ "# Feature scaling\n", "from sklearn.preprocessing import StandardScaler\n", "sc = StandardScaler()\n", "X_train = sc.fit_transform(X_train, y_train)\n", "X_valid = sc.transform(X_valid)" ] }, { "cell_type": "code", "execution_count": null, "id": "f0b91ce0", "metadata": { "id": "f0b91ce0" }, "outputs": [], "source": [ "batch_size = 64\n", "\n", "# 準備訓練資料集\n", "train_dataset = tf.data.Dataset.from_tensor_slices((X_train, y_train))\n", "train_dataset = train_dataset.shuffle(\n", " buffer_size=1024,\n", " seed=17).batch(batch_size).prefetch(tf.data.AUTOTUNE).cache()\n", "\n", "# 準備驗證資料集\n", "val_dataset = tf.data.Dataset.from_tensor_slices((X_valid, y_valid))\n", "val_dataset = val_dataset.batch(batch_size)" ] }, { "cell_type": "markdown", "id": "742ab396", "metadata": { "id": "742ab396" }, "source": [ "\n", "## 建構模型" ] }, { "cell_type": "code", "execution_count": null, "id": "461852f1", "metadata": { "id": "461852f1" }, "outputs": [], "source": [ "class my_net(keras.Model): # build model object by custom class\n", " def __init__(self, num_classes=4):\n", " super(my_net, self).__init__()\n", " keras.backend.clear_session() # 重置 keras 的所有狀態\n", " tf.random.set_seed(17) # 設定 tensorflow 隨機種子\n", " self.input_layer = layers.Input(shape=(11,))\n", " self.hidden_layer_1 = layers.Dense(\n", " 32, # 神經元個數\n", " activation='swish')\n", " self.hidden_layer_2 = layers.Dense(\n", " 32, # 神經元個數\n", " activation='swish')\n", " self.output_layer = layers.Dense(\n", " num_classes,\n", " activation='softmax')\n", " self.out = self.call(self.input_layer)\n", "\n", " def call(self, inputs):\n", " x = self.hidden_layer_1(inputs)\n", " x = self.hidden_layer_2(x)\n", " outputs = self.output_layer(x)\n", " return outputs" ] }, { "cell_type": "code", "execution_count": null, "id": "26658c00", "metadata": { "id": "26658c00" }, "outputs": [], "source": [ "model = my_net()\n", "model.build(input_shape=(None, 11))\n", "model.summary()" ] }, { "cell_type": "code", "execution_count": null, "id": "2517911f", "metadata": { "id": "2517911f" }, "outputs": [], "source": [ "class my_crossentropy(keras.losses.Loss): # build loss object by custom class\n", " def call(self, y_true, y_pred):\n", " return keras.losses.categorical_crossentropy(y_true,\n", " y_pred,\n", " from_logits=False)" ] }, { "cell_type": "code", "execution_count": null, "id": "03687000", "metadata": { "id": "03687000" }, "outputs": [], "source": [ "# 創建損失函數\n", "loss_fn = my_crossentropy()\n", "# 創建優化器\n", "optimizer = keras.optimizers.Nadam()\n", "\n", "# 創建評估函數\n", "train_acc_metric = keras.metrics.CategoricalAccuracy()\n", "val_acc_metric = keras.metrics.CategoricalAccuracy()" ] }, { "cell_type": "markdown", "id": "49051ce2", "metadata": { "id": "49051ce2" }, "source": [ "\n", "## 訓練模型" ] }, { "cell_type": "code", "execution_count": null, "id": "94b3db70", "metadata": { "id": "94b3db70" }, "outputs": [], "source": [ "import time\n", "import tqdm\n", "\n", "# 創建 list 分別存放訓練集 acc, loss 和驗證集 acc\n", "train_acc_list, train_loss_list = [], []\n", "val_acc_list, val_loss_list = [], []\n", "\n", "epochs = 10\n", "\n", "# 訓練的迭代過程\n", "for epoch in range(epochs):\n", " start_time = time.time()\n", " t_bar = tqdm.tqdm_notebook(enumerate(train_dataset),\n", " total=len(train_dataset),\n", " desc=f'Epoch {epoch}')\n", "\n", " # 每次的迭代讀取一個批次的資料量\n", " for step, (x_batch_train, y_batch_train) in t_bar:\n", " with tf.GradientTape() as tape:\n", " outputs = model(x_batch_train, training=True)\n", " loss_value = loss_fn(y_batch_train, outputs)\n", "\n", " grads = tape.gradient(loss_value, model.trainable_weights) # 計算參數上的梯度\n", " optimizer.apply_gradients(zip(grads, model.trainable_weights)) # 更新參數\n", "\n", " train_acc_metric.update_state(y_batch_train, outputs) # 存放每個批次的評估結果\n", "\n", " # 印出每個迭代回合的訓練評估結果\n", " print('Training loss over epoch: %.4f' % (float(loss_value),))\n", " train_acc = train_acc_metric.result() # 平均所有存放的評估結果\n", " print('Training acc over epoch: %.4f' % (float(train_acc),))\n", "\n", " # 將訓練的評估結果儲存下來\n", " train_acc_list.append(train_acc)\n", " train_loss_list.append(loss_value)\n", "\n", " train_acc_metric.reset_states() # 重置訓練集的評估函數\n", "\n", " # 驗證集的迭代結果\n", " for x_batch_val, y_batch_val in val_dataset:\n", " val_logits = model(x_batch_val, training=False)\n", " val_acc_metric.update_state(y_batch_val, val_logits) # 存放每個批次的評估結果\n", "\n", " val_loss = loss_fn(y_batch_val, val_logits) # 計算最後批次的損失值\n", "\n", " # 印出每個迭代回合的驗證評估結果\n", " print('Validation loss: %.4f' % (float(val_loss),))\n", " val_acc = val_acc_metric.result() # 平均所有存放的評估結果\n", " print('Validation acc: %.4f' % (float(val_acc),))\n", "\n", " # 將驗證的評估結果儲存下來\n", " val_acc_list.append(val_acc)\n", " val_loss_list.append(val_loss)\n", "\n", " val_acc_metric.reset_states() # 重置驗證集的評估函數\n", "\n", " print('Time taken: %.2fs' % (time.time() - start_time))" ] }, { "cell_type": "markdown", "id": "3be35ac0", "metadata": { "id": "3be35ac0" }, "source": [ "\n", "## 評估模型" ] }, { "cell_type": "markdown", "id": "3c95b461", "metadata": { "id": "3c95b461" }, "source": [ "* ### 視覺化訓練過程的評估指標 (Visualization)" ] }, { "cell_type": "code", "execution_count": null, "id": "a09f2782", "metadata": { "id": "a09f2782" }, "outputs": [], "source": [ "plt.figure(figsize=(15, 4))\n", "plt.subplot(1, 2, 1)\n", "plt.plot(range(len(train_loss_list)), train_loss_list, label='train_loss')\n", "plt.plot(range(len(val_loss_list)), val_loss_list, label='valid_loss')\n", "plt.xlabel('Epochs')\n", "plt.ylabel('Loss')\n", "plt.legend()\n", "\n", "plt.subplot(1, 2, 2)\n", "plt.plot(range(len(train_acc_list)), train_acc_list, label='train_acc')\n", "plt.plot(range(len(val_acc_list)), val_acc_list, label='valid_acc')\n", "plt.xlabel('Epochs')\n", "plt.ylabel('Accuracy')\n", "plt.legend()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "cebf0662", "metadata": { "id": "cebf0662" }, "source": [ "* ### 模型預測(Model predictions)" ] }, { "cell_type": "code", "execution_count": null, "id": "9f599421", "metadata": { "id": "9f599421" }, "outputs": [], "source": [ "val_pred = []\n", "for x_val, y_val in val_dataset:\n", " val_pred += list(model.predict(x_val).argmax(-1).flatten())" ] }, { "cell_type": "code", "execution_count": null, "id": "87a4b4fb", "metadata": { "id": "87a4b4fb" }, "outputs": [], "source": [ "val_pred[:10]" ] }, { "cell_type": "code", "execution_count": null, "id": "733852b8", "metadata": { "id": "733852b8" }, "outputs": [], "source": [ "len(val_pred)" ] }, { "cell_type": "code", "execution_count": null, "id": "fbb3fcd3", "metadata": { "id": "fbb3fcd3" }, "outputs": [], "source": [ "from sklearn.metrics import classification_report\n", "print(classification_report(y_valid.argmax(-1), val_pred))" ] }, { "cell_type": "markdown", "id": "4c319aa6", "metadata": { "id": "4c319aa6" }, "source": [ "## 使用 tf.function 加快訓練速度" ] }, { "cell_type": "code", "execution_count": null, "id": "821f7184", "metadata": { "id": "821f7184" }, "outputs": [], "source": [ "model = my_net()\n", "model.build(input_shape=(None, 11))\n", "model.summary()" ] }, { "cell_type": "code", "source": [ "# 創建損失函數\n", "loss_fn = my_crossentropy()\n", "# 創建優化器\n", "optimizer = keras.optimizers.Nadam()\n", "\n", "# 創建評估函數\n", "train_acc_metric = keras.metrics.CategoricalAccuracy()\n", "val_acc_metric = keras.metrics.CategoricalAccuracy()" ], "metadata": { "id": "hEvmYT_MqDED" }, "id": "hEvmYT_MqDED", "execution_count": null, "outputs": [] }, { "cell_type": "code", "execution_count": null, "id": "ff91a219", "metadata": { "id": "ff91a219" }, "outputs": [], "source": [ "@tf.function\n", "def train_step(x, y):\n", " with tf.GradientTape() as tape:\n", " outputs = model(x, training=True)\n", " loss_value = loss_fn(y, outputs)\n", " grads = tape.gradient(loss_value, model.trainable_weights) # 計算參數上的梯度\n", " optimizer.apply_gradients(zip(grads, model.trainable_weights)) # 更新參數\n", " train_acc_metric.update_state(y, outputs) # 存放評估結果\n", " return loss_value" ] }, { "cell_type": "code", "execution_count": null, "id": "d228984f", "metadata": { "id": "d228984f" }, "outputs": [], "source": [ "@tf.function\n", "def test_step(x, y):\n", " val_outputs = model(x, training=False)\n", " val_acc_metric.update_state(y, val_outputs) # 存放評估結果" ] }, { "cell_type": "code", "execution_count": null, "id": "735d801b", "metadata": { "id": "735d801b" }, "outputs": [], "source": [ "import time\n", "import tqdm\n", "\n", "# 創建 list 分別存放訓練集 acc, loss 和驗證集 acc\n", "train_acc_list, train_loss_list = [], []\n", "val_acc_list, val_loss_list = [], []\n", "\n", "epochs = 10\n", "\n", "# 訓練的迭代過程\n", "for epoch in range(epochs):\n", " start_time = time.time()\n", " t_bar = tqdm.tqdm_notebook(enumerate(train_dataset),\n", " total=len(train_dataset),\n", " desc=f'Epoch {epoch}')\n", "\n", " # 每次的迭代讀取一個批次的資料量\n", " for step, (x_batch_train, y_batch_train) in t_bar:\n", " loss_value = train_step(x_batch_train, y_batch_train)\n", "\n", " # 印出每個迭代回合的訓練評估結果\n", " print(\"Training loss over epoch: %.4f\" % (float(loss_value),))\n", " train_acc = train_acc_metric.result() # 平均所有存放的評估結果\n", " print(\"Training acc over epoch: %.4f\" % (float(train_acc),))\n", "\n", " # 將訓練的評估結果儲存下來\n", " train_acc_list.append(train_acc)\n", " train_loss_list.append(loss_value)\n", "\n", " train_acc_metric.reset_states() # 重置訓練集的評估函數\n", "\n", " # 驗證集的迭代結果\n", " for x_batch_val, y_batch_val in val_dataset:\n", " test_step(x_batch_val, y_batch_val)\n", "\n", " # 計算最後批次的損失值\n", " val_logits = model(x_batch_val, training=False)\n", " val_loss = loss_fn(y_batch_val, val_logits)\n", "\n", " # 印出每個迭代回合的驗證評估結果\n", " print('Validation loss: %.4f' % (float(val_loss),))\n", " val_acc = val_acc_metric.result() # 平均所有存放的評估結果\n", " print('Validation acc: %.4f' % (float(val_acc),))\n", "\n", " # 將驗證的評估結果儲存下來\n", " val_acc_list.append(val_acc)\n", " val_loss_list.append(val_loss)\n", "\n", " val_acc_metric.reset_states() # 重置驗證集的評估函數\n", "\n", " print('Time taken: %.2fs\\n' % (time.time() - start_time))" ] }, { "cell_type": "code", "execution_count": null, "id": "b8e649b0", "metadata": { "id": "b8e649b0" }, "outputs": [], "source": [ "# 繪製訓練過程中的評估指標\n", "plt.figure(figsize=(15, 4))\n", "plt.subplot(1, 2, 1)\n", "plt.plot(range(len(train_loss_list)), train_loss_list, label='train_loss')\n", "plt.plot(range(len(val_loss_list)), val_loss_list, label='valid_loss')\n", "plt.xlabel('Epochs')\n", "plt.ylabel('Loss')\n", "plt.legend()\n", "\n", "plt.subplot(1, 2, 2)\n", "plt.plot(range(len(train_acc_list)), train_acc_list, label='train_acc')\n", "plt.plot(range(len(val_acc_list)), val_acc_list, label='valid_acc')\n", "plt.xlabel('Epochs')\n", "plt.ylabel('Accuracy')\n", "plt.legend()\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "id": "096c9f57", "metadata": { "id": "096c9f57" }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.12" }, "colab": { "provenance": [] }, "accelerator": "GPU", "gpuClass": "standard" }, "nbformat": 4, "nbformat_minor": 5 }