昌都龙腾汽修 發表於 2025-9-9 11:29:00

彩笔运维勇闯机器学习--决策树

<h2 id="前言">前言</h2>
<p>决策树是一种常用的机器学习模型,用于分类和回归任务,它通过模拟“树”的结构来对数据进行决策。本节我们详细讨论的是决策树中的分类任务</p>
<h2 id="开始探索">开始探索</h2>
<h4 id="scikit-learn">scikit-learn</h4>
<p>假设以下运维场景</p>
<ul>
<li>CPU
<ul>
<li>低:&lt;40%</li>
<li>中:40%~70%</li>
<li>高:&gt;70%</li>
</ul>
</li>
<li>内存
<ul>
<li>低:&lt;60%</li>
<li>中:60%~85%</li>
<li>高:&gt;85%</li>
</ul>
</li>
<li>磁盘I/O
<ul>
<li>低:&lt;40%</li>
<li>中:40%~70%</li>
<li>高:&gt;70%</li>
</ul>
</li>
<li>日志报错数(条)
<ul>
<li>少:&lt;20</li>
<li>中:20~50</li>
<li>多:&gt;50</li>
</ul>
</li>
</ul>
<table>
<thead>
<tr>
<th>CPU</th>
<th>内存</th>
<th>磁盘I/O</th>
<th>日志报错数</th>
<th>系统是否稳定</th>
</tr>
</thead>
<tbody>
<tr>
<td>低</td>
<td>低</td>
<td>低</td>
<td>少</td>
<td>稳定</td>
</tr>
<tr>
<td>中</td>
<td>中</td>
<td>低</td>
<td>少</td>
<td>稳定</td>
</tr>
<tr>
<td>低</td>
<td>中</td>
<td>高</td>
<td>少</td>
<td>稳定</td>
</tr>
<tr>
<td>低</td>
<td>低</td>
<td>低</td>
<td>多</td>
<td>不稳定</td>
</tr>
<tr>
<td>低</td>
<td>低</td>
<td>高</td>
<td>中</td>
<td>不稳定</td>
</tr>
<tr>
<td>高</td>
<td>搞</td>
<td>低</td>
<td>少</td>
<td>不稳定</td>
</tr>
<tr>
<td>中</td>
<td>高</td>
<td>低</td>
<td>中</td>
<td>稳定</td>
</tr>
<tr>
<td>高</td>
<td>低</td>
<td>低</td>
<td>多</td>
<td>不稳定</td>
</tr>
<tr>
<td>中</td>
<td>高</td>
<td>高</td>
<td>少</td>
<td>不稳定</td>
</tr>
<tr>
<td>中</td>
<td>中</td>
<td>中</td>
<td>中</td>
<td>不稳定</td>
</tr>
<tr>
<td>高</td>
<td>高</td>
<td>低</td>
<td>少</td>
<td>稳定</td>
</tr>
</tbody>
</table>
<p>将其转换成代码:</p>
<pre><code>import pandas as pd
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import classification_report


level_map = {'低': 0, '中': 1, '高': 2, '少': 0, '中': 1, '高': 2, '多':2, '稳定': 1, '不稳定': 0}
data = [
    ['低', '低', '低', '少', '稳定'],
    ['中', '中', '低', '少', '稳定'],
    ['低', '中', '高', '少', '稳定'],
    ['低', '低', '低', '多', '不稳定'],
    ['低', '低', '高', '多', '不稳定'],
    ['高', '高', '低', '少', '不稳定'],
    ['中', '高', '低', '中', '稳定'],
    ['高', '低', '低', '多', '不稳定'],
    ['中', '高', '高', '少', '不稳定'],
    ['中', '中', '中', '中', '不稳定'],
    ['高', '高', '低', '少', '稳定'],
]

data_mapped = [ for v in row] for row in data]

df = pd.DataFrame(data_mapped, columns=['CPU', '内存', '磁盘IO', '日志报错数', '系统是否稳定'])

X = df[['CPU', '内存', '磁盘IO', '日志报错数']]
y = df['系统是否稳定']

clf = DecisionTreeClassifier(criterion='entropy', random_state=0)
clf.fit(X, y)
y_pred = clf.predict(X)

