Python实现决策树(Decision Tree)

💥 项目专栏:【探索机器学习之旅】原理与实践,Python算法实现


前言

🚀 欢迎莅临本专栏!在这里,我们将为机器学习的新手和初次尝试 sklearn 的用户群体提供一系列有趣的原生Python机器学习算法实现。我们的目标是让您轻松入门机器学习,并深入探索算法的内部奥秘。💡

在这个专栏中,我们专注于使用原生Python实现一些经典机器学习算法,比如决策树、逻辑回归等。通过自己亲手复现算法,我们将理论与实践紧密结合,让您对这些算法有更深入的理解。每篇文章都会附带完整的代码和详细讲解,助您逐步掌握算法的核心概念和运行原理。🔍📝

哪怕您是零基础,也不必担心!本专栏旨在为您提供一条通向机器学习世界的清晰路径。我们坚信,通过这些实践和理论指导,您将快速掌握机器学习的精髓,并在未来的学习和探索中展翅高飞!🌈🌟

来吧!让我们一起踏上机器学习之旅,探索这个神奇领域,畅游在算法的海洋中,感受数据科学的魅力吧!🌊💻🔬


🚨 欢迎来到我的机器学习探索项目环境!📊🐍

  • 平台:Windows 11 🖥️
  • 语言环境:Python 3.7 🐍
  • 编译器:Jupyter Lab 📘🔬
  • 数据处理:Pandas 1.3.5 🐼📈
  • 数值计算:Numpy 1.19.3 🔢🧮
  • 科学计算:Scipy 1.7.3 🧪📚
  • 数据可视化:Matplotlib 3.1.3 📊🎨

在这个酷炫的项目环境里,我们将使用强大的Python语言,搭配Jupyter Lab编译器,为您带来一流的机器学习学习体验。Pandas将为我们处理数据,Numpy则助力高效的数值计算,而Scipy则为我们的科学计算保驾护航。

让数据更加生动的秘密武器是Matplotlib,它将以图表的形式展现复杂的信息,让您轻松洞悉数据的特征和趋势。📊📈🔍

让我们一起在这个有趣的机器学习之旅中探索新的可能性吧!🚀🌟💻


让我们一起启程,探索机器学习的奇妙世界!🚀💡🤖

💥 项目专栏:【探索机器学习之旅】原理与实践,Python算法实现

在这里插入图片描述


一、基于原生Python实现决策树(Decision Tree)

🌳📊决策树,是机器学习的魔法般工具!它可以帮助我们进行分类和回归,无论是二元还是多元,连续还是离散,统统搞定!决策树的构建过程就像在寻宝一样,我们递归地挑选最优特征,然后根据这些特征将数据分割,直到发现宝藏——满足某种条件为止,这时,一颗美丽的决策树就呈现在我们面前!✨🏆

常见的决策树算法有三种: ID3C4.5CART。它们各有特色:ID3喜欢用信息增益来挑选特征,C4.5热衷于信息增益比,而CART则钟情于基尼指数。

决策树的魔法不仅在于它易于理解和解释,还能用可视化的图形展示模型。它还有神奇的本领,能处理各种类型的数据,连缺失值都能大胆面对,毫不畏惧!🧙‍♂️📈

当然,决策树也有一些小小的缺点:有时候它会变得有点过度自恋,容易过拟合。特别是当决策树变得太深,有点“自我作古”的感觉。此外,它对于特征的选择也是挑剔的,选个不合适的特征,它可是会生气的,影响预测性能哦!😱💔

不过别担心!我们还有Python来拯救我们!本篇文章将用Python语言实现经典的决策树算法 Decision Tree,让你轻松驾驭这项魔法!🐍🌟

在这里插入图片描述

二、决策树的算法原理

💡🌳 决策树,一个神奇的算法,让我们来揭开它的魔法面纱!✨

首先,决策树的核心就是选择最优的特征,它像在挑选漂亮衣服一样,找到最适合的特征来把数据集分割成小块。步骤可不简单哦:

  • 1️⃣ 首先要计算每个特征的“魔法值”——信息增益或信息增益比,就像测量魔法力量一样。信息增益是看特征分割后带来的信息变化,而信息增益比是对信息增益的绝妙评价。

  • 2️⃣ 然后,根据最优特征,把数据集变成小小的子集,每个子集对应一个特征取值,就像用魔法棒将数据分割成不同颜色的小球一样。🎈

  • 3️⃣ 接下来,咱们要递归地应用魔法,对每个子集重复步骤1和步骤2,直到找到宝藏——满足终止条件为止。✨🏰

  • 4️⃣ 最后,咱们大功告成,可以得到一颗漂亮的决策树,就像是一颗光芒四射的魔法树!🌳🌟在分类预测时,只需要按照魔法路线从根节点出发,逐渐找到对应的叶子节点,咦,原来这个叶子节点就是预测结果呢!🎁

