C++代码实现MATLAB中的fitrtree函数功能

cpp 复制代码
// fitrtree.cpp
// 编译: g++ -std=c++17 -O2 fitrtree.cpp -o fitrtree

#include <vector>
#include <memory>
#include <string>
#include <limits>
#include <algorithm>
#include <numeric>
#include <cmath>
#include <stdexcept>
#include <iostream>
#include <iomanip>
#include <map>

namespace mltree {

// ============================================================
// RegressionTree 节点 ------ 对齐 MATLAB RegressionTree 属性
//   CutPoint          : 连续型分裂的切点
//   CutPredictorIndex : 分裂使用的预测变量索引 (0-based)
//   NodeSize          : 节点样本数
//   NodeMean          : 节点响应均值 (叶节点预测值)
//   NodeMSE           : 节点 MSE
//   IsBranchNode      : 是否为分支节点
// ============================================================
struct Node {
    int    cut_predictor_index = -1;   // -1 表示叶节点
    double cut_point           = 0.0;  // 切点 u,规则: x_j < u -> 左
    double node_mean           = 0.0;  // 叶节点预测值
    double node_mse            = 0.0;
    int    node_size           = 0;
    std::unique_ptr<Node> left;
    std::unique_ptr<Node> right;

    bool is_branch() const { return cut_predictor_index >= 0; }
    bool is_leaf()   const { return cut_predictor_index < 0; }
};

// ============================================================
// fitrtree 参数 ------ 对齐 MATLAB Name-Value 对
//   MinParentSize : 默认 10
//   MinLeafSize   : 默认 1
//   MaxNumSplits  : 默认 size(X,1)-1
//   MergeLeaves   : 默认 'on'
//   Prune         : 默认 'on'
// ============================================================
struct FitRTreeParams {
    int    MinParentSize = 10;
    int    MinLeafSize   = 1;
    int    MaxNumSplits  = -1;   // -1 表示用 n-1
    bool   MergeLeaves   = true;
    bool   Prune         = true;
    double PruneAlpha    = 0.0;
};

// ============================================================
// 回归树 ------ 对应 MATLAB RegressionTree 类
// ============================================================
class RegressionTree {
public:
    RegressionTree() = default;

    // 对应 tree = fitrtree(X, Y, Name, Value)
    void fit(const std::vector<std::vector<double>>& X,
             const std::vector<double>& Y,
             const FitRTreeParams& params = FitRTreeParams()) {

        if (X.empty() || Y.empty() || X.size() != Y.size())
            throw std::invalid_argument("X and Y must have the same non-zero size");
        if (X[0].empty())
            throw std::invalid_argument("X must have at least one feature");

        params_ = params;
        n_features_ = static_cast<int>(X[0].size());
        n_samples_  = static_cast<int>(X.size());

        // MATLAB 默认 MaxNumSplits = n - 1
        if (params_.MaxNumSplits < 0)
            params_.MaxNumSplits = n_samples_ - 1;

        std::vector<int> indices(n_samples_);
        std::iota(indices.begin(), indices.end(), 0);

        // 步骤 1: 生长完整树
        root_ = build_tree(X, Y, indices, 0);

        // 步骤 2: MergeLeaves='on' ------ 合并同父叶节点 (MATLAB 默认)
        if (params_.MergeLeaves)
            merge_leaves(root_.get());

        // 步骤 3: Prune='on' ------ 代价复杂度剪枝 (MATLAB 默认)
        if (params_.Prune)
            prune_cost_complexity(root_.get(), params_.PruneAlpha);
    }

    // 对应 Yfit = predict(Mdl, X)
    // MATLAB 规则: 若 x_j < u 走左枝,否则走右枝
    double predict(const std::vector<double>& x) const {
        if (!root_) throw std::runtime_error("Model not fitted");
        const Node* node = root_.get();
        while (node->is_branch()) {
            node = (x[node->cut_predictor_index] < node->cut_point)
                   ? node->left.get() : node->right.get();
        }
        return node->node_mean;
    }

    std::vector<double> predict(const std::vector<std::vector<double>>& X) const {
        std::vector<double> result;
        result.reserve(X.size());
        for (const auto& x : X) result.push_back(predict(x));
        return result;
    }

    int num_nodes() const {
        int count = 0;
        count_nodes(root_.get(), count);
        return count;
    }

    int num_leaves() const { return count_leaves(root_.get()); }

    // 对应 resubLoss(Mdl)
    double resub_loss(const std::vector<std::vector<double>>& X,
                      const std::vector<double>& Y) const {
        double sse = 0.0;
        for (size_t i = 0; i < X.size(); ++i) {
            double d = Y[i] - predict(X[i]);
            sse += d * d;
        }
        return sse / Y.size();
    }

private:
    FitRTreeParams params_;
    std::unique_ptr<Node> root_;
    int n_features_ = 0;
    int n_samples_  = 0;