print("分类报告:\n", classification_report(y, y_pred, target_names=['不稳定', '稳定']))

</code></pre>
<p>脚本!启动</p>
<p><img alt="watermarked-decision_trees_1_1" loading="lazy" src="https://img2024.cnblogs.com/blog/1416773/202509/1416773-20250909105745454-18376028.png" class="lazyload"></p>
<p>绘图更能直接观察决策树</p>
<pre><code>from sklearn import tree
import matplotlib.pyplot as plt

plt.figure(figsize=(14, 8))
tree.plot_tree(clf,
               feature_names=['CPU', 'memory', 'storage I/O', 'error count'],
               class_names=['not stable', 'stable'],
               filled=True)
plt.show()

</code></pre>
<p><img alt="watermarked-decision_trees_1_2" loading="lazy" src="https://img2024.cnblogs.com/blog/1416773/202509/1416773-20250909105754559-1832304630.png" class="lazyload"></p>
<h4 id="分析绘图">分析绘图</h4>
<ul>
<li>每个矩形框代表一个决策节点(叶子节点),比如第一个框,<code>error count&lt;1.5</code>表示用该特征来分类,1.5是基于输入的数据计算出来的,模型认为1.5是特征分割点</li>
<li>entropy:当前节点的不确定性(越低越纯)</li>
<li>samples:这个节点上的样本数</li>
<li>value=[不稳定数, 稳定数]:类别分布</li>
<li>class:当前节点最终被分类的类别</li>
</ul>
<h2 id="深入理解决策树">深入理解决策树</h2>
<h4 id="熵entropy">熵(entropy)</h4>
<p>在图中有一个重要的指标,entropy。它是衡量数据纯度(不确定性)的指标,熵越低,表示类别越“纯”(不确定性越低);熵越高,代表数据越混杂</p>
<p></p><div class="math display">\[Entropy(D) = - \sum_{i=1}^{k} p_i \log_2 p_i
\]</div><p></p><ul>
<li>D 是某个数据集(或某个节点上的样本)</li>
<li>k 是类别数量(如“稳定”“不稳定”就是2类)</li>
<li><span class="math inline">\(p_i\)</span> 是第i类在该数据集中的比例</li>
</ul>
<p>比如某节点:<br>
<img alt="watermarked-decision_trees_1_3" loading="lazy" src="https://img2024.cnblogs.com/blog/1416773/202509/1416773-20250909105814699-668896883.png" class="lazyload"></p>
<p>样本数<code>samples=5</code>,类别<code>value=</code></p>
<p></p><div class="math display">\[p_1=\frac{1}{5},p_2=\frac{4}{5}
\]</div><p></p><p></p><div class="math display">\[Entropy(D) = - (\frac{1}{5}⋅log_2\frac{1}{5}+\frac{4}{5}⋅log_2\frac{4}{5}) = 0.7215
\]</div><p></p><p>熵越小,那就代表了该节点不确定性越低,如果该节点的上为0,那就不会继续分裂下去产生子节点;而不确定性越高的节点,就会继续分裂产生叶子节点</p>
<h4 id="信息增益">信息增益</h4>
<p>是决策树中用于选择划分特征的核心指标。它本质上衡量的是:“使用某个特征进行划分后,数据的不确定性减少了多少”</p>
<p>信息增益=原始熵−加权熵</p>
<p>比如以下节点,想要计算出信息增益</p>
<p><img alt="watermarked-decision_trees_1_4" loading="lazy" src="https://img2024.cnblogs.com/blog/1416773/202509/1416773-20250909105824403-2070613091.png" class="lazyload"></p>
<p>原始熵我们已经有了,那就是0.994,需要计算加权熵</p>
<p></p><div class="math display">\[加权熵=\frac{9}{11}⋅0.991+\frac{2}{11}⋅0=0.811
\]</div><p></p><p></p><div class="math display">\[原始熵=0.994−0.811= 0.183
\]</div><p></p><p>通过特征<code>error count &lt;= 1.5</code>划分所带来的信息增益为0.183,也就是减少了 18.3% 的“混乱”程度</p>
<ul>
<li>那该特征就是信息增益最大的特征了?</li>
<li>特征选的0.5、1.5的值是怎么来的?</li>
</ul>
<h4 id="特征选取的0515从何而来">特征选取的0.5、1.5从何而来</h4>
<p>特征从“低”、“中”、“高”等文字描述转换成,<code>0</code> <code>1</code> <code>2</code>等数字,比如CPU可能的取值是,那就可以尝试划分出阈值中位点</p>
<pre><code>(0+1)/2=0.5
(1+2)/2=1.5
</code></pre>
<p>所以在选取划分点的时候就会参考中位点进行划分</p>
<h4 id="选取信息增益最大的特征">选取信息增益最大的特征</h4>
<table>
<thead>
<tr>
<th>日志报错数</th>
<th>日志报错数数字化</th>
<th>系统是否稳定</th>
</tr>
</thead>
<tbody>
<tr>
<td>少</td>
<td>0</td>
<td>稳定</td>
</tr>
<tr>
<td>少</td>
<td>0</td>
<td>稳定</td>
</tr>
<tr>
<td>少</td>
<td>0</td>
<td>稳定</td>
</tr>
<tr>
<td>多</td>
<td>2</td>
<td>不稳定</td>
</tr>
<tr>
<td>中</td>
<td>1</td>
<td>不稳定</td>
</tr>
<tr>
<td>少</td>
<td>0</td>
<td>不稳定</td>
</tr>
<tr>
<td>中</td>
<td>1</td>
<td>稳定</td>
</tr>
<tr>
<td>多</td>
<td>2</td>
<td>不稳定</td>
</tr>
<tr>
<td>少</td>
<td>0</td>
<td>不稳定</td>
</tr>
<tr>
<td>中</td>
<td>1</td>
<td>不稳定</td>
</tr>
<tr>
<td>少</td>
<td>0</td>
<td>稳定</td>
</tr>
</tbody>
</table>
<hr>
<p>先选择<code>error count</code>以1.5为划分点</p>
<ul>
<li><code>error count&lt;=1.5</code>有9条,稳定与不稳定的比例为</li>
<li><code>error count&gt;1.5</code>有2条,并且都是不稳定的</li>
</ul>
<p>计算一下信息增益:</p>
<ul>
<li>首先计算原始熵:11个样本中,5个稳定,6个不稳定,<span class="math inline">\(Entropy=-(\frac{6}{11}⋅log_2\frac{6}{11}+\frac{5}{11}⋅log_2\frac{5}{11}) \approx 0.994\)</span>
<ul>
<li>再计算<code>error count&gt;=1.5</code>的熵:<span class="math inline">\(Entropy_1=-(\frac{4}{9}⋅log_2\frac{4}{9}+\frac{5}{9}⋅log_2\frac{5}{9}) \approx 0.991\)</span></li>
<li>再计算<code>error count&gt;1.5</code>的熵,由于全是<code>不稳定</code>,所以熵是0</li>
</ul>
</li>
<li>计算加权熵:<span class="math inline">\(Entropy_{weighted}=\frac{9}{11}⋅0.991+\frac{2}{11}⋅0 \approx 0.811\)</span></li>
<li>信息增益:0.994-0.811=0.183</li>
</ul>
<hr>
<p>选择<code>error count</code>以0.5为划分点</p>
<ul>
<li>
<p><code>error count&lt;=0.5</code>有6条,稳定与不稳定的比例为</p>
</li>
<li>
<p><code>error count&gt;0.5</code>有5条,稳定与不稳定的比例为</p>
</li>
<li>
<p>首先计算原始熵:上面已经计算过了,0.994</p>
<ul>
<li>再计算<code>error count&lt;=0.5</code>的熵:<span class="math inline">\(Entropy_1=-(\frac{4}{6}⋅log_2\frac{4}{6}+\frac{2}{6}⋅log_2\frac{2}{6}) \approx 0.918\)</span></li>
<li>再计算<code>error count&gt;0.5</code>的熵:<span class="math inline">\(Entropy_2=-(\frac{1}{5}⋅log_2\frac{1}{5}+\frac{4}{5}⋅log_2\frac{4}{5}) \approx 0.722\)</span></li>
</ul>
</li>
<li>
<p>计算加权熵:<span class="math inline">\(Entropy_{weighted}=\frac{6}{11}⋅0.918+\frac{5}{11}⋅0.722 \approx 0.829\)</span></p>
</li>
<li>
<p>信息增益:0.994-0.829=0.165</p>
</li>
</ul>
<hr>
<p>综上所述,<code>error count</code>为特征,以1.5为划分点,是合适的选择。并且将每个特征的每个划分点遍历一遍,得出<code>error count&lt;=1.5</code>是熵下降最快的划分点</p>
<p><img alt="watermarked-decision_trees_1_5" loading="lazy" src="https://img2024.cnblogs.com/blog/1416773/202509/1416773-20250909105835806-1278850724.png" class="lazyload"></p>
<h4 id="小结">小结</h4>
<p>算法步骤:</p>
<ul>
<li>计算当前数据集的熵</li>
<li>对每个特征求出信息增益</li>
<li>选择信息增益最大的特征进行节点分类</li>
<li>重复以上步骤,直至熵为0,或者特征用完,或者数到达规定层数</li>
</ul>
<p>有位彦祖说到,这不就是ID3算法嘛,基于信息熵作为划分标准。其实说对了一部分,文中使用的代码</p>
<pre><code>clf = DecisionTreeClassifier(criterion='entropy', random_state=0)
</code></pre>
<p><code>criterion='entropy'</code>使用的是的算法是整合了CART算法+ID3的信息熵分类等多种算法思想融合而来</p>
<h2 id="基尼指数">基尼指数</h2>
<p>之前讨论了通过熵的方式来划分节点,本节讨论基尼指数来划分节点</p>
<p></p><div class="math display">\[Gini(S) = 1 - \sum_{i=1}^{n} p_i^2
\]</div><p></p><ul>
<li>n:数据集中类别的数量</li>
<li><span class="math inline">\(p_i\)</span>:数据集中第 <span class="math inline">\(i\)</span> 类的概率</li>
</ul>
<pre><code>clf = DecisionTreeClassifier(criterion='gini', random_state=0)
</code></pre>
<p><img alt="watermarked-decision_trees_1_7" loading="lazy" src="https://img2024.cnblogs.com/blog/1416773/202509/1416773-20250909105845529-1323525171.png" class="lazyload"></p>
<p>使用基尼指数,选择了<code>error count&lt;=0.5</code>作为划分特征,与熵不一样</p>
<h4 id="基尼增益">基尼增益</h4>
<p>选择<code>error count&lt;=0.5</code></p>
<ul>
<li><code>error count&lt;=0.5</code>有6条,稳定与不稳定的比例为</li>
<li><code>error count&gt;0.5</code>有5条,稳定与不稳定的比例为</li>
</ul>
<p>计算一下基尼增益:</p>
<ul>
<li>原始基尼指数:<span class="math inline">\(Gini = 1 - \sum_{i=1}^{n} p_i^2 = 1-((\frac{5}{11})^2-(\frac{6}{11})^2 \approx 0.496)\)</span>
<ul>
<li>计算<code>error count&lt;=0.5</code>的基尼指数,<span class="math inline">\(Gini_1=1-((\frac{4}{6})^2+(\frac{2}{6})^2) \approx 0.4445\)</span></li>
<li>计算<code>error count&gt;0.5</code>的基尼指数,<span class="math inline">\(Gini_2=1-((\frac{1}{5})^2+(\frac{4}{5})^2) = 0.32\)</span></li>
</ul>
</li>
<li>计算加权平均基尼指数:<span class="math inline">\(Gini_{weighted}=\frac{6}{11}⋅0.4445 + \frac{5}{11}⋅0.32 \approx 0.3870\)</span></li>
<li>基尼增益:0.4959 - 0.3870=0.1089</li>
</ul>
<hr>
<p>选择<code>error count&lt;=1.5</code></p>
<ul>
<li><code>error count&lt;=1.5</code>有9条,稳定与不稳定的比例为</li>
<li><code>error count&gt;1.5</code>有2条,并且都是不稳定的</li>
</ul>
<p>计算一下基尼增益:</p>
<ul>
<li>原始基尼指数:上面已经计算过,0.0496
<ul>
<li>计算<code>error count&lt;=1.5</code>的基尼指数,<span class="math inline">\(Gini_1=1-((\frac{5}{9})^2+(\frac{4}{9})^2) \approx 0.4939\)</span></li>
<li>计算<code>error count&gt;1.5</code>的基尼指数,由于全是不稳定,<span class="math inline">\(Gini_2=1-((\frac{0}{2})^2+(\frac{2}{2})^2) = 0\)</span></li>
</ul>
</li>
<li>计算加权平均基尼指数:<span class="math inline">\(Gini_{weighted}=\frac{9}{11}⋅0.4939 \approx 0.4041\)</span></li>
<li>基尼增益:0.4959 - 0.4041=0.0918</li>
</ul>
<h4 id="小结-1">小结</h4>
<p>综上所述,error count为特征,以0.5为划分点,是合适的选择</p>
<p>使用基尼指数,选择特征划分点的时候,与熵的行为完全不一样,下面列一下基尼指数与熵的对比</p>
<table>
<thead>
<tr>
<th></th>
<th>基尼指数</th>
<th>熵</th>
</tr>
</thead>
<tbody>
<tr>
<td>参数</td>
<td>criterion='gini'</td>
<td>criterion='entropy'</td>
</tr>
<tr>
<td>划分标准</td>
<td>基尼指数(Gini Index)</td>
<td>信息增益(Information Gain)</td>
</tr>
<tr>
<td>计算效率</td>
<td>更高(无对数运算)</td>
<td>较低(需计算对数)</td>
</tr>
<tr>
<td>分裂倾向</td>
<td>偏向生成平衡的树</td>
<td>可能生成更深的树</td>
</tr>
<tr>
<td>类别</td>
<td>对类别分布变化更敏感(如某一类别占比从40%→50%时,基尼指数变化更明显)</td>
<td>对极端概率更敏感(如某一类别占比接近0%或100%时,熵变化剧烈)</td>
</tr>
<tr>
<td>sklearn</td>
<td>默认使用</td>
<td>需指定criterion='entropy'</td>
</tr>
</tbody>
</table>
<h2 id="剪枝">剪枝</h2>
<p>所谓剪枝,是提高决策树泛化能力的一种策略,剪枝的核心思想是减少数的复杂度,决策树通过找到最大的信息增益,不断迭代的划分节点分类,不断的迭代划分,就会造成树的复杂度不断提高</p>
<h4 id="预剪枝">预剪枝</h4>
<p>预剪枝:在构建决策树的过程中就进行限制,提前停止树的生长</p>
<ul>
<li>设置最大深度,<code>max_depth</code></li>
<li>设置叶子节点最小样本数,<code>min_samples_leaf</code></li>
<li>设置内部节点最小样本数,<code>min_samples_split</code></li>
<li>设置信息增益阈值(如增益低于某一阈值就不分裂)</li>
</ul>
<pre><code>from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_predict, StratifiedKFold
from sklearn.metrics import classification_report