🌈 哇,真是太有趣了!决策树有很多种实现方式,就像不同魔法一样,有ID3、C4.5和CART等,每种都有自己独特的魅力哦!❤️

决策树算法可不止魅力,它还有许多优点:易于理解和解释,处理离散和连续数据,而且不怕缺失值,真是万能魔法师!🧚‍♀️💖当然啦,每个魔法都有小小的缺点:有时候会过拟合,有时候对特征挑剔,但这都不影响它的魅力!

三、算法实现

💡🌳 欢迎来到决策树算法的魔法学院!✨🎓 今天我们要学习的是如何用原生Python来亲手实现决策树算法,这样你就能亲身感受算法内部的奥秘啦!🧚‍♀️💖

别担心,我们不会一口气把所有细节都告诉你,而是采用阉割版的决策树算法,让你轻松入门!😉 相比于sklearn框架的复杂实现,我们只保留了算法的核心部分,就像是为你准备了一颗炫酷的决策树小样本!🌲✨

你知道吗?掌握算法的主干才是最重要的,小细节和优化策略可以在日后再来考虑哦!所以,让我们一起开启决策树算法的奇幻冒险之旅吧!🚀💫

在这里,我们注重你的学习体验,就像你在魔法学院里学习一样,我们会用可爱的语言和简洁的步骤带你一步步深入理解算法的精髓,就像和好友一起探索决策树的魅力!🌟🤗

咱们不急,一起走进决策树的魔法森林,感受每一步决策的魔力,为你量身打造的决策树小样本,带你掌握决策树算法的精华!🎉🧙‍♀️

让我们手牵手,一起踏上决策树之旅吧!💞🌳✨

3.1 导包

🌟 我们的工具箱里准备了一些超酷的第三方库,都是些相当常见且厉害的小伙伴们哦!🔧🤩

  • 首先,我们要请来最可爱的小数学家——“numpy”!它可是我们的得力助手,擅长解决各种科学计算难题,就像一个小小的数学精灵!🧚‍♀️🧮

  • 当然,我们还有一位超级绘图专家——“matplotlib”!🎨✨ 它是我们的美术指导,能够帮我们绘制出绚丽多彩的图像,让我们的数据更有生气!🌈🖌️

  • 为了让我们的训练更加顺利,我们还有两位不可或缺的训练伙伴——“sklearn.datasets"和"train_test_split”!👥🔍 它们能够帮我们导入和划分训练数据,确保我们的决策树旅程顺利无阻!🛤️🗺️

  • 别忘了还有一个重要的小助手——“Counter”!🔢🤖 它是我们的计数器,可以帮我们统计各种事情,让我们的数据更加井井有条!📊📋

  • 最后,当然少不了我们的好朋友"math"!🎉🧙‍♀️ 它是我们的魔法计算师,擅长处理各种数学问题,让我们的算法更加神奇!✨🔮

嘿嘿~我们的小伙伴们组成了一支超级强大的决策树团队,将带领我们一起踏上决策树冒险之旅!🌲🚀

让我们齐心协力,用这些可爱的工具一起展开决策树的奇妙冒险吧!💖🌈✨

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from collections import Counter
import math

3.2 定义随机数种子

🎲🌈 在机器学习的大冒险中,随机数种子可谓是一把神奇的魔法棒!✨🪄 它的使命是确保我们的实验结果可爱又可重复,就像魔法一样,每次运行都能得到一样的结果!🌟🔁

你知道吗?机器学习的世界充满了各种神秘的随机性~🧙‍♀️ 比如那些调皮的随机初始化参数,捣蛋的随机选择训练样本等,它们总是让我们的实验结果五花八门~🌪️🙈

但是,有了可爱的随机数种子,一切都变得不一样了!🌈🤗 它就像是实验的守护精灵,每次都保持着相同的魔法节奏,让我们的结果一致又可靠~🎉✨

小小的随机数种子,却有着非常重要的使命哦~💖 它是我们实验的稳定器,消除了随机性的魔咒,让我们的算法评估更加精准可靠~🧚‍♀️🎯

但是记住哦~🌸🌟 使用随机数种子时,要保证所有条件都是一模一样的~🔍👯‍♀️ 这样,我们的实验结果才能如期而至,稳稳地掌握在我们手中!🤝📈

让我们握紧魔法棒,带上可爱的随机数种子,一起探索机器学习的奇幻世界吧!🚀💕🔮

# 设置随机种子
seed_value = 2023
np.random.seed(seed_value)

3.3 定义决策树模型

