決策樹,是每個游戲人必須要掌握的游戲AI構建技術,難度小,速度快,結果直觀,本篇將對決策樹進行小小解讀~~~~
目錄
1. 定義
2. 發展歷史
3. 決策樹的算法公式和函數
3.1. 信息增益(Information Gain)
3.2. 信息增益比(Gain Ratio)?
3.3. 基尼指數(Gini Index)
3.4. 三種劃分標準對比
4. 運行原理及步驟
5. 優缺點
6. 游戲AI使用決策樹算法進行NPC行為控制
6.1. 概述
6.2. 詳細過程
6.3. 實例
6.4. Python實現
7. 總述
1. 定義
決策樹算法是一種常用的機器學習算法,它通過遞歸地選擇最佳特征來對數據進行分類或回歸。
決策樹由節點和有向邊組成,內部節點表示一個特征或屬性,葉節點表示分類或回歸的結果。
在游戲AI中,決策樹可以幫助NPC更智能地做出決策,提高游戲的趣味性和挑戰性。
2. 發展歷史
決策樹算法的發展可以追溯到1959年,當時的研究人員試圖解決人工智能中的決策問題。
1986年,喬治·盧卡斯(George A. Quinlan)提出了ID3算法,這是決策樹學習的第一個主要成果。
隨后,盧卡斯又提出了C4.5算法,這是ID3算法的改進版本,具有更強的魯棒性和泛化能力。
CART(Classification and Regression Trees)算法也在此基礎上發展,它可以處理連續型特征,適用于回歸問題。
3. 決策樹的算法公式和函數
決策樹算法的核心在于如何選擇最優劃分特征,通常使用信息增益(Information Gain)、信息增益比(Gain Ratio)或基尼指數(Gini Index)作為劃分標準。
3.1. 信息增益(Information Gain)
-
信息增益(Information Gain)公式
其中,是數據集
的信息熵,
是特征
上取值為
的樣本子集。
-
Python 實現信息增益
import numpy as np def entropy(y): n = len(y) labels_count = {} for label in y: if label not in labels_count: labels_count[label] = 0 labels_count[label] += 1 ent = 0.0 for label in labels_count: p = labels_count[label] / n ent -= p * np.log2(p) return ent def info_gain(X, y, feature): n = len(y) ent_before = entropy(y) total_gain = 0.0 feature_values = np.unique(X[:, feature]) for value in feature_values: sub_X = X[X[:, feature] == value] sub_y = y[X[:, feature] == value] prob = len(sub_X) / n total_gain += prob * entropy(sub_y) return ent_before - total_gain
3.2. 信息增益比(Gain Ratio)?
決策樹使用信息增益比(Gain Ratio)作為劃分標準時,其公式是基于信息增益的,但增加了一個分裂信息(Split Information)的項來規范化信息增益,從而避免偏向于選擇取值較多的特征。
- 信息增益比公式
-
信息增益(Information Gain):
-
分裂信息(Split Information):
-
信息增益比(Gain Ratio):
- Python實現代碼
以下是使用Python實現信息增益比的代碼:
import numpy as np def entropy(y): """計算信息熵""" n = len(y) labels_count = {} for label in y: if label not in labels_count: labels_count[label] = 0 labels_count[label] += 1 ent = 0.0 for label in labels_count: p = labels_count[label] / n ent -= p * np.log2(p) return ent def split_info(X, feature): """計算分裂信息""" n = len(X) feature_values = np.unique(X[:, feature]) split_info = 0.0 for value in feature_values: sub_X = X[X[:, feature] == value] prob = len(sub_X) / n split_info -= prob * np.log2(prob) return split_info def info_gain(X, y, feature): """計算信息增益""" n = len(y) ent_before = entropy(y) total_gain = 0.0 feature_values = np.unique(X[:, feature]) for value in feature_values: sub_X = X[X[:, feature] == value] sub_y = y[X[:, feature] == value] prob = len(sub_X) / n total_gain += prob * entropy(sub_y) return ent_before - total_gain def gain_ratio(X, y, feature): """計算信息增益比""" gain = info_gain(X, y, feature) split_info = split_info(X, feature) if split_info == 0: # 避免除以0的情況 return 0 return gain / split_info # 示例數據
X = np.array([[1, 0], [1, 1], [0, 0], [0, 1]])
y = np.array([0, 0, 1, 1]) # 計算特征0和特征1的信息增益比
print("Gain Ratio for feature 0:", gain_ratio(X, y, 0))
print("Gain Ratio for feature 1:", gain_ratio(X, y, 1))
這段代碼定義了計算信息熵、分裂信息、信息增益和信息增益比的函數,然后在示例數據上計算了特征0和特征1的信息增益比。
3.3. 基尼指數(Gini Index)
決策樹使用基尼指數(Gini index)作為劃分標準時,其公式主要用于衡量數據集或數據子集的不純度。基尼指數越小,表示數據集或數據子集的純度越高,即選定的特征越好。
- 基尼指數公式
對于給定的數據集?D,其基尼指數?Gini(D)?可以通過以下公式計算:
其中,pk??是數據集?D?中選擇第?k?類的概率,J?是類的總數。
對于特征?,假設它有?
?個可能的取值?{a1,a2,…,aV},如果使用特征?A?對數據集?D?進行劃分,可以得到?V?個子集?{D1,D2,…,DV},其中?Dv?包含所有在特征?A?上取值為?av?的樣本。此時,特征?A?對數據集?D?的基尼指數?
可以通過以下公式計算:
其中,是子集?Dv?的基尼指數,∣Dv∣?是子集?Dv?的樣本數量,∣D∣?是數據集?D?的樣本總數。
- Python實現代碼
以下是使用Python實現基尼指數的代碼:
import numpy as np def gini_index(y): """計算數據集的基尼指數""" n = len(y) labels_count = {} for label in y: if label not in labels_count: labels_count[label] = 0 labels_count[label] += 1 gini = 1.0 for label in labels_count: p = labels_count[label] / n gini -= p**2 return gini def gini_index_feature(X, y, feature): """計算特征對數據集的基尼指數""" n = len(y) feature_values = np.unique(X[:, feature]) total_gini = 0.0 for value in feature_values: sub_X = X[X[:, feature] == value] sub_y = y[X[:, feature] == value] prob = len(sub_X) / n total_gini += prob * gini_index(sub_y) return total_gini # 示例數據
X = np.array([[1, 0], [1, 1], [0, 0], [0, 1]])
y = np.array([0, 0, 1, 1]) # 計算特征0和特征1的基尼指數
print("Gini index for feature 0:", gini_index_feature(X, y, 0))
print("Gini index for feature 1:", gini_index_feature(X, y, 1))
這段代碼定義了計算數據集基尼指數的函數?gini_index。
然后定義了計算特征對數據集基尼指數的函數?gini_index_feature
。
最后,在示例數據上計算了特征0和特征1的基尼指數。
3.4. 三種劃分標準對比
決策樹在構建過程中,選擇合適的劃分標準至關重要,這直接影響到決策樹的性能和效率。信息增益(Information Gain)、信息增益比(Gain Ratio)和基尼指數(Gini Index)是三種常用的劃分標準,它們各自具有不同的優點和缺陷。
- 信息增益(Information Gain)
優點:
- 直觀易懂:信息增益的計算過程簡單明了,易于理解和解釋。它反映了使用某個特征進行劃分后,數據集純度的提升程度。
- 適應性強:信息增益可以適應多個類別之間的分類問題,并可以應用于離散型和連續型的特征。
- 多特征值偏好:信息增益在計算時考慮了特征的不確定性程度,因此對于具有更多特征值的特征更有利,這有助于捕捉數據中的細節信息。
缺陷:
- 特征值偏好:信息增益傾向于選擇具有更多特征值的特征作為劃分特征,這可能導致決策樹過于復雜,增加過擬合的風險。
- 忽略相關性:信息增益獨立地計算每個特征的重要性,可能忽視了特征之間的相關性,而特征之間的相關性對于分類問題具有重要意義。
- 信息增益比(Gain Ratio)
優點:
- 規范化信息增益:信息增益比通過引入分裂信息(Split Information)對信息增益進行了規范化,從而減少了信息增益對具有更多特征值特征的偏好。
- 處理特征值偏差:在信息增益的基礎上,信息增益比更好地處理了特征值分布偏差大的情況,提高了決策樹的健壯性。
缺陷:
- 計算復雜度:由于需要計算分裂信息,信息增益比的計算復雜度略高于信息增益,特別是在處理大規模數據集時。
- 可能偏向取值少的特征:在某些情況下,信息增益比可能過于偏向取值數目較少的特征,這需要根據具體數據集進行權衡。
- 基尼指數(Gini Index)
優點:
- 計算效率高:基尼指數的計算方式相對簡單,不涉及對數運算,因此在處理大規模數據集時具有較高的效率。
- 不偏向特征值:基尼指數在計算特征重要性時不考慮特征的不確定性程度,因此不會偏向于具有更多特征值的特征。
- 二分類友好:基尼指數特別適用于二分類問題,能夠直觀地反映分類的準確性。
缺陷:
- 不直觀:基尼指數作為一個概率指標,其計算過程相對較為抽象,不如信息增益直觀易懂。
- 多分類問題:雖然基尼指數可以應用于多分類問題,但在處理多個類別之間的分類時,其效果可能不如信息增益或信息增益比。
- 小結
三種劃分標準各有優缺點,選擇哪種標準取決于具體問題的特點和數據集的特征。
在實際應用中,可以通過交叉驗證等方法來評估不同劃分標準對決策樹性能的影響,從而選擇最適合的劃分標準。
4. 運行原理及步驟
決策樹的構建過程包括選擇最佳劃分特征、遞歸地劃分數據集、剪枝等步驟。
- 步驟
- 選擇最佳劃分特征:根據信息增益、信息增益比或基尼指數選擇最佳劃分特征。
- 劃分數據集:根據選擇的特征和特征值劃分數據集。
- 遞歸構建決策樹:對每個子數據集重復上述步驟,直到滿足停止條件(如所有樣本屬于同一類,或沒有更多特征等)。
- 剪枝:為了防止過擬合,通常需要對決策樹進行剪枝。
- Python 實現決策樹構建
class DecisionTree: def __init__(self, max_depth=None, min_samples_split=2): self.max_depth = max_depth self.min_samples_split = min_samples_split self.root = None def fit(self, X, y): self.root = self._build_tree(X, y, 0) def _build_tree(self, X, y, depth): # 停止條件 if depth >= self.max_depth or len(np.unique(y)) == 1 or len(X) < self.min_samples_split: return Node(value=np.argmax(np.bincount(y))) # 選擇最佳劃分特征 best_feature, best_thresh = self._best_split(X, y) # 劃分數據集 left_idxs, right_idxs = self._split(X[:, best_feature], best_thresh) # 遞歸構建子樹 left = self._build_tree(X[left_idxs, :], y[left_idxs], depth + 1) right = self._build_tree(X[right_idxs, :], y[right_idxs], depth + 1) return Node(best_feature, best_thresh, left, right) # 輔助函數:選擇最佳劃分特征和閾值、劃分數據集等
5. 優缺點
- 優點
- 易于理解和實現。
- 計算效率高,對于分類問題表現良好。
- 能夠處理非線性關系。
- 可以清晰地顯示決策過程,便于解釋。
- 缺點
- 容易過擬合,需要通過剪枝等技術解決。
- 對缺失值比較敏感,需要額外處理。
- 在某些復雜問題上可能不如神經網絡等算法表現優異。
6. 游戲AI使用決策樹算法進行NPC行為控制
在游戲開發中,NPC(非玩家角色)的行為控制是一個重要方面。
使用決策樹算法可以有效地管理和控制NPC的行為,使其根據游戲環境和玩家行為做出合理的反應。
6.1. 概述
決策樹是一種基于樹結構的決策模型,其中每個內部節點表示一個屬性上的判斷,每個分支代表一個判斷結果的輸出,最后每個葉節點代表一種分類結果。
6.2. 詳細過程
- 定義行為: 確定NPC可能執行的所有行為,例如攻擊、逃跑、對話等。
- 確定屬性: 選擇影響NPC行為的屬性,如玩家距離、玩家狀態(攻擊、防御等)、NPC當前狀態等。
- 構建決策樹: 使用屬性構建決策節點,每個節點根據屬性做出決策,并決定下一步的行為。
- 實施行為: 根據決策樹的結果,NPC執行相應的行為。
6.3. 實例
假設我們有一個簡單的游戲,NPC可以執行兩種行為:攻擊和逃跑。我們考慮兩個屬性:玩家距離和玩家狀態。
- 如果玩家距離小于5米且玩家處于攻擊狀態,NPC選擇逃跑。
- 如果玩家距離小于5米且玩家不處于攻擊狀態,NPC選擇對話。
- 如果玩家距離大于等于5米,NPC選擇巡邏。
6.4. Python實現
class NPC: def __init__(self, name): self.name = name def decide_action(self, player_distance, player_is_attacking): # 根據玩家距離和玩家狀態決定NPC的行為 if player_distance < 5: if player_is_attacking: return "逃跑" else: return "對話" else: return "巡邏" # 實例化NPC
npc = NPC("守衛") # 調用decide_action方法決定NPC的行為,并打印結果
action = npc.decide_action(3, True)
print(f"{npc.name}決定{action}")
這段代碼定義了一個NPC
類,其中__init__
方法是構造函數,用于初始化NPC的姓名。
decide_action
方法根據傳入的玩家距離(player_distance
)和玩家是否處于攻擊狀態(player_is_attacking
)來決定NPC應該執行的行為,并返回該行為。
最后,代碼創建了一個名為“守衛”的NPC實例,并調用decide_action
方法來決定其行為,然后打印出結果。
7. 總述
決策樹算法運用于游戲的AI中,歷史悠久,難度相對其他算法來說最低,也是運用最廣的一種方法。簡單的決策樹早就在紅白機時代就已初見雛形。隨著近年來的發展,決策樹更多的關注于自動決策樹生成,后面有機會會著重于此部分論述。