    // ------------------------------------------------------------
    // 递归生长树 ------ 标准 CART
    // ------------------------------------------------------------
    std::unique_ptr<Node> build_tree(
        const std::vector<std::vector<double>>& X,
        const std::vector<double>& Y,
        const std::vector<int>& indices,
        int splits_so_far) {

        auto node = std::make_unique<Node>();
        node->node_size = static_cast<int>(indices.size());

        double sum = 0.0;
        for (int i : indices) sum += Y[i];
        node->node_mean = sum / indices.size();

        double sse = 0.0;
        for (int i : indices) {
            double d = Y[i] - node->node_mean;
            sse += d * d;
        }
        node->node_mse = sse / indices.size();

        // 停止条件 (对齐 MATLAB Growing Decision Trees)
        if (node->node_size < params_.MinParentSize) return node;
        if (splits_so_far >= params_.MaxNumSplits) return node;
        if (sse < 1e-12) return node;

        SplitResult best = find_best_split(X, Y, indices, sse);
        if (best.feature_index < 0) return node;

        node->cut_predictor_index = best.feature_index;
        node->cut_point           = best.threshold;

        std::vector<int> left_idx, right_idx;
        left_idx.reserve(best.n_left);
        right_idx.reserve(node->node_size - best.n_left);
        // 使用 < 而非 <= ------ 与 MATLAB 的 x_j < u 规则一致
        for (int i : indices) {
            if (X[i][best.feature_index] < best.threshold)
                left_idx.push_back(i);
            else
                right_idx.push_back(i);
        }

        node->left  = build_tree(X, Y, left_idx,  splits_so_far + 1);
        node->right = build_tree(X, Y, right_idx, splits_so_far + 1);

        return node;
    }

    struct SplitResult {
        int    feature_index = -1;
        double threshold = 0.0;
        double mse_improvement = 0.0;
        int    n_left = 0;
    };

    // ------------------------------------------------------------
    // 最优分裂搜索
    // ------------------------------------------------------------
    SplitResult find_best_split(
        const std::vector<std::vector<double>>& X,
        const std::vector<double>& Y,
        const std::vector<int>& indices,
        double parent_sse) {

        SplitResult best;

        for (int f = 0; f < n_features_; ++f) {
            std::vector<int> sorted_idx = indices;
            std::sort(sorted_idx.begin(), sorted_idx.end(),
                      [&](int a, int b) { return X[a][f] < X[b][f]; });

            double total_sum = 0.0, total_sq_sum = 0.0;
            for (int i : indices) {
                total_sum    += Y[i];
                total_sq_sum += Y[i] * Y[i];
            }

            double left_sum = 0.0, left_sq_sum = 0.0;
            int    left_n   = 0;

            for (int k = 0; k < static_cast<int>(sorted_idx.size()) - 1; ++k) {
                int i = sorted_idx[k];
                left_sum    += Y[i];
                left_sq_sum += Y[i] * Y[i];
                left_n++;

                if (X[sorted_idx[k]][f] == X[sorted_idx[k + 1]][f]) continue;

                int right_n = static_cast<int>(indices.size()) - left_n;
                if (left_n < params_.MinLeafSize || right_n < params_.MinLeafSize)
                    continue;

                double left_sse  = left_sq_sum - left_sum * left_sum / left_n;
                double right_sse = (total_sq_sum - left_sq_sum)
                                   - (total_sum - left_sum) * (total_sum - left_sum) / right_n;
                if (left_sse < 0)  left_sse  = 0;
                if (right_sse < 0) right_sse = 0;

                double improvement = parent_sse - (left_sse + right_sse);

                if (improvement > best.mse_improvement) {
                    best.feature_index   = f;
                    best.threshold       = (X[sorted_idx[k]][f]
                                          + X[sorted_idx[k + 1]][f]) / 2.0;
                    best.mse_improvement = improvement;
                    best.n_left          = left_n;
                }
            }
        }
        return best;
    }

    // ------------------------------------------------------------
    // MergeLeaves ------ 对应 MATLAB 默认 MergeLeaves='on'
    // ------------------------------------------------------------
    void merge_leaves(Node* node) {
        if (!node || node->is_leaf()) return;

        merge_leaves(node->left.get());
        merge_leaves(node->right.get());

        if (node->left->is_leaf() && node->right->is_leaf()) {
            double left_mse  = node->left->node_mse  * node->left->node_size;
            double right_mse = node->right->node_mse * node->right->node_size;
            double parent_mse = node->node_mse * node->node_size;

            if (left_mse + right_mse >= parent_mse) {
                node->cut_predictor_index = -1;
                node->cut_point = 0.0;
                node->left.reset();
                node->right.reset();
            }
        }
    }

