「機械学習の式の導出とコードの実装」 - 第 7 章 デシジョン ツリー

「機械学習の公式導出とコード実装」の学習ノート、自分の学習プロセスを記録します。詳細な内容については、著者の本を購入してください。

デシジョンツリー

デシジョン ツリー(意思決定木) は、データ インスタンスを特徴に基づく条件に従って継続的に分割し、最終的に分類または回帰の目的を達成します。

この章の著者は主に、分類モデルにデシジョン ツリーを使用する方法を紹介します。
決定木モデル予測のプロセスは、一連の if-then 条件だけでなく、特徴空間およびクラス空間で定義された条件付き確率分布とみなすこともできます。
デシジョン ツリー モデルの中核となる概念には特征选择方法决策树构造过程、 、が含まれます决策树剪枝一般的な特徴選択方法には信息增益、 、信息增益比、 が含まれ基尼指数(Gini index)、対応する 3 つの一般的なデシジョン ツリー アルゴリズムはID3、 、C4.5および ですCART

決定木モデルは 2 つの観点から理解できます。1 つ目は、決定木を if-then ルールの集合とみなして、決定木のルート ノードからリーフ ノードまでのパスごとにルールを構築するというものです。パス の内部ノード機能はルールの条件を表し、リーフ ノードはこのルールの結論を表します。デシジョン ツリーのすべての if-then ルールは相互に排他的であり、完全です。if-then ルールは本質的に分類ルールのセットであり、決定木の学習の目標は、データに基づいてそのようなルールのセットを誘導することです。

2 つ目は、条件付き確率分布の観点から決定木を理解することです。特徴空間が互いに素な領域に分割され、各領域で定義されるクラスの確率分布が条件付き確率分布を構成すると仮定します。決定木で表現される条件付き確率分布は、各領域内の指定されたクラスの条件付き確率分布から構成されます。X を特性の確率変数、Y をクラスの確率変数とすると、葉ノード上の条件付き確率分布があるクラスに偏っている場合、対応する条件付き確率分布は P(Y|X) で表されます。このクラスに属する確率は比較的大きいです。

私たちの学習目標は、できる限り正しく分類できる決定木を見つけることですが、一般化を確実にするためには、この決定木が正確になりすぎず、パラメーターを正規化しながら経験的誤差を最小限に抑える必要があります。

決定木学習の目標は、この損失関数を最小化することです。
L a ( T ) = ∑ t = 1 ∣ T ∣ N t H t ( T ) + α ∣ T ∣ L_{a}\left ( T \right ) = \ sum_{t=1}^{\left | T \right | } N_{t}H_{t}\left ( T \right ) +\alpha\left | T \right |L( T )=t = 1 TNH( T )+あるT
H(t)は、リーフ ノード 上のempirical entropy)a≥0であり、これは正則化パラメータです。 t はツリー T のリーフ ノードであり、各リーフ ノードには Nt 個のサンプルがあります。

1 機能の選択

優れた分類パフォーマンスを備えたデシジョン ツリーを構築するには、トレーニング セットから分類能力のある特徴を継続的に選択する必要があります。特徴を使用してデータ セットを分類する効果が、ランダムに選択した分類の効果と変わらない場合、その特徴のデータ セットを分類する能力は低いと考えることができます。逆に、特徴が分類された分岐を作成できる場合は、可能な限りノードが同じカテゴリに属している、つまりノードの純度が高い( purity) 場合、特徴はデータ セットに対して強力な分類能力を持っています。
デシジョン ツリーには、信息增益、 、信息增益比および を含む、最適な特徴を選択する 3 つの方法があります基尼指数

1.1 情報エントロピー ( information entropy)

情報理論と確率統計では、エントロピーは確率変数の不確実性を説明する尺度です。また、サンプル セットの純度を説明するためにも使用できます。情報エントロピーが低いほど、サンプルの不確実性は小さくなり、対応するサンプルの不確実性は高くなります。純度。
現在のサンプル データ セット D の k 番目のクラスの割合が Pk (k=1,2…Y) であると仮定すると、サンプル データ セットのエントロピーは次のように定義できます。 E ( D ) = − ∑ k =
1 Y pk log ⁡ pk E\left ( D \right )= -\sum_{k=1}^{Y}p_{k}\log_{}{p_{k}}E( D )=k = 1pログ_p