为了更容易理解,本文定义决策树模型的方式和 sklearn 封装的算法一致,都是定义 fitpredictscore 接口。

构建一棵决策树的详细过程可以分为以下几个步骤:

  1. 首先,我们要创建一棵神奇的决策树,它的根节点包含着所有的特征和标签🌿🏠 这个根节点就像是一片神奇的土地,等待着我们去探索

  2. 接着,我们要选择最优特征,这可是个重要的任务哦~🔍💡 我们要遍历所有特征,计算它们的信息增益,然后找出最最最大的那个,就是我们的划分标准啦!🌟🎯

  3. 一旦找到最优特征,我们就要用它来划分数据集~🎈📦 每个特征不同的取值,都会给我们带来一份神奇的子集,它们里面的数据样本特别特别有趣哦!🎁🎉

  4. 接下来,我们要让这个神奇的过程不停地进行下去🔄💫 对于每个子集,我们都要重复上面的步骤,直到它们变得越来越小,最后都变成同一个类别了,或者达到了我们预先设定的小目标

  5. 最终,我们将会构建出一颗充满魔力的决策树!🌳🏰 里面的每个非叶子节点都代表一个特征,每个叶子节点都闪闪发光,代表一个特别的类别~🌟💖

🌳💕 哇,剪枝策略就像是给决策树一把小剪刀,让它变得更聪明更可爱啦!✂️🌟

首先,我们有两种超酷的剪枝方式:预剪枝和后剪枝~🌿🌱

预剪枝是个超级聪明的小策略哦!在我们构建决策树的时候,每次遇到一个节点,我们会想一想:如果划分这个节点,会不会让测试集的准确率变得不怎么样呢?如果不会大大提升准确率,那我们就决定不划分啦!因为不划分反而更可爱!😊🎈

接着,我们还有后剪枝~这个剪枝策略可是超级神奇哦!🌟🌠 我们在决策树构建完成后,会从底部开始往上看,发现有一些非叶子节点,其实可以把它们变成更可爱的叶子节点哦!这样会让测试集的准确率更高~🌱🍃

你看,剪枝策略就像是为了让我们的决策树变得更加聪明和可爱!它们可以避免过拟合的麻烦,让我们的决策树更有用!🌳💖

构建一颗决策树需要注意以下问题:

  1. 特征选择问题:选择合适的特征作为决策树的划分标准是决策树算法的核心。常用的特征选择方法包括信息增益、信息增益比、基尼指数等。
  2. 过拟合问题:决策树容易对训练数据过度拟合,导致泛化性能差。通过剪枝、限制树的深度等方法可以缓解过拟合现象。
  3. 缺失值处理问题:在实际数据中经常会存在缺失值,如何处理缺失值对决策树的构建影响很大。常用的处理方法包括忽略带有缺失值的数据样本、使用平均值、中位数等填充缺失值、使用最近邻样本进行填充等。
  4. 类别不平衡问题:在实际应用中,可能存在不同类别样本数量差异较大的情况。针对这种情况,可以采用过采样、欠采样等方法平衡数据样本分布,或者在构建决策树时引入加权误差等方法。

总体而言,决策树算法的构建过程需要结合具体问题具体分析,对不同问题选择合适的特征选择方法、剪枝方法、缺失值处理方法等。

3.3.1 模型训练

基于训练数据和选择的分类标准,使用决策树算法训练出一颗决策树模型。训练过程包括以下步骤:

  • (1)计算数据集的熵,用于度量数据集的无序程度。

  • (2)对每个特征计算信息增益,选择信息增益最大的特征作为划分数据集的标准。

  • (3)按照选定的特征划分数据集,递归地对子集进行上述操作,直到所有数据集都为同一类别或者不能再划分为止。

首先,我们有一个超级神奇的 Node 类,它就像是决策树的一个小小节点~每个节点都有着自己的特征索引,一个阈值,还有左边的小伙伴 left 和右边的小伙伴 right,当然还有一个值 value~如果 value 不为空,那就说明这个节点是个可爱的叶子节点哦!🍃🌟

然后,还有我们的 DecisionTreeClassifier 类,这可是个大魔法师哦~在 fit 方法里,我们会拿着数据集 X 和标签 y,然后在决策树森林中一边逛一边训练我们的决策树!当然要记得记录分类的数量和特征数量哦!🌳🌈

接下来,在 predict 方法里,我们会像探险家一样勇敢地遍历决策树,来预测输入数据的标签~我们会用魔法查找正确的路径,找到宝藏一样的结果!💖🗺️

