20.AI大模型开发:机器学习----集成学习与聚类算法

从"三个臭皮匠"到"物以类聚",用最通俗的方式带你入门两大核心算法


前言:为什么要学这两个东西?

如果你刚接触机器学习,一定会被各种算法名字搞得头晕。今天咱们不讲那些高深数学,只用大白话+生活例子,把集成学习聚类算法彻底讲清楚。

  • 集成学习:解决"一个模型不够准"的问题 ------ 多个人一起决策,比一个人靠谱。

  • 聚类算法:解决"数据没有标签,怎么分组"的问题 ------ 让相似的数据自动抱团。

读完这篇,你能看懂面试题、能自己跑代码、能知道什么时候该用哪个。


第一部分:集成学习 ------ 团结就是力量

1.1 什么是集成学习?

想象你要判断明天会不会下雨。你问了一个 天气预报员,他说"会下"。但他可能看走眼。

于是你问了10个预报员,让他们投票------8人说"下",2人说"不下",那你大概率相信"下"。

这就是集成学习的核心思想:把多个弱学习器(不太准的模型)组合成一个强学习器(较准的模型)

弱学习器:比随机猜强一点点(比如准确率55%)。

强学习器:准确率能达到90%以上。

1.2 集成学习的两大流派

集成学习主要分两派,区别如下表:

对比项 Bagging(装袋法) Boosting(提升法)
数据采样 有放回地随机采样(同一份数据可被多次选中) 每次用全部数据,但调整每个样本的权重
训练方式 并行------所有弱学习器互不依赖,可同时训练 串行------后一个学习器依赖前一个的错误
投票方式 平权投票(每人一票,平等) 加权投票(表现好的票权重大)
代表算法 随机森林 Adaboost、GBDT、XGBoost、LightGBM
生活类比 班级投票,每个人同等重要 专家会诊,主任医师的意见权重更高
Bagging 的精髓 ------ 有放回抽样

Bagging 全称 Bootstrap Aggregating。它做的事情很简单:

  1. 从原始数据集中有放回地抽取 N 个样本(可能重复),形成一个子训练集。

  2. 用这个子集训练一个弱学习器(比如决策树)。

  3. 重复上述步骤 M 次,得到 M 个弱学习器。

  4. 预测时,让 M 个学习器投票(分类)或取平均(回归)。

为什么要"有放回"?

如果不放回,每个学习器拿到的数据完全不同,各自学到的规律"偏科严重"。有放回能让各个子集既有重叠又有差异,投票时才不会一边倒。

Boosting 的精髓 ------ 知错就改

Boosting 串行训练:第一个学习器先学,然后看它在哪些样本上犯了错,下一个学习器就重点训练这些难样本(提高它们的权重)。如此迭代,后面的学习器越来越关注"硬骨头",整体性能步步提升。

生活中的例子:你学数学,先做基础题,错了的题反复练,再逐步上难度,最后成为高手。


1.3 随机森林 ------ Bagging 的明星选手

随机森林 = Bagging + 决策树。但它比纯 Bagging 多了一个"随机":

  • 随机选样本:有放回抽样(和Bagging一样)。

  • 随机选特征 :每棵树在分裂时,不是从所有特征中选最好的,而是随机挑出一部分特征(比如总特征数的平方根),再从这些里选最好的。这增加了树与树之间的差异,让森林更"茂盛"。

为什么随机选特征很重要?

如果所有树都用全部特征,它们会很像,投票效果差。随机选特征让每棵树"各有所长",综合起来更全面。

随机森林的优缺点
  • 优点:抗过拟合、能处理高维数据、可以输出特征重要性、几乎不用调参就能有不错效果。

  • 缺点:模型较大、预测速度稍慢、可解释性差(黑盒)。


1.4 手写一个简易集成分类器(不用 sklearn,纯 Python 理解)

我们不调库,自己实现一个 Bagging 版本的投票分类器,感受一下集成思想。

python 复制代码
import numpy as np
from collections import Counter

# 模拟一个弱分类器:就是一个简单的"阈值判断"
class WeakClassifier:
    def __init__(self, feature_idx, threshold, direction):
        self.feature_idx = feature_idx   # 用哪个特征
        self.threshold = threshold       # 阈值
        self.direction = direction       # 'left' 或 'right' 表示小于阈值判为哪类

    def predict(self, X):
        # 简单二分类,返回 0 或 1
        if self.direction == 'left':
            return (X[:, self.feature_idx] < self.threshold).astype(int)
        else:
            return (X[:, self.feature_idx] >= self.threshold).astype(int)

