TensorFlow 2.0學習筆記(5):寶可夢對戰預測分析

本文程式碼參考自:

輕鬆學會 Google TensorFlow 2.0 人工智慧深度學習實作開發書中之範例程式

購買連結

Pokemon classification

本次範例使用Kaggle上的數據集,請讀者先註冊Kaggle帳號,再至該連結下載

資料說明

  • Name:寶可夢名子
  • Type 1:寶可夢的屬性
  • Type 2:寶可夢的屬性(寶可夢可能擁有兩種屬性)
  • HP:生命直
  • Attack:攻擊力
  • Defense:守備力
  • Sp. Atk:特殊攻擊力
  • Sp. Def:特殊防禦力
  • Speed:速度
  • Generation:進化階段
  • Legendary:神獸 (1=神獸, 0=一般)

Import

import os
import numpy as np
import pandas as pd
import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow import keras
from tensorflow.keras import layers

讀取數據並分析

# 讀取寶可夢數據
pokemon_df = pd.read_csv('./pokemon-challenge/pokemon.csv')
pokemon_df.head() # 顯示前5筆資料
# 將"#"數據設定為索引值:pokemon_df= pokemon_df.set_index("#") # 將索引設定為'#'列
pokemon_df.head() # 顯示前5筆資料
# 讀取寶可夢對戰數據:# 讀取對戰數據
combats_df = pd.read_csv('./pokemon-challenge/combats.csv')
combats_df.head() # 顯示前5筆資料
# 補齊缺失資料pokemon_df.info() # 顯示精靈寶可夢的數據資訊# 查看Type2每個類別的數量:
# 透過傳入參數dropna=False,可以將缺失數據(NaN)也考慮進去,NaN代表寶可夢並沒# 有第二種屬性。
pokemon_df["Type 2"].value_counts(dropna =False)# 填補缺失數據:使用empty將缺失的欄位填上。pokemon_df["Type 2"].fillna('empty',inplace=True)
pokemon_df["Type 2"].value_counts()

數據前處理

print(combats_df.dtypes)  # 顯示對戰數據的資料型態
print('-' * 30)
print(pokemon_df.dtypes) # 顯示寶可夢數據的資料型態
# 轉換數據格式pokemon_df['Type 1'] = pokemon_df['Type 1'].astype('category') # 將'Type 1'轉成category型態pokemon_df['Type 2'] = pokemon_df['Type 2'].astype('category') # 將'Type 2'轉成category型態pokemon_df['Legendary'] = pokemon_df['Legendary'].astype('int') # 將'Legendary'轉成int型態pokemon_df.dtypes # 顯示目前寶可用數據集資料型態# 將寶可夢的Type1和Type2轉為One-hot Encoding表示:df_type1_one_hot = pd.get_dummies(pokemon_df['Type 1']) # 取得'Type 1'數據的One-hot編碼
df_type1_one_hot.head() # 顯示前5筆資料
# 使用Pandas的get_dummies函數,取得Type2(寶可夢第二種屬性)
# 的One-hot Encoding:
df_type2_one_hot = pd.get_dummies(pokemon_df['Type 2']) # 取得'Type 2'數據的One-hot編碼
df_type2_one_hot.head() # 顯示前5筆資料
# 將兩組One-hot Encoding合併回數據集:# 將上方兩個One-hot編碼資料加起來,將缺少的值補0,並轉呈int64型態
combine_df_one_hot = df_type1_one_hot.add(df_type2_one_hot, fill_value=0).astype('int64')
# 將顯示列數設定為30,不然會有部份資料無法顯示
pd.options.display.max_columns = 30
# 將One-hot編碼資料加到寶可夢數據中
pokemon_df = pokemon_df.join(combine_df_one_hot)
pokemon_df.head() # 顯示前5筆資料
# 將寶可夢屬性轉為數值表示(0, 1, 2, …18):透過cat.categories查詢類別的標籤。dict(enumerate(pokemon_df['Type 2'].cat.categories))# 透過cat.codes可以取得類別的編碼值。pokemon_df['Type 2'].cat.codes.head(10)# 用數值表示(0, 1, 2…18)取代原本的標籤值:pokemon_df['Type 1'] = pokemon_df['Type 1'].cat.codes
pokemon_df['Type 2'] = pokemon_df['Type 2'].cat.codes
pokemon_df.head() # 顯示前5筆資料
# 將沒有使用到的資料剔除(name):pokemon_df.drop('Name', axis='columns', inplace=True)
pokemon_df.head() # 顯示前5筆資料
# 將寶可夢對戰數據中勝利方的表示改為0與1:# apply方法第一個參數為自訂function,而axis為columns的話會將數據一行一
# 行放入function中處理,最後將所有結果組合成一個數據結構返回。
combats_df['Winner'] = combats_df.apply(lambda x: 0 if x.Winner == x.First_pokemon else 1, axis='columns')combats_df.head() # 顯示前5筆資料