让我们穿上可爱的冒险装,一起来训练决策树吧!🎒🌳💪

  • _grow_tree :首先检查是否达到了最大深度、是否只有一个标签或者是否只有一个样本,如果是的话,我们返回一个叶节点。否则,我们计算每个特征和阈值的信息增益,并选择信息增益最大的特征和阈值进行分裂。我们再递归地构建左右子树,直到叶节点。
  • _best_criteria :计算每个特征和阈值的信息增益,并返回信息增益最大的特征和阈值。
  • _information_gain :计算分裂前和分裂后的熵,并返回信息增益。
  • _entropy :计算标签的熵。
  • _split :根据给定的特征列和阈值将数据集拆分成两个子集。
  • _most_common_label :返回最常见的标签。

这个实现中可能还有一些需要改进的地方,但是这个代码可以用来实现简单的决策树分类任务。

3.3.1.1 构建一颗决策树

决策树的构建是一个递归的过程,其基本思路可以概括为以下几个步骤:

嘿嘿决策树的构建过程其实就像是在探险,我们需要勇敢地选择最优特征,就像是挑选最闪闪发光的宝石一样!💎✨ 这些宝石可以是信息增益、信息增益比、还有基尼系数等每个宝石都代表着不同的特征选择方法,我们要根据不同的问题来选择哦~🤩🗝️

然后,我们会用这些宝石来划分数据集,把数据集分成好多小小的子集,每个子集都是特征的不同取值哦~就像是探险队伍分成了好几个小组,分别去寻找宝藏一样!🗺️🌳

接着,我们会勇敢地递归构建子树,就像是一层一层探险下去,直到找到宝藏或者满足终止条件~🚀🌈 然后,我们就可以把这些子树拼成一棵完整的决策树,每个子树就是一个节点,通过宝藏的特征值,我们可以把数据分配到不同的子树中~🌳🍃

不过,冒险途中还有个问题要注意哦就是过拟合和欠拟合问题,我们得小心翼翼避免掉进这些坑里~😱🚫 还有要好好准备特征选择、数据预处理等工作,就像是装备准备要充分哦!🛡️🧰

让我们戴上可爱的冒险帽,踏上决策树的奇妙冒险之旅吧!💕🎒🌟

# 生成树
def _grow_tree(self, X, y, depth=0):
    n_samples, n_features = X.shape
    n_labels = len(set(y))

    if depth >= self.max_depth or n_labels == 1 or n_samples < 2:
        leaf_value = self._most_common_label(y)
        return Node(value=leaf_value)

    feature_indices = range(n_features)
    best_feature, best_threshold = self._best_criteria(X, y, feature_indices)
    left_indices, right_indices = self._split(X[:, best_feature], best_threshold)

    left = self._grow_tree(X[left_indices, :], y[left_indices], depth + 1)
    right = self._grow_tree(X[right_indices, :], y[right_indices], depth + 1)
    return Node(best_feature, best_threshold, left, right)
3.3.1.2 计算每个特征的信息增益

信息增益其实就是一个特征带给我们的"惊喜"程度,它告诉我们如果用某个特征来分割数据集,会让我们对数据有更多的了解,就像是解开谜题一样~🧩🕵️‍♀️

当我们找到信息增益最大的那个特征时,就像是找到了藏在森林深处的一颗珍贵宝石,我们会选择这个特征作为我们的划分标准,然后继续探索下去,找到更多宝藏!💎💫

通过计算信息增益,我们不仅可以得到一颗宝石,还可以得到整个决策树的构建方案!🌳🗺️ 就像是解开了一个个谜题,揭开了一幅幅冒险地图,我们可以一步一步构建出一棵精美的决策树!🧭🗝️

# 信息增益最大的特征和阈值
def _best_criteria(self, X, y, feature_indices):
    best_gain = -1
    split_index, split_threshold = None, None
    for i in feature_indices:
        column = X[:, i]
        thresholds = set(column)
        for threshold in thresholds:
            gain = self._information_gain(y, column, threshold)
            if gain > best_gain:
                best_gain = gain
                split_index = i
                split_threshold = threshold
    return split_index, split_threshold
3.3.1.3 计算分裂前后的信息增益

当我们选择一个最优特征进行划分时,就像是在用一把神奇的魔杖给数据集增添了新的魔力!🪄🔮 通过分裂数据,我们让数据集的信息熵减少了,就好像是给数据集洒下了一片信息的"减肥魔法"!🧙‍♀️🌟

每次信息增益都是一场"魔法表演",它反映了我们的划分有多么成功,有多少信息被解密了!🎩🐇 信息增益的大小告诉我们每次划分都让我们对数据有了更清晰的认识,就像是解开了一个个神秘宝盒!🗝️💡