X, y = load_iris(return_X_y=True)

cv = StratifiedKFold(n_splits=5, shuffle=True)

model = DecisionTreeClassifier(max_depth=3, min_samples_leaf=3, random_state=0)
y_pred = cross_val_predict(model, X, y, cv=cv)

print("预剪枝 分类报告:\n")
print(classification_report(y, y_pred, zero_division=0))

</code></pre>
<h4 id="后剪枝">后剪枝</h4>
<p>后剪枝:先让树尽可能长大(完全生长),然后再自底向上地修剪枝叶。sklearn中默认采用:成本复杂度剪枝(Cost Complexity Pruning,也叫 α-剪枝):用代价函数考虑准确率与模型复杂度的权衡</p>
<pre><code>from sklearn.model_selection import cross_val_score
import numpy as np

model = DecisionTreeClassifier(random_state=0)
path = model.cost_complexity_pruning_path(X, y)
ccp_alphas = path.ccp_alphas[:-1]

mean_scores = []

for alpha in ccp_alphas:
    clf = DecisionTreeClassifier(random_state=0, ccp_alpha=alpha)
    scores = cross_val_score(clf, X, y, cv=5, scoring='accuracy')
    mean_scores.append(scores.mean())

best_alpha = ccp_alphas
print("最佳 ccp_alpha:", best_alpha)