分割數據集

將數據集分為3個部份分別為:

  • 訓練集
  • 驗證集
  • 測試集
data_num = combats_df.shape[0]# 取得一筆與data數量相同的亂數索引,主要目的是用於打散資料
indexes = np.random.permutation(data_num)
# 並將亂數索引值分為Train、validation和test分為,這裡劃分比例為6:2:2
train_indexes = indexes[:int(data_num *0.6)]
val_indexes = indexes[int(data_num *0.6):int(data_num *0.8)]
test_indexes = indexes[int(data_num *0.8):]
# 透過上方的索引值從對戰數據中提取資料
train_data = combats_df.loc[train_indexes]
val_data = combats_df.loc[val_indexes]
test_data = combats_df.loc[test_indexes]

Normalization 標準化

將數值表示的屬性除以19(因為加上empty共有19種屬性),讓數值縮放置0~1之間。

pokemon_df['Type 1'] = pokemon_df['Type 1'] / 19
pokemon_df['Type 2'] = pokemon_df['Type 2'] / 19

使用Standard Score將生命值、攻擊力和防禦力等數值標準化。

mean = pokemon_df.loc[:, 'HP':'Generation'].mean()  # 計算平均值
std = pokemon_df.loc[:, 'HP':'Generation'].std() # 計算標準差
# 標準化數據
pokemon_df.loc[:,'HP':'Generation'] = (pokemon_df.loc[:,'HP':'Generation']-mean)/std
pokemon_df.head()

建立Numpy array格式的訓練數據

準備對戰數據中每個寶可夢對應能力值的索引。

x_train_index = np.array(train_data.drop('Winner', axis='columns'))
x_val_index = np.array(val_data.drop('Winner', axis='columns'))
x_test_index = np.array(test_data.drop('Winner', axis='columns'))
print(x_train_index)
# 準備訓練目標y_train = np.array(train_data['Winner'])
y_val = np.array(val_data['Winner'])
y_test = np.array(test_data['Winner'])
# 準備兩種輸入數據:第一種:寶可夢的屬性為數值表示。# 取得寶可夢的能力值
pokemon_data_normal = np.array(pokemon_df.loc[:, :'Legendary'])
print(pokemon_data_normal.shape)
# 透過前面準備的索引產生輸入數據
x_train_normal = pokemon_data_normal[x_train_index -1].reshape((-1, 20))
x_val_normal = pokemon_data_normal[x_val_index -1].reshape((-1, 20))
x_test_normal = pokemon_data_normal[x_test_index -1].reshape((-1, 20))
print(x_train_normal.shape)
第二種:寶可夢的屬性為One-hot encoding表示。# 取得寶可夢的能力值
pokemon_data_one_hot = np.array(pokemon_df.loc[:, 'HP':])
print(pokemon_data_one_hot.shape)
# 透過前面準備的索引產生輸入數據
x_train_one_hot = pokemon_data_one_hot[x_train_index -1].reshape((-1, 54))
x_val_one_hot = pokemon_data_one_hot[x_val_index -1].reshape((-1, 54))
x_test_one_hot = pokemon_data_one_hot[x_test_index -1].reshape((-1, 54))
print(x_train_one_hot.shape)

使用數值編碼訓練網路(Model 1)

inputs = keras.Input(shape=(20, ))  # 建立輸入層
# 加入全層全連接層,每一層輸出使用ReLU激活函數,並加上Dropout(每次丟棄30%)
x = layers.Dense(64, activation='relu')(inputs)
x = layers.Dropout(0.3)(x)
x = layers.Dense(128, activation='relu')(x)
x = layers.Dropout(0.3)(x)
x = layers.Dense(64, activation='relu')(x)
x = layers.Dropout(0.3)(x)
x = layers.Dense(32, activation='relu')(x)
x = layers.Dropout(0.3)(x)
# 最後一層全連接層,輸出維度類別數量,並且使用sigmoid激活函數
# outputs = layers.Dense(1, activation='sigmoid')(x)
outputs = layers.Dense(1)(x)
# 建立網路模型(將輸入到輸出所有經過的網路層連接起來)
model_1 = keras.Model(inputs, outputs, name='model-1')
model_1.summary() # 顯示網路架構
model_dir = 'lab3-logs/models' # 設定儲存權重目錄
os.makedirs(model_dir) # 創建儲存權重目錄
# 儲存訓練記錄檔
log_dir = os.path.join('lab3-logs', 'model-1')
model_cbk = keras.callbacks.TensorBoard(log_dir=log_dir)
# 儲存最好的網路模型權重
model_mckp = keras.callbacks.ModelCheckpoint(model_dir + '/Best-model-1.h5', monitor='val_binary_accuracy',save_best_only=True, mode='max')
model_1.compile(keras.optimizers.Adam(),
# loss=keras.losses.BinaryCrossentropy(),
loss=keras.losses.BinaryCrossentropy(from_logits=True),
metrics=[keras.metrics.BinaryAccuracy()])
history_1 = model_1.fit(x_train_normal, y_train,
batch_size=64 ,
epochs=200,
validation_data=(x_val_normal, y_val),
callbacks=[model_cbk, model_mckp])

