記事ディレクトリ
序文
Decision Treesはツリー構造に基づく機械学習アルゴリズムで、分類や回帰問題に使用できる、近年最も一般的なデータ マイニング アルゴリズムです。
サンプルの観察データからサンプルの予測結果を推測する予測モデルとして使用できます。予測結果の違いに応じて、決定木の学習は 2 つのカテゴリに分類できます。
- 予測が離散値のセットに限定される分類ツリー。ツリーの各枝は論理 AND で接続された分類特徴のセットに対応し、枝上の葉ノードは上記の特徴によって予測できる分類ラベルに対応します。
- 予測が連続値である回帰木。
決定木は、if-then ルールの集合として、または特徴空間およびクラス空間で定義された条件付き確率分布として考えることができます。
- if-then ルールとは、決定木モデルにおける判断プロセスを記述するために使用される形式的な表現方法を指します。各ルールは前提と結論で構成されます。たとえば、意思決定ツリーを使用して、ある人が製品を購入するかどうかを予測する場合、「この人が 30 歳未満で、収入が 50,000 ドル以上であれば、この製品を購入するだろう」というルールが考えられます。このルールの前提は「この人は 30 歳未満で、所得が 50,000 ドル以上である」であり、結論は「この商品を買うだろう」です。
- 特徴空間とは、すべてのサンプルの特徴ベクトルで構成される空間を指し、特徴空間では各サンプルをベクトルとして表現できます。
- クラス空間とは、考えられるすべてのクラスで構成される空間を指します。クラス空間では、各クラスは点または領域として表現できます。
デシジョン ツリー アルゴリズムの目標は、各パーティション内のサンプルが同じクラスに属するように、特徴空間内のパーティションを見つけることです。
1. はじめに
1.1 原則
決定木アルゴリズムの基本原理は、分割されたサブセットが可能な限り純粋になるように、つまり、同じサブセット内のサンプルが同じカテゴリに属するように、特定のルールに従ってデータセットを分割することです。このプロセスは、すべてのサンプルが同じカテゴリに属するか、それ以上分割できなくなるまで、毎回分割に最適な特徴を選択する再帰的なプロセスとみなすことができます。
決定木を構築するときは、分割に最適な特徴を選択する方法を考慮する必要があります。一般的に使用される手法には、ID3 (反復二分法 3)、C4.5、CART (分類および回帰ツリー) などが含まれます。このうち、ID3 と C4.5 は特徴選択に情報ゲインを使用しますが、CART は特徴選択に Gini 不純物を使用します。
- 情報利得: データセットを分割する前後の情報の変化
- ジニ不純度: 簡単に言えば、データセットからサブアイテムをランダムに選択し、他のグループに誤って分類される確率を測定することです。
1.2 プロセス
デシジョン ツリーの基本的なフローは、ルートからリーフまでの再帰的なプロセスであり、各中間ノードでパーティション属性を検索します。再帰で重要なのは、停止条件を設定することです。
- 現在のノードに含まれるサンプルは同じカテゴリに属しているため、分割する必要はありません。
- 現在のアトリビュート セットが空であるか、すべてのサンプルがすべてのアトリビュートで同じ値を持ち、分割できません。単純に理解すると、このノードが割り当てられると、すべてのアトリビュート フィーチャが使い果たされ、利用可能なフィーチャがなくなるということです。ラベルの数: このノードはリーフ ノードになるようにラベル付けされます (実際には、サンプル出現の事後確率が事前確率として使用されます)。
- 現在のノードに含まれるサンプル セットは空であるため、分割できません。この状況は、サンプル データにこの属性の値が欠如しており、ノードが親ノードのラベルに従ってマークされているために発生します (実際には、親ノードが最も多く出現するラベルが事前確率として使用されます)。
1.3 情報エントロピー、情報利得、ジニ不純物
-
情報エントロピー (エントロピー) はサンプルセットの不確実性の尺度であり、その値が小さいほどサンプルセットの純度が高くなります。
デシジョン ツリー アルゴリズムでは、情報エントロピーを使用してサンプル セットの純度を計算します。サンプル集合 D 内のクラス k のサンプルの割合がpk ( k = 1 , 2 , … , y ) p_k(k=1,2,…,y) であると仮定します。pk( k=1 、2 、…、y )の場合、 D の情報エントロピーは次のように定義されます。
E nt ( D ) = − ∑ k = 1 ypklog 2 pk Ent(D)=-\sum_{k=1}^{y}p_klog_2p_kエント( D ) _ _=−k = 1∑はいpkログ_ _2pk
その中には、yyyはカテゴリの数です。
-
情報利得は、情報エントロピーに基づいて、取得された情報によってもたらされる変化の量を表し、通常、最適な分割特徴を選択するために使用されます。情報ゲインの計算式は次のとおりです。
Gain ( D , A ) = E nt ( D ) − ∑ v = 1 V ∣ D v ∣ ∣ D ∣ E nt ( D v ) Gain(D, A) = Ent(D) - \sum_{v=1 }^{V}\frac{|D^v|}{|D|}Ent(D^v)ゲイン( D 、_あ)=エント( D ) _ _−v = 1∑V∣ D ∣∣ Dv ∣エント( D _ _v )
その中で、DDD は現在のノードAAのトレーニング データ セットを表します。A は候補特徴のセット、VVV は候補特徴セット内の特徴の数を表します、D v D^vDv は、現在のノードがフィーチャーAAAさんvv分割されたv値のサブセット、Ent ( D ) Ent(D)Ent ( D )は現在のノードのエントロピーを表します。Ent ( D v ) Ent(D^v )エント( D _ _v )現在のノードが機能AAAさんvvv値を分割した後のサブセットのエントロピー
情報ゲインが高くなるほど、その特徴の分類能力への寄与が大きくなります。つまり、その特徴はさまざまなカテゴリのサンプルをよりよく区別できるようになります。
-
ジニ不純度は、データ セットの純度を測定するために使用される指標であり、データ セットから 2 つのサンプルがランダムに選択され、それらのカテゴリが一致しない確率を表します。
G ini ( D ) = ∑ k = 1 ypk ( 1 − pk ) Gini(D) = \sum_{k=1}^y{p_k(1- p_k)}ジニ( D ) _=k = 1∑はいpk( 1−pk)ジニ不純度が低いほど、データ セットの純度は高くなります。これは通常、ノードの分割効果を測定するために使用されます。ノードがノードの分割を表すと、子ノードの純度が高くなります。子ノードに含まれる同じカテゴリのサンプルの割合が大きくなります。
2. デシジョンツリーを構築する
2.1 機能の選択
特徴選択とは、学習データ内の多数の特徴の中から現在のノードの分割基準として特徴を選択することを指し、メリットとデメリットは次のとおりです。
アドバンテージ:
- デシジョン ツリーの複雑さを軽減し、モデルを簡素化し、過適合 (モデルがトレーニング セットでは良好に機能するが、テスト セットでは不十分であることを意味します) のリスクを軽減し、モデルの汎化能力を向上させます。
- デシジョン ツリーのトレーニング時間とストレージ スペースを削減します。
欠点:
- 一部の重要な情報が失われる可能性があり、その結果、モデルの精度が低下します。
- 多少のノイズが混入し、モデルの精度が低下する可能性があります。
- データがより複雑になり、モデルの汎化能力の低下につながる可能性があります。
一般的に使用される特徴選択方法には、情報ゲイン、情報ゲイン比、ジニ指数などが含まれます。
2.2 決定木の生成
決定木生成とは、トレーニング データから決定木を生成するプロセスを指します。上記の特徴選択方法に従って、一般的に使用される決定木生成アルゴリズムには、ID3、C4.5、CART などが含まれます。
決定木は学習データを再帰的に分割することで木構造を生成し、新たなデータの分類を実現します。デシジョン ツリーを生成するプロセスは、次のステップに分割できます。
- 特徴の選択: 現在のノードの分割基準としてトレーニング データの特徴から特徴を選択します。
- ノード分割: 現在のノードのトレーニング データを分割基準に従っていくつかのサブセットに分割します。各サブセットは子ノードに対応します。
- サブツリーを再帰的に生成する: 停止条件が満たされるまで、各子ノードに対してステップ 1 と 2 を再帰的に実行します。
停止条件には通常、次のタイプがあります。
- 現在のノードのトレーニング データはすべて同じカテゴリに属します。
- 現在のノードのトレーニング データは空です。
- 現在のノードのトレーニング データ内のすべての特徴は同じであり、それ以上分割することはできません。
2.3 剪定
決定木の枝刈りは、決定木の複雑さを軽減するために使用される手法であり、その目的は、不要なノードとサブツリーを削除することでモデルの汎化能力を向上させることです。一般的に使用されるデシジョン ツリー プルーニング アルゴリズムには、プレプルーニングとポストプルーニングの 2 つがあります。
-
事前枝刈りとは、決定木を生成するプロセスで各ノードが評価されることを意味します。現在のノードの分割によってモデルの汎化能力が改善できない場合、分割は停止され、現在のノードがリーフ ノードとしてマークされます。事前剪定の利点は、簡単で迅速であることですが、フィッティング不足につながる可能性があります。
-
ポスト枝刈りとは、決定木が生成された後に決定木を枝刈りすることを指し、それによって決定木の複雑さを軽減します。剪定後のプロセスには通常、次の手順が含まれます。
- 各非リーフ ノードを評価し、検証セットでの枝刈り前後のモデルのパフォーマンスの違いを計算します。
- 枝刈りのためにパフォーマンスの差が最も小さいノードを選択し、そのノードとそのサブツリーを削除し、そのノードをリーフ ノードとしてマークします。
- トリミングできなくなるまで手順 1 と 2 を繰り返します。
ポストプルーニングの利点は、アンダーフィッティングを回避できることですが、オーバーフィッティングにつながる可能性があります。
3. 古典的なアルゴリズム
3.1 ID3
ID3 アルゴリズムの中心的な考え方は、情報ゲインによって特徴の選択を測定し、分割のために最大の情報ゲインを持つ特徴を選択することです。
- データセットの情報エントロピーを計算します。
- 特徴ごとに、その情報ゲインを計算します。
- パーティション属性として最大の情報利得を持つ機能を選択します。
- この属性の値に従って、データ セットを複数のサブセットに分割します。
- ステップ 1 ~ 4 は、すべてのサンプルが同じクラスに属するか、それ以上の分割が不可能になるまで、サブセットごとに再帰的に呼び出されます。
欠点:
- ID3 には枝刈り戦略がなく、過剰適合する傾向があります
- 情報利得基準では、可能な値の数が多い特徴が優先され、「数値」に類似した特徴の情報利得は 1 に近くなります。
- 特徴の個別の分散を処理する場合にのみ使用できます
- 欠損値は考慮されません
コードは以下のように表示されます。
%matplotlib inline
import math
from collections import Counter,defaultdict
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
font_set = FontProperties(fname=r"c:\\windows\\fonts\\simsun.ttc", size=15)#导入宋体字体文件
class Id3DecideTree:
def __init__(self, data_set, labels_set):
self.tree = self.create_tree(data_set,labels_set)
def calc_entropy(self, data):
"""计算数据集的信息熵"""
label_counts = Counter(sample[-1] for sample in data)
probs = [count / len(data) for count in label_counts.values()]
return -sum(p * math.log(p, 2) for p in probs)
def split_data(self, data, axis, value):
"""根据特征划分数据集"""
return [sample[:axis] + sample[axis+1:] for sample in data if sample[axis] == value]
def choose_best_feature(self, dataSet):
"""选择最好的数据集划分方式"""
numFeatures = len(dataSet[0]) - 1 # 最后一列用于标签
baseEntropy = self.calc_entropy(dataSet) # 计算数据集的熵
bestFeature = -1
for i in range(numFeatures): # 遍历所有特征
featList = [example[i] for example in dataSet] # 创建该特征的所有样本列表
uniqueVals = set(featList) # 获取唯一值的集合
newEntropy = 0.0
for value in uniqueVals:
subDataSet = self.split_data(dataSet, i, value) # 划分数据集
prob = len(subDataSet)/float(len(dataSet))
newEntropy += prob * self.calc_entropy(subDataSet)
infoGain = baseEntropy - newEntropy # 计算信息增益;即熵的减少量
if (infoGain > bestInfoGain): # 比较目前为止最好的增益
bestInfoGain = infoGain # 如果比当前最好的更好,则设置为最好的
bestFeature = i
return bestFeature
def majority_count(labels):
"""统计出现次数最多的类别"""
label_counts = defaultdict(int)
for label in labels:
label_counts[label] += 1
return max(label_counts, key=label_counts.get)
def create_tree(self, data, labels):
"""创建决策树"""
class_list = [sample[-1] for sample in data]
# 所有样本同一类别
if class_list.count(class_list[0]) == len(class_list):
return class_list[0]
# 只有一个特征
if len(data[0]) == 1:
return majority_count(class_list)
# 选择最优划分特征
best_feature_index = self.choose_best_feature(data)
best_feature_label = labels[best_feature_index]
tree = {
best_feature_label: {
}}
del(labels[best_feature_index])
feature_values = [sample[best_feature_index] for sample in data]
unique_values = set(feature_values)
for value in unique_values:
sub_labels = labels[:]
tree[best_feature_label][value] = self.create_tree(self.split_data(data, best_feature_index, value), sub_labels)
return tree
class DecisionTreePlotter:
def __init__(self, tree):
self.tree = tree
self.decisionNode = dict(boxstyle="sawtooth", fc="0.8")
self.leafNode = dict(boxstyle="round4", fc="0.8")
self.arrow_args = dict(arrowstyle="<-")
self.font_set = font_set
def getNumLeafs(self, node):
firstStr = list(node.keys())[0]
secondDict = node[firstStr]
return sum([self.getNumLeafs(secondDict[key]) if isinstance(secondDict[key], dict) else 1 for key in secondDict.keys()])
def getTreeDepth(self, node):
firstStr = list(node.keys())[0]
secondDict = node[firstStr]
return max([1 + self.getTreeDepth(secondDict[key]) if isinstance(secondDict[key], dict) else 1 for key in secondDict.keys()])
def plotNode(self, nodeTxt, centerPt, parentPt, nodeType):
self.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction',
xytext=centerPt, textcoords='axes fraction',
va="center", ha="center", bbox=nodeType, arrowprops=self.arrow_args, fontproperties=self.font_set )
def plotMidText(self, cntrPt, parentPt, txtString):
xMid = (parentPt[0]-cntrPt[0])/2.0 + cntrPt[0]
yMid = (parentPt[1]-cntrPt[1])/2.0 + cntrPt[1]
self.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30, fontproperties=self.font_set)
def plotTree(self):
self.totalW = float(self.getNumLeafs(self.tree))
self.totalD = float(self.getTreeDepth(self.tree))
self.xOff = -0.5/self.totalW
self.yOff = 1.0
self.fig = plt.figure(1, facecolor='white')
self.fig.clf()
self.axprops = dict(xticks=[], yticks=[])
self.ax1 = plt.subplot(111, frameon=False, **self.axprops)
self.plotTreeHelper(self.tree, (0.5,1.0), '')
plt.show()
def plotTreeHelper(self, node, parentPt, nodeTxt):
numLeafs = self.getNumLeafs(node)
depth = self.getTreeDepth(node)
firstStr = list(node.keys())[0]
cntrPt = (self.xOff + (1.0 + float(numLeafs))/2.0/self.totalW, self.yOff)
self.plotMidText(cntrPt, parentPt, nodeTxt)
self.plotNode(firstStr, cntrPt, parentPt, self.decisionNode)
secondDict = node[firstStr]
self.yOff = self.yOff - 1.0/self.totalD
for key in secondDict.keys():
if isinstance(secondDict[key], dict):
self.plotTreeHelper(secondDict[key],cntrPt,str(key))
else:
self.xOff = self.xOff + 1.0/self.totalW
self.plotNode(secondDict[key], (self.xOff, self.yOff), cntrPt, self.leafNode)
self.plotMidText((self.xOff, self.yOff), cntrPt, str(key))
self.yOff = self.yOff + 1.0/self.totalD
labels_set = ['不浮出水面', '拥有鳍','有头']
data_set = [
['是', '是', '是', '是鱼类'],
['是', '是', '否', '不是鱼类'],
['是', '否', '是', '不是鱼类'],
['否', '是', '否', '不是鱼类'],
['否', '否', '是', '不是鱼类']
]
dt = Id3DecideTree(data_set, labels_set)
print(dt.tree)
plotter = DecisionTreePlotter(dt.tree)
plotter.plotTree()
3.2 C4.5
C4.5 アルゴリズムは、情報ゲイン比を使用して特徴の選択を測定し、分割用に最大の情報ゲイン比を持つ特徴を選択します。
C4.5 アルゴリズムは ID3 アルゴリズムの改良版であり、その具体的なプロセスは次のとおりです。
- データセットの情報エントロピーを計算します。
- 各特徴について、その情報利得率を計算します。
- 情報利得率が最も大きいフィーチャをパーティション属性として選択します。
- この属性の値に従って、データ セットを複数のサブセットに分割します。
- ステップ 1 ~ 4 は、すべてのサンプルが同じクラスに属するか、それ以上の分割が不可能になるまで、サブセットごとに再帰的に呼び出されます。
ID3 アルゴリズムに対する C4.5 アルゴリズムの利点は次のとおりです。
-
情報利得比を使用して最適なパーティション特徴を選択し、ID3 アルゴリズムでより多くの値を持つ特徴の偏った選択の問題を回避します。
-
連続属性と離散属性の両方を処理します
-
属性値が欠落しているトレーニング データを処理する
-
作成後にツリーを剪定する
欠点は次のとおりです。
- C4.5 はマルチフォーク ツリーを使用しており、バイナリ ツリーを使用する方が効率的です。
- C4.5 は分類にのみ使用できます
- C4.5 で使用されるエントロピー モデルには、時間のかかる対数演算、連続値、ソート演算が多く含まれています
- C4.5 ツリーを構築するプロセスでは、数値属性値をサイズに応じて並べ替える必要があり、そこから分割ポイントを選択する必要があるため、メモリ内に常駐できるデータ セットにのみ適しています。トレーニング セットが大きすぎてメモリに収まらない場合、プログラムは実行できません
def calc_info_gain_ratio(data, feature_index):
"""计算信息增益比"""
base_entropy = calc_entropy(data)
feature_values = [sample[feature_index] for sample in data]
unique_values = set(feature_values)
new_entropy = 0.0
split_info = 0.0
for value in unique_values:
sub_data = [sample for sample in data if sample[feature_index] == value]
prob = len(sub_data) / float(len(data))
new_entropy += prob * calc_entropy(sub_data)
split_info -= prob * math.log(prob, 2)
info_gain = base_entropy - new_entropy
if split_info == 0:
return 0
return info_gain / split_info
def choose_best_feature(data):
"""选择最好的数据集划分方式"""
num_features = len(data[0]) - 1
base_entropy = calc_entropy(data)
best_info_gain_ratio = 0.0
best_feature_index = -1
for i in range(num_features):
info_gain_ratio = calc_info_gain_ratio(data, i)
if info_gain_ratio > best_info_gain_ratio:
best_info_gain_ratio = info_gain_ratio
best_feature_index = i
return best_feature_index
3.3 カート
CART は、特徴選択を測定するためにジニ不純物を選択し、分割のために最小のジニ不純物を持つ特徴を選択します。これは、生成された各非リーフ ノードが 2 つの分岐を持つように、現在のサンプルを 2 つのサブサンプルに分割するバイナリ再帰セグメンテーション手法です。したがって、CART アルゴリズムによって生成される決定木は、簡潔な構造を持つ二分木になります。
CART アルゴリズムは二分決定木であり、その具体的なプロセスは次のとおりです。
- 特徴としきい値を選択して、データセットを 2 つのサブセットに分割します。
- ステップ 1 は、すべてのサンプルが同じクラスに属するか、それ以上の分割が不可能になるまで、サブセットごとに再帰的に呼び出されます。
ID3 アルゴリズムおよび C4.5 アルゴリズムに対する CART アルゴリズムの改良点は、Gini インデックスを使用して最適なパーティション機能を選択することです。
import numpy as np
class CARTDecisionTree:
def __init__(self):
self.tree = {
}
def calc_gini(self, data):
"""计算基尼指数"""
label_counts = {
}
for sample in data:
label = sample[-1]
if label not in label_counts:
label_counts[label] = 0
label_counts[label] += 1
gini = 1.0
for count in label_counts.values():
prob = float(count) / len(data)
gini -= prob ** 2
return gini
def split_data(self, data, feature_index, value):
"""根据特征划分数据集"""
new_data = []
for sample in data:
if sample[feature_index] == value:
new_sample = sample[:feature_index]
new_sample.extend(sample[feature_index+1:])
new_data.append(new_sample)
return new_data
def choose_best_feature(self, data):
"""选择最佳划分特征"""
num_features = len(data[0]) - 1
best_gini_index = np.inf
best_feature_index = -1
best_split_value = None
for i in range(num_features):
feature_values = [sample[i] for sample in data]
unique_values = set(feature_values)
for value in unique_values:
sub_data = self.split_data(data, i, value)
prob = len(sub_data) / float(len(data))
gini_index = prob * self.calc_gini(sub_data)
gini_index += (1 - prob) * self.calc_gini([sample for sample in data if sample[i] != value])
if gini_index < best_gini_index:
best_gini_index = gini_index
best_feature_index = i
best_split_value = value
return best_feature_index, best_split_value
def majority_count(self, labels):
"""统计出现次数最多的类别"""
label_counts = {
}
for label in labels:
if label not in label_counts:
label_counts[label] = 0
label_counts[label] += 1
sorted_label_counts = sorted(label_counts.items(), key=lambda x: x[1], reverse=True)
return sorted_label_counts[0][0]
def create_tree(self, data, labels):
"""创建决策树"""
class_list = [sample[-1] for sample in data]
if class_list.count(class_list[0]) == len(class_list):
return class_list[0]
if len(data[0]) == 1:
return self.majority_count(class_list)
best_feature_index, best_split_value = self.choose_best_feature(data)
best_feature_label = labels[best_feature_index]
tree = {
best_feature_label: {
}}
del(labels[best_feature_index])
feature_values = [sample[best_feature_index] for sample in data]
unique_values = set(feature_values)
for value in unique_values:
sub_labels = labels[:]
tree[best_feature_label][value] = self.create_tree(self.split_data(data, best_feature_index, value), sub_labels)
return tree
def fit(self, X_train, y_train):
"""训练模型"""
data_set = np.hstack((X_train, y_train.reshape(-1, 1)))
labels_set=['feature_{}'.format(i) for i in range(X_train.shape[1])]
labels_set.append('label')
self.tree=self.create_tree(data_set.tolist(),labels_set)
def predict(self,X_test):
"""预测"""
y_pred=[]
for x_test in X_test:
node=self.tree.copy()
while isinstance(node,dict):
feature=list(node.keys())[0]
node=node[feature]
feature_idx=int(feature.split('_')[-1])
if x_test[feature_idx]==list(node.keys())[0]:
node=node[node.keys()[0]]
else:
node=node[node.keys()[1]]
y_pred.append(node)
return np.array(y_pred)
4. ケース
4.1 アヤメデータセット アヤメ分類
アイリスのデータセット。このデータセットには 150 個のサンプルが含まれており、それぞれに 4 つの特徴 (がく片の長さ、がく片の幅、花弁の長さ、および花びらの幅) があり、各サンプルは 3 つのクラス (Iris albicans、Iris versicolor、または Virginie sub-iris) のいずれかに属します。
sklearnライブラリ実装を直接呼び出す
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier, export_graphviz
from sklearn.model_selection import train_test_split
import graphviz
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.3, random_state=42)
clf = DecisionTreeClassifier(criterion='entropy')
clf.fit(X_train, y_train)
class_names = ['山鸢尾', '变色鸢尾', '维吉尼亚鸢尾']
feature_names = ['萼片长度', '萼片宽度', '花瓣长度', '花瓣宽度']
dot_data = export_graphviz(clf, out_file=None, feature_names=feature_names, class_names=class_names, filled=True, rounded=True, special_characters=True)
graph = graphviz.Source(dot_data)
graph.render('iris_decision_tree')
graph
- エントロピーはノードの情報エントロピーを表します
- サンプルは、ノードにサンプル数があることを示します。
- 値はノード内の各カテゴリのサンプル数を表します
- class はノードがどのカテゴリに分類されるかを示します
上記の決定木は少し複雑で、パラメーター制御と枝刈りを使用して最適化されています。
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier, export_graphviz
from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score
import graphviz
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.3, random_state=42)
# 定义参数范围
param_grid = {
'max_depth': range(1, 10), 'min_samples_leaf': range(1, 10)}
# 使用网格搜索找到最佳参数
grid_search = GridSearchCV(DecisionTreeClassifier(criterion='entropy'), param_grid, cv=5)
grid_search.fit(X_train, y_train)
# 使用最佳参数训练模型
clf = DecisionTreeClassifier(criterion='entropy', **grid_search.best_params_)
clf.fit(X_train, y_train)
# 交叉验证评估每个子树的性能
cv_scores = []
for i in range(1, clf.tree_.max_depth + 1):
clf_pruned = DecisionTreeClassifier(criterion='entropy', max_depth=i)
scores = cross_val_score(clf_pruned, X_train, y_train, cv=5)
cv_scores.append((i, scores.mean()))
# 选择最佳子树进行剪枝
best_depth = max(cv_scores, key=lambda x: x[1])[0]
clf_pruned = DecisionTreeClassifier(criterion='entropy', max_depth=best_depth)
clf_pruned.fit(X_train, y_train)
class_names = ['山鸢尾', '变色鸢尾', '维吉尼亚鸢尾']
feature_names = ['萼片长度', '萼片宽度', '花瓣长度', '花瓣宽度']
dot_data = export_graphviz(clf_pruned, out_file=None, feature_names=feature_names, class_names=class_names, filled=True, rounded=True, special_characters=True)
graph = graphviz.Source(dot_data)
graph.render('iris_decision_tree_pruned')
graph
4.2 デシジョンツリーに基づくリーグ・オブ・レジェンドの試合結果の予測
データセットソース:
- https://aistudio.baidu.com/aistudio/datasetdetail/168986
- https://www.kaggle.com/datasets/bobbyscience/league-of-legends-diamond-ranked-games-10-min
機能名 | 意味 |
---|---|
ゲームID | ゲームID |
ブルーウィンズ | 青側が勝つかどうか |
ブルーワード配置 | 量を見てください |
ブルーワーズ破壊 | 折れた目の数 |
ブルーファーストブラッド | 最初の血液を受け取りますか |
ブルーキルズ | キル |
ブルーデス | 死亡者数 |
ブルーアシスト | アシスト |
ブルーエリートモンスター | ドラゴンとヴァンガードのナンバー |
ブルードラゴンズ | ドラゴンナンバー |
ブルーヘラルズ | キャニオンのパイオニア番号 |
青タワー破壊された | プッシュタワーの数 |
ブルートータルゴールド | トータルエコノミー |
青平均レベル | 平均評価 |
ブルートータルエクスペリエンス | 総経験値 |
ブルー合計ミニオン殺害数 | 総補充 |
青合計ジャングル手下が殺されました | モンスターを倒す |
ブルーゴールドディフ | 経済格差 |
ブルー経験の差 | 経験の差 |
ブルーCSPerMin | 1分あたりの平均補充数 |
ブルーゴールド毎分 | 1分あたりの経済性 |
コードリファレンス: https://www.kaggle.com/code/xiyuewang/lol-how-to-win# Introduction
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn import tree
from sklearn.model_selection import GridSearchCV
import graphviz
# %matplotlib inline
# sns.set_style('darkgrid')
df = pd.read_csv('high_diamond_ranked_10min.csv')
df_clean = df.copy()
# 删除冗余的列
cols = ['gameId', 'redFirstBlood', 'redKills', 'redEliteMonsters', 'redDragons','redTotalMinionsKilled',
'redTotalJungleMinionsKilled', 'redGoldDiff', 'redExperienceDiff', 'redCSPerMin', 'redGoldPerMin', 'redHeralds',
'blueGoldDiff', 'blueExperienceDiff', 'blueCSPerMin', 'blueGoldPerMin', 'blueTotalMinionsKilled']
df_clean = df_clean.drop(cols, axis = 1)
# g = sns.PairGrid(data=df_clean, vars=['blueKills', 'blueAssists', 'blueWardsPlaced', 'blueTotalGold'], hue='blueWins', size=3, palette='Set1')
# g.map_diag(plt.hist)
# g.map_offdiag(plt.scatter)
# g.add_legend();
# plt.figure(figsize=(16, 12))
# sns.heatmap(df_clean.drop('blueWins', axis=1).corr(), cmap='YlGnBu', annot=True, fmt='.2f', vmin=0);
# 进一步抉择
cols = ['blueAvgLevel', 'redWardsPlaced', 'redWardsDestroyed', 'redDeaths', 'redAssists', 'redTowersDestroyed',
'redTotalExperience', 'redTotalGold', 'redAvgLevel']
df_clean = df_clean.drop(cols, axis=1)
print(df_clean)
# 计算与第一列的相关性,原理为计算皮尔逊相关系数,取值范围为[-1,1],可以用来衡量两个变量之间的线性相关程度。
corr_list = df_clean[df_clean.columns[1:]].apply(lambda x: x.corr(df_clean['blueWins']))
cols = []
for col in corr_list.index:
if (corr_list[col]>0.2 or corr_list[col]<-0.2):
cols.append(col)
df_clean = df_clean[cols]
# df_clean.hist(alpha = 0.7, figsize=(12,10), bins=5);
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier, export_graphviz
X = df_clean
y = df['blueWins']
# scaler = MinMaxScaler()
# scaler.fit(X)
# X = scaler.transform(X)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
tree = tree.DecisionTreeClassifier(max_depth=3)
# search the best params
grid = {
'min_samples_split': [5, 10, 20, 50, 100]},
clf_tree = GridSearchCV(tree, grid, cv=5)
clf_tree.fit(X_train, y_train)
pred_tree = clf_tree.predict(X_test)
# get the accuracy score
acc_tree = accuracy_score(pred_tree, y_test)
print(acc_tree)
# 0,1
class_names = ['红色方胜', '蓝色方胜']
feature_names = cols
dot_data = export_graphviz(clf_tree.best_estimator_, out_file=None, feature_names=feature_names, class_names=class_names, filled=True, rounded=True, special_characters=True)
graph = graphviz.Source(dot_data)
graph.render('lol_decision_tree')
graph