当前位置:首页>python>Python 零基础100天—Day87 分类与聚类

Python 零基础100天—Day87 分类与聚类

  • 2026-09-29 17:31:19
Python 零基础100天—Day87 分类与聚类

🐍 Python Day87:分类与聚类 — 决策树、随机森林、K-Means

🕐 预计用时:3-4 小时 | 🎯 目标:掌握三种核心算法 + 交叉验证


📖 今日目录

  1. 决策树
  2. 随机森林
  3. K-Means 聚类
  4. 交叉验证
  5. 算法对比实验
  6. 今日练习
  7. 今日小结

1. 决策树

1.1 什么是决策树?

决策树就像你每天做的选择题。比如今天穿什么衣服?

                    今天冷吗?
                   /          \
                 是            否
                /                \
          下雨吗?            穿短袖
          /      \
        是        否
       /            \
    穿外套+带伞    穿外套

机器学习中的决策树也一样——它通过一系列是/否问题,把数据逐步分类。每个"问题"就是一个节点,每个回答就是一条分支。

1.2 动手做决策树

from sklearn.tree import DecisionTreeClassifier, export_text
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report

# 加载数据
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, test_size=0.2, random_state=42
)

# 创建决策树(限制最大深度为3,防止过拟合)
tree = DecisionTreeClassifier(max_depth=3, random_state=42)
tree.fit(X_train, y_train)

# 预测
y_pred = tree.predict(X_test)
print(f"准确率: {accuracy_score(y_test, y_pred):.2%}")

# 打印决策规则(文本版)
print("\n决策规则:")
print(export_text(tree, feature_names=iris.feature_names))
准确率: 100.00%

决策规则:
|--- petal length (cm) <= 2.45
|   |--- class: 0
|--- petal length (cm) >  2.45
|   |--- petal width (cm) <= 1.75
|   |   |--- petal length (cm) <= 4.95
|   |   |   |--- class: 1
|   |   |--- petal length (cm) >  4.95
|   |   |   |--- class: 2
|   |--- petal width (cm) >  1.75
|   |   |--- class: 2

1.3 特征重要性

# 哪个特征最重要?
importances = tree.feature_importances_
for name, imp in sorted(zip(iris.feature_names, importances), key=lambda x: -x[1]):
    bar = "█" * int(imp * 30)
    print(f"  {name:>20s}: {imp:.3f} {bar}")
  petal length (cm): 0.950 ████████████████████████████
   petal width (cm): 0.050 █
  sepal length (cm): 0.000
   sepal width (cm): 0.000

💡 决策树优点:可解释性极强——你可以直接看到模型是怎么做决策的,就像看一棵"问题树"。缺点是容易过拟合(死记硬背训练数据),通过限制 max_depth 可以缓解。

1.4 可视化决策树

from sklearn.tree import plot_tree

plt.figure(figsize=(16, 8))
plot_tree(tree,
          feature_names=iris.feature_names,
          class_names=iris.target_names,
          filled=True,       # 颜色填充
          rounded=True,      # 圆角框
          fontsize=11)
plt.title('鸢尾花分类决策树')
plt.tight_layout()
plt.savefig('decision_tree.png', dpi=150)
plt.show()

2. 随机森林

2.1 什么是随机森林?

一棵决策树容易犯错,那种一片森林呢?

随机森林的核心思想:三个臭皮匠,赛过诸葛亮。训练很多棵决策树,每棵树看不同的数据子集,最后投票决定结果。

对比
决策树
随机森林
数量
1 棵
很多棵(通常 100~500)
过拟合
容易
不容易(互相抵消)
准确率
一般
通常更高
速度
快
较慢(树多)
可解释性
强
较弱(黑盒)

2.2 动手做随机森林

from sklearn.ensemble import RandomForestClassifier

# 用乳腺癌数据集(569个样本,30个特征)
from sklearn.datasets import load_breast_cancer
data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    data.data, data.target, test_size=0.2, random_state=42
)

# 创建随机森林(100棵树)
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X_train, y_train)

y_pred = rf.predict(X_test)
print(f"准确率: {accuracy_score(y_test, y_pred):.2%}")
print(f"\n分类报告:")
print(classification_report(y_test, y_pred, target_names=data.target_names))

2.3 特征重要性排名

# 随机森林可以告诉你哪些特征最重要
importances = rf.feature_importances_
indices = np.argsort(importances)[::-1]

print("特征重要性排名:")
for i in range(10):  # 前10个
    print(f"  {i+1}. {data.feature_names[indices[i]]}: {importances[indices[i]]:.4f}")