    // ------------------------------------------------------------
    // 代价复杂度剪枝 ------ 对应 Prune='on'
    // ------------------------------------------------------------
    void prune_cost_complexity(Node* node, double alpha) {
        if (!node || node->is_leaf()) return;

        prune_cost_complexity(node->left.get(),  alpha);
        prune_cost_complexity(node->right.get(), alpha);

        if (node->left->is_leaf() && node->right->is_leaf()) {
            double sse_before = node->left->node_mse  * node->left->node_size
                              + node->right->node_mse * node->right->node_size
                              + alpha * 2.0;
            double sse_after  = node->node_mse * node->node_size
                              + alpha * 1.0;

            if (sse_after <= sse_before) {
                node->cut_predictor_index = -1;
                node->cut_point = 0.0;
                node->left.reset();
                node->right.reset();
            }
        }
    }

    int count_leaves(const Node* node) const {
        if (!node) return 0;
        if (node->is_leaf()) return 1;
        return count_leaves(node->left.get()) + count_leaves(node->right.get());
    }

    void count_nodes(const Node* node, int& count) const {
        if (!node) return;
        count++;
        count_nodes(node->left.get(),  count);
        count_nodes(node->right.get(), count);
    }
};

} // namespace mltree

// ============================================================
// 使用示例
// ============================================================
int main() {
    std::vector<std::vector<double>> X = {
        {1.0}, {2.0}, {3.0}, {4.0}, {5.0}, {6.0}, {7.0}, {8.0}
    };
    std::vector<double> Y = {1.0, 1.5, 2.0, 3.5, 5.0, 5.5, 7.0, 8.5};

    // --------------------------------------------------------
    // 场景 A: 默认参数 (MinParentSize=10)
    //   8 样本 < 10 -> 根节点即为叶节点
    // --------------------------------------------------------
    {
        mltree::FitRTreeParams p_default;
        mltree::RegressionTree tree;
        tree.fit(X, Y, p_default);

        std::cout << "=== Scenario A: MATLAB defaults ===\n";
        std::cout << "NumNodes  : " << tree.num_nodes()  << "\n";
        std::cout << "NumLeaves : " << tree.num_leaves() << "\n";
        std::cout << "RootMean  : " << tree.predict(std::vector<double>{0.0}) << "\n";
        std::cout << "ResubLoss : " << tree.resub_loss(X, Y) << "\n\n";
    }

    // --------------------------------------------------------
    // 场景 B: MinParentSize=4 (MATLAB 文档示例)
    // --------------------------------------------------------
    {
        mltree::FitRTreeParams p;
        p.MinParentSize = 4;
        p.MinLeafSize   = 1;
        p.MaxNumSplits  = -1;

        mltree::RegressionTree tree;
        tree.fit(X, Y, p);

        std::cout << "=== Scenario B: MinParentSize=4 ===\n";
        std::cout << "NumNodes  : " << tree.num_nodes()  << "\n";
        std::cout << "NumLeaves : " << tree.num_leaves() << "\n";

        std::vector<double> Xtest = {1.5, 4.5, 7.5};
        for (double v : Xtest) {
            std::cout << "predict(" << v << ") = "
                      << std::setprecision(4) << tree.predict(std::vector<double>{v}) << "\n";
        }
        std::cout << "ResubLoss : " << tree.resub_loss(X, Y) << "\n";
    }

    return 0;
}
相关推荐
OPEN-F1 小时前
ROS2系列教程:上位机建图与导航(原理综合)
算法·机器人
AI wins1 小时前
c++新增頭文件
c++
Shadow(⊙o⊙)1 小时前
仿muduo实现高并发服务器——事件就绪管理Channel.hpp
服务器·c++
爱读书的小胖1 小时前
Codewars:Rot13(C)解题分析
c语言·开发语言
tomwang03221 小时前
路径优化建模、优化算法分类及工程化小结
算法·数学建模·分类
小羊没烦恼!1 小时前
Ajax与JSON的一些总结
android·java·前端·数据库·mysql·算法·okhttp
鸽鸽程序猿1 小时前
【项目】【在线判题系统】b端题目增删改
开发语言·python
by209991 小时前
哈希表——快速找到数据:冲突、删除与扩容的实现
数据结构·c++·哈希算法·散列表
Lhappy嘻嘻1 小时前
读懂 JVM:内存模型、类加载机制、分代回收
java·开发语言·jvm