吴恩达机器学习课程(PyTorch 适配)学习笔记:3.1 无监督学习基础
3.1 无监督学习基础
3.1.1 无监督学习概述
无监督学习(Unsupervised Learning)是机器学习的重要分支,其核心特点是从无标签数据中自动发现潜在的模式、结构或规律。与监督学习依赖人工标注的标签不同,无监督学习更接近人类认知世界的方式——通过观察和归纳自主形成概念。
核心特点
- 输入数据没有标签(XXX 存在,yyy 不存在)
- 目标是发现数据的内在结构而非预测
- 常作为数据探索的第一步,为后续分析提供基础
- 结果的解释性往往需要领域知识辅助
与监督学习的关键区别
维度 | 无监督学习 | 监督学习 |
---|---|---|
数据要求 | 仅需特征数据 XXX | 需要特征 XXX + 标签 yyy |
核心任务 | 发现结构、模式、关联 | 学习映射 f:X→yf: X \rightarrow yf:X→y |
评估方式 | 主观(领域知识)+ 间接指标 | 客观(预测准确率等) |
典型应用 | 聚类、降维、异常检测 | 分类、回归、序列预测 |
数据依赖 | 对数据量要求高,质量敏感 | 对标签质量要求高 |
主要应用领域
- 数据探索与可视化(如降维分析)
- 客户分群与市场细分
- 异常检测与欺诈识别
- 特征学习与表示学习
- 推荐系统中的用户/物品聚类
常见算法分类
- 聚类算法:K均值、层次聚类、DBSCAN、谱聚类等
- 降维与可视化:PCA、t-SNE、UMAP、自编码器等
- 异常检测:基于统计的方法、孤立森林、One-Class SVM等
- 关联规则学习:Apriori、FP-Growth等
- 生成模型:高斯混合模型、变分自编码器、生成对抗网络等
3.1.2 聚类任务(定义 + 场景)
定义
聚类(Clustering)是无监督学习的核心任务之一,其目标是将数据集划分为若干个簇(Cluster),使得同一簇内的样本具有较高的相似性,而不同簇的样本具有较低的相似性。
数学描述:给定数据集 X={x1,x2,...,xn}X = \{x_1, x_2, ..., x_n\}X={x1,x2,...,xn},聚类算法将其划分为 kkk 个不相交的子集 C1,C2,...,CkC_1, C_2, ..., C_kC1,C2,...,Ck,满足:
- ⋃i=1kCi=X\bigcup_{i=1}^{k} C_i = X⋃i=1kCi=X
- Ci⋂Cj=∅C_i \bigcap C_j = \emptysetCi⋂Cj=∅(i≠ji \neq ji=j)
- 对于任意 x,y∈Cix, y \in C_ix,y∈Ci,相似度 sim(x,y)sim(x, y)sim(x,y) 较大
- 对于任意 x∈Ci,y∈Cjx \in C_i, y \in C_jx∈Ci,y∈Cj(i≠ji \neq ji=j),相似度 sim(x,y)sim(x, y)sim(x,y) 较小
相似度度量
聚类效果高度依赖于相似度(或距离)的定义,常用度量方式:
-
欧氏距离(Euclidean Distance):
d(x,y)=∑i=1d(xi−yi)2d(x, y) = \sqrt{\sum_{i=1}^{d} (x_i - y_i)^2}d(x,y)=i=1∑d(xi−yi)2
适用于连续型特征,对异常值敏感 -
曼哈顿距离(Manhattan Distance):
d(x,y)=∑i=1d∣xi−yi∣d(x, y) = \sum_{i=1}^{d} |x_i - y_i|d(x,y)=i=1∑d∣xi−yi∣
对异常值的鲁棒性优于欧氏距离 -
余弦相似度(Cosine Similarity):
sim(x,y)=x⋅y∣∣x∣∣⋅∣∣y∣∣sim(x, y) = \frac{x \cdot y}{||x|| \cdot ||y||}sim(x,y)=∣∣x∣∣⋅∣∣y∣∣x⋅y
适用于高维稀疏数据(如文本),关注方向而非大小 -
杰卡德系数(Jaccard Index):
J(A,B)=∣A⋂B∣∣A⋃B∣J(A, B) = \frac{|A \bigcap B|}{|A \bigcup B|}J(A,B)=∣A⋃B∣∣A⋂B∣
适用于二进制特征或集合数据
典型应用场景
-
客户分群与精准营销
- 根据购买历史、浏览行为等将客户分为不同群体
- 针对不同群体设计个性化营销策略
- 例:电商平台识别高价值客户群体与潜在流失客户群体
-
文本主题聚类
- 对新闻、评论等文本数据自动归类主题
- 例:将新闻分为政治、经济、体育等类别
-
图像分割与目标识别
- 对图像像素进行聚类,实现前景与背景分离
- 例:医学影像中肿瘤区域的自动识别
-
异常检测
- 识别与大多数样本差异显著的离群点
- 例:信用卡欺诈交易检测、网络入侵检测
-
特征工程辅助
- 将聚类结果作为新特征加入模型
- 例:将用户聚类ID作为特征用于推荐系统
聚类算法分类
类别 | 代表算法 | 核心思想 | 优点 | 缺点 |
---|---|---|---|---|
划分式 | K均值、K中心点 | 先确定簇数,再优化样本分配 | 高效、适合大规模数据 | 需预先指定K、对初始值敏感 |
层次式 | 凝聚聚类、分裂聚类 | 构建聚类树,自底向上或自顶向下 | 无需指定K、提供层次结构 | 计算复杂度高、不适合大规模数据 |
密度式 | DBSCAN、OPTICS | 基于样本密度划分,发现任意形状簇 | 可发现任意形状簇、抗噪声 | 对密度参数敏感、不适合高维数据 |
模型式 | 高斯混合模型 | 假设数据来自多个概率分布的混合 | 提供概率解释、软聚类 | 假设较强、计算复杂 |
谱聚类 | 谱聚类 | 基于图论,利用相似度矩阵特征值 | 适合高维数据、聚类效果好 | 计算复杂、参数敏感 |
3.1.3 K均值算法(原理 + 优化目标 + 初始化 + 聚类数选择)
K均值(K-means)是最经典的划分式聚类算法,由MacQueen于1967年提出,以其简单高效的特点成为工业界最常用的聚类算法之一。
原理与流程
K均值算法的核心思想是:通过迭代方式将数据集划分为K个簇,使得簇内样本相似度高,簇间样本相似度低。
算法流程:
- 初始化:随机选择K个样本作为初始聚类中心(Centroid)
- 分配样本:计算每个样本与各聚类中心的距离,将样本分配到最近的簇
- 更新中心:计算每个簇内所有样本的均值,作为新的聚类中心
- 收敛判断:若聚类中心变化小于阈值或达到最大迭代次数,则停止;否则返回步骤2
优化目标
K均值的优化目标是最小化簇内平方和(Within-Cluster Sum of Squares, WCSS),也称为惯性(Inertia):
J=∑k=1K∑x∈Ck∣∣x−μk∣∣2J = \sum_{k=1}^{K} \sum_{x \in C_k} ||x - \mu_k||^2J=k=1∑Kx∈Ck∑∣∣x−μk∣∣2
其中:
- CkC_kCk 是第 kkk 个簇
- μk\mu_kμk 是第 kkk 个簇的中心(均值向量)
- ∣∣x−μk∣∣2||x - \mu_k||^2∣∣x−μk∣∣2 是样本 xxx 与簇中心 μk\mu_kμk 的欧氏距离平方
该目标函数衡量了簇内样本的紧密程度,值越小表示聚类效果越好。
初始化方法
K均值算法对初始聚类中心的选择非常敏感,不好的初始化可能导致收敛到局部最优解。
-
随机初始化
- 从数据集中随机选择K个样本作为初始中心
- 优点:简单
- 缺点:可能导致聚类效果差,需要多次运行取最优结果
-
K-means++ 初始化(推荐)
- 由Arthur和Vassilvitskii于2007年提出,旨在选择距离较远的初始中心
- 步骤:
- 随机选择一个样本作为第一个中心
- 计算每个样本与最近已选中心的距离 d(x)2d(x)^2d(x)2
- 以与 d(x)2d(x)^2d(x)2 成正比的概率选择下一个中心
- 重复步骤2-3,直到选择K个中心
- 优点:提高了聚类质量和稳定性,减少了对初始值的依赖
聚类数K的选择
聚类数K是K均值算法的关键超参数,如何选择合适的K是实际应用中的常见难题。
-
肘部法(Elbow Method)
- 原理:随着K增大,WCSS会逐渐减小;当K超过真实聚类数后,WCSS下降速度会明显减缓,形成"肘部"
- 步骤:
- 尝试不同的K值(如1到10)
- 对每个K计算WCSS
- 绘制K-WCSS曲线,选择肘部对应的K值
- 缺点:肘部可能不明显,主观性强
-
轮廓系数法(Silhouette Score)
- 原理:衡量样本与自身簇的相似度(a)和与最近其他簇的相似度(b),轮廓系数 s=(b−a)/max(a,b)s = (b - a)/max(a, b)s=(b−a)/max(a,b)
- 取值范围:[-1, 1],越接近1表示聚类效果越好
- 步骤:
- 计算不同K值下的平均轮廓系数
- 选择系数最大的K值
- 优点:提供了更明确的判断标准
- 缺点:计算成本高,对凸形簇更有效
-
Gap统计量(Gap Statistic)
- 原理:比较实际数据的聚类效果与参考分布(如随机数据)的聚类效果
- 步骤:
- 生成参考分布数据(与原始数据同分布的随机数据)
- 计算实际数据与参考数据的Gap统计量
- 选择Gap统计量稳定时的K值
- 优点:更客观,适用于各种数据分布
- 缺点:计算复杂,耗时较长
-
领域知识
- 结合业务场景确定K值,例如:
- 客户分群通常选择3-5个群体
- 颜色聚类根据实际需求确定(如RGB三原色)
- 结合业务场景确定K值,例如:
PyTorch实现
import torch
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs# 生成模拟数据
def generate_data(n_samples=300, n_features=2, centers=3, random_state=42):X, y_true = make_blobs(n_samples=n_samples, n_features=n_features,centers=centers, cluster_std=0.60, random_state=random_state)return torch.tensor(X, dtype=torch.float32), y_true# K-means++初始化
def kmeans_plus_plus_initialization(X, k):n_samples, n_features = X.shapecenters = torch.zeros((k, n_features), dtype=X.dtype, device=X.device)# 随机选择第一个中心centers[0] = X[torch.randint(0, n_samples, (1,))]# 选择剩余的中心for i in range(1, k):# 计算每个样本到最近中心的距离平方distances = torch.min(torch.cdist(X, centers[:i])**2, dim=1).values# 计算概率分布probabilities = distances / torch.sum(distances)# 按概率选择下一个中心idx = torch.multinomial(probabilities, 1)centers[i] = X[idx]return centers# K-means算法实现
def kmeans(X, k, max_iter=100, tol=1e-4):n_samples, n_features = X.shapedevice = X.device# 初始化聚类中心centers = kmeans_plus_plus_initialization(X, k)for iter in range(max_iter):# 计算每个样本到各中心的距离distances = torch.cdist(X, centers) # 形状: [n_samples, k]# 分配样本到最近的簇labels = torch.argmin(distances, dim=1) # 形状: [n_samples]# 计算新的聚类中心new_centers = torch.zeros_like(centers)for i in range(k):cluster_points = X[labels == i]if len(cluster_points) > 0:new_centers[i] = torch.mean(cluster_points, dim=0)else:# 如果簇为空,重新随机选择一个样本作为中心new_centers[i] = X[torch.randint(0, n_samples, (1,))]# 检查收敛center_shift = torch.sum(torch.abs(new_centers - centers))if center_shift < tol:print(f"在第{iter+1}轮迭代收敛")breakcenters = new_centers# 计算最终的WCSSwcss = 0.0for i in range(k):cluster_points = X[labels == i]if len(cluster_points) > 0:wcss += torch.sum(torch.cdist(cluster_points, centers[i:i+1])**2)return labels, centers, wcss# 可视化聚类结果
def plot_clusters(X, labels, centers, title):X_np = X.numpy()centers_np = centers.numpy()plt.figure(figsize=(8, 6))plt.scatter(X_np[:, 0], X_np[:, 1], c=labels, cmap='viridis', s=50, alpha=0.7)plt.scatter(centers_np[:, 0], centers_np[:, 1], c='red', s=200, marker='X', label='聚类中心')plt.title(title)plt.xlabel('特征1')plt.ylabel('特征2')plt.legend()plt.grid(True, alpha=0.3)plt.show()# 肘部法选择最佳K值
def elbow_method(X, k_range):wcss_values = []for k in k_range:_, _, wcss = kmeans(X, k)wcss_values.append(wcss.item())print(f"K={k}, WCSS={wcss.item():.2f}")plt.figure(figsize=(8, 6))plt.plot(k_range, wcss_values, 'bo-')plt.xlabel('聚类数K')plt.ylabel('WCSS')plt.title('肘部法选择最佳K值')plt.grid(True, alpha=0.3)plt.show()# 主函数
def main():# 生成数据X, y_true = generate_data(n_samples=300, centers=4)print(f"数据形状: {X.shape}")# 展示原始数据plt.figure(figsize=(8, 6))plt.scatter(X[:, 0], X[:, 1], c=y_true, cmap='viridis', s=50, alpha=0.7)plt.title('原始数据分布')plt.xlabel('特征1')plt.ylabel('特征2')plt.grid(True, alpha=0.3)plt.show()# 使用肘部法确定最佳K值elbow_method(X, range(1, 11))# 使用最佳K值进行聚类best_k = 4 # 根据肘部法结果选择labels, centers, wcss = kmeans(X, best_k)print(f"最佳K值={best_k}, 最终WCSS={wcss.item():.2f}")# 可视化聚类结果plot_clusters(X, labels, centers, f'K-means聚类结果 (K={best_k})')if __name__ == "__main__":main()
优缺点与注意事项
优点:
- 算法简单易懂,实现容易
- 计算效率高,适合大规模数据集
- 收敛速度快,对高维数据有一定适应性
缺点:
- 需要预先指定聚类数K
- 对初始聚类中心敏感,可能收敛到局部最优
- 只能发现凸形簇,对非球形分布数据效果差
- 对异常值敏感
- 不适合处理类别不平衡的数据
注意事项与易错点:
- 特征标准化:K均值基于距离计算,不同量级的特征会影响聚类结果,需先进行标准化(如Z-score)
- 异常值处理:异常值会严重影响聚类中心,建议先进行异常检测和处理
- 多次运行:即使使用K-means++,也建议多次运行取最优结果
- 簇的解释性:聚类结果需要结合业务知识解释,避免为聚类而聚类
- 高维数据:高维空间中距离度量的意义减弱,建议先降维再聚类
- 空簇处理:当某个簇没有分配到样本时,需要重新初始化该簇中心
3.1.4 异常检测(场景 + 高斯分布基础)
异常检测定义
异常检测(Anomaly Detection)是识别与大多数数据显著不同的观测值或模式的过程。这些异常值也被称为离群点(Outliers)、异常样本(Anomalies)或异常事件(Novelties)。
异常的本质特征:
- 稀有性:异常样本在数据集中占比通常较低
- 差异性:与正常样本具有显著不同的特征模式
- 上下文依赖性:同一数据在不同场景下可能被视为正常或异常(如信用卡大额交易在节假日可能正常)
典型应用场景
-
欺诈检测
- 信用卡/银行欺诈交易检测
- 保险欺诈索赔识别
- 网络诈骗行为分析
-
工业故障诊断
- 设备传感器异常读数检测
- 生产过程中的质量异常监控
- 预测性维护中的早期故障识别
-
网络安全
- 网络入侵检测
- 异常登录行为识别
- DDoS攻击检测
-
医疗诊断
- 疾病早期预警(如心电图异常检测)
- 医疗影像异常区域识别
- 患者生命体征异常监测
-
其他领域
- 零售业中的异常退货行为
- 能源消耗异常监测
- 文本中的异常内容过滤
高斯分布(正态分布)基础
基于统计的异常检测方法广泛使用高斯分布(Gaussian Distribution)来建模正常数据的分布特征。
- 单变量高斯分布
若随机变量 xxx 服从均值为 μ\muμ、方差为 σ2\sigma^2σ2 的高斯分布,记为 x∼N(μ,σ2)x \sim \mathcal{N}(\mu, \sigma^2)x∼N(μ,σ2),其概率密度函数为:
p(x;μ,σ2)=12πσ2exp(−(x−μ)22σ2)p(x; \mu, \sigma^2) = \frac{1}{\sqrt{2\pi\sigma^2}} \exp\left(-\frac{(x - \mu)^2}{2\sigma^2}\right)p(x;μ,σ2)=2πσ21exp(−2σ2(x−μ)2)
其中:
- μ\muμ 是均值(期望),表示数据的中心位置
- σ2\sigma^2σ2 是方差,表示数据的离散程度
- σ=σ2\sigma = \sqrt{\sigma^2}σ=σ2 是标准差
- 参数估计
对于给定的数据集 x1,x2,...,xmx_1, x_2, ..., x_mx1,x2,...,xm,可以通过最大似然估计得到参数:
μ=1m∑i=1mxi\mu = \frac{1}{m} \sum_{i=1}^{m} x_iμ=m1i=1∑mxi
σ2=1m∑i=1m(xi−μ)2\sigma^2 = \frac{1}{m} \sum_{i=1}^{m} (x_i - \mu)^2σ2=m1i=1∑m(xi−μ)2
- 多变量高斯分布
对于 nnn 维特征向量 x=(x1,x2,...,xn)Tx = (x_1, x_2, ..., x_n)^Tx=(x1,x2,...,xn)T,多变量高斯分布记为 x∼N(μ,Σ)x \sim \mathcal{N}(\mu, \Sigma)x∼N(μ,Σ),其概率密度函数为:
p(x;μ,Σ)=1(2π)n/2∣Σ∣1/2exp(−12(x−μ)TΣ−1(x−μ))p(x; \mu, \Sigma) = \frac{1}{(2\pi)^{n/2} |\Sigma|^{1/2}} \exp\left(-\frac{1}{2}(x - \mu)^T \Sigma^{-1} (x - \mu)\right)p(x;μ,Σ)=(2π)n/2∣Σ∣1/21exp(−21(x−μ)TΣ−1(x−μ))
其中:
- μ\muμ 是 nnn 维均值向量
- Σ\SigmaΣ 是 n×nn \times nn×n 协方差矩阵,描述特征间的相关性
- ∣Σ∣|\Sigma|∣Σ∣ 是协方差矩阵的行列式
- Σ−1\Sigma^{-1}Σ−1 是协方差矩阵的逆
3.1.5 异常检测算法(流程 + 评估 + 与监督学习对比)
基于高斯分布的异常检测算法流程
基于高斯分布的异常检测是一种经典的统计方法,其核心思想是:假设正常数据服从高斯分布,通过计算样本的概率密度来判断是否为异常。
算法流程:
-
数据准备
- 收集主要包含正常样本的训练数据(异常样本比例应极低)
- 选择合适的特征(见3.1.6节特征选择)
- 对特征进行预处理(标准化、归一化等)
-
参数估计
- 对每个特征 xjx_jxj,计算均值 μj\mu_jμj 和方差 σj2\sigma_j^2σj2:
μj=1m∑i=1mxj(i)\mu_j = \frac{1}{m} \sum_{i=1}^{m} x_j^{(i)}μj=m1i=1∑mxj(i)
σj2=1m∑i=1m(xj(i)−μj)2\sigma_j^2 = \frac{1}{m} \sum_{i=1}^{m} (x_j^{(i)} - \mu_j)^2σj2=m1i=1∑m(xj(i)−μj)2 - (可选)使用多变量高斯分布时,计算协方差矩阵 Σ\SigmaΣ
- 对每个特征 xjx_jxj,计算均值 μj\mu_jμj 和方差 σj2\sigma_j^2σj2:
-
计算概率密度
- 对每个样本 xxx,计算其概率密度:
p(x)=∏j=1np(xj;μj,σj2)p(x) = \prod_{j=1}^{n} p(x_j; \mu_j, \sigma_j^2)p(x)=j=1∏np(xj;μj,σj2)
(单变量高斯分布假设特征独立) - 或使用多变量高斯分布计算联合概率密度
- 对每个样本 xxx,计算其概率密度:
-
确定阈值 ϵ\epsilonϵ
- 使用验证集确定阈值 ϵ\epsilonϵ
- 当 p(x)<ϵp(x) < \epsilonp(x)<ϵ 时,判定为异常
-
异常检测
- 对新样本计算 p(x)p(x)p(x)
- 根据与阈值的比较结果判断是否为异常
异常检测算法实现(PyTorch)
import torch
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs# 生成包含异常值的数据集
def generate_anomaly_data(n_normal=500, n_anomalies=20, n_features=2, random_state=42):# 生成正常样本(服从高斯分布)normal_data, _ = make_blobs(n_samples=n_normal,n_features=n_features,centers=1,cluster_std=0.6,random_state=random_state)# 生成异常样本(远离正常样本的随机点)np.random.seed(random_state)anomalies = np.random.uniform(low=normal_data.min() - 2,high=normal_data.max() + 2,size=(n_anomalies, n_features))# 合并数据并创建标签(0表示正常,1表示异常)X = np.vstack([normal_data, anomalies])y = np.hstack([np.zeros(n_normal), np.ones(n_anomalies)])# 打乱数据shuffle_idx = np.random.permutation(len(X))X = X[shuffle_idx]y = y[shuffle_idx]return torch.tensor(X, dtype=torch.float32), torch.tensor(y, dtype=torch.float32)# 分割训练集(主要是正常样本)、验证集和测试集
def split_data(X, y, train_ratio=0.6, val_ratio=0.2):n_samples = len(X)n_train = int(n_samples * train_ratio)n_val = int(n_samples * val_ratio)# 训练集尽量选择正常样本normal_indices = (y == 0).nonzero().squeeze()anomaly_indices = (y == 1).nonzero().squeeze()# 训练集中包含大部分正常样本n_train_normal = min(n_train, len(normal_indices))train_normal_idx = normal_indices[:n_train_normal]train_indices = train_normal_idx# 剩余样本用于验证集和测试集remaining_normal_idx = normal_indices[n_train_normal:]remaining_indices = torch.cat([remaining_normal_idx, anomaly_indices])np.random.shuffle(remaining_indices.numpy())val_indices = remaining_indices[:n_val]test_indices = remaining_indices[n_val:]X_train, y_train = X[train_indices], y[train_indices]X_val, y_val = X[val_indices], y[val_indices]X_test, y_test = X[test_indices], y[test_indices]print(f"训练集: {len(X_train)} 样本, 异常比例: {y_train.mean():.2%}")print(f"验证集: {len(X_val)} 样本, 异常比例: {y_val.mean():.2%}")print(f"测试集: {len(X_test)} 样本, 异常比例: {y_test.mean():.2%}")return X_train, y_train, X_val, y_val, X_test, y_test# 估计高斯分布参数
def estimate_gaussian_parameters(X):"""计算每个特征的均值和方差"""mu = torch.mean(X, dim=0) # 均值向量sigma2 = torch.var(X, dim=0, unbiased=False) # 方差向量(使用有偏估计)return mu, sigma2# 计算概率密度
def multivariate_gaussian(X, mu, sigma2):"""计算样本的概率密度(假设特征独立)"""n = X.shape[1]sigma_diag = torch.diag(sigma2)sigma_inv = torch.inverse(sigma_diag)X_mu = X - mu# 计算概率密度term1 = 1.0 / torch.pow(2 * torch.tensor(np.pi), n / 2)term2 = 1.0 / torch.sqrt(torch.det(sigma_diag))term3 = torch.exp(-0.5 * torch.diag(torch.matmul(torch.matmul(X_mu, sigma_inv), X_mu.T)))return term1 * term2 * term3# 选择最佳阈值
def select_threshold(y_val, p_val):"""根据验证集选择最佳阈值y_val: 验证集标签(0=正常,1=异常)p_val: 验证集样本的概率密度"""best_epsilon = 0.0best_f1 = 0.0f1 = 0.0# 尝试不同的阈值step_size = (torch.max(p_val) - torch.min(p_val)) / 1000for epsilon in torch.arange(torch.min(p_val), torch.max(p_val), step_size):# 预测标签predictions = (p_val < epsilon).float()# 计算精确率、召回率和F1分数tp = torch.sum((predictions == 1) & (y_val == 1)).item()fp = torch.sum((predictions == 1) & (y_val == 0)).item()fn = torch.sum((predictions == 0) & (y_val == 1)).item()# 避免除零错误precision = tp / (tp + fp) if (tp + fp) > 0 else 0recall = tp / (tp + fn) if (tp + fn) > 0 else 0# 计算F1分数if precision + recall > 0:f1 = 2 * precision * recall / (precision + recall)else:f1 = 0# 更新最佳阈值if f1 > best_f1:best_f1 = f1best_epsilon = epsilon.item()return best_epsilon, best_f1# 评估模型
def evaluate_model(y_true, p, epsilon):"""评估异常检测模型性能"""predictions = (p < epsilon).float()# 计算各类指标tp = torch.sum((predictions == 1) & (y_true == 1)).item()tn = torch.sum((predictions == 0) & (y_true == 0)).item()fp = torch.sum((predictions == 1) & (y_true == 0)).item()fn = torch.sum((predictions == 0) & (y_true == 1)).item()# 计算评估指标precision = tp / (tp + fp) if (tp + fp) > 0 else 0recall = tp / (tp + fn) if (tp + fn) > 0 else 0f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0accuracy = (tp + tn) / (tp + tn + fp + fn) if (tp + tn + fp + fn) > 0 else 0print(f"评估指标:")print(f"精确率 (Precision): {precision:.4f}")print(f"召回率 (Recall): {recall:.4f}")print(f"F1分数: {f1:.4f}")print(f"准确率 (Accuracy): {accuracy:.4f}")print(f"真正例 (TP): {tp}, 真负例 (TN): {tn}")print(f"假正例 (FP): {fp}, 假负例 (FN): {fn}")return {'precision': precision,'recall': recall,'f1': f1,'accuracy': accuracy,'tp': tp, 'tn': tn, 'fp': fp, 'fn': fn}# 可视化异常检测结果
def plot_anomaly_detection(X, y_true, p, epsilon):"""可视化异常检测结果"""X_np = X.numpy()y_pred = (p < epsilon).float().numpy()# 绘制所有样本plt.figure(figsize=(10, 8))# 正常样本normal_mask = (y_true == 0)plt.scatter(X_np[normal_mask, 0], X_np[normal_mask, 1],c='blue', label='正常样本', alpha=0.6, s=50)# 真实异常样本anomaly_mask = (y_true == 1)plt.scatter(X_np[anomaly_mask, 0], X_np[anomaly_mask, 1],c='red', label='真实异常', alpha=0.8, s=80, marker='X')# 预测的异常样本pred_anomaly_mask = (y_pred == 1)plt.scatter(X_np[pred_anomaly_mask, 0], X_np[pred_anomaly_mask, 1],c='none', edgecolors='green', label='预测异常', s=150, linewidth=2)# 绘制概率等高线x1_min, x1_max = X_np[:, 0].min() - 1, X_np[:, 0].max() + 1x2_min, x2_max = X_np[:, 1].min() - 1, X_np[:, 1].max() + 1xx1, xx2 = np.meshgrid(np.linspace(x1_min, x1_max, 100), np.linspace(x2_min, x2_max, 100))grid_points = torch.tensor(np.c_[xx1.ravel(), xx2.ravel()], dtype=torch.float32)mu = torch.mean(X, dim=0)sigma2 = torch.var(X, dim=0, unbiased=False)p_grid = multivariate_gaussian(grid_points, mu, sigma2)p_grid = p_grid.reshape(xx1.shape)plt.contour(xx1, xx2, p_grid, levels=[epsilon], linewidths=2, colors='black', linestyles='dashed')plt.title('异常检测结果可视化')plt.xlabel('特征1')plt.ylabel('特征2')plt.legend()plt.grid(True, alpha=0.3)plt.show()# 主函数
def main():# 生成数据X, y = generate_anomaly_data(n_normal=500, n_anomalies=30, n_features=2)# 分割数据X_train, y_train, X_val, y_val, X_test, y_test = split_data(X, y)# 估计高斯分布参数mu, sigma2 = estimate_gaussian_parameters(X_train)print(f"估计的均值: {mu.numpy()}")print(f"估计的方差: {sigma2.numpy()}")# 计算概率密度p_train = multivariate_gaussian(X_train, mu, sigma2)p_val = multivariate_gaussian(X_val, mu, sigma2)p_test = multivariate_gaussian(X_test, mu, sigma2)# 选择最佳阈值epsilon, f1 = select_threshold(y_val, p_val)print(f"最佳阈值 ε: {epsilon:.6f}, 对应的F1分数: {f1:.4f}")# 在测试集上评估print("\n测试集性能:")evaluate_model(y_test, p_test, epsilon)# 可视化结果plot_anomaly_detection(X_test, y_test, p_test, epsilon)if __name__ == "__main__":main()
异常检测评估方法
异常检测的评估面临特殊挑战:数据高度不平衡(异常样本通常只占1%-5%),传统的准确率指标会产生误导。
-
混淆矩阵(Confusion Matrix)
预测/实际 正常(0) 异常(1) 正常(0) 真负例(TN) 假负例(FN) 异常(1) 假正例(FP) 真正例(TP) -
核心评估指标
-
精确率(Precision):预测为异常的样本中真正异常的比例
Precision=TPTP+FP\text{Precision} = \frac{TP}{TP + FP}Precision=TP+FPTP
关注"少误报",适用于FP成本高的场景(如信用卡欺诈检测) -
召回率(Recall):所有真实异常中被正确检测出的比例
Recall=TPTP+FN\text{Recall} = \frac{TP}{TP + FN}Recall=TP+FNTP
关注"少漏报",适用于FN成本高的场景(如疾病诊断) -
F1分数:精确率和召回率的调和平均
F1=2×Precision×RecallPrecision+RecallF1 = 2 \times \frac{\text{Precision} \times \text{Recall}}{\text{Precision} + \text{Recall}}F1=2×Precision+RecallPrecision×Recall
综合评价指标,适用于需要平衡精确率和召回率的场景 -
ROC曲线与AUC:
- ROC曲线:不同阈值下假正例率(FPR)与真正例率(TPR)的关系
- AUC:ROC曲线下面积,取值范围[0,1],越大表示检测效果越好
-
-
评估注意事项
- 避免使用准确率(Accuracy)作为主要指标
- 异常检测中,召回率通常比精确率更重要
- 阈值选择需根据业务场景中FP和FN的成本权衡
- 建议使用交叉验证评估模型稳定性
与监督学习的对比
异常检测与监督学习在处理异常问题时各有优劣,选择需根据具体场景:
维度 | 异常检测(无监督/半监督) | 监督学习(分类) |
---|---|---|
数据要求 | 主要需要正常样本,异常样本可很少 | 需要大量标注的正常和异常样本 |
异常类型 | 可检测未知类型的异常 | 只能检测训练过的已知异常类型 |
适应性 | 对新出现的异常类型有一定适应性 | 对新异常类型适应性差 |
标注成本 | 低(无需标注或少量标注) | 高(需要大量标注数据) |
应用场景 | 异常样本少、类型多变的场景(如欺诈检测) | 异常类型固定、样本充足的场景(如垃圾邮件分类) |
算法特点 | 建模正常模式,偏离者为异常 | 同时建模正常和异常模式 |
典型算法 | 基于高斯分布、孤立森林、One-Class SVM | 逻辑回归、随机森林、神经网络 |
何时选择异常检测而非监督学习:
- 异常样本非常稀少(<5%)
- 异常类型不断变化(如网络攻击方式)
- 标注异常样本成本高或困难
- 需要检测未知类型的异常
3.1.6 异常检测特征选择(思路)
特征选择是异常检测成功的关键步骤,高质量的特征能显著提升检测效果。异常检测的特征选择有其特殊性,需要重点关注能区分正常与异常的特征。
特征选择的核心原则
- 区分性原则:选择正常样本与异常样本差异显著的特征
- 稳定性原则:选择在正常样本中表现稳定,在异常样本中表现不稳定的特征
- 相关性原则:避免高度相关的冗余特征,选择信息互补的特征
- 可解释性原则:优先选择具有业务解释性的特征,便于后续结果分析
特征选择思路与方法
-
基于领域知识的特征选择
- 结合业务理解选择与异常相关的特征
- 例:
- 信用卡欺诈检测:交易金额、交易频率、异地交易、新设备交易等
- 设备故障检测:温度、压力、振动频率、能耗等
- 优势:直接有效,减少无效特征
- 注意:需与领域专家密切合作
-
基于统计分析的特征选择
- 分析特征在正常样本和异常样本中的分布差异
- 常用方法:
- 均值差异分析:比较特征在两类样本中的均值差异
- 方差分析:异常样本通常具有更大的方差
- 分布检验:使用KS检验等方法检验分布是否存在显著差异
- 实现示例:
def analyze_feature_distributions(X, y):"""分析特征在正常和异常样本中的分布差异"""n_features = X.shape[1]normal_mask = (y == 0)for i in range(n_features):# 分离正常和异常样本的特征值normal_vals = X[normal_mask, i]anomaly_vals = X[~normal_mask, i]# 计算基本统计量normal_mean = torch.mean(normal_vals)anomaly_mean = torch.mean(anomaly_vals)mean_diff = torch.abs(normal_mean - anomaly_mean)normal_std = torch.std(normal_vals)# 打印统计信息print(f"特征 {i+1}:")print(f" 正常样本: 均值={normal_mean:.4f}, 标准差={normal_std:.4f}, 样本数={len(normal_vals)}")print(f" 异常样本: 均值={anomaly_mean:.4f}, 样本数={len(anomaly_vals)}")print(f" 均值差异: {mean_diff:.4f}, 差异倍数: {mean_diff/normal_std:.2f}x")print("-" * 50)# 绘制分布直方图plt.figure(figsize=(8, 4))plt.hist(normal_vals.numpy(), bins=20, alpha=0.5, label='正常样本')plt.hist(anomaly_vals.numpy(), bins=20, alpha=0.5, label='异常样本')plt.axvline(normal_mean, color='blue', linestyle='dashed', linewidth=1)plt.axvline(anomaly_mean, color='orange', linestyle='dashed', linewidth=1)plt.title(f'特征 {i+1} 分布')plt.xlabel('特征值')plt.ylabel('频率')plt.legend()plt.show()
-
基于特征重要性的选择
- 使用树模型或线性模型评估特征重要性
- 方法:
- 随机森林的特征重要性得分
- 线性模型的系数绝对值
- 特征消除法(递归删除重要性低的特征)
- 注意:需使用有标签的验证集进行评估
-
基于异常检测性能的选择
- 评估特征对异常检测性能的贡献
- 方法:
- 逐一移除特征,观察模型性能变化
- 组合不同特征子集,选择性能最佳的组合
- 使用特征选择算法(如遗传算法、粒子群优化)搜索最优特征子集
- 示例流程:
- 初始化包含所有特征的集合
- 计算当前特征集合的检测性能(如F1分数)
- 移除一个特征,计算新集合的性能
- 保留性能提升或下降最小的特征集合
- 重复步骤2-4,直到达到预设特征数量
特征工程技巧
-
特征转换
- 对偏态分布的特征进行对数、平方根等转换
- 标准化或归一化特征,消除量纲影响
- 对时间序列数据,可提取滚动统计特征(如滑动窗口均值、方差)
-
特征组合
- 创建比率特征(如交易金额/平均交易金额)
- 创建交互特征(如时间×地点)
- 创建聚合特征(如用户过去24小时的交易次数)
-
时间相关特征
- 对于时间序列数据,添加时间差特征(如距上次交易的时间)
- 添加周期性特征(如是否周末、是否节假日)
- 添加趋势特征(如最近7天的交易增长率)
注意事项与常见错误
- 过度依赖高维特征:高维特征可能导致"维度灾难",降低检测效果
- 忽略特征相关性:高度相关的特征会引入冗余,放大噪声影响
- 忽视特征稳定性:选择在正常情况下稳定的特征,避免受无关因素影响
- 特征太少或太多:特征太少可能丢失信息,特征太多可能引入噪声
- 不考虑特征时效性:某些特征的分布可能随时间变化,需定期重新评估
特征选择检查清单:
- 特征是否与异常检测目标相关?
- 特征在正常样本和异常样本中是否有显著差异?
- 特征是否具有业务解释性?
- 特征是否稳定可靠,不受噪声干扰?
- 特征之间是否存在冗余?
通过精心的特征选择和工程,可以显著提升异常检测模型的性能,减少误报和漏报,使模型更具实用价值。