# 信息增益
def _information_gain(self, y, X_column, split_threshold):
    parent_entropy = self._entropy(y)
    left_indices, right_indices = self._split(X_column, split_threshold)
    if len(left_indices) == 0 or len(right_indices) == 0:
        return 0
    n = len(y)
    nl, nr = len(left_indices), len(right_indices)
    el = self._entropy(y[left_indices])
    er = self._entropy(y[right_indices])
    child_entropy = (nl / n) * el + (nr / n) * er
    ig = parent_entropy - child_entropy
    return ig
3.3.1.4 计算标签的熵

熵就是那个神奇的"混乱计量器",它能帮我们看清样本集合的混乱程度,就像是一把魔法尺子,测量着样本集合的纯度!📏🔍

当样本集合的熵高高飙升时,就像是它们都跳进了一个"混乱大坑",变得一团糟糕,这时候它们的纯度可就不太行啦!🌀🤷‍♀️ 熵高,纯度低,这是一个让决策树哭泣的时刻!😢💔

所以,我们决策树算法就是要通过"魔法纯净器",也就是熵,来找到样本集合的纯度最高的划分特征!💫🧙‍♀️ 熵低,纯度高,这是一个让决策树开心的时刻!🌟😄

# 熵
def _entropy(self, y):
    hist = np.bincount(y)
    ps = hist / np.sum(hist)
    return -np.sum([p * np.log2(p) for p in ps if p > 0])
3.3.1.5 分割节点

当决策树在成长的过程中,它需要做很多重要的决策呢!🌳🧭这些重要的决策,就好像是树上的每一个"分割节点",它们帮助决策树在特征的海洋中寻找正确的方向!🌊🌈

每次到了一个"分割节点",决策树都会拿出一把"特征尺子",然后在某个特征上进行二分类或多分类。📏✂️ 就像是把样本集合分成了两部分,有点像是给它们做了个可爱的小分组!💕🎉

这样,决策树就能通过每个"分割节点"一步步地走向正确的方向,找到最优的路径,让每个样本都能找到自己的"家园"!🏠💖 所以,每个"分割节点"都是一个决策树成长中的"小站",它们相互协作,一起构建出一棵美丽的决策树!🤝🌟

# 分割节点
def _split(self, X_column, split_threshold):
    left_indices = np.argwhere(X_column <= split_threshold).flatten()
    right_indices = np.argwhere(X_column > split_threshold).flatten()
    return left_indices, right_indices
3.3.1.6 返回标签

在决策树冒险的旅程中,当它终于来到了叶子节点的家园时,它就知道该给每个家庭成员一个特殊的"标签"啦!🏠🏷️ 这些标签就是叶子节点的预测结果,它们帮助我们告诉每个样本都属于哪个家族!👪💖

当决策树决定要给样本划上"家谱"时,它会先检查自己是不是已经成为了"叶子节点",如果是的话,那真是太棒了,它就可以直接将自己的"标签"公布给大家啦!🎉🌳

但如果还不是叶子节点,决策树就会拿出它的"魔法尺子",用它来对样本进行二分类,然后继续向下冒险,找到属于它的叶子节点!📏🌟

这样,决策树就可以逐层向下,直到找到每个样本的"家园",然后为它们取上特殊的"家庭标签"啦!🏷️💕 让我们为决策树的聪明和勇敢点个赞,它帮助我们预测每个样本的归属,让我们更好地了解数据的故事!👏🤗📖

# 返回标签
def _most_common_label(self, y):
    counter = Counter(y)
    most_common = counter.most_common(1)[0][0]
    return most_common

3.3.2 模型预测

让我们带上决策树模型来进行一场精彩的测试吧!🌟🎉 我们将用测试数据集来考验它的分类魔力!🔮💫

决策树模型,准备好了吗?🌳🧚‍♂️让我们逐个拿出测试数据样本,用它们来进行预测!🔍💭 看看我们的决策树是否能够猜中它们的真实身份!🔮🕵️‍♀️

每个数据样本都有它自己的故事,让我们倾听它们的声音,并用决策树的预测来揭开谜底!📚💬 一一对比预测结果和真实标签,看看它们是否相符!🔍🏷️

哇!😲看起来我们的决策树表现得相当棒呢!它为多少个样本成功猜对了身份呢?让我们算一算,来计算它的准确率吧!🧮📊

决策树的准确率是一个很重要的指标,它告诉我们我们的模型有多聪明!🤩💡 准确率越高,我们对数据的理解就越准确!💯🎯

使用如下代码实现预测过程:

# 模型预测
def predict(self, X):
    return np.array([self._predict(inputs) for inputs in X])

# 预测节点类型
def _predict(self, inputs):
    node = self.tree_
    while node.value is None:
        if inputs[node.feature_index] <= node.threshold:
            node = node.left
        else:
            node = node.right
    return node.value

3.3.3 模型分数