# 随机生成弱分类器(随机特征、随机阈值)
def generate_random_weak_classifier(n_features):
    feat = np.random.randint(0, n_features)
    thresh = np.random.rand() * 10  # 假设特征值在0~10之间
    direction = np.random.choice(['left', 'right'])
    return WeakClassifier(feat, thresh, direction)

# Bagging集成:训练多个弱分类器,然后投票
class BaggingEnsemble:
    def __init__(self, n_estimators=10):
        self.n_estimators = n_estimators
        self.classifiers = []

    def fit(self, X, y):
        n_samples = X.shape[0]
        for _ in range(self.n_estimators):
            # 有放回采样
            idx = np.random.choice(n_samples, n_samples, replace=True)
            X_sub, y_sub = X[idx], y[idx]
            # 随机生成一个弱分类器(这里简化,实际应基于数据训练)
            clf = generate_random_weak_classifier(X.shape[1])
            self.classifiers.append(clf)

    def predict(self, X):
        # 所有弱分类器预测结果
        preds = np.array([clf.predict(X) for clf in self.classifiers])
        # 按行投票(多数表决)
        final_pred = []
        for i in range(X.shape[0]):
            votes = preds[:, i]
            majority = Counter(votes).most_common(1)[0][0]
            final_pred.append(majority)
        return np.array(final_pred)

# 演示:生成简单数据
np.random.seed(42)
X = np.random.rand(100, 2) * 10
y = (X[:, 0] + X[:, 1] > 10).astype(int)  # 简单线性可分

ensemble = BaggingEnsemble(n_estimators=20)
ensemble.fit(X, y)
pred = ensemble.predict(X)
accuracy = np.mean(pred == y)
print(f"集成模型在训练集上的准确率: {accuracy:.2f}")

实际工作中我们直接使用 sklearn.ensemble.RandomForestClassifier,上面的代码只是为了帮你理解底层逻辑。


第二部分:聚类算法 ------ 让数据自己"物以类聚"

2.1 什么是聚类?

聚类是无监督学习 ------数据没有标签,我们不知道哪个样本属于哪一类。算法的目标是:让相似的样本聚在一起,不相似的样本分开

相似度怎么衡量? 最常用的是欧氏距离(直线距离)。两个点距离越近,越相似。

生活场景:

  • 电商把用户分成"高消费""中等消费""低消费"群体,针对不同群体做营销。

  • 新闻网站自动把相似主题的文章归为一类。

  • 图像压缩:把相近的颜色聚成一类,用中心色代替。

2.2 K-Means ------ 最经典的聚类算法

K-Means 的思路极其简单,一共 4 步:

  1. 定 K:事先决定要聚成几类(比如 K=3)。

  2. 初始化:随机选 K 个样本点作为初始的"聚类中心"(质心)。

  3. 分配:计算每个样本到 K 个中心的距离,把它分给最近的那个中心。

  4. 更新:对每个类别,重新计算该类所有样本的均值,作为新的中心。

  5. 重复 3、4,直到中心不再变化(或变化很小)。

这个过程就像:你先随机在操场上插几面旗子,然后让每个同学站到离自己最近的旗子那里,站好后重新计算旗子的位置(取平均),再让同学重新站......直到稳定。

K-Means 的痛点 ------ K 怎么选?

这是最大难题。我们有两个常用方法:

  • 肘方法(Elbow Method) :计算每个 K 值对应的 SSE(误差平方和)------所有样本到其所属中心距离的平方和。K 越大,SSE 越小(因为每个簇更紧凑)。但 SSE 下降速度会出现一个"拐点",就像胳膊肘,拐点处的 K 就是最佳值。

  • 轮廓系数(Silhouette Score):综合考虑簇内紧密度和簇间分离度,值在 -1, 1 之间,越大越好。

2.3 聚类效果怎么评估?

除了上面两个,还有一个常用指标:

  • Calinski-Harabasz 指数 :簇间离散度与簇内离散度的比值,值越大越好(sklearn 中有 calinski_harabasz_score)。

2.4 手写一个 K-Means 简易实现(不用 sklearn)