使用One-hot編碼訓練網路(Model 2)

inputs = keras.Input(shape=(54, ))  # 建立輸入層
# 加入全層全連接層,每一層輸出使用ReLU激活函數,並加上Dropout(每次丟棄30%)
x = layers.Dense(64, activation='relu')(inputs)
x = layers.Dropout(0.3)(x)
x = layers.Dense(128, activation='relu')(x)
x = layers.Dropout(0.3)(x)
x = layers.Dense(64, activation='relu')(x)
x = layers.Dropout(0.3)(x)
x = layers.Dense(32, activation='relu')(x)
x = layers.Dropout(0.3)(x)
# 最後一層全連接層,輸出維度為類別數量,並且使用sigmoid激活函數
outputs = layers.Dense(1)(x)
# outputs = layers.Dense(1, activation='sigmoid')(x)
# 建立網路模型(將輸入到輸出所有經過的網路層連接起來)
model_2 = keras.Model(inputs, outputs, name='model-2')
model_2.summary() # 顯示網路架構
# 儲存訓練記錄檔
log_dir = os.path.join('lab3-logs', 'model-2')
model_cbk = keras.callbacks.TensorBoard(log_dir=log_dir)
# 儲存最好的網路模型權重
model_mckp = keras.callbacks.ModelCheckpoint(model_dir + '/Best-model-2.h5',
monitor='val_binary_accuracy', save_best_only=True, mode='max')
model_2.compile(keras.optimizers.Adam(),
# loss=keras.losses.BinaryCrossentropy(),
loss=keras.losses.BinaryCrossentropy(from_logits=True),
metrics=[keras.metrics.BinaryAccuracy()])
history_2 = model_2.fit(x_train_one_hot, y_train,
batch_size=64 ,
epochs=200,
validation_data=(x_val_one_hot, y_val),
callbacks=[model_cbk, model_mckp])

比較兩種網路的訓練結果

plt.figure(figsize=(8, 6), dpi=400)
plt.plot(history_1.history['loss'], label='model-1-training')
plt.plot(history_1.history['val_loss'], label='model-1-validation')
plt.plot(history_2.history['loss'], label='model-2-training')
plt.plot(history_2.history['val_loss'], label='model-2-validation')
plt.ylabel('accuracy')
plt.xlabel('epochs')
plt.legend()
plt.figure(figsize=(8, 6))
plt.plot(history_1.history['binary_accuracy'], label='model-1-training')
plt.plot(history_1.history['val_binary_accuracy'], label='model-1-validation')
plt.plot(history_2.history['binary_accuracy'], label='model-2-training')
plt.plot(history_2.history['val_binary_accuracy'], label='model-2-validation')
plt.ylabel('accuracy')
plt.xlabel('epochs')
plt.legend()

驗證在測試集上

# 載入Model 1準確率最高的模型權重
model_1.load_weights(model_dir + '/Best-model-1.h5')
# 載入Model 2準確率最高的模型權重
model_2.load_weights(model_dir + '/Best-model-2.h5')
loss_1, accuracy_1 = model_1.evaluate(x_test_normal, y_test)
loss_2, accuracy_2 = model_2.evaluate(x_test_one_hot, y_test)
print("Model-1: {}%\nModel-2: {}%".format(accuracy_1, accuracy_2))

寶可夢PK

venusaur = np.expand_dims(pokemon_data_one_hot[3], axis=0)  # 妙蛙花
charizard = np.expand_dims(pokemon_data_one_hot[7], axis=0) # 噴火龍
blastoise = np.expand_dims(pokemon_data_one_hot[12], axis=0) # 水箭龜

Written by

Machine Learning / Deep Learning / Python / Flutter cakeresume.com/yanwei-liu

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store