# 可视化
plt.figure(figsize=(12, 6))
top_n = 15
plt.barh(range(top_n), importances[indices[:top_n]], color='
#07c160')
plt.yticks(range(top_n), [data.feature_names[i] for i in indices[:top_n]])
plt.xlabel('重要性')
plt.title('随机森林 - 特征重要性 Top 15')
plt.gca().invert_yaxis()
plt.tight_layout()
plt.savefig('feature_importance.png', dpi=150)
plt.show()

🔍 为什么随机森林更好?每棵树只看随机的一部分数据和特征,所以它们各自有"偏见"。但把它们的投票汇总起来,偏见就互相抵消了。这就是集成学习的力量。


3. K-Means 聚类

3.1 什么是聚类?

前面的算法都是监督学习——有标签(正确答案)。聚类是无监督学习——没有标签,让机器自己找规律。

K-Means 的思路很直觉:

  1. 你告诉它要分 K 组
  2. 它随机选 K 个"中心点"
  3. 每个数据点归到最近的中心点
  4. 重新计算每组的中心点
  5. 重复 3-4 直到不再变化

想象你有 1000 个客户的消费数据,你想把他们分成 3 类(高/中/低消费),但你不知道谁属于哪类——K-Means 帮你搞定。

3.2 动手做 K-Means

from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs

# 生成模拟数据(3个簇)
X, y_true = make_blobs(n_samples=300, centers=3,
                       cluster_std=0.8, random_state=42)

# K-Means 聚类
kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
kmeans.fit(X)

# 聚类结果
y_pred = kmeans.labels_
centers = kmeans.cluster_centers_

print(f"聚类中心:")
for i, center in enumerate(centers):
    print(f"  簇{i+1}: ({center[0]:.2f}, {center[1]:.2f})")

# 可视化
plt.figure(figsize=(10, 6))
colors = ['#07c160', '#1890ff', '#ff4d4f']
for i in range(3):
    mask = y_pred == i
    plt.scatter(X[mask, 0], X[mask, 1], c=colors[i], alpha=0.5, label=f'簇 {i+1}')

# 画中心点
plt.scatter(centers[:, 0], centers[:, 1], c='black', marker='X', s=200, linewidths=2, label='中心点')
plt.xlabel('特征 1')
plt.ylabel('特征 2')
plt.title('K-Means 聚类结果')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('kmeans_clustering.png', dpi=150)
plt.show()

3.3 肘部法则:选多少个簇?

K-Means 需要你提前指定 K(簇的数量),但怎么知道该分几组?肘部法则来帮忙:

# 用鸢尾花数据集
iris = load_iris()
X = iris.data

# 测试不同的 K 值
inertias = []
K_range = range(1, 10)

for k in K_range:
    km = KMeans(n_clusters=k, random_state=42, n_init=10)
    km.fit(X)
    inertias.append(km.inertia_)

# 画肘部图
plt.figure(figsize=(8, 5))
plt.plot(K_range, inertias, 'bo-', linewidth=2)
plt.xlabel('K (簇的数量)')
plt.ylabel('惯性 (Inertia)')
plt.title('肘部法则')
plt.grid(True, alpha=0.3)

# 标注"肘部"
plt.annotate('肘部 (K=3)', xy=(3, inertias[2]),
             xytext=(5, inertias[0] * 0.7),
             arrowprops=dict(arrowstyle='->', color='red'),
             fontsize=12, color='red')
plt.tight_layout()
plt.savefig('elbow_method.png', dpi=150)
plt.show()

🎯 肘部法则:惯性(Inertia)衡量的是"数据点到中心的平均距离"。K 越大,惯性越小(分得更细)。当 K 增大到某个值后,惯性下降变缓——这个"拐点"就是最佳 K。图形看起来像胳膊肘,所以叫"肘部法则"。

3.4 聚类的实际应用

# 客户分群实战
np.random.seed(42)
n_customers = 200

# 特征:月消费金额、月访问次数
spending = np.concatenate([
    np.random.normal(500, 100, 80),   # 低消费
    np.random.normal(1500, 200, 70),  # 中消费
    np.random.normal(3000, 300, 50),  # 高消费
])
visits = np.concatenate([
    np.random.normal(3, 1, 80),
    np.random.normal(8, 2, 70),
    np.random.normal(15, 3, 50),
])
X_customers = np.column_stack([spending, visits])

# 聚类
kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
labels = kmeans.fit_predict(X_customers)

# 分析每组特征
for i in range(3):
    mask = labels == i
    avg_spending = X_customers[mask, 0].mean()
    avg_visits = X_customers[mask, 1].mean()
    count = mask.sum()
    print(f"簇 {i+1}: {count}人, 平均消费{avg_spending:.0f}元, 平均访问{avg_visits:.1f}次")

# 给每组命名
group_names = {}
for i in range(3):
    avg = X_customers[labels == i, 0].mean()
    if avg < 800:
        group_names[i] = "低价值客户"
    elif avg < 2000:
        group_names[i] = "中价值客户"
    else:
        group_names[i] = "高价值客户"

print("\n客户分群结果:")
for i, name in group_names.items():
    print(f"  簇{i+1} → {name}")

4. 交叉验证

4.1 为什么要交叉验证?

昨天我们用 train_test_split 把数据分成训练集和测试集。但这样有个问题:你只测了一次。如果你运气不好,测试集刚好特别简单或特别难呢?

交叉验证(Cross Validation)的解决方案:把数据切成 K 份,轮流用每一份当测试集,其余当训练集,最后取平均分。

数据分成5份:
第1轮: [测试] [训练] [训练] [训练] [训练] → 得分 0.95
第2轮: [训练] [测试] [训练] [训练] [训练] → 得分 0.92
第3轮: [训练] [训练] [测试] [训练] [训练] → 得分 0.97
第4轮: [训练] [训练] [训练] [测试] [训练] → 得分 0.93
第5轮: [训练] [训练] [训练] [训练] [测试] → 得分 0.96
平均: 0.946 ± 0.018

4.2 动手做交叉验证

from sklearn.model_selection import cross_val_score

# 鸢尾花数据集
iris = load_iris()
X, y = iris.data, iris.target

# 决策树 + 5折交叉验证
tree = DecisionTreeClassifier(max_depth=3, random_state=42)
scores = cross_val_score(tree, X, y, cv=5, scoring='accuracy')

print("5折交叉验证结果:")
for i, score in enumerate(scores):
    print(f"  第{i+1}折: {score:.4f}")
print(f"  平均: {scores.mean():.4f} ± {scores.std():.4f}")

# 随机森林 + 5折交叉验证
rf = RandomForestClassifier(n_estimators=100, random_state=42)
scores_rf = cross_val_score(rf, X, y, cv=5, scoring='accuracy')
print(f"\n随机森林: {scores_rf.mean():.4f} ± {scores_rf.std():.4f}")

4.3 用交叉验证选模型

from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.svm import SVC
from sklearn.neighbors import KNeighborsClassifier

# 对比多种算法
models = {
    '决策树(depth=3)': DecisionTreeClassifier(max_depth=3, random_state=42),
    '决策树(depth=5)': DecisionTreeClassifier(max_depth=5, random_state=42),
    '随机森林(100)': RandomForestClassifier(n_estimators=100, random_state=42),
    'KNN(k=5)': KNeighborsClassifier(n_neighbors=5),
    'SVM': SVC(random_state=42),
}

print("算法对比 (5折交叉验证):")
print("-" * 50)
results = {}
for name, model in models.items():
    scores = cross_val_score(model, X, y, cv=5, scoring='accuracy')
    results[name] = scores.mean()
    print(f"  {name:>20s}: {scores.mean():.4f} ± {scores.std():.4f}")

best = max(results, key=results.get)
print(f"\n🏆 最佳模型: {best} ({results[best]:.4f})")

5. 算法对比实验

# 完整对比:在乳腺癌数据集上测试
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression

data = load_breast_cancer()
X, y = data.data, data.target

models = {
    '逻辑回归': LogisticRegression(max_iter=10000),
    '决策树(depth=3)': DecisionTreeClassifier(max_depth=3),
    '决策树(depth=5)': DecisionTreeClassifier(max_depth=5),
    '随机森林(100)': RandomForestClassifier(n_estimators=100),
    'KNN(k=5)': KNeighborsClassifier(n_neighbors=5),
    'SVM': SVC(),
}

print("=" * 55)
print(f"{'模型':>20s} | {'准确率':>8s} | {'标准差':>8s}")
print("-" * 55)

for name, model in models.items():
    scores = cross_val_score(model, X, y, cv=5, scoring='accuracy')
    print(f"{name:>20s} | {scores.mean():>7.4f} | {scores.std():>7.4f}")

print("=" * 55)

⚠️ 没有"万能算法":不同数据集适合不同的算法。决策树可解释性强,随机森林性能稳定,SVM 在高维数据上表现好。实际项目中,要多试几种,用交叉验证选最好的。

注意:KNN 和 SVM 对特征量纲非常敏感,使用前务必做 StandardScaler 标准化,否则准确率会明显偏低。


6. 今日练习

练习 1:手写数字识别

用 datasets.load_digits() 数据集,分别训练决策树和随机森林,对比准确率。

练习 2:K-Means 客户分群

自己生成 200 个客户的消费数据(月消费 + 访问次数),用 K-Means 分成 4 群,分析每群特征。

练习 3:交叉验证选参数

用交叉验证测试随机森林不同的 n_estimators(10, 50, 100, 200),找出最佳值。


7. 今日小结

算法
类型
核心思想
适用场景
决策树
监督/分类
一系列是/否问题
需要可解释性
随机森林
监督/分类
多棵树投票
追求高准确率
K-Means
无监督/聚类
分 K 组,迭代优化
客户分群、数据探索
交叉验证
评估方法
K 折轮流测试
模型选择、参数调优

🎯 一句话总结:决策树是"一棵树的智慧",随机森林是"一群树的投票",K-Means 是"没有答案的分组",交叉验证是"多考几次取平均"。

🔮 明天预告:Day88 我们将运用所学知识完成一个完整的房价预测项目——从数据探索、特征工程到模型训练、评估、预测,走完一个机器学习项目的全流程!

最新文章

随机文章