機械学習 - デシジョン ツリーの補足

前回のブログでは決定木の作成方法とそれを可視化する方法について触れましたが、ここでは連続性と欠落の概念説明と枝刈りのコード実装について補足します。

連続値と欠損値

連続値処理

継続的とは何ですか?

定義: 一定の範囲内で任意の値をとり得る変数を連続変数といい、その値は連続であり、隣接する 2 つの値は無限に分割することができ、つまり無限個の値を取ることができます。--百度百科事典

定義からわかるように、連続値の値は離散値の有限値ではなく無限であるため、連続属性の取り得る値に従ってノードを直接分割することはできません。そのため、連続属性の離散化技術が存在します。最も単純な戦略は、C4.5 デシジョン ツリーで採用されているメカニズムを使用して連続値を二分法で処理することです。

サンプル集合 D と連続属性 a が与えられ、 a が D 上で n 個の異なる値を持つと仮定して、これらの値を小さいものから大きいものまで並べて、 と表しますa1、a2、...a^n分割点 t に基づいて、 D はサブセット sumD_{t}^{+}に分割できますD_{t}^{-}。これにはD_{t}^{-}、属性 a の値が t 以下であるサンプルと、D_{t}^{+}属性 a の値が t より大きいサンプルが含まれます。隣接する属性の値 ai、ai+1 を取得し、[ai, ai+1] の t の任意の値を取得して、同じ除算結果を生成します。連続属性aの場合、区間[ai,ai+1]の中点が分割候補点となる。
T a = ai + ai + 1 2 ∣ 1 ≤ i ≤ n − 1 T_{a} =\frac{a^i + a^{i+1}}{2} | 1\le{i}\le{ n-1}T=2ある+ある+ 11n1
離散属性値法を使用してこれらの分割点の情報ゲインを計算し、サンプル セットを分割するための最適な分割点を選択します。

ゲイン ( D , a ) = max ⁡ t ∈ T a Gain ( D , a , t ) = max ⁡ t ∈ Ta E nt ( D ) − ∑ λ ∈ ( − , + ) ∣ D t λ ∣ ∣ D ∣ E nt ( D t λ ) Gain(D,a) = \max_{t\in T_a}Gain(D,a,t) = \max_{t\in T_a}Ent(D) - \sum_{\ラムダ \in{(-,+)}}\frac{|D_{t}^{\lambda } |}{|D|}Ent(D_{t}^{\lambda })ゲイン( D _ _ __=t Tマックスゲイン( D _ _ __t )=t Tマックスエント( D ) _ _λ ( , + )D ∣D _tエント( D _ _t)

欠損値の処理

実際には、データセットのサンプルには不完全なサンプル (特徴の値が空) が頻繁に発生します。これは、サンプルの一部の属性値が欠落していると呼ばれます。この場合、手動で検出して再度マークしたり、これらのデータセットを破棄したりするのは良くなく、手動で完了すると多大な時間の無駄が発生し、破棄するとデータ情報が膨大に無駄になります。

スイカの本では、欠損値に対処するために解決する必要がある 2 つの問題を再提案しています。

  1. 属性値が欠落しているパーティション属性の選択を行うにはどうすればよいですか?
  2. パーティション属性が与えられた場合、この属性のサンプルの値が欠落している場合にサンプルをパーティション化するにはどうすればよいでしょうか?

解決:

質問 1 について:

トレーニング セット D と属性 a が与えられた場合、\ワイドティルダ{D}属性 a に欠損値がない D のサンプルのサブセットを示します。\ワイドティルダ{D}属性 a の良し悪しを判断するために使用できます。属性 a に V 個の可能な値 { } があると仮定して 属性 a の値を取るサンプル サブセットを表し、k 番目のクラス (k=1,2,...,|y|) に属するサンプル サブセットを表します。 )。それからあります a1、a2、...、a^v\ワイドティルダ{D}^{v}\ワイドティルダ{D}a^v\ワイドティルダ{D_{k}}

\ワイドティルデ{D}=\cup_{k=1}^{|y|}\ワイドティルデ{D_{k}} \ワイドティルデ{D}=\カップ _{v=1}{V}\ワイドティルデ{D{v}}

各サンプル x に重みを割り当て、次のように定義するとします。

\rho =\frac{\sum_{x\in\widetilde{D}}{w_{x}}}{\sum_{x \in D}{w_{x}}}\チルダ{r_v}\cdot w_x

\widetilde{p_{k}}=\frac{\sum_{x\in\widetilde{D_{k}}}{w_{x}}}{\sum_{x \in D}{w_{x}}}

\widetilde{r_{v}}=\frac{\sum_{x\in\widetilde{D_{v}}}{w_{x}}}{\sum_{x \in \widetilde{D}}{w_{バツ}}}