没错,决策树可真是个厉害的分类算法呢!🌳🎯 它总是尽力揭示数据背后的秘密,让我们能够快速又准确地分类数据样本!💨💡

对的,我们通常使用准确率来衡量决策树的分类表现!🎯📊 准确率就像是它的魔法指数,告诉我们有多少样本被它成功猜对啦!🔮🎉

让我们为我们的决策树鼓掌👏,感谢它带来这场精彩的预测表演!🎉🎇 当然,如果你感兴趣,我们还可以探索其他酷炫的指标,比如F1-score、AUC等!🌟🎲

F1-score是个神奇的指标哦!它考虑了模型的召回率和准确率,综合衡量了模型的全面性和精确性!🎯📈

而AUC,它更像是决策树的舞台秀!🌟🎭 它衡量了分类器在不同阈值下的表现,让我们感受到决策树的优雅和稳定性!🔮🎶

无论选择哪个指标,我们都可以更全面地了解我们的决策树模型!让我们继续探索这个神奇的算法世界,发现更多有趣的事情吧!💫🌈🔍

# 精度
def score(self, y_pred, y):
    accuracy = (y_pred == y).sum() / len(y)
    return accuracy

3.3.4 Decision Tree模型

以下是决策树模型的完整定义:

# 定义节点
class Node:
    def __init__(self, feature_index=None, threshold=None, left=None, right=None, value=None):
        self.feature_index = feature_index  
        self.threshold = threshold  
        self.left = left  
        self.right = right  
        self.value = value  

# 定义决策树模型
class DecisionTreeClassifier:
    def __init__(self, max_depth=None):
        self.max_depth = max_depth # 决策树深度

    # 模型训练
    def fit(self, X, y):
        self.n_classes_ = len(set(y))
        self.n_features_ = X.shape[1]
        self.tree_ = self._grow_tree(X, y)

    # 模型预测
    def predict(self, X):
        return np.array([self._predict(inputs) for inputs in X])

    # 预测节点类型
    def _predict(self, inputs):
        node = self.tree_
        while node.value is None:
            if inputs[node.feature_index] <= node.threshold:
                node = node.left
            else:
                node = node.right
        return node.value

    # 生成树
    def _grow_tree(self, X, y, depth=0):
        n_samples, n_features = X.shape
        n_labels = len(set(y))

        if depth >= self.max_depth or n_labels == 1 or n_samples < 2:
            leaf_value = self._most_common_label(y)
            return Node(value=leaf_value)

        feature_indices = range(n_features)
        best_feature, best_threshold = self._best_criteria(X, y, feature_indices)
        left_indices, right_indices = self._split(X[:, best_feature], best_threshold)

        left = self._grow_tree(X[left_indices, :], y[left_indices], depth + 1)
        right = self._grow_tree(X[right_indices, :], y[right_indices], depth + 1)
        return Node(best_feature, best_threshold, left, right)

    # 信息增益最大的特征和阈值
    def _best_criteria(self, X, y, feature_indices):
        best_gain = -1
        split_index, split_threshold = None, None
        for i in feature_indices:
            column = X[:, i]
            thresholds = set(column)
            for threshold in thresholds:
                gain = self._information_gain(y, column, threshold)
                if gain > best_gain:
                    best_gain = gain
                    split_index = i
                    split_threshold = threshold
        return split_index, split_threshold

    # 信息增益
    def _information_gain(self, y, X_column, split_threshold):
        parent_entropy = self._entropy(y)
        left_indices, right_indices = self._split(X_column, split_threshold)
        if len(left_indices) == 0 or len(right_indices) == 0:
            return 0
        n = len(y)
        nl, nr = len(left_indices), len(right_indices)
        el = self._entropy(y[left_indices])
        er = self._entropy(y[right_indices])
        child_entropy = (nl / n) * el + (nr / n) * er
        ig = parent_entropy - child_entropy
        return ig

    # 熵
    def _entropy(self, y):
        hist = np.bincount(y)
        ps = hist / np.sum(hist)
        return -np.sum([p * np.log2(p) for p in ps if p > 0])

    # 分割节点
    def _split(self, X_column, split_threshold):
        left_indices = np.argwhere(X_column <= split_threshold).flatten()
        right_indices = np.argwhere(X_column > split_threshold).flatten()
        return left_indices, right_indices

    # 返回标签
    def _most_common_label(self, y):
        counter = Counter(y)
        most_common = counter.most_common(1)[0][0]
        return most_common
    
    # 精度
    def score(self, y_pred, y):
        accuracy = (y_pred == y).sum() / len(y)
        return accuracy

3.4 导入数据

哇,鸢尾花数据集是个超级有名的入门级数据呢!🌺🌼 这个数据集就像是机器学习的门票一样,让我们跨入了算法的神奇世界!🚀🎫

