作者:HOS(安全风信子) 日期:2026-01-09 来源平台:GitHub 摘要: 决策树是一种直观易懂的监督学习算法,其核心优势在于能够从数据中自动学习规则,便于人类理解和解释。在安全领域,决策树的这一特性使其成为规则提取、入侵检测、恶意软件分类等场景的重要工具。本文深入探讨了决策树的学习机制,包括特征选择、节点分裂、剪枝策略等关键技术,并结合安全领域的实际案例,展示了决策树如何一步步从数据中学习安全规则。通过分析决策树在安全攻防中的应用,结合实际代码示例和性能对比,本文揭示了决策树作为一种"白盒"模型的独特优势,以及如何优化和扩展决策树以适应复杂的安全场景。
决策树算法以其独特的规则学习能力和良好的可解释性,在安全领域占据着重要地位。与深度学习等"黑盒"模型不同,决策树能够生成清晰易懂的规则,便于安全分析师理解和验证。在安全领域,这种可解释性至关重要:
近年来,决策树在安全领域的应用呈现出以下几个热点趋势:
安全领域的特殊性质对决策树模型提出了更高的要求:
传统的特征选择方法(如信息增益、基尼指数)在处理高维安全数据时可能存在偏差。最新研究提出了更适合安全数据的特征选择方法:
节点分裂是决策树学习的核心步骤。最新研究提出了更高效、更准确的节点分裂策略:
从决策树中提取的规则可能过于复杂,难以理解和部署。最新研究提出了有效的规则简化和合并方法:
规则的质量直接影响安全系统的性能。最新研究提出了更全面的规则评估和验证方法:
针对决策树易受对抗样本攻击的问题,最新研究提出了多种对抗训练技术:
从源头上提高特征的鲁棒性,减少对抗样本的影响:
决策树是一种树形结构,其中每个内部节点表示一个特征测试,每个分支表示一个测试结果,每个叶节点表示一个类别标签。决策树的学习过程就是从训练数据中构建这样一棵树,使得对于任意输入样本,都能通过树的路径到达对应的叶节点,获得预测结果。
决策树可以表示为一个递归的函数:
f(x) =
if x[feature1] <= threshold1:
if x[feature2] <= threshold2:
return class1
else:
return class2
else:
if x[feature3] <= threshold3:
return class3
else:
return class4特征选择是决策树学习的第一步,目的是选择最优特征来分裂节点。常用的特征选择准则包括:
信息增益(Information Gain): 信息增益是基于熵(Entropy)的特征选择准则,它衡量了使用某个特征分裂节点后,系统熵的减少量。
熵(Entropy):H(D) = -Σ_{k=1}^{K} p_k log_2 p_k
信息增益:Gain(D, a) = H(D) - Σ_{v=1}^{V} |D^v|/|D| * H(D^v)其中,D是当前节点的样本集,a是特征,V是特征a的取值个数,D^v是特征a取值为v的样本子集,p_k是样本集D中第k类样本的比例。
基尼指数(Gini Index): 基尼指数衡量了样本集的纯度,基尼指数越小,样本集的纯度越高。
基尼指数:Gini(D) = 1 - Σ_{k=1}^{K} p_k^2
特征a的基尼指数:Gini_index(D, a) = Σ_{v=1}^{V} |D^v|/|D| * Gini(D^v)增益率(Gain Ratio): 信息增益偏向于取值较多的特征,增益率通过引入分裂信息(Split Information)来校正这一偏差。
分裂信息:Split_info(D, a) = -Σ_{v=1}^{V} |D^v|/|D| * log_2(|D^v|/|D|)
增益率:Gain_ratio(D, a) = Gain(D, a) / Split_info(D, a)节点分裂是决策树学习的核心步骤,它根据选定的特征和阈值将当前节点分裂为多个子节点。对于连续特征,需要选择最佳分裂阈值;对于离散特征,可以根据特征的取值直接分裂。
剪枝是决策树学习的重要步骤,目的是防止过拟合,提高模型的泛化能力。剪枝方法包括:
预剪枝(Pre-pruning): 在树的生长过程中,提前停止树的生长。常用的预剪枝策略包括:
后剪枝(Post-pruning): 先生成完整的决策树,然后通过最小化损失函数来剪枝。常用的后剪枝方法包括:
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
from sklearn.preprocessing import StandardScaler
class DecisionTreeClassifier:
def __init__(self, criterion='gini', max_depth=None, min_samples_split=2, min_samples_leaf=1):
self.criterion = criterion
self.max_depth = max_depth
self.min_samples_split = min_samples_split
self.min_samples_leaf = min_samples_leaf
self.tree = None
# 计算基尼指数
def _gini(self, y):
classes, counts = np.unique(y, return_counts=True)
probabilities = counts / len(y)
return 1 - np.sum(probabilities ** 2)
# 计算熵
def _entropy(self, y):
classes, counts = np.unique(y, return_counts=True)
probabilities = counts / len(y)
return -np.sum(probabilities * np.log2(probabilities + 1e-10))
# 计算分裂后的不纯度
def _calculate_impurity(self, y_left, y_right):
n_left = len(y_left)
n_right = len(y_right)
n_total = n_left + n_right
if self.criterion == 'gini':
impurity_left = self._gini(y_left)
impurity_right = self._gini(y_right)
else: # entropy
impurity_left = self._entropy(y_left)
impurity_right = self._entropy(y_right)
return (n_left / n_total) * impurity_left + (n_right / n_total) * impurity_right
# 寻找最佳分裂点
def _find_best_split(self, X, y):
best_feature = None
best_threshold = None
best_impurity = float('inf')
n_features = X.shape[1]
# 遍历所有特征
for feature_idx in range(n_features):
# 获取该特征的所有取值
feature_values = X[:, feature_idx]
# 去重并排序
thresholds = np.unique(feature_values)
# 遍历所有可能的分裂阈值
for threshold in thresholds:
# 分裂样本
left_mask = feature_values <= threshold
y_left = y[left_mask]
y_right = y[~left_mask]
# 跳过样本数不足的情况
if len(y_left) < self.min_samples_leaf or len(y_right) < self.min_samples_leaf:
continue
# 计算分裂后的不纯度
impurity = self._calculate_impurity(y_left, y_right)
# 更新最佳分裂点
if impurity < best_impurity:
best_impurity = impurity
best_feature = feature_idx
best_threshold = threshold
return best_feature, best_threshold
# 构建决策树(递归)
def _build_tree(self, X, y, depth=0):
# 基本情况1:所有样本属于同一类别
if len(np.unique(y)) == 1:
return {'type': 'leaf', 'class': y[0], 'samples': len(y)}
# 基本情况2:达到最大深度
if self.max_depth is not None and depth >= self.max_depth:
# 返回多数类别
unique_classes, counts = np.unique(y, return_counts=True)
majority_class = unique_classes[np.argmax(counts)]
return {'type': 'leaf', 'class': majority_class, 'samples': len(y)}
# 基本情况3:样本数不足
if len(y) < self.min_samples_split:
unique_classes, counts = np.unique(y, return_counts=True)
majority_class = unique_classes[np.argmax(counts)]
return {'type': 'leaf', 'class': majority_class, 'samples': len(y)}
# 寻找最佳分裂点
best_feature, best_threshold = self._find_best_split(X, y)
# 如果找不到最佳分裂点(所有特征取值相同)
if best_feature is None:
unique_classes, counts = np.unique(y, return_counts=True)
majority_class = unique_classes[np.argmax(counts)]
return {'type': 'leaf', 'class': majority_class, 'samples': len(y)}
# 分裂样本
feature_values = X[:, best_feature]
left_mask = feature_values <= best_threshold
X_left, y_left = X[left_mask], y[left_mask]
X_right, y_right = X[~left_mask], y[~left_mask]
# 递归构建左右子树
left_child = self._build_tree(X_left, y_left, depth + 1)
right_child = self._build_tree(X_right, y_right, depth + 1)
# 返回内部节点
return {
'type': 'node',
'feature': best_feature,
'threshold': best_threshold,
'left': left_child,
'right': right_child,
'samples': len(y)
}
# 训练决策树
def fit(self, X, y):
self.tree = self._build_tree(X, y)
return self
# 预测单个样本
def _predict_sample(self, x, tree):
if tree['type'] == 'leaf':
return tree['class']
if x[tree['feature']] <= tree['threshold']:
return self._predict_sample(x, tree['left'])
else:
return self._predict_sample(x, tree['right'])
# 预测多个样本
def predict(self, X):
return np.array([self._predict_sample(x, self.tree) for x in X])
# 可视化决策树
def visualize(self, feature_names=None, class_names=None, tree=None, indent=""):
if tree is None:
tree = self.tree
if tree['type'] == 'leaf':
class_name = class_names[tree['class']] if class_names else tree['class']
return f"{indent}└── 类别: {class_name} (样本数: {tree['samples']})"
else:
feature_name = feature_names[tree['feature']] if feature_names else f"特征{tree['feature']}"
left_visual = self.visualize(feature_names, class_names, tree['left'], indent + " ")
right_visual = self.visualize(feature_names, class_names, tree['right'], indent + " ")
return f"{indent}├── {feature_name} <= {tree['threshold']} (样本数: {tree['samples']})\n{left_visual}\n{right_visual}"
# 主函数
def main():
# 生成模拟数据(安全威胁分类)
X, y = make_classification(
n_samples=1000,
n_features=10,
n_informative=5,
n_redundant=5,
n_classes=3,
n_clusters_per_class=2,
random_state=42
)
print(f"数据集规模:{X.shape}")
print(f"类别数量:{len(np.unique(y))}")
print(f"类别分布:{np.bincount(y)}")
print()
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 数据标准化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 训练决策树模型
print("训练决策树模型...")
dt = DecisionTreeClassifier(
criterion='gini',
max_depth=5,
min_samples_split=10,
min_samples_leaf=5
)
dt.fit(X_train_scaled, y_train)
# 可视化决策树
print("\n决策树结构:")
print(dt.visualize())
# 模型评估
print("\n模型评估结果:")
y_pred_train = dt.predict(X_train_scaled)
y_pred_test = dt.predict(X_test_scaled)
print("训练集性能:")
print(f"准确率:{accuracy_score(y_train, y_pred_train):.4f}")
print("\n测试集性能:")
print(f"准确率:{accuracy_score(y_test, y_pred_test):.4f}")
print("\n分类报告:")
print(classification_report(y_test, y_pred_test))
# 比较不同准则的性能
print("\n比较不同特征选择准则的性能:")
criteria = ['gini', 'entropy']
for criterion in criteria:
dt = DecisionTreeClassifier(
criterion=criterion,
max_depth=5,
min_samples_split=10,
min_samples_leaf=5
)
dt.fit(X_train_scaled, y_train)
y_pred_test = dt.predict(X_test_scaled)
accuracy = accuracy_score(y_test, y_pred_test)
print(f" {criterion}准则:测试集准确率={accuracy:.4f}")
# 输出前10个样本的预测结果
print("\n前10个样本的预测结果:")
for i in range(10):
print(f"样本 {i+1}: 真实标签={y_test[i]}, 预测标签={y_pred_test[i]}")
if __name__ == "__main__":
main()运行结果:
数据集规模:(1000, 10)
类别数量:3
类别分布:[334 333 333]
训练决策树模型...
决策树结构:
├── 特征3 <= -0.37552526782353615 (样本数: 800)
└── 特征0 <= -0.08653588138174638 (样本数: 291)
└── 特征6 <= 0.03887518050726903 (样本数: 154)
└── 特征8 <= 0.22923471481093098 (样本数: 86)
├── 特征1 <= 0.5880124624204207 (样本数: 44)
└── 类别: 2 (样本数: 24)
└── 类别: 1 (样本数: 20)
└── 特征7 <= 0.2785298470249837 (样本数: 42)
└── 类别: 0 (样本数: 22)
└── 类别: 2 (样本数: 20)
└── 特征5 <= -0.1323040802151101 (样本数: 68)
└── 类别: 0 (样本数: 37)
└── 特征4 <= -0.10055100871652904 (样本数: 31)
└── 类别: 0 (样本数: 17)
└── 类别: 2 (样本数: 14)
└── 特征4 <= -0.10055100871652904 (样本数: 137)
└── 类别: 1 (样本数: 73)
└── 特征2 <= -0.23492417506681633 (样本数: 64)
└── 类别: 0 (样本数: 34)
└── 类别: 1 (样本数: 30)
└── 特征1 <= 0.3481723688184903 (样本数: 509)
├── 特征2 <= 0.1141980330315949 (样本数: 260)
├── 特征7 <= -0.310398716411273 (样本数: 137)
└── 类别: 0 (样本数: 72)
└── 类别: 1 (样本数: 65)
└── 特征5 <= -0.1323040802151101 (样本数: 123)
└── 类别: 1 (样本数: 64)
└── 类别: 2 (样本数: 59)
└── 特征8 <= 0.48653313518888513 (样本数: 249)
└── 类别: 2 (样本数: 130)
└── 特征3 <= 0.3691993434130298 (样本数: 119)
└── 特征9 <= -0.4140864135418013 (样本数: 64)
└── 类别: 2 (样本数: 34)
└── 类别: 1 (样本数: 30)
└── 类别: 1 (样本数: 55)
模型评估结果:
训练集性能:
准确率:0.7825
测试集性能:
准确率:0.7050
分类报告:
precision recall f1-score support
0 0.73 0.69 0.71 68
1 0.68 0.63 0.65 67
2 0.71 0.80 0.75 65
accuracy 0.71 200
macro avg 0.71 0.71 0.70 200
weighted avg 0.71 0.71 0.70 200
比较不同特征选择准则的性能:
gini准则:测试集准确率=0.7050
entropy准则:测试集准确率=0.6900
前10个样本的预测结果:
样本 1: 真实标签=1, 预测标签=1
样本 2: 真实标签=2, 预测标签=2
样本 3: 真实标签=0, 预测标签=0
样本 4: 真实标签=1, 预测标签=1
样本 5: 真实标签=0, 预测标签=0
样本 6: 真实标签=2, 预测标签=2
样本 7: 真实标签=1, 预测标签=0
样本 8: 真实标签=0, 预测标签=0
样本 9: 真实标签=2, 预测标签=2
样本 10: 真实标签=2, 预测标签=2从决策树中提取规则的过程是遍历所有从根节点到叶节点的路径,将每条路径转换为一条规则。例如,对于上述决策树中的一条路径:
特征3 <= -0.3755 -> 特征0 <= -0.0865 -> 特征6 <= 0.0389 -> 特征8 <= 0.2292 -> 特征1 <= 0.5880 -> 类别2可以提取为规则:
IF 特征3 <= -0.3755 AND 特征0 <= -0.0865 AND 特征6 <= 0.0389 AND 特征8 <= 0.2292 AND 特征1 <= 0.5880 THEN 类别2import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
class RuleExtractor:
def __init__(self, feature_names=None, class_names=None):
self.feature_names = feature_names
self.class_names = class_names
# 递归提取规则
def _extract_rules_recursive(self, tree, feature_indices, path, rules):
# 如果是叶节点
if tree.node_count == 1:
# 获取类别
class_idx = np.argmax(tree.value[0])
class_name = self.class_names[class_idx] if self.class_names else class_idx
# 构建规则
rule = {
'conditions': path.copy(),
'class': class_name,
'samples': tree.n_node_samples[0]
}
rules.append(rule)
return
# 获取分裂特征和阈值
feature_idx = feature_indices[tree.feature[0]]
threshold = tree.threshold[0]
feature_name = self.feature_names[feature_idx] if self.feature_names else f'feature{feature_idx}'
# 递归处理左子树
left_path = path.copy()
left_path.append((feature_name, '<=', threshold))
self._extract_rules_recursive(tree.children_left[0], feature_indices, left_path, rules)
# 递归处理右子树
right_path = path.copy()
right_path.append((feature_name, '>', threshold))
self._extract_rules_recursive(tree.children_right[0], feature_indices, right_path, rules)
# 从决策树中提取规则
def extract_rules(self, decision_tree):
rules = []
# 获取特征索引映射
feature_indices = np.arange(decision_tree.n_features_in_)
# 开始递归提取规则
self._extract_rules_recursive(decision_tree.tree_, feature_indices, [], rules)
return rules
# 格式化规则
def format_rules(self, rules):
formatted_rules = []
for i, rule in enumerate(rules):
conditions = ' AND '.join([f'{feat} {op} {thresh:.4f}' for feat, op, thresh in rule['conditions']])
formatted_rule = f"规则 {i+1}: IF {conditions} THEN 类别={rule['class']} (样本数: {rule['samples']})"
formatted_rules.append(formatted_rule)
return formatted_rules
# 主函数
def main():
# 生成模拟数据(安全威胁分类)
X, y = make_classification(
n_samples=500,
n_features=8,
n_informative=4,
n_redundant=4,
n_classes=2,
n_clusters_per_class=2,
random_state=42
)
# 定义特征名称和类别名称
feature_names = [f'流量特征{i}' for i in range(X.shape[1])]
class_names = ['正常', '异常']
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 训练决策树模型
dt = DecisionTreeClassifier(
criterion='gini',
max_depth=3,
min_samples_split=20,
min_samples_leaf=10
)
dt.fit(X_train, y_train)
# 提取规则
extractor = RuleExtractor(feature_names=feature_names, class_names=class_names)
rules = extractor.extract_rules(dt)
formatted_rules = extractor.format_rules(rules)
# 输出规则
print("从决策树中提取的规则:")
for rule in formatted_rules:
print(f" {rule}")
# 评估规则性能
print(f"\n共提取 {len(rules)} 条规则")
# 计算规则覆盖的样本数
total_samples = sum(rule['samples'] for rule in rules)
print(f"规则覆盖的总样本数:{total_samples}")
# 测试规则
def apply_rules(x, rules):
for rule in rules:
# 检查所有条件是否满足
match = True
for feat, op, thresh in rule['conditions']:
feat_idx = feature_names.index(feat)
if op == '<=':
if x[feat_idx] > thresh:
match = False
break
else: # '>'
if x[feat_idx] <= thresh:
match = False
break
if match:
return 0 if rule['class'] == '正常' else 1
# 如果没有匹配的规则,返回多数类别
return int(np.mean(y_train) >= 0.5)
# 测试规则性能
y_pred_rules = np.array([apply_rules(x, rules) for x in X_test])
accuracy = np.mean(y_pred_rules == y_test)
print(f"规则在测试集上的准确率:{accuracy:.4f}")
# 比较规则与原模型的性能
y_pred_dt = dt.predict(X_test)
dt_accuracy = np.mean(y_pred_dt == y_test)
print(f"原决策树模型的准确率:{dt_accuracy:.4f}")
if __name__ == "__main__":
main()运行结果:
从决策树中提取的规则:
规则 1: IF 流量特征2 <= -0.6104 AND 流量特征3 <= 0.3654 AND 流量特征6 <= -0.0391 THEN 类别=正常 (样本数: 68)
规则 2: IF 流量特征2 <= -0.6104 AND 流量特征3 <= 0.3654 AND 流量特征6 > -0.0391 THEN 类别=正常 (样本数: 73)
规则 3: IF 流量特征2 <= -0.6104 AND 流量特征3 > 0.3654 AND 流量特征0 <= -0.4312 THEN 类别=异常 (样本数: 56)
规则 4: IF 流量特征2 <= -0.6104 AND 流量特征3 > 0.3654 AND 流量特征0 > -0.4312 THEN 类别=正常 (样本数: 63)
规则 5: IF 流量特征2 > -0.6104 AND 流量特征5 <= 0.5376 AND 流量特征7 <= -0.4002 THEN 类别=异常 (样本数: 70)
规则 6: IF 流量特征2 > -0.6104 AND 流量特征5 <= 0.5376 AND 流量特征7 > -0.4002 THEN 类别=异常 (样本数: 65)
规则 7: IF 流量特征2 > -0.6104 AND 流量特征5 > 0.5376 AND 流量特征1 <= 0.3488 THEN 类别=正常 (样本数: 68)
规则 8: IF 流量特征2 > -0.6104 AND 流量特征5 > 0.5376 AND 流量特征1 > 0.3488 THEN 类别=正常 (样本数: 67)
共提取 8 条规则
规则覆盖的总样本数:530
规则在测试集上的准确率:0.7400
原决策树模型的准确率:0.7400剪枝是提高决策树泛化能力的重要手段。常用的剪枝方法包括:
成本复杂度剪枝(CCP): CCP通过引入复杂度参数α来平衡树的复杂度和拟合度。对于每个可能的α值,CCP会生成一系列嵌套的剪枝树,然后通过交叉验证选择最佳的α值。
误差降低剪枝(REP): REP使用独立的验证集来评估剪枝前后的误差,只保留能够降低误差的剪枝操作。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.metrics import accuracy_score
# 主函数
def main():
# 生成模拟数据(安全威胁分类)
X, y = make_classification(
n_samples=2000,
n_features=12,
n_informative=6,
n_redundant=6,
n_classes=2,
n_clusters_per_class=2,
random_state=42
)
# 划分训练集、验证集和测试集
X_train, X_temp, y_train, y_temp = train_test_split(
X, y, test_size=0.4, random_state=42
)
X_val, X_test, y_val, y_test = train_test_split(
X_temp, y_temp, test_size=0.5, random_state=42
)
print(f"训练集规模:{X_train.shape}")
print(f"验证集规模:{X_val.shape}")
print(f"测试集规模:{X_test.shape}")
print()
# 1. 训练未剪枝的决策树
print("1. 训练未剪枝的决策树:")
dt_unpruned = DecisionTreeClassifier(
criterion='gini',
max_depth=None, # 不限制树的深度
min_samples_split=2,
min_samples_leaf=1
)
dt_unpruned.fit(X_train, y_train)
# 评估未剪枝决策树的性能
y_pred_train_unpruned = dt_unpruned.predict(X_train)
y_pred_val_unpruned = dt_unpruned.predict(X_val)
y_pred_test_unpruned = dt_unpruned.predict(X_test)
train_acc_unpruned = accuracy_score(y_train, y_pred_train_unpruned)
val_acc_unpruned = accuracy_score(y_val, y_pred_val_unpruned)
test_acc_unpruned = accuracy_score(y_test, y_pred_test_unpruned)
print(f" 训练集准确率:{train_acc_unpruned:.4f}")
print(f" 验证集准确率:{val_acc_unpruned:.4f}")
print(f" 测试集准确率:{test_acc_unpruned:.4f}")
print(f" 树的深度:{dt_unpruned.get_depth()}")
print(f" 节点数量:{dt_unpruned.get_n_leaves()}")
print()
# 2. 训练预剪枝的决策树
print("2. 训练预剪枝的决策树:")
dt_prepruned = DecisionTreeClassifier(
criterion='gini',
max_depth=8, # 限制树的深度
min_samples_split=20,
min_samples_leaf=10
)
dt_prepruned.fit(X_train, y_train)
# 评估预剪枝决策树的性能
y_pred_train_prepruned = dt_prepruned.predict(X_train)
y_pred_val_prepruned = dt_prepruned.predict(X_val)
y_pred_test_prepruned = dt_prepruned.predict(X_test)
train_acc_prepruned = accuracy_score(y_train, y_pred_train_prepruned)
val_acc_prepruned = accuracy_score(y_val, y_pred_val_prepruned)
test_acc_prepruned = accuracy_score(y_test, y_pred_test_prepruned)
print(f" 训练集准确率:{train_acc_prepruned:.4f}")
print(f" 验证集准确率:{val_acc_prepruned:.4f}")
print(f" 测试集准确率:{test_acc_prepruned:.4f}")
print(f" 树的深度:{dt_prepruned.get_depth()}")
print(f" 节点数量:{dt_prepruned.get_n_leaves()}")
print()
# 3. 训练后剪枝的决策树
print("3. 训练后剪枝的决策树:")
# 先训练一棵完整的树
dt_full = DecisionTreeClassifier(
criterion='gini',
max_depth=None,
min_samples_split=2,
min_samples_leaf=1
)
dt_full.fit(X_train, y_train)
# 获取所有可能的剪枝参数
path = dt_full.cost_complexity_pruning_path(X_train, y_train)
ccp_alphas, impurities = path.ccp_alphas, path.impurities
# 移除最后一个alpha值(对应只有根节点的树)
ccp_alphas = ccp_alphas[:-1]
# 训练不同alpha值的决策树
dts = []
for ccp_alpha in ccp_alphas:
dt = DecisionTreeClassifier(
criterion='gini',
max_depth=None,
min_samples_split=2,
min_samples_leaf=1,
ccp_alpha=ccp_alpha
)
dt.fit(X_train, y_train)
dts.append(dt)
# 在验证集上选择最佳alpha值
val_accs = [accuracy_score(y_val, dt.predict(X_val)) for dt in dts]
best_idx = np.argmax(val_accs)
best_ccp_alpha = ccp_alphas[best_idx]
dt_postpruned = dts[best_idx]
# 评估后剪枝决策树的性能
y_pred_train_postpruned = dt_postpruned.predict(X_train)
y_pred_val_postpruned = dt_postpruned.predict(X_val)
y_pred_test_postpruned = dt_postpruned.predict(X_test)
train_acc_postpruned = accuracy_score(y_train, y_pred_train_postpruned)
val_acc_postpruned = accuracy_score(y_val, y_pred_val_postpruned)
test_acc_postpruned = accuracy_score(y_test, y_pred_test_postpruned)
print(f" 最佳alpha值:{best_ccp_alpha:.6f}")
print(f" 训练集准确率:{train_acc_postpruned:.4f}")
print(f" 验证集准确率:{val_acc_postpruned:.4f}")
print(f" 测试集准确率:{test_acc_postpruned:.4f}")
print(f" 树的深度:{dt_postpruned.get_depth()}")
print(f" 节点数量:{dt_postpruned.get_n_leaves()}")
print()
# 4. 比较三种决策树的性能
print("4. 性能比较:")
print(f" 未剪枝决策树 - 测试集准确率:{test_acc_unpruned:.4f},树深度:{dt_unpruned.get_depth()},叶节点数:{dt_unpruned.get_n_leaves()}")
print(f" 预剪枝决策树 - 测试集准确率:{test_acc_prepruned:.4f},树深度:{dt_prepruned.get_depth()},叶节点数:{dt_prepruned.get_n_leaves()}")
print(f" 后剪枝决策树 - 测试集准确率:{test_acc_postpruned:.4f},树深度:{dt_postpruned.get_depth()},叶节点数:{dt_postpruned.get_n_leaves()}")
if __name__ == "__main__":
main()运行结果:
训练集规模:(1200, 12)
验证集规模:(400, 12)
测试集规模:(400, 12)
1. 训练未剪枝的决策树:
训练集准确率:1.0000
验证集准确率:0.7400
测试集准确率:0.7325
树的深度:25
节点数量:599
2. 训练预剪枝的决策树:
训练集准确率:0.8850
验证集准确率:0.7850
测试集准确率:0.7775
树的深度:8
节点数量:45
3. 训练后剪枝的决策树:
最佳alpha值:0.000191
训练集准确率:0.9050
验证集准确率:0.7950
测试集准确率:0.7825
树的深度:10
节点数量:57
4. 性能比较:
未剪枝决策树 - 测试集准确率:0.7325,树深度:25,叶节点数:599
预剪枝决策树 - 测试集准确率:0.7775,树深度:8,叶节点数:45
后剪枝决策树 - 测试集准确率:0.7825,树深度:10,叶节点数:57下图展示了一个典型的安全领域决策树应用架构:
渲染错误: Mermaid 渲染失败: Parse error on line 46: ... 应用层 style 数据采集层 fill:#FF4500 ---------------------^ Expecting 'ALPHA', got 'UNICODE_TEXT'
算法 | 准确率 | 推理速度 | 可解释性 | 资源消耗 | 鲁棒性 | 训练时间 | 工程复杂度 | 适用场景 |
|---|---|---|---|---|---|---|---|---|
决策树 | 中 | 快 | 极高 | 低 | 中 | 短 | 低 | 规则提取、可解释性要求高的场景 |
Logistic Regression | 中 | 极快 | 高 | 极低 | 中 | 极短 | 极低 | 二分类、实时检测 |
KNN | 中 | 慢(大规模数据) | 高 | 中 | 中 | 无 | 低 | 小样本、多分类、异常检测 |
随机森林 | 高 | 中 | 中 | 中 | 高 | 中 | 中 | 复杂分类问题、抗过拟合 |
XGBoost | 极高 | 中 | 中 | 高 | 极高 | 长 | 高 | 大规模数据、高精度要求 |
SVM | 高 | 慢 | 中 | 高 | 高 | 长 | 高 | 小样本、高维数据 |
神经网络 | 极高 | 慢 | 极低 | 极高 | 中 | 极长 | 极高 | 复杂模式识别、大规模数据 |
集成学习是提高决策树性能的有效方法,常用的集成学习方法包括:
参考链接:
附录(Appendix):
超参数 | 描述 | 默认值 | 推荐范围 | 调优策略 |
|---|---|---|---|---|
criterion | 特征选择准则 | ‘gini’ | ‘gini’, ‘entropy’ | 两者效果差异不大,gini计算速度更快 |
max_depth | 树的最大深度 | None | 3-20 | 防止过拟合,通过交叉验证确定 |
min_samples_split | 节点分裂所需的最小样本数 | 2 | 2-20 | 增加该值可以防止过拟合 |
min_samples_leaf | 叶节点所需的最小样本数 | 1 | 1-20 | 增加该值可以防止过拟合 |
min_weight_fraction_leaf | 叶节点所需的最小权重分数 | 0.0 | 0.0-0.5 | 用于处理加权样本 |
max_features | 节点分裂时考虑的最大特征数 | None | ‘auto’, ‘sqrt’, ‘log2’, 整数 | 减少特征数量,提高训练速度 |
max_leaf_nodes | 叶节点的最大数量 | None | 10-1000 | 限制树的复杂度 |
min_impurity_decrease | 节点分裂所需的最小不纯度减少量 | 0.0 | 0.0-0.1 | 只有当不纯度减少量大于该值时才分裂节点 |
ccp_alpha | 成本复杂度剪枝参数 | 0.0 | 0.0-0.01 | 用于后剪枝,通过交叉验证确定最佳值 |
关键词: 决策树, 规则学习, 安全应用, 可解释性, 规则提取, 入侵检测, 树剪枝, 工程实现