このうち、 は\rho欠損値のないサンプルの割合を表し、\ワイドティルダ{p_{k}}は欠損値のないサンプルにおけるクラスkの割合を表し、 は\ワイドティルダ{r_{v}}欠損値のないサンプルのうち属性aの値をa^{v}とるサンプルの割合を表します。

上記の定義に基づいて、情報ゲインの計算式は次のように拡張されます。

Gain(D,a)=\rho \times Gain(\tilde{D},a) =\rho \times (Ent(\tilde{D})-\sum_{v=1}{V}\tilde{r_v }Ent(\チルダ{Dt}))

の、

Ent(\tilde{D})=-\sum_{k=1}^{|y|} \tilde{p_k}log_2\tilde{p_k}

質問 2 については、次のようになります。

分割属性 a のサンプル x の値が既知の場合、 x をその値に対応する子ノードに分割し、サンプルの重みは として子ノードに残りますw_x分割属性 a のサンプル x の値が不明な場合、x はすべての子ノードに同時に分割され、属性値に対応する子ノードでサンプルの重みが調整されます。これは直観的にa^v\チルダ{r_v}\cdot w_x、同じサンプルでは異なる確率が使用され、異なるサブノードに分割されます。

コード

枝刈りの定義と概念については以前の記事で説明したので、ここでは詳しく説明しませ

ここでは集美大学旗守チームの身長、チーム年齢、怪我の有無の3つの特徴を判断して、10月1日の旗掲揚チームに参加できるかどうかを判断します(データは仮想です)。

データ表示:

ここに画像の説明を挿入

メインコード (例として「プルーニング後」を取り上げます) は次のようになります。

# 剪枝策略
def postPruningTree(inputTree, dataSet, data_test, labels, labelProperties):
    firstStr = list(inputTree.keys())[0]
    secondDict = inputTree[firstStr]
    classList = [example[-1] for example in dataSet]
    featkey = copy.deepcopy(firstStr)
    if '<' in firstStr:  # 对连续的特征值,使用正则表达式获得特征标签和value
        featkey = re.compile("(.+<)").search(firstStr).group()[:-1]
        featvalue = float(re.compile("(<.+)").search(firstStr).group()[1:])
    labelIndex = labels.index(featkey)
    temp_labels = copy.deepcopy(labels)
    temp_labelProperties = copy.deepcopy(labelProperties)
    if labelProperties[labelIndex] == 0:  # 离散特征
        del (labels[labelIndex])
        del (labelProperties[labelIndex])
    for key in secondDict.keys():  # 对每个分支
        if type(secondDict[key]).__name__ == 'dict':  # 如果不是叶子节点
            if temp_labelProperties[labelIndex] == 0:  # 离散的
                subDataSet = splitDataSet_c(dataSet, labelIndex, key)
                subDataTest = splitDataSet_c(data_test, labelIndex, key)
            else:
                if key == 'Y':
                    subDataSet = splitDataSet_c(dataSet, labelIndex, featvalue,
                                               'L')
                    subDataTest = splitDataSet_c(data_test, labelIndex,
                                                featvalue, 'L')
                else:
                    subDataSet = splitDataSet_c(dataSet, labelIndex, featvalue,
                                               'R')
                    subDataTest = splitDataSet_c(data_test, labelIndex,
                                                featvalue, 'R')
            if len(subDataTest) > 0:
                inputTree[firstStr][key] = postPruningTree(secondDict[key],
                                                       subDataSet, subDataTest,
                                                       copy.deepcopy(labels),
                                                       copy.deepcopy(
                                                           labelProperties))
    print(testing(inputTree,  data_test, temp_labels,
               temp_labelProperties))
    print(testingMajor(majorityCnt(classList), data_test))
    if testing(inputTree, data_test, temp_labels,
               temp_labelProperties) <= testingMajor(majorityCnt(classList),
                                                     data_test):
        return inputTree
    return majorityCnt(classList)
# 测试决策树正确率
def testing(myTree, data_test, labels, labelProperties):
    error = 0.0
    for i in range(len(data_test)):
        classLabelSet = classify(myTree, labels, labelProperties, data_test[i])
        
        maxWeight = 0.0
        classLabel = ''
        for item in classLabelSet.items():
            if item[1] > maxWeight:
                classLabel = item[0]
        if classLabel !=  data_test[i][-1]:
            error += 1
    return float(error)


# 测试投票节点正确率
def testingMajor(major, data_test):
    error = 0.0
    for i in range(len(data_test)):
        if major[0] != data_test[i][-1]:
            error += 1
    return float(error)
