{
"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
}