python 复制代码
import numpy as np
import matplotlib.pyplot as plt

class MyKMeans:
    def __init__(self, n_clusters=3, max_iters=100):
        self.n_clusters = n_clusters
        self.max_iters = max_iters
        self.centroids = None

    def fit(self, X):
        # 随机初始化中心
        idx = np.random.choice(len(X), self.n_clusters, replace=False)
        self.centroids = X[idx]

        for _ in range(self.max_iters):
            # 分配样本到最近中心
            distances = np.array([[np.linalg.norm(x - c) for c in self.centroids] for x in X])
            labels = np.argmin(distances, axis=1)

            # 更新中心
            new_centroids = np.array([X[labels == k].mean(axis=0) for k in range(self.n_clusters)])
            
            # 检查是否收敛(中心变化很小)
            if np.allclose(self.centroids, new_centroids, atol=1e-4):
                break
            self.centroids = new_centroids

        self.labels_ = labels
        return self

    def predict(self, X):
        distances = np.array([[np.linalg.norm(x - c) for c in self.centroids] for x in X])
        return np.argmin(distances, axis=1)

# 生成示例数据:3个簇
from sklearn.datasets import make_blobs
X, y_true = make_blobs(n_samples=300, centers=3, cluster_std=0.6, random_state=42)

kmeans = MyKMeans(n_clusters=3)
kmeans.fit(X)
labels = kmeans.labels_

# 可视化
plt.scatter(X[:, 0], X[:, 1], c=labels, cmap='viridis', alpha=0.7)
plt.scatter(kmeans.centroids[:, 0], kmeans.centroids[:, 1], 
            marker='X', s=200, color='red', label='Centroids')
plt.title('My K-Means Clustering')
plt.legend()
plt.show()

# 计算 SSE
sse = sum(np.linalg.norm(X[labels == k] - kmeans.centroids[k])**2 for k in range(3))
print(f"SSE = {sse:.2f}")

实际开发中直接用 sklearn.cluster.KMeans,上面的代码帮你理解迭代过程。


第三部分:实战小项目 ------ 用户分层(聚类+集成综合应用)

假设你有一家线上商店,收集了用户的 年收入月均消费额。你想用聚类把用户分成几类,然后针对不同群体用随机森林预测他们是否会购买高价商品。

3.1 聚类部分(无监督)

  • 用 K-Means 将用户分成 4 类(精英、工薪、节俭、学生)。

  • 肘方法确认 K=4 是最佳。

3.2 集成部分(有监督)

  • 给每个用户打上聚类标签后,再结合其他特征(年龄、浏览时长等),用随机森林训练一个分类器,预测"是否购买超过1000元商品"。

这样,你既理解了数据的自然结构,又利用集成提升了预测精度。


第四部分: 易错点

  1. Bagging 和 Boosting 哪个更不容易过拟合?

    Bagging(如随机森林)更不容易过拟合,因为并行训练、投票平均降低了方差。Boosting 如果迭代次数过多容易过拟合,但通过早停可控制。

  2. K-Means 对初始中心敏感吗?

    非常敏感。实际中会多次随机初始化,选 SSE 最小的结果(sklearn 的 n_init 参数就是干这个的)。

  3. 聚类时特征需要归一化吗?

    必须!因为距离受量纲影响很大(比如收入上万,年龄几十,收入会主导距离)。通常用标准化或 Min-Max 缩放。

  4. 随机森林的特征随机性体现在哪?

    每次分裂时,只从总特征中随机选一个子集(如 max_features='sqrt'),在这子集中找最优分裂特征。


第五部分:总结与速查表

算法 类型 核心思想 关键参数 适用场景
随机森林 集成(Bagging) 多棵树投票,样本&特征双随机 n_estimators, max_depth, max_features 分类/回归,高维数据,特征重要度分析
Adaboost 集成(Boosting) 串行,提升错误样本权重 n_estimators, learning_rate 二分类,小数据集
K-Means 聚类 迭代更新中心,距离最近 n_clusters, n_init, max_iter 客户分群,图像压缩,异常检测

一句话记忆

  • 集成学习 = 三个臭皮匠,顶个诸葛亮(Bagging 是平权投票,Boosting 是专家会诊)。

  • 聚类 = 物以类聚,人以群分(距离近的归一起)。