第6章 无监督学习:没有标准答案怎么办
一句话点题: 监督学习是"做题对答案",无监督学习是"发一堆混在一起的扑克牌,没人告诉你怎么分,你自己整理"。没有标准答案,但机器照样能找出规律来。
写在前面
上一章我们学的监督学习有个前提:数据得有标签(标准答案)。
但现实中,大量数据是没有标签的。你有一堆用户行为日志,没人给每条记录标上"这是高价值用户"或"这是流失用户";你有几万条商品评论,没人标过"正面"或"负面";你有一堆传感器数据,没人告诉你哪条是"正常"哪条是"异常"。
标注数据费时费力——请人标1万条数据,可能要花几万块、花几周时间。
没有标签,机器还能学吗?能。这就是无监督学习干的事。
一、无监督学习到底在干什么
1. 跟监督学习的区别
| 有没有标签 | 有 | 没有 |
| 目标 | 学会预测(给新数据一个答案) | 发现结构(在数据里找规律) |
| 比方 | 做题对答案 | 自己找规律分类 |
| 例子 | 判断邮件是不是垃圾邮件 | 把用户自动分成几群 |
| 能验证对错吗 | 能(有标准答案) | 不能(没有标准答案,只能看"分得合不合理") |
2. 三大任务
无监督学习主要干三件事:
| 聚类 | 把相似的数据分到一组 | 自动分组 | 用户分群、文档归类 |
| 降维 | 把高维数据压缩到低维 | 浓缩信息 | 数据可视化、特征压缩 |
| 异常检测 | 找出跟大多数不一样的数据 | 找"异类" | 欺诈检测、故障预警 |
下面逐个讲。
二、聚类:让机器自动分组
1. 什么是聚类
聚类的目标很简单:把相似的东西放一起,不相似的分开。
你不需要告诉机器"分几类"或"怎么分",机器自己根据数据的特征来找天然的分群。
生活类比: 你去一个陌生超市买水果,没人告诉你哪个是苹果哪个是梨。但你看一眼就能分出来——红的一堆、黄的一堆、长得不一样嘛。聚类就是让机器干这个事。
2. K-Means——最经典的聚类算法
K-Means(K均值)是最常用的聚类算法,原理非常直白:
用图来理解:
第1轮:随机放中心点 第2轮:重新分组 第3轮:稳定了
· · ☆① · · | ① · · | ①
· ·· ··· · ··|·· · ·· | ··
··· ···· → ···|···· → ··· | ····
····· ☆② ·····|② ····· | ②
· ·· · ··| · ·· |
☆③
①②③ = 三个中心点 竖线 = 分界线 分组完成
大白话: 你说"分3组",机器就把数据分成3堆,让每堆里的数据尽量相似,堆跟堆之间尽量不同。
3. K-Means的优缺点
| 简单、速度快 | 你得自己定K(分几组) |
| 大数据量也能扛 | 只能找圆形的簇(形状怪的搞不定) |
| 解释性好(中心点就是每组的"代表") | 对异常值敏感(一个离群点能把中心点拉偏) |
| 使用广泛、工具成熟 | 初始中心点随机放,每次结果可能不同 |
4. DBSCAN——不需要你定分几组的聚类
K-Means的痛点是你得提前告诉它"分几组",但你往往不知道该分几组。DBSCAN解决了这个问题。
DBSCAN的原理:密度大的地方就是一群,密度小的地方就是边界。 它自动根据数据的密集程度来分群,不需要你指定组数。
生活类比: K-Means是你跟机器说"把这100个人分3组";DBSCAN是机器自己看"这30个人挤在一起算一组,那边50个人挤一起算一组,剩下20个散在各地的算异常"。
| 需要指定分几组吗 | 需要 | 不需要 |
| 能处理不规则形状吗 | 不能 | 能 |
| 能识别异常点吗 | 不能 | 能 |
| 速度 | 快 | 中等 |
| 适合场景 | 数据大致成圆形分布 | 数据形状不规则、需要识别异常 |
5. 聚类实战:用户分群
一个常见的业务场景:你有1万个用户的消费数据,想把用户自动分群,做精细化运营。
import pandas as pd
import numpy as np
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
# ========== 第1步:准备数据 ==========
# 模拟用户消费数据
np.random.seed(42)
data = {
'月消费金额': np.concatenate([
np.random.normal(500, 100, 300), # 低消费群体
np.random.normal(3000, 500, 500), # 中等消费群体
np.random.normal(8000, 1000, 200), # 高消费群体
]),
'月访问次数': np.concatenate([
np.random.normal(5, 2, 300), # 低频用户
np.random.normal(20, 5, 500), # 中频用户
np.random.normal(40, 8, 200), # 高频用户
])
}
df = pd.DataFrame(data)
# ========== 第2步:数据标准化 ==========
# 不同特征的量纲不同(消费金额几千、访问次数几十),必须标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(df)
# ========== 第3步:选K(用手肘法) ==========
inertias = []
K_range = range(1, 10)
for k in K_range:
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
kmeans.fit(X_scaled)
inertias.append(kmeans.inertia_)
# 手肘法:画图找"拐点",拐点对应的K就是最佳分组数
plt.figure(figsize=(8, 4))
plt.plot(K_range, inertias, 'bo-')
plt.xlabel('K (分组数)')
plt.ylabel('Inertia (组内方差)')
plt.title('手肘法选K')
plt.axvline(x=3, color='r', linestyle='–', label='最佳K=3')
plt.legend()
plt.tight_layout()
plt.savefig('elbow.png', dpi=100)
# ========== 第4步:训练模型 ==========
kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
df['群体'] = kmeans.fit_predict(X_scaled)
# ========== 第5步:分析结果 ==========
print("各群体特征:")
print(df.groupby('群体').agg({
'月消费金额': ['mean', 'count'],
'月访问次数': 'mean'
}).round(0))
# 输出结果示例:
# 群体 月消费金额(mean) 月访问次数(mean) 人数
# 0 502 5 300 ← 低频低消费
# 1 3001 20 500 ← 中频中消费
# 2 7998 40 200 ← 高频高消费
6. 手肘法——怎么确定分几组
K-Means最头疼的问题是"K设多少合适"。手肘法是最常用的解决方案:
原理:试不同的K值(1到10),算每个K对应的"组内方差"(每个点到自己中心点的距离之和)。K越大,方差越小(分组越细当然越紧凑)。但方差下降到某个点会突然变缓——这个拐点就像胳膊的"手肘",就是最佳K值。
Inertia
|
| ·
| ·
| ·
| · ← 拐点(手肘),K=3最佳
| · · · · ·
|
└────────────────── K
1 2 3 4 5 6 7
大白话: 分1组太粗、分10组太细,找个"刚好"的拐点。
三、降维:把高维数据压缩
1. 为什么需要降维
假设你有一个用户画像,有200个特征(年龄、性别、收入、消费频次、浏览时长、地域……)。这200个特征里:
- 有些是冗余的("月收入"和"年收入"其实是一回事)
- 有些是噪声("用户ID"对分析没有意义)
- 有些维度太高没法可视化(你能画2维3维的图,但画不了200维的)
降维就是把200个特征压缩成10个或2个,同时尽量不丢失关键信息。
| 减少计算量 | 数据瘦身后跑得更快 |
| 去掉冗余 | 把重复的信息合并 |
| 可视化 | 降到2维3维就能画图看了 |
| 降噪 | 去掉没用的特征,留下核心信息 |
2. PCA——最常用的降维方法
PCA(主成分分析)的原理用大白话说:找出数据中"信息量最大"的几个方向,把数据投影到这些方向上,用更少的维度表示原始数据。
生活类比: 你拍一个3D的物体照片,从不同角度拍信息量不同——正面拍能看出形状,俯拍可能只能看到一个圆。PCA就是帮你找到"最能体现这个物体特征"的拍摄角度。
| 维度 | 200维 | 2维 |
| 信息量 | 100% | ~85%(损失15%) |
| 可视化 | 不行 | 可以画散点图 |
| 计算速度 | 慢 | 快 |
关键取舍:降维必然丢信息,问题是你愿意丢多少。 通常降到能保留85%-95%信息量的维度就够了。
3. 降维的典型应用
| 数据可视化 | 200维降到2维画散点图 | 直观看到数据分布和聚类 |
| 模型加速 | 降维后再训练模型 | 减少计算量,训练更快 |
| 特征压缩 | 把相关特征合并 | 减少冗余,降低过拟合风险 |
| 大模型中的Embedding | 768维向量降到更低维 | 减少存储和检索压力(RAG中常用) |
4. 降维实战:可视化高维数据
from sklearn.decomposition import PCA
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
# 加载鸢尾花数据集(4个特征:花萼长宽、花瓣长宽)
iris = load_iris()
X = iris.data # 4维
y = iris.target # 3种花
# PCA降到2维
pca = PCA(n_components=2)
X_2d = pca.fit_transform(X)
print(f"原始维度: {X.shape[1]}维")
print(f"降维后: {X_2d.shape[1]}维")
print(f"保留信息量: {pca.explained_variance_ratio_.sum():.1%}")
# 画图
plt.figure(figsize=(8, 5))
colors = ['red', 'green', 'blue']
for i, color in enumerate(colors):
plt.scatter(X_2d[y==i, 0], X_2d[y==i, 1], c=color, label=iris.target_names[i])
plt.xlabel('主成分1')
plt.ylabel('主成分2')
plt.title('PCA降维可视化')
plt.legend()
plt.tight_layout()
plt.savefig('pca.png', dpi=100)
4维数据降到2维,保留了97.8%的信息量,还能画图看到三种花明显分成三堆——这就是降维的价值。
四、异常检测:找出"不对劲"的数据
1. 什么是异常检测
异常检测的目标:从大量数据中找出"跟大多数不一样"的少数数据。
这些"不一样"的数据往往是重要的:
- 信用卡交易里的欺诈交易
- 服务器日志里的异常请求
- 工厂传感器数据里的设备故障前兆
- 网络流量里的攻击行为
2. 为什么不用监督学习
你可能会想:用监督学习训练一个"正常vs异常"的分类器不就行了?
问题在于:
| 异常样本太少 | 欺诈交易可能只占0.01%,类别极不平衡 |
| 异常种类未知 | 今天的欺诈手段和明天的不一样,你没法穷举 |
| 标注困难 | 哪些是异常?很多时候事后才知道 |
所以异常检测通常用无监督方法:只学"正常长什么样",跟正常不一样的就是异常。
3. 常用方法
| Isolation Forest | 随机切分数据,异常点容易被孤立 | 异常点"人缘差",几下就单独切出来了 | 通用异常检测 |
| One-Class SVM | 学一个边界把正常数据包住 | 画个圈,圈外的是异常 | 边界清晰的场景 |
| DBSCAN | 密度低的地方的点算异常 | 不属于任何一群的就是异类 | 兼顾聚类和异常检测 |
| 自编码器 | 用神经网络学重建正常数据,重建误差大的就是异常 | 只见过正常的,遇到异常就"不认识"了 | 复杂高维数据 |
4. 异常检测实战
from sklearn.ensemble import IsolationForest
import numpy as np
# 模拟正常交易数据(金额0-1000,频率1-10次/天)
np.random.seed(42)
normal_data = np.column_stack([
np.random.normal(200, 100, 1000), # 交易金额
np.random.normal(5, 2, 1000), # 日交易次数
])
# 模拟异常交易
anomalies = np.array([
[5000, 50], # 大额高频
[8000, 1], # 超大额
[50, 30], # 小额超高频
[3000, 25], # 大额高频
])
# 训练Isolation Forest
model = IsolationForest(contamination=0.05, random_state=42)
model.fit(normal_data)
# 预测异常
predictions = model.predict(anomalies)
for i, pred in enumerate(predictions):
status = "异常 ⚠️" if pred == –1 else "正常 ✅"
print(f"交易(金额={anomalies[i][0]:.0f}, 次数={anomalies[i][1]:.0f}) → {status}")
# 预测正常数据(应该都判为正常)
normal_pred = model.predict(normal_data[:10])
print(f"\\n正常数据检测: {sum(normal_pred == 1)}/10 判为正常")
5. 异常检测的实践要点
| contamination参数 | 预估异常比例,设高了误报多,设低了漏报多 |
| 特征选择 | 选跟异常相关的特征,无关特征会干扰检测 |
| 阈值调优 | 异常检测输出的是"异常分数",阈值设多少需要根据业务调 |
| 人工反馈 | 检测出的异常需要人工确认,反馈结果用来持续优化 |
五、监督学习 vs 无监督学习:什么时候用哪个
| 有标签吗 | 有 | 没有 |
| 目标明确吗 | 明确(要预测什么) | 不明确(想探索数据) |
| 能验证对错吗 | 能 | 不能 |
| 效果评估 | 有明确指标(准确率等) | 靠业务判断(分得合不合理) |
| 典型场景 | 垃圾邮件检测、房价预测 | 用户分群、异常检测、数据探索 |
实践中的组合使用
很多时候不是二选一,而是先无监督、后监督:
第一步:无监督聚类,把用户分成5群
↓
第二步:人工看看每群的特征,给每群起名字(高价值/低价值/潜力/流失风险/新用户)
↓
第三步:用这些带标签的数据训练监督学习模型
↓
第四步:新用户来了,模型自动判断属于哪一群
这就是"半监督学习"的思路——用无监督方法生成标签,再用监督方法做预测。 两全其美。
六、一个常见的认知误区
误区:“无监督学习不需要标签,所以更简单”
事实恰恰相反。 无监督学习往往比监督学习更难用好,原因:
所以无监督学习不是"偷懒"的方案,而是"探索"的方案。 它适合用来发现你不知道的规律,而不是替代监督学习做预测。
七、本章涉及算法速查表
| K-Means | 聚类 | 找K个中心点,按距离分组 | 用户分群、文档归类 |
| DBSCAN | 聚类 | 密度大的地方就是一群 | 不规则形状、识别异常 |
| 层次聚类 | 聚类 | 自底向上或自顶向下逐步合并/拆分 | 需要看分群层次关系的场景 |
| PCA | 降维 | 找信息量最大的方向投影 | 数据可视化、特征压缩 |
| t-SNE | 降维 | 保持局部结构的非线性降维 | 高维数据可视化(比PCA更精细) |
| Isolation Forest | 异常检测 | 异常点容易被随机切分孤立 | 通用异常检测 |
| One-Class SVM | 异常检测 | 学一个边界包住正常数据 | 边界清晰的异常检测 |
本章小结
| 无监督学习 | 没有标签,让机器自己发现数据中的结构和规律 |
| 三大任务 | 聚类(自动分组)、降维(压缩信息)、异常检测(找异类) |
| K-Means | 最经典聚类算法,需要指定K,手肘法选K |
| DBSCAN | 不需要指定分组数,能识别异常点,适合不规则形状 |
| PCA | 最常用降维方法,用更少维度保留更多信息 |
| 异常检测 | 只学"正常长什么样",跟正常不一样的就是异常 |
| 实践建议 | 先无监督探索→人工标注→后监督预测;无监督不是偷懒而是探索 |
| 核心认知 | 没有标准答案不代表没有规律,机器照样能找出隐藏的结构 |
下一章预告: 第7章「特征工程:数据质量决定AI上限」—— "垃圾进垃圾出"是机器学习的铁律。同样的算法,好的特征工程能让效果提升30%以上。这章教你怎么从原始数据中"榨"出最有价值的特征。
网硕互联帮助中心






评论前必须登录!
注册