里面有三种不同的鸢尾花,每种有50个样本,一共150个样本!🌸🌺🌻 这些花儿们真是可爱又多样啊!🌈🌸

每个样本都带着鸢尾花的四个特征,分别是萼片长度、萼片宽度、花瓣长度和花瓣宽度!🌿🌷🌼 这些特征就像是鸢尾花的小秘密,我们可以通过它们来帮它们找到归属!🔍🎩

山鸢尾、变色鸢尾和维吉尼亚鸢尾,这些名字听起来就像是童话里的角色!🏰👸🌟 它们有着不同的形状和大小,所以我们可以通过这些可爱的特征来识别它们!💫🔮

虽然这里只选择了两个特征,但也足够让我们搭上数据可视化的快车!🚄📊 如果你感兴趣,以后还可以用四个特征,然后用PCA来做个小魔术,让我们的图像更加神奇!🎩🎆

让我们来一起探索这个美丽的数据集,解开鸢尾花的秘密吧!🔍🌺🌈

# 导入数据
X, y = load_iris(return_X_y=True)
X = X[:, :2]

3.5 划分训练集、测试集

在机器学习中,将数据集划分为训练集和测试集是非常重要的一步。训练集用于训练模型,而测试集用于评估模型的性能。

这里使用了 sklearn 中的 train_test_split 方法来实现数据集划分:

# 划分训练集、测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.15, random_state=seed_value)

3.6 模型训练

接下来我们就可以定义一个模型对象,指定模型训练的超参数 决策树深度 (如果熟练掌握后,可以在本算法骨架上添加其他超参数,例如 max_featuresmin_samples_split 等),然后将训练数据送给模型调用模型的 fit 方法完成训练过程:

# 训练决策树
model = DecisionTreeClassifier(max_depth=5)
model.fit(X_train, y_train)

3.7 打印结果

太棒了,训练完成后,我们可以用这个超级简单的代码来看看我们的模型有多厉害!🎉🤖

用准确率来衡量模型的表现,就像是看小伙伴们比赛谁更厉害一样!🏆🎯 谁猜对的多,准确率就高啦!🎯👏

如果你有能力,还可以绘制AUC曲线,这就像是给模型戴上时尚的帽子,看看它在不同情况下的表现!🎩💃 这样我们就可以更直观地看到模型的效果啦!🌟📈

# 结果
y_train_pred = model.predict(X_train)
y_test_pred = model.predict(X_test)

score_train = model.score(y_train_pred, y_train)
score_test = model.score(y_test_pred, y_test)

print('训练集Accuracy: ', score_train)
print('测试集Accuracy: ', score_test)

>>>训练集Accuracy:  1.0
>>>测试集Accuracy:  0.9130434782608695

3.8 可视化决策边界

哇!我们还加入了一个酷炫的功能来绘制决策边界图,看看我们的模型分类效果如何!🎨🌈

决策边界图就像是给我们的模型穿上时尚的外衣,展现出它的魅力和风采!💃💕 我们可以一睹模型在样本空间中如何划分不同的类别,简直是太有趣啦!🔍👀

让我们一起瞧瞧,是不是看起来很赞呢?💁✨ 用这个决策边界图,我们可以更直观地了解模型的分类效果,有木有觉得超级厉害!🌟🤩
在这里插入图片描述

# 可视化决策边界
x1_min, x1_max = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5
x2_min, x2_max = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5
xx1, xx2 = np.meshgrid(np.linspace(x1_min, x1_max, 100), np.linspace(x2_min, x2_max, 100))
Z = model.predict(np.c_[xx1.ravel(), xx2.ravel()])
Z = Z.reshape(xx1.shape)
plt.contourf(xx1, xx2, Z, cmap=plt.cm.Spectral)
plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Spectral)
plt.xlabel("Sepal length")
plt.ylabel("Sepal width")
plt.savefig('a.png', dpi=720)
plt.show()

完整源码

🚨🚨🚨 注意啦!这里有个特别声明 📢📢📢 :

我们的专栏可是为新手小白量身打造的,考虑到方便起见,我们采用了 Python 单文件实现的方式,让大家可以轻松一键复制、调试和运行,省去了复杂的项目构造步骤。😉🔧🐍

嗯,还有一点哦!我们的目标是帮助新人理解机器学习算法的基本训练预测过程,所以源码里只包含了算法的基本框架结构。虽然有些地方可能显得略显简陋,但也是为了让大家可以轻松理解。当然,有能力的小伙伴可以根据自己的能力在此基础上大展身手,尝试更多的参数和拓展功能,甚至可以进行分文件编写,搭建出完整的项目开发流程哦!🌟💪🔍