# 测试算法
def classify(inputTree,featLabels, featLabelProperties, testVec):
    firstStr = list(inputTree.keys())[0]  # 根节点
    firstLabel = firstStr
    lessIndex = str(firstStr).find('<')
    if lessIndex > -1:  # 如果是连续型的特征
        firstLabel = str(firstStr)[:lessIndex]
    secondDict = inputTree[firstStr]
    featIndex = featLabels.index(firstLabel)  # 跟节点对应的特征
    classLabel = {
    
    }
    
    for key in secondDict.keys():  # 对每个分支循环
        if featLabelProperties[featIndex] == 0:  # 离散的特征
            if testVec[featIndex] == key:  # 测试样本进入某个分支
                if type(secondDict[key]).__name__ == 'dict':  # 该分支不是叶子节点,递归
                    classLabelSub = classify(secondDict[key],  featLabels,
                                          featLabelProperties, testVec)
                    for classKey in classLabel.keys():
                        classLabel[classKey] += classLabelSub[classKey]
                else:  # 如果是叶子, 返回结果
                    for classKey in classLabel.keys():
                        if classKey == secondDict[key][0]:
                            classLabel[classKey] += secondDict[key][1]
                        else:
                            classLabel[classKey] += secondDict[key][2]
            elif testVec[featIndex] == 'N':  # 如果测试样本的属性值缺失,则进入每个分支
                if type(secondDict[key]).__name__ == 'dict':  # 该分支不是叶子节点,递归
                    classLabelSub = classify(secondDict[key],  featLabels,
                                          featLabelProperties, testVec)
                    for classKey in classLabel.keys():
                        classLabel[classKey] += classLabelSub[key]
                else:  # 如果是叶子, 返回结果
                    for classKey in classLabel.keys():
                        if classKey == secondDict[key][0]:
                            classLabel[classKey] += secondDict[key][1]
                        else:
                            classLabel[classKey] += secondDict[key][2]
        else:
            partValue = float(str(firstStr)[lessIndex + 1:])
            if testVec[featIndex] == 'N':  # 如果测试样本的属性值缺失,则对每个分支的结果加和
                # 进入左子树
                if type(secondDict[key]).__name__ == 'dict':  # 该分支不是叶子节点,递归
                    classLabelSub = classify(secondDict[key],  featLabels,
                                          featLabelProperties, testVec)
                    for classKey in classLabel.keys():
                        classLabel[classKey] += classLabelSub[classKey]
                else:  # 如果是叶子, 返回结果
                    for classKey in classLabel.keys():
                        if classKey == secondDict[key][0]:
                            classLabel[classKey] += secondDict[key][1]
                        else:
                            classLabel[classKey] += secondDict[key][2]
            elif float(testVec[featIndex]) <= partValue and key == 'Y':  # 进入左子树
                if type(secondDict['Y']).__name__ == 'dict':  # 该分支不是叶子节点,递归
                    classLabelSub = classify(secondDict['Y'], featLabels,
                                             featLabelProperties, testVec)
                    for classKey in classLabel.keys():
                        classLabel[classKey] += classLabelSub[classKey]
                else:  # 如果是叶子, 返回结果
                    for classKey in classLabel.keys():
                        if classKey == secondDict[key][0]:
                            classLabel[classKey] += secondDict['Y'][1]
                        else:
                            classLabel[classKey] += secondDict['Y'][2]
            elif float(testVec[featIndex]) > partValue and key == 'N':
                if type(secondDict['N']).__name__ == 'dict':  # 该分支不是叶子节点,递归
                    classLabelSub = classify(secondDict['N'], featLabels,
                                             featLabelProperties, testVec)
                    for classKey in classLabel.keys():
                        classLabel[classKey] += classLabelSub[classKey]
                else:  # 如果是叶子, 返回结果
                    for classKey in classLabel.keys():
                        if classKey == secondDict[key][0]:
                            classLabel[classKey] += secondDict['N'][1]
                        else:
                            classLabel[classKey] += secondDict['N'][2]

    return classLabel

操作結果:
ここに画像の説明を挿入

剪定後:

ここに画像の説明を挿入

分析する

上記の剪定効果を確認すると、次のような問題が見つかると思います。

  1. 嘘だよ、剪定後は自分の部門とは違う特徴で直接判断できるんだよ?
  2. なぜそのような結果につながるのでしょうか?他人の作品や教科書が何層にも重なっています。

上記の問題に関して:

  1. データ セットは私自身が設計したものです。データのサイズと特徴の数は比較的小さいため、結果はより良いものになるでしょう。テスト用に、より大きな桁数のデータ セットをいくつか見つけることができます。後の結果は次のとおりだと思います。紙を切るのは確実です。私とは違います。
  2. 一般化すると、後枝刈りの効果はより良い傾向にありますが、トレーニング時間は比較的長く、データ量のサイズと特徴の数にも関係します。

枝刈りの目的は、判断が不明確な意思決定パスを減らし、意思決定パスを減らし、判断の精度をできるだけ高くすることです。私たちと同じように、一度決断をした後、その後考え方が変わったり、無駄な(悪影響を与える)判断基準が取り除かれたりすることがあります(枝刈り後)。

コード取得

リンク: https://pan.baidu.com/s/1BynAi2uPx1eyZLhR6cHnww
抽出コード: 1234

おすすめ

転載: blog.csdn.net/weixin_51961968/article/details/127982576