从SQL视角构建与评估决策树:DuckDB在机器学习中的妙用

文摘   2024-11-08 07:45   福建  

.01

概述
决策树就像我们日常做的选择,例如出门时考虑是否带伞:如果天阴就带伞,如果晴天则不用。这样的决策逻辑,其实正是决策树模型的工作方式,通过逐步划分数据来做出简单而有逻辑的决策。而决策树模型被广泛应用于各种场景,尤其在分类任务中大放异彩。
通常,像scikit-learn这样的库让构建决策树变得简单。然而,若抛开这些便捷的工具,从零开始构建决策树呢?这需要手动确定最佳的节点划分、计算Gini不纯度、评估模型效果。本文将带领大家深入剖析决策树模型的构建原理,并通过SQL数据库DuckDB来实现这一过程。这种手动构建决策树的方法不仅能加深对模型数学原理的理解,也能帮助大家掌握决策树背后的核心逻辑。

.02

决策树构建与DuckDB实现的核心步骤
Step 1:认识决策树的分裂标准——Gini不纯度
在构建决策树时,模型需要找到最优的划分点。这个划分过程决定了每个节点上如何将数据进行二分处理,从而形成更纯净的子集。Gini不纯度是决策树中常用的评估标准,用来衡量一个节点的“混乱程度”。Gini不纯度越低,表示分类越纯净。
其计算公式为:
其中,q分别是属于某类的概率。
Step 2:手动计算最佳分裂点
我们使用一个简单的数据集,其中包含客户的年龄、年收入等信息,以预测他们是否购买某保险产品。首先,从年龄特征出发,我们会在DuckDB中手动计算每个可能的分裂点,并逐步找到最优划分。
-- 创建一个年龄分段的表
CREATE OR REPLACE TABLE age_split AS 
(SELECT * FROM generate_series(25905AS age);
ALTER TABLE age_split 
RENAME COLUMN generate_series TO age;

Step 3:根据年龄划分数据并计算Gini不纯度
我们将客户分为两组:年龄低于40岁(“左节点”)和40岁以上(“右节点”),并计算每个组的购买概率及其对应的Gini不纯度。这一过程通过DuckDB的SQL查询实现。
-- 按年龄分割数据并计算Gini不纯度
WITH split AS (
    SELECT CASE WHEN age < 40 THEN 'left' ELSE 'right' END as node, buy
    FROM insurance
)
SELECT node,
       COUNT(*) as total_customers,
       SUM(buy) as total_buys,
       SUM(1 - buy) as total_not_buys,
       (1 - POWER(SUM(buy) / COUNT(*), 2) - POWER(SUM(1 - buy) / COUNT(*), 2)) as gini
FROM split
GROUP BY node;

这种分组使我们能够看到不同年龄段客户购买决策的差异。通过计算各组的加权Gini不纯度,我们可以确定整体的分裂效果是否理想。
Step 4:选择最优分裂点
接下来,我们针对所有年龄分组(例如25、30、35……90)计算加权Gini不纯度,选择加权Gini不纯度最小的分裂点,这将成为我们在年龄特征上的最优划分。
WITH total_customers AS (
    SELECT COUNT(*) AS total FROM insurance
),
gini_calc AS (
    SELECT a.age AS split_point,
           SUM(CASE WHEN i.age < a.age THEN 1 ELSE 0 ENDAS left_count,
           SUM(CASE WHEN i.age < a.age AND i.buy = 1 THEN 1 ELSE 0 ENDAS left_buy,
           SUM(CASE WHEN i.age >= a.age THEN 1 ELSE 0 ENDAS right_count,
           SUM(CASE WHEN i.age >= a.age AND i.buy = 1 THEN 1 ELSE 0 ENDAS right_buy
    FROM age_split a
    CROSS JOIN insurance i
    GROUP BY a.age
),
gini_values AS (
    SELECT split_point,
           (1 - POWER(left_buy * 1.0 / left_count, 2) - POWER((left_count - left_buy) * 1.0 / left_count, 2)) as left_gini,
           (1 - POWER(right_buy * 1.0 / right_count, 2) - POWER((right_count - right_buy) * 1.0 / right_count, 2)) as right_gini,
           ((left_count * left_gini) + (right_count * right_gini)) / total AS weighted_gini
    FROM gini_calc, total_customers total
)
SELECT split_point, weighted_gini
FROM gini_values
ORDER BY weighted_gini
LIMIT 1;

Step 5:年收入特征的加权Gini不纯度计算
和年龄特征相似,我们对年收入特征进行相同的加权Gini计算,以便找到收入特征上的最佳分裂点。最终得到的最优年龄分裂点和年收入分裂点,分别为40岁和80000元。
Step 6:生成预测与混淆矩阵
在找出最佳分裂点后,我们可以开始预测客户的购买情况:
SELECT CASE
           WHEN age < 40 THEN
               CASE WHEN income < 80000 THEN 0 ELSE 1 END
           ELSE
               1
       END AS predicted_buy,
       buy AS actual_buy
FROM insurance;

通过预测结果与实际数据的对比,我们能得到一个混淆矩阵,用于评估模型的性能。
WITH predictions AS (
    SELECT CASE
               WHEN age < 40 THEN CASE WHEN income < 80000 THEN 0 ELSE 1 END
               ELSE 1
           END AS predicted_buy,
           buy AS actual_buy
    FROM insurance
)
SELECT predicted_buy, actual_buy, COUNT(*) AS count
FROM predictions
GROUP BY predicted_buy, actual_buy
ORDER BY predicted_buy, actual_buy;

Step 7:模型优化建议
在预测中,我们可以看到模型预测存在一些误差,例如一些假阳性(错误预测客户会购买)。若要提升模型表现,可以考虑:
    • 优化特征选择:增加如职业、家庭等影响购买决策的特征。
    • 调整超参数:使用不同分裂准则、最小叶节点数等,来提升分类准确性。
    • 应用更复杂的模型:探索其他机器学习算法,如随机森林、提升树等。
虽然SQL在数据查询、聚合方面非常高效,但在递归或迭代处理(如决策树的复杂构建)上存在一定的局限性。这类任务通常更适合Python的scikit-learn或R的rpart等专用库。

.03

结语
本文通过DuckDB数据库的SQL实现了决策树模型的构建和评估流程,从划分标准的计算到模型的预测验证,为数据分析与建模提供了一种新的视角。对于SQL熟练的从业者,这种方法不仅能加深对模型的理解,还能拓展SQL在数据科学领域的应用场景。未来,若能结合更多特征和模型优化手段,相信这一模型会在预测准确性上取得更大的提升。
  

参考:






Halo咯咯
专注于技术知识整理,包含人工智能、大模型、机器学习、深度学习、大数据等多个领域的技术知识,以及各种开源的内容~
 最新文章