嗯嗯,我们始终坚信,学习是一步步积累的过程,我们陪你一起成长,助你成为机器学习的大神!🚀🌈💻

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from collections import Counter
import math

# 设置随机种子
seed_value = 2023
np.random.seed(seed_value)

# 定义节点
class Node:
    def __init__(self, feature_index=None, threshold=None, left=None, right=None, value=None):
        self.feature_index = feature_index  
        self.threshold = threshold  
        self.left = left  
        self.right = right  
        self.value = value  

# 定义决策树模型
class DecisionTreeClassifier:
    def __init__(self, max_depth=None):
        self.max_depth = max_depth # 决策树深度

    # 模型训练
    def fit(self, X, y):
        self.n_classes_ = len(set(y))
        self.n_features_ = X.shape[1]
        self.tree_ = self._grow_tree(X, y)

    # 模型预测
    def predict(self, X):
        return np.array([self._predict(inputs) for inputs in X])

    # 预测节点类型
    def _predict(self, inputs):
        node = self.tree_
        while node.value is None:
            if inputs[node.feature_index] <= node.threshold:
                node = node.left
            else:
                node = node.right
        return node.value

    # 生成树
    def _grow_tree(self, X, y, depth=0):
        n_samples, n_features = X.shape
        n_labels = len(set(y))

        if depth >= self.max_depth or n_labels == 1 or n_samples < 2:
            leaf_value = self._most_common_label(y)
            return Node(value=leaf_value)

        feature_indices = range(n_features)
        best_feature, best_threshold = self._best_criteria(X, y, feature_indices)
        left_indices, right_indices = self._split(X[:, best_feature], best_threshold)

        left = self._grow_tree(X[left_indices, :], y[left_indices], depth + 1)
        right = self._grow_tree(X[right_indices, :], y[right_indices], depth + 1)
        return Node(best_feature, best_threshold, left, right)

    # 信息增益最大的特征和阈值
    def _best_criteria(self, X, y, feature_indices):
        best_gain = -1
        split_index, split_threshold = None, None
        for i in feature_indices:
            column = X[:, i]
            thresholds = set(column)
            for threshold in thresholds:
                gain = self._information_gain(y, column, threshold)
                if gain > best_gain:
                    best_gain = gain
                    split_index = i
                    split_threshold = threshold
        return split_index, split_threshold

    # 信息增益
    def _information_gain(self, y, X_column, split_threshold):
        parent_entropy = self._entropy(y)
        left_indices, right_indices = self._split(X_column, split_threshold)
        if len(left_indices) == 0 or len(right_indices) == 0:
            return 0
        n = len(y)
        nl, nr = len(left_indices), len(right_indices)
        el = self._entropy(y[left_indices])
        er = self._entropy(y[right_indices])
        child_entropy = (nl / n) * el + (nr / n) * er
        ig = parent_entropy - child_entropy
        return ig

    # 熵
    def _entropy(self, y):
        hist = np.bincount(y)
        ps = hist / np.sum(hist)
        return -np.sum([p * np.log2(p) for p in ps if p > 0])

    # 分割节点
    def _split(self, X_column, split_threshold):
        left_indices = np.argwhere(X_column <= split_threshold).flatten()
        right_indices = np.argwhere(X_column > split_threshold).flatten()
        return left_indices, right_indices

    # 返回标签
    def _most_common_label(self, y):
        counter = Counter(y)
        most_common = counter.most_common(1)[0][0]
        return most_common
    
    # 精度
    def score(self, y_pred, y):
        accuracy = (y_pred == y).sum() / len(y)
        return accuracy

# 导入数据
X, y = load_iris(return_X_y=True)
X = X[:, :2]

# 划分训练集、测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.15, random_state=seed_value)

# 训练决策树
model = DecisionTreeClassifier(max_depth=5)
model.fit(X_train, y_train)

# 结果
y_train_pred = model.predict(X_train)
y_test_pred = model.predict(X_test)

score_train = model.score(y_train_pred, y_train)
score_test = model.score(y_test_pred, y_test)

print('训练集Accuracy: ', score_train)
print('测试集Accuracy: ', score_test)

# 可视化决策边界
x1_min, x1_max = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5
x2_min, x2_max = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5
xx1, xx2 = np.meshgrid(np.linspace(x1_min, x1_max, 100), np.linspace(x2_min, x2_max, 100))
Z = model.predict(np.c_[xx1.ravel(), xx2.ravel()])
Z = Z.reshape(xx1.shape)
plt.contourf(xx1, xx2, Z, cmap=plt.cm.Spectral)
plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Spectral)
plt.xlabel("Sepal length")
plt.ylabel("Sepal width")
plt.savefig('a.png', dpi=720)
plt.show()
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Natasha❀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值