best_model = DecisionTreeClassifier(random_state=0, ccp_alpha=best_alpha)
best_model.fit(X, y)
y_pred = best_model.predict(X)
print("后剪枝 分类报告:")
print(classification_report(y, y_pred))

</code></pre>
<ul>
<li>首先训练处一颗完整树</li>
<li>从底部开始,找到最小的子树节点t,将其子树 <span class="math inline">\(T_t\)</span> 剪成一个叶子节点</li>
</ul>
<p><img alt="watermarked-decision_trees_1_6" loading="lazy" src="https://img2024.cnblogs.com/blog/1416773/202509/1416773-20250909105900493-278303933.png" class="lazyload"></p>
<ul>
<li>计算剪枝后误差的变化,得到<span class="math inline">\(\alpha_t\)</span>
<ul>
<li>其中损失函数可以使用多种方式计算,由于sklearn默认使用的是基尼指数,那就用基尼指数来计算误差$$\alpha = \frac{R(t)-R(T_t)}{|T_t|-1}$$
<ul>
<li><span class="math inline">\(R(t)\)</span>:父节点的基尼指数</li>
<li><span class="math inline">\(R(T_t)\)</span>:剪枝前加权基尼指数</li>
<li><span class="math inline">\(|T_t|\)</span>:叶节点数</li>
</ul>
</li>
</ul>
</li>
<li>上一步拿到的<span class="math inline">\(\alpha\)</span>,与<code>ccp_alpha</code>超参数(提前设置)进行对比,如果<code>ccp_alpha</code>&gt;<span class="math inline">\(\alpha\)</span>,则减掉该子树</li>
<li>重复以上过程</li>
</ul>
<p>举一个例子,来看下该节点(<code>CPU&lt;=1.5</code>)是否应该被剪枝</p>
<p><img alt="watermarked-decision_trees_1_8" loading="lazy" src="https://img2024.cnblogs.com/blog/1416773/202509/1416773-20250909105908151-1710293826.png" class="lazyload"></p>
<ul>
<li>父节点的基尼指数:<span class="math inline">\(R(t)=0.444\)</span></li>
<li>加权平均基尼指数:
<ul>
<li>父节点的加权基尼指数:<span class="math inline">\(\frac{3}{6}⋅0.444=0.222\)</span></li>
<li>左子树的加权基尼指数:<span class="math inline">\(\frac{1}{6}⋅0\)</span></li>
<li>右子树的加权基尼指数:<span class="math inline">\(\frac{2}{6}⋅0.5 \approx 0.166\)</span></li>
<li>子树的加权基尼指数:<span class="math inline">\(0.222+0+0.166=0.388\)</span></li>
</ul>
</li>
<li><span class="math inline">\(\alpha = \frac{0.444-0.166}{3-1}=0.028\)</span></li>
</ul>
<p>如果超参数<code>ccp_alpha</code>&gt;0.028,那该节点就将剪枝</p>
<h2 id="联系我">联系我</h2>
<ul>
<li>联系我,做深入的交流<br>
<img alt="" width="500" height="200" loading="lazy" src="https://img2024.cnblogs.com/blog/1416773/202411/1416773-20241121135740959-1907948957.png#" class="lazyload"></li>
</ul>
<hr>
<p>至此,本文结束<br>
在下才疏学浅,有撒汤漏水的,请各位不吝赐教...</p>


</div>
<div id="MySignature" role="contentinfo">
    <p>本文来自博客园,作者:it排球君,转载请注明原文链接:https://www.cnblogs.com/MrVolleyball/p/19081396</p>
<div>本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。 </div><br><br>
来源:https://www.cnblogs.com/MrVolleyball/p/19081396
頁: [1]
查看完整版本: 彩笔运维勇闯机器学习--决策树