## 信息熵计算定义
from math import log
import pandas as pd

# 信息熵计算函数
def entropy(ele): # ele 包含类别取值的列表

    probs = [ele.count(i) / len(ele) for i in set(ele)] # 计算列表中取值的概率分布
    return -sum([prob * log(prob, 2) for prob in probs]) # 计算信息熵

# 试运行
df = pd.read_csv('./golf_data.csv')
entropy(df['play'].to_list())
0.9402859586706309

1.2 情報の獲得

離散確率変数(X,Y)の同時確率分布が次のようになると仮定
します。 P(X=xi, Y=yi) = pij(i=1,2,...m,j=1,2,...n) 条件付き
エントロピー不確実性の尺度は、E(Y|X)既知の確率変数で表されます与えられた条件下での X の条件付き確率分布のエントロピーの数学的期待値として定義できます。条件付きエントロピーは次のように表すことができます: E ( Y ∣ X ) = ∑ i = 1 mpi E ( Y ∣ X = xi ) E\left ( Y \mid X \right )=\sum_{i=1}^{m} p_ {i}E\left ( Y\mid X=x_{i} \right )XYE(Y|X)XY
E( Y× )=i = 1メートルp私はE( Yバツ=バツ私は)
実際のデータを計算に使用する場合、エントロピーおよび条件付きエントロピーの確率計算は最尤推定に基づいており、対応するエントロピーおよび条件付きエントロピーは経験的エントロピーおよび経験的条件付きエントロピーとも呼ばれます。

