基于决策树数据挖掘技术的肿瘤良恶性判别开发指导请求
决策树实现开发指导(肿瘤良恶性分类)
一、核心思路:先从节点结构入手,别着急搞类继承
你之前考虑的父决策类+属性子类方案,对于入门级决策树来说属于过度设计,反而会放大指针使用的复杂度。先从最基础的节点结构搭起,逻辑会更清晰。
1. 定义决策树节点结构体
用struct足够覆盖需求,节点分两种类型:
- 决策节点:存储属性索引、分割阈值、左右子节点指针
- 叶子节点:存储最终分类结果
示例代码(C++):
#include <string> #include <vector> // 分类结果枚举 enum class TumorType { BENIGN, MALIGNANT }; // 决策树节点结构体 struct TreeNode { bool is_leaf = false; // 决策节点专属字段 int feature_idx = -1; // 属性对应的CSV列索引 double threshold = 0.0; TreeNode* left = nullptr; // 满足条件分支(如<=阈值) TreeNode* right = nullptr; // 不满足条件分支(如>阈值) // 叶子节点专属字段 TumorType result; // 构造函数简化初始化 TreeNode(int idx, double thresh) : is_leaf(false), feature_idx(idx), threshold(thresh) {} TreeNode(TumorType res) : is_leaf(true), result(res) {} // 析构函数:递归释放子节点,避免内存泄漏 ~TreeNode() { delete left; delete right; } };
二、CSV数据转模型可用格式
你已经完成CSV读取,现在需要把每行数据转换成特征向量+标签的结构:
- 特征向量:将所有数值型属性(如Radius Mean、Texture Mean)转成
vector<double> - 标签:将诊断结果(B/M)转成
TumorType枚举
示例转换逻辑:
// 假设CSVReader返回vector<CSVLine>,CSVLine提供getCol(int idx)获取列值 struct DataSample { vector<double> features; TumorType label; }; vector<DataSample> convertCSVToSamples(const vector<CSVLine>& csv_lines) { vector<DataSample> samples; for (size_t i = 1; i < csv_lines.size(); ++i) { // 跳过表头行 DataSample sample; // 读取第2到第31列(假设30个数值属性从第2列开始) for (int col = 2; col <= 31; ++col) { sample.features.push_back(stod(csv_lines[i].getCol(col))); } // 读取最后一列诊断结果 string diag = csv_lines[i].getCol(csv_lines[i].getColCount() - 1); sample.label = (diag == "M") ? TumorType::MALIGNANT : TumorType::BENIGN; samples.push_back(sample); } return samples; }
三、实现决策树核心构建逻辑
决策树构建的关键是选择最优分割属性和阈值,优先用基尼系数(Gini Impurity),实现更简单:
1. 计算基尼系数
double calculateGini(const vector<DataSample>& samples) { if (samples.empty()) return 0.0; int benign_count = 0, malignant_count = 0; for (const auto& s : samples) { s.label == TumorType::BENIGN ? benign_count++ : malignant_count++; } double p_benign = (double)benign_count / samples.size(); double p_malignant = (double)malignant_count / samples.size(); return 1 - (p_benign*p_benign + p_malignant*p_malignant); }
2. 按属性和阈值分割样本,计算分割后基尼系数
pair<pair<vector<DataSample>, vector<DataSample>>, double> splitSamples( const vector<DataSample>& samples, int feature_idx, double threshold) { vector<DataSample> left, right; for (const auto& s : samples) { s.features[feature_idx] <= threshold ? left.push_back(s) : right.push_back(s); } double gini_left = calculateGini(left); double gini_right = calculateGini(right); double weighted_gini = (left.size()*gini_left + right.size()*gini_right) / samples.size(); return {{left, right}, weighted_gini}; }
3. 遍历所有属性,找到最优分割点
pair<int, double> findBestSplit(const vector<DataSample>& samples) { int best_feature = -1; double best_threshold = 0.0; double best_gini = 1.0; // 初始设为最大基尼系数 int num_features = samples[0].features.size(); for (int f_idx = 0; f_idx < num_features; ++f_idx) { // 收集当前属性的所有唯一值作为候选阈值 vector<double> values; for (const auto& s : samples) values.push_back(s.features[f_idx]); sort(values.begin(), values.end()); values.erase(unique(values.begin(), values.end()), values.end()); // 遍历候选阈值,找基尼系数最小的分割点 for (size_t i = 0; i < values.size()-1; ++i) { double threshold = (values[i] + values[i+1]) / 2; auto [split, gini] = splitSamples(samples, f_idx, threshold); if (gini < best_gini) { best_gini = gini; best_feature = f_idx; best_threshold = threshold; } } } return {best_feature, best_threshold}; }
4. 递归构建决策树
TreeNode* buildTree(const vector<DataSample>& samples) { // 终止条件1:所有样本属于同一类别,返回叶子节点 bool all_same = true; TumorType first_label = samples[0].label; for (const auto& s : samples) { if (s.label != first_label) { all_same = false; break; } } if (all_same) return new TreeNode(first_label); // 统计样本类别数量 int benign_count = 0, malignant_count = 0; for (const auto& s : samples) { s.label == TumorType::BENIGN ? benign_count++ : malignant_count++; } // 终止条件2:无法分割,返回占多数的类别 if (benign_count == 0 || malignant_count == 0) { return new TreeNode(benign_count > malignant_count ? TumorType::BENIGN : TumorType::MALIGNANT); } // 找到最优分割点 auto [best_feature, best_threshold] = findBestSplit(samples); if (best_feature == -1) { return new TreeNode(benign_count > malignant_count ? TumorType::BENIGN : TumorType::MALIGNANT); } // 分割样本并递归构建子树 auto [split, _] = splitSamples(samples, best_feature, best_threshold); auto [left_samples, right_samples] = split; TreeNode* node = new TreeNode(best_feature, best_threshold); node->left = buildTree(left_samples); node->right = buildTree(right_samples); return node; }
四、实现预测逻辑
用构建好的决策树对新样本做预测:
TumorType predict(TreeNode* root, const DataSample& sample) { if (root->is_leaf) return root->result; return sample.features[root->feature_idx] <= root->threshold ? predict(root->left, sample) : predict(root->right, sample); }
五、指针问题简化建议
- 先跑通上述基于结构体的指针逻辑,每个节点只管理自身的左右子节点,析构函数递归释放即可,逻辑清晰。
- 后续若要扩展(如支持不同分割指标),再考虑用策略模式,而非属性继承父类,避免指针复杂度飙升。
六、测试与优化
- 将CSV数据按8:2分成训练集和测试集,用训练集构建树,测试集验证准确率
- 加入剪枝逻辑(如限制树的最大深度),避免过拟合
内容的提问来源于stack exchange,提问作者Dylan
相关产品推荐
相关产品推荐