情報利得( information gain) は、特徴 X の情報によってクラス Y の情報の不確実性が低減される度合いとして定義され、つまり、情報利得は、ターゲット カテゴリの確実性の増加を表す量です。特徴量が増加するほど、ターゲット カテゴリが大きくなり、確実性も高くなります。トレーニングセット D の経験的エントロピーが E(D) であり、与えられた特徴 A の条件下での D の経験的条件付きエントロピーが E(D|A) であると仮定すると、情報ゲインは経験的エントロピー E として定義できます。 (D) と経験的な条件付きエントロピー E (D|A) の差:
g ( D , A ) = E ( D ) − E ( D ∣ A ) g\left ( D,A \right ) =E\left ( D \right )-E\left ( D\mid A \right )g( D =E( D )E( DA )
情報ゲインは、決定木アルゴリズムを構築する際の特徴選択に使用できます。トレーニング セットDと特徴がA、経験的エントロピーはデータ セットの分類の不確実性E(D)として表現でき後のデータ セットの分類、指定された特徴 データセット情報獲得に基づいた特徴の選択。DE(D|A)ADDID3

# 划分数据集
def df_split(df, col): # 数据,特征
    unique_col_val = df[col].unique() # 获取依据特征的不同取值
    res_dict = {
    
    elem: pd.DataFrame for elem in unique_col_val}
    for key in res_dict.keys():
        res_dict[key] = df[:][df[col] == key]
    return res_dict
res_dict = df_split(df, 'outlook')
res_dict

ここに画像の説明を挿入

# 信息增益计算
def info_gain(df, col):
    res_dict = df_split(df, col)
    entropy_D = entropy(df['play'].to_list()) # 计算数据集的经验熵
    entropy_DA = 0 # 天气特征的经验条件熵
    for key in res_dict.keys():
        entropy_DA += len(res_dict[key]) / len(df) * entropy(res_dict[key]['play'].tolist()) # p * 经验条件熵
    return entropy_D - entropy_DA # 天气特征的信息增熵

# 增益越大,代表对应的特征分类能力越强
print(f"humility:{
      
      info_gain(df, 'humility')}")
print(f"outlook:{
      
      info_gain(df, 'outlook')}")
print(f"windy:{
      
      info_gain(df, 'windy')}")
print(f"temp:{
      
      info_gain(df, 'temp')}")
humility:0.15183550136234136
outlook:0.2467498197744391
windy:0.04812703040826927
temp:0.029222565658954647

1.3 情報利得比

情報ゲインは非常に優れた特徴選択方法ですが、いくつかの問題もあります。特定の特徴カテゴリに多くの値がある場合、データセットに「数値」特徴を追加するなど、この特徴の情報ゲイン計算結果が大きくなります。 、最初のレコードから最後のレコードまで、合計 14 の異なる値。この機能は 14 のデシジョン ツリー ブランチを生成します。各ブランチには 1 つのサンプルのみが含まれ、各ノードの情報純度は比較的高く、最終的に結果の情報を計算します。ゲインも他の機能よりもはるかに大きくなります。しかし、実際の状況からすると、「数字」などの特徴量は分類が難しく、このようにして構築された決定木は無効であることがわかっているため、情報利得に基づいて特徴量を選択すると、特徴量が多い特徴量に偏ってしまうことになります。より大きな値。

print(f"humility:{
      
      info_gain(df, 'humility')}")
# 为数据集加一个“编号”特征
df['counter'] = range(len(df))
print(f"counter:{
      
      info_gain(df, 'counter')}")
df

ここに画像の説明を挿入
上記の問題は、情報利得比を使用して修正されます。Aデータ セットに対する特徴の情報ゲイン比は、特徴の値に関するデータ セットのエントロピーに対するD情報ゲイン比率として定義できます。 n は A g R (D, A ) = g ( D , A ) EA ( D ) g_{R}\left ( D, A \right )=\frac{g\left ( D, A \right ) }{E_{A}\left ( D \右 )}g(D,A)DAEA(D)
gR( D =E( D )g( D
EA ( D ) = − ∑ i = 1 n ∣ D i ∣ D log ⁡ 2 ∣ D i ∣ D E_{A} \left ( D \right )=-\sum_{i=1}^{n}\frac {\左 | D_{i} \右 | }{D}\log_{2}\frac{\左 | D_{i} \右 |}{D}E( D )=i = 1DD私はログ_2DD私は

# 信息增益比计算
def information_gain_ratio(df, col):
    g = info_gain(df, col)
    entropy_EAD = entropy(df[col].to_list())
    return g / entropy_EAD

# 试运行
print(f"outlook:{
      
      information_gain_ratio(df, 'outlook')}")
print(f"counter:{
      
      information_gain_ratio(df, 'counter')}")
outlook:0.15642756242117517
counter:0.2469656698468429

情報利得と情報利得比率に加えて、ジニ指数( Gini index) も優れた特徴選択方法です。ジニ指数は確率分布用です。サンプルにKクラスがあり、kサンプルが最初のクラスに属する確率が pk であると仮定すると、サンプル クラスの確率分布のジニ指数は次のように定義できます。 G ini ( p )
= ∑ k = 1 K pk ( 1 − pk ) = 1 − ∑ k = 1 K pk 2 Gini\left ( p \right )=\sum_{k=1}^{K}p_{k}\left ( 1-p_{k} \right )=1-\sum_{k=1 }^{K}p_{k}^{2}ジニ_( p )=k = 1Kp( 1p)=1k = 1Kpk2
最初のカテゴリに属する​​サンプルのセットである特定のトレーニング セットについてD、トレーニング セットのジニ指数は次のように定義できます。 G ini ( D ) = 1 − ∑ k = 1 K ( ∣ C k ∣ ∣ D ∣ ) 2 ジニ\ left ( D \right )=1-\sum_{k=1}^{K}\left ( \frac{\left | C_{k} \right | }{\left | D \right | } \右 ) ^ {2}Ckk
ジニ_( D )=1k = 1K(D ∣C _)2
トレーニング セットがD特徴のA特定の値2 つのaに分割される、特徴の条件下で、トレーニング セットのジニ インデックスは次のように定義できます。G ini ( D , A ) = D 1 DG ini ( D 1 ) + D 2 DG ini ( D 2 ) Gini\left ( D,A \right )=\frac{D_{1}}{D}Gini\left ( D_{1} \right )+\frac {D_{2}} {D}ジニ\左 (D_{2} \右)D1D2AD
ジニ_( D =DD1ジニ_( D1)+DD2ジニ_( D2)
は情報エントロピーの定義に似ており、トレーニング セットのジニ指数はDセットの不確実性を表し、後のGini(D,A)トレーニング セットの不確実性分類タスクの場合、トレーニング セットの不確実性が小さいほど、トレーニング サンプルの対応する特徴の分類能力が向上し、より強力になることが期待されます。このアルゴリズムは、特徴選択のジニ指数に基づいています。DA=aCART

# 计算基尼指数
import numpy as np
def calculate_gini(y): # y 包含类别取值的列表
    # 将数组转化为列表
    y = y.tolist()
    probs = [y.count(i)/len(y) for i in np.unique(y)]
    gini = sum([p*(1-p) for p in probs])
    return gini

# 划分数据集并计算基尼指数
def gini_da(df, col, key): # g根据天气特征取值为晴与非晴划分为两个子集
    col_val = [key, 'other']
    new_dict = {
    
    elem: pd.DataFrame for elem in col_val} # 创建划分结果的数据框字典
    new_dict[key] = df[:][df[col] == key]
    new_dict['other'] = df[:][df[col] != key]
    gini_DA = 0
    for key in new_dict.keys():
        gini_DA += len(new_dict[key]) / len(df) * calculate_gini(new_dict[key]['play'])
    return gini_DA

# 计算天气特征条件下数据集的基尼指数
print(f"sunny:{
      
      gini_da(df, 'outlook','sunny')}")
print(f"rainy:{
      
      gini_da(df, 'outlook','rainy')}")
print(f"overcast:{
      
      gini_da(df, 'outlook','overcast')}")
sunny:0.3936507936507937
rainy:0.4571428571428572
overcast:0.35714285714285715

2 デシジョンツリーモデル

情報ゲイン、情報ゲイン比、およびジニ係数の 3 つの特徴選択方法に基づいて、 3 つの古典的な決定木アルゴリズムID3およびC4.5それぞれ存在します。CARTこれら 3 つのアルゴリズムは、分類決定木の構築においては基本的に同じであり、すべて特徴選択法を使用して、構築に最適な特徴を再帰的に選択します。このうちID3C4.5中和アルゴリズムは決定木のみを生成し、決定木の枝刈り部分を含まないため、これら 2 つのアルゴリズムは過学習になりやすい場合があります。CARTこのアルゴリズムは分類に使用されるだけでなく、回帰にも使用でき、アルゴリズムには決定木の枝刈りが含まれます。

2.1 ID3

ID3アルゴリズムの正式名はIterative Dichotomiser 3(3 世代反復バイナリ ツリー) で、その核心は、情報利得に基づいて決定木を構築するために最適な特徴を再帰的に選択することです。

具体的な方法は次のとおりです。最初に決定木のルート ノードをプリセットし、次にすべての特徴の情報ゲインを計算し、最大の情報ゲインを持つ特徴を最適な特徴として選択し、さまざまな値に応じてサブノードを確立します。 feature を作成し、各サブノードの情報ゲインを計算します。ノードは、情報ゲインが小さくなるか、選択する機能がなくなるまで、上記のメソッドを再帰的に呼び出します。その後、最終的な ID3 決定木を構築できます。

# ID3算法的核心步骤-选择最优特征

def choose_best_feature(df, label):
    '''
    思想:根据训练集和标签选择信息增益最大的特征作为最优特征
    输入:
    df:待划分的训练数据
    label:训练标签
    输出:
    max_value:最大信息增益值
    best_feature:最优特征
    max_splited:根据最优特征划分后的数据字典
    '''

    entropy_D = entropy(df[label].tolist()) # 计算训练标签的信息熵
    cols = [col for col in df.columns if col not in [label]] # 特征集
    max_value, best_feature, max_splited = -999, None, None # 初始化最大信息增益、最优特征和划分后的数据集
    for col in cols: # 遍历特征并根据特征取值进行划分
        splited_set = df_split(df, col)
        entropy_DA = 0 # 初始化经验条件熵
        for subset_col, subset in splited_set.items():
            entropy_DA += len(subset) / len(df) * entropy(subset[label].tolist()) # 计算当前特征的经验条件熵
        info_gain = entropy_D - entropy_DA # 计算当前特征的特征增益
        if info_gain > max_value: # 获取最大信息增熵,并保存对应的特征和划分结果 
            max_value, best_feature = info_gain, col
            max_splited = splited_set
    return max_value, best_feature, max_splited

# 试运行
df = df.drop(labels='counter', axis=1)
choose_best_feature(df, 'play')

ここに画像の説明を挿入

# 封装构建ID3决策树的算法类

class ID3Tree: # ID3算法类

    class TreeNode: # 定义树结点
        
        def __init__(self, name): # 定义
            self.name = name
            self.connections = dict()
            
        def connect(self, label, node):
            self.connections[label] = node

    def __init__(self, df, label):
        self.columns = df.columns
        self.df = df
        self.label = label
        self.root = self.TreeNode('Root')
    
    def construct_tree(self): # 构建树的调用
        self.construct(self.root, '', self.df, self.columns)
    
    def construct(self, parent_node, parent_label, sub_df, columns): # 决策树构建方法
        max_value, best_feature, max_splited = choose_best_feature(sub_df[columns], self.label) # 选择最优特征
        if not best_feature: # 如果选不到最优特征,则构造单结点树
            node = self.TreeNode(sub_df[self.label].iloc[0])
            parent_node.connect(parent_label, node)
            return
        
        # 根据最优特征以及子结点构建树
        node = self.TreeNode(best_feature)
        parent_node.connect(parent_label, node)

        new_columns = [col for col in columns if col != best_feature] # 以A-Ag为新的特征集

        # 递归的构造决策树
        for splited_value, splited_data in max_splited.items():
            self.construct(node, splited_value, splited_data, new_columns)
    
    # 打印树
    def print_tree(self, node, tabs):
        print(tabs + node.name)
        for connection, child_node in node.connections.items():
            print(tabs + "\t" + "(" + str(connection) + ")")
            self.print_tree(child_node, tabs + "\t\t")
# 构建id3决策树
id3_tree = ID3Tree(df, 'play')
id3_tree.construct_tree()
id3_tree.print_tree(id3_tree.root, '')

ここに画像の説明を挿入

2.2 カート

CARTアルゴリズムの正式名称はclassification and regression tree(Classification and Regression Tree)で、CARTは与えられた確率変数Xの条件下で確率変数Yの条件付き確率分布を出力する学習アルゴリズムとして理解できます。CARTによって生成される決定木はすべて二分決定木であり、内部ノードの値は「yes」と「no」です。このノード分割方法は、各特徴を再帰的に二等分し、特徴空間を有限数の単位に分割することに相当します。そして、これらのユニットの予測確率分布、つまり前述の予測条件付き確率分布を決定します。
CARTアルゴリズムを使用して回帰ツリーを構築することもできます。回帰木は特徴空間の分割と分割単位の出力値に対応します。特徴空間にM分割単位R1, R2,… がありRM、各分割単位に出力重み があると仮定するcmと、回帰木モデルは次のように表すことができます。
f ( x ) = ∑ m = 1 M cm I ( x ∈ R m ) f\ left ( x \right )=\sum_{m=1}^{M}c_{m}I\left ( x\in R_{m} \right )f( × )=m = 1Mcメートル( ×Rメートル)
線形回帰と同様に、回帰ツリー モデルのトレーニングの目的も、最適な出力重みを取得するために平均二乗損失を最小限に抑えることですcm_hat具体的には、二乗誤差を最小化する方法を使用して、各ユニットの最適な重みを解決します。最適な出力重みは、各ユニットのすべての入力インスタンスに対応する出力値の平均値によって決定できます: cm ^ = 平均 ( yi ∣
xi ∈ R m ) \hat{c_{m}}=平均\left ( y_{i} \mid x_{i}\in R_{m}\right )cメートル^=平均_ _ _ _ _( y私はバツ私はRメートル)
回帰木が最初のj特徴x(j)とそれに対応する値をs分割特徴および分割点として選択し、同時に 2 つの領域を定義すると仮定します。
R 1 ( j , s ) = { x ∣ x ( j ) ≤ s } ; R 2 ( j , s ) = { x ∣ x ( j ) > s } R_{1}\left ( j,s \right )=\left \{ x\mid x^{(j)}\le s \ right \}; R_{2}\left ( j,s \right )=\left \{ x\mid x^{(j)}> s \right \}R1( j ,s )={ ×バツ( j )s };R2( j ,s )={ ×バツ( j )>s } を
計算し、それを解いて入力特徴jと最適な分割点s
minjs [ minc 1 ∑ xi ∈ R 1 ( j , s ) ( yi − c 1 ) 2 + minc 2 ∑ xi ∈ R 2 ( j , s ) ( yi − c 2 ) 2 ] min_{js}\left [ min_ {c_{1}\sum_{x_{i}\in R_{1}(j,s)}^{}}(y_{i}-c_{1})^{2}+min_{c_{2} \sum_{x_{i}\in R_{2}(j,s)}^{}}(y_{i}-c_{2})^{2}\right ]_js _[_c1バツ私はR1( j , s )( y私はc1)2+_c2バツ私はR2( j , s )( y私はc2)2 ]

# 定义树结点
class TreeNode:
    def __init__(self, feature_i=None, threshold=None, leaf_value=None, left_branch=None, right_branch=None):
        
        self.feature_i = feature_i # 特征索引
        self.threshold = threshold # 特征划分阈值
        self.leaf_value = leaf_value # 叶子节点取值
        self.left_branch = left_branch # 左子树
        self.right_branch = right_branch # 右子树
# 定义二叉特征分裂函数
def feature_split(X, feature_i, threshold):
    split_func = None
    if isinstance(threshold, int) or isinstance(threshold, float):
        split_func = lambda sample: sample[feature_i] >= threshold
    else:
        split_func = lambda sample: sample[feature_i] == threshold

    X_left = np.array([sample for sample in X if split_func(sample)])
    X_right = np.array([sample for sample in X if not split_func(sample)])

    return np.array([X_left, X_right])
# 定义二叉决策树
class BinaryDecisionTree:
    def __init__(self, min_samples_split=3, min_gini_impurity=999, max_depth=float('inf'), loss=None): # 决策树初始参数
        
        self.root = None  # 根结点
        self.min_samples_split = min_samples_split # 节点最小分裂样本数
        self.mini_gini_impurity = min_gini_impurity # 节点初始化基尼不纯度
        self.max_depth = max_depth # 树最大深度
        self.impurity_calculation = None # 基尼不纯度计算函数
        self._leaf_value_calculation = None # 叶子节点值预测函数
        self.loss = loss # 损失函数
    
    def fit(self, X, y, loss=None): # 决策树拟合函数
        self.root = self._build_tree(X, y) # 递归构建决策树
        self.loss=None
    
    def _build_tree(self, X, y, current_depth=0): # 决策树构建函数
        init_gini_impurity = 999 # 初始化最小基尼不纯度
        best_criteria = None # 初始化最佳特征索引和阈值
        best_sets = None # 初始化数据子集

        Xy = np.concatenate((X, y), axis=1) # 合并输入和标签
        n_samples, n_features = X.shape # 获取样本数和特征数
        
        # 设定决策树构建条件
        if n_samples >= self.min_samples_split and current_depth <= self.max_depth: # 训练样本数量大于节点最小分裂样本数且当前树深度小于最大深度
            
            for feature_i in range(n_features):
                unique_values = np.unique(X[:, feature_i]) # 获取第i个特征的唯一取值
                
                for threshold in unique_values: # 遍历取值并寻找最佳特征分裂阈值
                    Xy1, Xy2 = feature_split(Xy, feature_i, threshold) # 特征节点二叉分裂

                    if len(Xy1) > 0 and len(Xy2) > 0: # 如果分裂后的子集大小都不为0
                        y1, y2 = Xy1[:, n_features:], Xy2[:, n_features:] # 获取两个子集的标签值
                        impurity = self.impurity_calculation(y, y1, y2) # 计算基尼不纯度

                        if impurity < init_gini_impurity:
                            init_gini_impurity = impurity # 获取最小基尼不纯度
                            best_criteria = {
    
    "feature_i": feature_i, "threshold": threshold} # 最佳特征索引和分裂阈值
                            best_sets = {
    
    
                                "leftX": Xy1[:, :n_features],   
                                "lefty": Xy1[:, n_features:],   
                                "rightX": Xy2[:, :n_features],  
                                "righty": Xy2[:, n_features:]   
                                }

        if init_gini_impurity < self.mini_gini_impurity: # 如果计算的最小不纯度小于设定的最小不纯度
            
            # 分别构建左右子树
            left_branch = self._build_tree(best_sets["leftX"], best_sets["lefty"], current_depth + 1)
            right_branch = self._build_tree(best_sets["rightX"], best_sets["righty"], current_depth + 1)
            return TreeNode(feature_i=best_criteria["feature_i"], threshold=best_criteria["threshold"], left_branch=left_branch, right_branch=right_branch) 

        # 计算叶子计算取值
        leaf_value = self._leaf_value_calculation(y)
        return TreeNode(leaf_value=leaf_value)

    def predict_value(self, x, tree=None): # 定义二叉树值预测函数
        if tree is None:
            tree = self.root

        if tree.leaf_value is not None: # 如果叶子节点已有值,则直接返回已有值
            return tree.leaf_value
        
        feature_value = x[tree.feature_i] # 选择特征并获取特征值

        # 判断落入左子树还是右子树
        branch = tree.right_branch
        if isinstance(feature_value, int) or isinstance(feature_value, float):
            if feature_value >= tree.threshold:
                branch = tree.left_branch
        elif feature_value == tree.threshold:
            branch = tree.left_branch
        
        return self.predict_value(x, branch) # 测试子集

    def predict(self, X): # 数据集预测函数
        y_pred = [self.predict_value(sample) for sample in X]
        return y_pred
# CART分类树
class ClassificationTree(BinaryDecisionTree): # 分类树
    
    def _calculate_gini_impurity(self, y, y1, y2): # 定义基尼不纯度计算过程
        p = len(y1) / len(y)
        gini_impurity = p * calculate_gini(y1) + (1-p) * calculate_gini(y2)
        return gini_impurity
    
    def _majority_vote(self, y): # 多数投票
        most_common = None
        max_count = 0
        for label in np.unique(y):
            # 统计多数
            count = len(y[y == label])
            if count > max_count:
                most_common = label
                max_count = count
        return most_common
    
    def fit(self, X, y): # 分类树拟合
        self.impurity_calculation = self._calculate_gini_impurity
        self._leaf_value_calculation = self._majority_vote
        super(ClassificationTree, self).fit(X, y)
# 测试CART分类树
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

data = datasets.load_iris() # 鸢尾花数据集
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(X, y.reshape(-1,1), test_size=0.3)
clf = ClassificationTree()
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)

print(accuracy_score(y_test, y_pred))
0.9777777777777777
# CART回归树
class RegressionTree(BinaryDecisionTree): # 回归树
    def _calculate_variance_reduction(self, y, y1, y2):
        var_tot = np.var(y, axis=0)
        var_y1 = np.var(y1, axis=0)
        var_y2 = np.var(y2, axis=0)
        frac_1 = len(y1) / len(y)
        frac_2 = len(y2) / len(y)
        
        variance_reduction = var_tot - (frac_1 * var_y1 + frac_2 * var_y2) # 计算方差减少量
        return 1/sum(variance_reduction) # 方差减少量越大越好,所以取倒数

    def _mean_of_y(self, y): # 节点值取平均
        value = np.mean(y, axis=0)
        return value if len(value) > 1 else value[0]

    def fit(self, X, y):
        self.impurity_calculation = self._calculate_variance_reduction
        self._leaf_value_calculation = self._mean_of_y
        super(RegressionTree, self).fit(X, y)
# 测试CART回归树
from sklearn.metrics import mean_squared_error
from sklearn.datasets import load_boston
X, y = load_boston(return_X_y=True)
y = y.reshape(-1, 1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
model = RegressionTree()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
mse = mean_squared_error(y_test, y_pred)

print("Mean Squared Error:", mse)
Mean Squared Error: 18.304654605263156

完全な決定木アルゴリズムには、決定木生成アルゴリズムに加えて、決定木枝刈りアルゴリズムも含まれています。決定木生成アルゴリズムは決定木を再帰的に生成します。生成された決定木は大きくて完全ですが、過剰適合しやすいです。デシジョン ツリー プルーニング( pruning) は、生成されたデシジョン ツリーの一部のサブツリーまたはリーフ ノードを切り取り、そのルート ノードまたは親ノードを新しいリーフ ノードとして使用することによって、生成されたデシジョン ツリーを単純化して、次の目的を達成するプロセスです。デシジョンツリーを単純化します。

デシジョン ツリーの枝刈りには通常、事前枝刈り( pre-pruning) と事後枝刈り( psot-pruning) という 2 つの方法があります。いわゆる事前枝刈りは、決定木の生成プロセス中に木の成長を事前に停止する枝刈りアルゴリズムです。主なアイデアは、デシジョン ツリー ノードを分割する前に、現在のノード分割によってモデルの一般化能力が向上するかどうかを計算することです。そうでない場合、デシジョン ツリーはこのノードで成長を停止します。事前剪定を行うと木の成長が早期に止まり、ある程度のフィット不足になるリスクがあります。

実際のアプリケーションでは、現在でも主に剪定後の方法が使用されています。ポスト枝刈りは主に、決定木の全体的な損失関数を最小化することによって実現されます。前述したように、決定木は次の関数を最小化します。
L a ( T ) = ∑ t = 1 ∣ T ∣ N t H t ( T ) + α ∣ T ∣ L_{a}\left ( T \right ) = \ sum_{ t=1}^{\left | T \right | } N_{t}H_{t}\left ( T \right ) +\alpha\left | T \right |L( T )=t = 1 TNH( T )+あるT
最初の項目の経験的エントロピーは次のように表すことができます:
H t ( T ) = − ∑ k N tk N tlog N tk N t H_{t}(T)=-\sum_{k}^{}\ frac {N_{tk}}{N_{t}}log\frac{N_{tk}}{N_{t}}H( T )=kNNt kログ_ _NNt k
L(T)の最初の部分は次のように表すことができます:
L a ( T ) = ∑ t = 1 ∣ T ∣ N t H t ( T ) = − ∑ t = 1 ∣ T ∣ ∑ k = 1 KN tklog N tk N t L_{a }\left ( T \right ) = \sum_{t=1}^{\left | 右 | } N_{t}H_{t}\left ( T \right )=-\sum_{t=1}^{\left | T \right |}\sum_{k=1}^{K} N_{tk}log\frac{N_{tk}}{N_{t}}L( T )=t = 1 TNH( T )=t = 1 Tk = 1KNt kログ_ _NNt k
決定木最適化関数を次のように書き換えます。
L a ( T ) = L ( T ) + α ∣ T ∣ L_{a} (T)=L(T)+\alpha\left | T \right |L( T )=L ( T )+あるT
L(T)はモデルの経験的誤差項、|T|は決定木の複雑さ、αは正則化パラメーターです。

決定木後の枝刈りは、α複雑さが決定されたときにLα(T)最小の損失関数を持つ決定木モデルを選択することです。生成アルゴリズムによって得られた決定木Tと正則化パラメーターを考慮するとα、決定木後の枝刈りアルゴリズムは次のように説明されます:
(1). 各ツリー ノードの経験的エントロピーを計算しますHt(T)
(2). 下から上へ再帰的に退避します。リーフ ノードのグループがそれぞれ親ノードの前後のツリーに退避され、Tbefore対応Tafterする損失関数はそれぞれ と であるとLα(Tbefore)仮定しますLα(Tafter)Lα(Tafter)Lα(Tbefore)の場合、親ノードは枝刈りされます。ノードは新しいリーフノードになります。
(3).最小の損失関数を持つ部分木が得られるまで (2) を繰り返します

CART后剪枝枝刈りは、部分木の損失関数を計算することによって達成され、部分木シーケンスが得られ、その後、交叉验证この方法により部分木シーケンスから最適な部分木が選択される。

2.3 sklearnに基づく分類木と回帰木の実装

# 基于sklearn实现分类树
from sklearn.tree import DecisionTreeClassifier
data = datasets.load_iris() # 鸢尾花数据集
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(X, y.reshape(-1,1), test_size=0.3)
clf = DecisionTreeClassifier()
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
print(accuracy_score(y_test, y_pred))
0.9333333333333333
# 基于sklearn实现回归树
from sklearn.tree import DecisionTreeRegressor
X, y = load_boston(return_X_y=True)
y = y.reshape(-1, 1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
reg = DecisionTreeRegressor()
reg.fit(X_train, y_train)
y_pred = reg.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
print("Mean Squared Error:", mse)
Mean Squared Error: 14.449605263157896

Notebook_Github アドレス

おすすめ

転載: blog.csdn.net/cjw838982809/article/details/131205405