
作者:HOS(安全风信子) 日期:2026-01-09 来源平台:GitHub 摘要: 本文深入剖析无监督学习结果的评估方法,揭示其在安全攻防场景中的核心挑战和解决方案。通过对比监督学习与无监督学习评估的本质差异,结合安全领域的实际应用案例,展示如何构建有效的无监督模型评价体系。文章包含3个完整代码示例、2个Mermaid架构图,并通过TRAE元素(Table、Reference、Appendix、Example)全面阐述无监督结果评估的技术深度与安全工程实践价值。
无监督学习在安全领域的应用日益广泛,尤其是在异常检测、聚类分析和表示学习等场景中。然而,与监督学习不同,无监督学习没有明确的真实标签用于模型评估,这给无监督模型的性能评价带来了巨大挑战。在安全领域,不准确的评估可能导致严重后果,如误报率过高影响系统可用性,或漏报率过高导致安全事件未被及时发现。
根据GitHub 2025年安全ML趋势报告,超过70%的企业级安全团队在无监督模型评估方面面临困难,尤其是在缺乏真实标签的情况下[^1]。有效的无监督结果评估方法对于确保无监督模型的可靠性和有效性至关重要,是无监督学习在安全领域成功应用的关键。
尽管无监督结果评估至关重要,但很多实践者对其存在误解,认为无监督模型无法进行有效评估,或者简单地使用监督学习的评估指标。这种误区导致在实际应用中无法准确评价无监督模型的性能,影响模型的选择和优化。在安全场景下,这种误解可能导致选择了不适合的模型,或者对模型性能过于自信,从而引发安全风险。
无监督评估和监督评估具有根本的差异,主要体现在以下几个方面:
根据arXiv 2025年最新论文《A Comprehensive Evaluation Framework for Unsupervised Anomaly Detection in Cybersecurity》,研究者提出了一种全面的无监督异常检测评估框架(CEFUAD),该框架结合了内部指标、外部指标和领域特定指标,在多个安全数据集上实现了对无监督模型的有效评估[^2]。这一研究成果表明,无监督结果评估在安全领域正在形成一套完整的方法论体系。
无监督评估方法主要包括以下几类:

渲染错误: Mermaid 渲染失败: Parse error on line 23: ... style A fill:#32CD32,stroke:#333 ----------------------^ Expecting 'SOLID_OPEN_ARROW', 'DOTTED_OPEN_ARROW', 'SOLID_ARROW', 'BIDIRECTIONAL_SOLID_ARROW', 'DOTTED_ARROW', 'BIDIRECTIONAL_DOTTED_ARROW', 'SOLID_CROSS', 'DOTTED_CROSS', 'SOLID_POINT', 'DOTTED_POINT', got 'TXT'
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.cluster import KMeans, DBSCAN
from sklearn.metrics import silhouette_score, calinski_harabasz_score, davies_bouldin_score, adjusted_rand_score, normalized_mutual_info_score
# 生成示例聚类数据
X, y_true = make_blobs(n_samples=500, centers=3, n_features=10, random_state=42)
# 1. 使用KMeans进行聚类
kmeans = KMeans(n_clusters=3, random_state=42)
y_pred_kmeans = kmeans.fit_predict(X)
# 2. 使用DBSCAN进行聚类
dbscan = DBSCAN(eps=1.5, min_samples=5)
y_pred_dbscan = dbscan.fit_predict(X)
# 3. 计算内部评估指标
def evaluate_clustering(X, y_pred, algorithm_name):
print(f"\n{algorithm_name} 聚类评估结果:")
# 计算轮廓系数
if len(set(y_pred)) > 1: # 至少需要2个簇才能计算轮廓系数
silhouette = silhouette_score(X, y_pred)
print(f"轮廓系数: {silhouette:.4f}")
# 计算Calinski-Harabasz指数
ch_score = calinski_harabasz_score(X, y_pred)
print(f"Calinski-Harabasz指数: {ch_score:.4f}")
# 计算Davies-Bouldin指数
db_score = davies_bouldin_score(X, y_pred)
print(f"Davies-Bouldin指数: {db_score:.4f}")
return {
'algorithm': algorithm_name,
'silhouette': silhouette if len(set(y_pred)) > 1 else None,
'ch_score': ch_score,
'db_score': db_score
}
# 评估KMeans和DBSCAN的聚类结果
kmeans_results = evaluate_clustering(X, y_pred_kmeans, "KMeans")
dbscan_results = evaluate_clustering(X, y_pred_dbscan, "DBSCAN")
# 4. 计算外部评估指标(使用真实标签)
print(f"\n外部评估指标(与真实标签对比):")
# 调整兰德指数
ari_kmeans = adjusted_rand_score(y_true, y_pred_kmeans)
ari_dbscan = adjusted_rand_score(y_true, y_pred_dbscan)
print(f"KMeans 调整兰德指数: {ari_kmeans:.4f}")
print(f"DBSCAN 调整兰德指数: {ari_dbscan:.4f}")
# 归一化互信息
nmi_kmeans = normalized_mutual_info_score(y_true, y_pred_kmeans)
nmi_dbscan = normalized_mutual_info_score(y_true, y_pred_dbscan)
print(f"KMeans 归一化互信息: {nmi_kmeans:.4f}")
print(f"DBSCAN 归一化互信息: {nmi_dbscan:.4f}")
# 5. 可视化评估结果
results = [kmeans_results, dbscan_results]
algorithms = [result['algorithm'] for result in results]
silhouette_scores = [result['silhouette'] for result in results]
ch_scores = [result['ch_score'] for result in results]
db_scores = [result['db_score'] for result in results]
plt.figure(figsize=(15, 5))
# 轮廓系数对比
plt.subplot(1, 3, 1)
plt.bar(algorithms, silhouette_scores, color=['#FF4500', '#32CD32'])
plt.title('轮廓系数对比')
plt.ylabel('轮廓系数')
plt.ylim(0, 1)
# Calinski-Harabasz指数对比
plt.subplot(1, 3, 2)
plt.bar(algorithms, ch_scores, color=['#FF4500', '#32CD32'])
plt.title('Calinski-Harabasz指数对比')
plt.ylabel('Calinski-Harabasz指数')
# Davies-Bouldin指数对比
plt.subplot(1, 3, 3)
plt.bar(algorithms, db_scores, color=['#FF4500', '#32CD32'])
plt.title('Davies-Bouldin指数对比')
plt.ylabel('Davies-Bouldin指数')
plt.tight_layout()
plt.show()import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.ensemble import IsolationForest
from sklearn.neighbors import LocalOutlierFactor
from sklearn.metrics import roc_auc_score, precision_recall_curve, auc, accuracy_score, f1_score
# 生成示例异常检测数据
# 生成正常样本
X_normal, _ = make_blobs(n_samples=1000, centers=3, n_features=10, random_state=42)
# 生成异常样本
X_anomaly = np.random.randn(100, 10) * 3 + 10 # 远离正常样本的异常点
# 合并正常样本和异常样本
test_data = np.vstack([X_normal, X_anomaly])
y_true = np.array([0] * 1000 + [1] * 100) # 0: 正常, 1: 异常
# 1. 使用Isolation Forest进行异常检测
iso = IsolationForest(contamination=0.09, random_state=42)
iso.fit(X_normal)
# 获取异常分数(需要转换为正数,数值越大越可能是异常)
iso_scores = -iso.decision_function(test_data)
y_pred_iso = (iso.predict(test_data) == -1).astype(int)
# 2. 使用Local Outlier Factor进行异常检测
lof = LocalOutlierFactor(n_neighbors=20, contamination=0.09)
# LOF直接提供异常分数(正数表示异常)
lof_scores = -lof._decision_function(test_data)
y_pred_lof = (lof.fit_predict(test_data) == -1).astype(int)
# 3. 评估异常检测结果
def evaluate_anomaly_detection(y_true, y_pred, scores, algorithm_name):
print(f"\n{algorithm_name} 异常检测评估结果:")
# 计算准确率
accuracy = accuracy_score(y_true, y_pred)
print(f"准确率: {accuracy:.4f}")
# 计算F1分数
f1 = f1_score(y_true, y_pred)
print(f"F1分数: {f1:.4f}")
# 计算AUROC
auroc = roc_auc_score(y_true, scores)
print(f"AUROC: {auroc:.4f}")
# 计算AUPRC
precision, recall, _ = precision_recall_curve(y_true, scores)
auprc = auc(recall, precision)
print(f"AUPRC: {auprc:.4f}")
# 计算误报率和漏报率
false_positive = np.sum((y_pred == 1) & (y_true == 0))
false_negative = np.sum((y_pred == 0) & (y_true == 1))
total_normal = np.sum(y_true == 0)
total_anomaly = np.sum(y_true == 1)
false_positive_rate = false_positive / total_normal
false_negative_rate = false_negative / total_anomaly
print(f"误报率: {false_positive_rate:.4f}")
print(f"漏报率: {false_negative_rate:.4f}")
return {
'algorithm': algorithm_name,
'accuracy': accuracy,
'f1': f1,
'auroc': auroc,
'auprc': auprc,
'false_positive_rate': false_positive_rate,
'false_negative_rate': false_negative_rate
}
# 评估Isolation Forest和LOF的异常检测结果
iso_results = evaluate_anomaly_detection(y_true, y_pred_iso, iso_scores, "Isolation Forest")
lof_results = evaluate_anomaly_detection(y_true, y_pred_lof, lof_scores, "Local Outlier Factor")
# 4. 可视化评估结果
results = [iso_results, lof_results]
algorithms = [result['algorithm'] for result in results]
auroc_scores = [result['auroc'] for result in results]
auprc_scores = [result['auprc'] for result in results]
fpr_scores = [result['false_positive_rate'] for result in results]
fnr_scores = [result['false_negative_rate'] for result in results]
plt.figure(figsize=(15, 10))
# AUROC和AUPRC对比
plt.subplot(2, 2, 1)
bar_width = 0.35
index = np.arange(len(algorithms))
bar1 = plt.bar(index, auroc_scores, bar_width, label='AUROC', color='#FF4500')
bar2 = plt.bar(index + bar_width, auprc_scores, bar_width, label='AUPRC', color='#32CD32')
plt.xlabel('算法')
plt.ylabel('分数')
plt.title('AUROC和AUPRC对比')
plt.xticks(index + bar_width / 2, algorithms)
plt.legend()
# 误报率对比
plt.subplot(2, 2, 2)
plt.bar(algorithms, fpr_scores, color=['#FF4500', '#32CD32'])
plt.title('误报率对比')
plt.ylabel('误报率')
plt.ylim(0, 0.5)
# 漏报率对比
plt.subplot(2, 2, 3)
plt.bar(algorithms, fnr_scores, color=['#FF4500', '#32CD32'])
plt.title('漏报率对比')
plt.ylabel('漏报率')
plt.ylim(0, 0.5)
# F1分数对比
plt.subplot(2, 2, 4)
plt.bar(algorithms, [result['f1'] for result in results], color=['#FF4500', '#32CD32'])
plt.title('F1分数对比')
plt.ylabel('F1分数')
plt.ylim(0, 1)
plt.tight_layout()
plt.show()import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.decomposition import PCA, KernelPCA
from sklearn.manifold import TSNE, UMAP
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 生成示例分类数据
X, y = make_classification(n_samples=1000, n_features=20, n_informative=10, n_redundant=10, random_state=42)
# 数据划分
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 1. 无监督表示学习方法
# PCA
pca = PCA(n_components=10, random_state=42)
pca.fit(X_train)
X_train_pca = pca.transform(X_train)
X_test_pca = pca.transform(X_test)
# Kernel PCA
kernel_pca = KernelPCA(n_components=10, kernel='rbf', random_state=42)
kernel_pca.fit(X_train)
X_train_kpca = kernel_pca.transform(X_train)
X_test_kpca = kernel_pca.transform(X_test)
# UMAP
umap = UMAP(n_components=10, random_state=42)
umap.fit(X_train)
X_train_umap = umap.transform(X_train)
X_test_umap = umap.transform(X_test)
# 2. 评估表示学习的质量:通过分类任务
# 在原始特征上训练分类器
lr_original = LogisticRegression(random_state=42)
lr_original.fit(X_train, y_train)
y_pred_original = lr_original.predict(X_test)
accuracy_original = accuracy_score(y_test, y_pred_original)
# 在PCA表示上训练分类器
lr_pca = LogisticRegression(random_state=42)
lr_pca.fit(X_train_pca, y_train)
y_pred_pca = lr_pca.predict(X_test_pca)
accuracy_pca = accuracy_score(y_test, y_pred_pca)
# 在Kernel PCA表示上训练分类器
lr_kpca = LogisticRegression(random_state=42)
lr_kpca.fit(X_train_kpca, y_train)
y_pred_kpca = lr_kpca.predict(X_test_kpca)
accuracy_kpca = accuracy_score(y_test, y_pred_kpca)
# 在UMAP表示上训练分类器
lr_umap = LogisticRegression(random_state=42)
lr_umap.fit(X_train_umap, y_train)
y_pred_umap = lr_umap.predict(X_test_umap)
accuracy_umap = accuracy_score(y_test, y_pred_umap)
# 3. 评估表示学习的质量:通过重构误差
# PCA重构误差
def compute_reconstruction_error(X, X_reconstructed):
return np.mean(np.sum((X - X_reconstructed) ** 2, axis=1))
X_train_reconstructed_pca = pca.inverse_transform(X_train_pca)
reconstruction_error_pca = compute_reconstruction_error(X_train, X_train_reconstructed_pca)
# 4. 打印评估结果
print("表示学习评估结果:")
print(f"原始特征分类准确率: {accuracy_original:.4f}")
print(f"PCA表示分类准确率: {accuracy_pca:.4f}")
print(f"Kernel PCA表示分类准确率: {accuracy_kpca:.4f}")
print(f"UMAP表示分类准确率: {accuracy_umap:.4f}")
print(f"PCA重构误差: {reconstruction_error_pca:.4f}")
# 5. 可视化评估结果
methods = ['原始特征', 'PCA', 'Kernel PCA', 'UMAP']
accuracies = [accuracy_original, accuracy_pca, accuracy_kpca, accuracy_umap]
plt.figure(figsize=(12, 5))
# 分类准确率对比
plt.subplot(1, 2, 1)
plt.bar(methods, accuracies, color=['#FF4500', '#32CD32', '#4169E1', '#DA70D6'])
plt.title('不同表示的分类准确率对比')
plt.xlabel('表示方法')
plt.ylabel('分类准确率')
plt.ylim(0.8, 1.0)
# 添加数值标签
for i, v in enumerate(accuracies):
plt.text(i, v + 0.005, f"{v:.4f}", ha='center', va='bottom')
# 可视化UMAP降维到2D的效果
umap_2d = UMAP(n_components=2, random_state=42)
umap_2d.fit(X_train)
X_train_umap_2d = umap_2d.transform(X_train)
X_test_umap_2d = umap_2d.transform(X_test)
plt.subplot(1, 2, 2)
plt.scatter(X_test_umap_2d[:, 0], X_test_umap_2d[:, 1], c=y_test, cmap='viridis', alpha=0.8)
plt.title('UMAP降维到2D的可视化效果')
plt.xlabel('UMAP维度1')
plt.ylabel('UMAP维度2')
plt.tight_layout()
plt.show()评估维度 | 内部指标 | 外部指标 | 相对指标 | 领域特定指标 |
|---|---|---|---|---|
核心原理 | 基于数据本身的统计特性 | 基于部分真实标签 | 与其他模型对比 | 针对特定领域设计 |
适用场景 | 无标签数据 | 有部分标签数据 | 模型选择和对比 | 特定应用领域 |
评估难度 | 中等 | 中等 | 简单 | 复杂 |
计算复杂度 | 低到中等 | 低到中等 | 低 | 中等 |
可解释性 | 中等 | 高 | 高 | 高 |
安全领域适用性 | 高(初步评估) | 高(有标签时) | 高(模型选择) | 高(实际部署) |
实时评估 | 适合 | 适合 | 适合 | 适合 |
优缺点 | 优点:无需标签缺点:可能与实际任务无关 | 优点:与真实情况相关缺点:需要标签 | 优点:简单直观缺点:依赖基线模型 | 优点:与业务紧密相关缺点:设计复杂 |
代表指标 | 轮廓系数、Calinski-Harabasz指数 | AUROC、AUPRC、ARI | 与随机模型对比、与SOTA对比 | 误报率、漏报率、平均检测时间 |
作为一名安全领域的研究者和实践者,我认为无监督结果评估将在未来成为安全ML领域的重要研究方向。随着无监督学习在安全领域的广泛应用,有效的评估方法将变得越来越重要。
在工程实践中,我建议安全团队关注以下几点:
无监督结果评估是无监督学习在安全领域成功应用的关键。通过建立有效的评估体系,我们可以更好地理解无监督模型的性能,选择合适的模型,并持续优化,从而提高安全系统的检测能力和可靠性。
参考链接:
附录(Appendix):
指标类型 | 指标名称 | 计算方式 | 取值范围 | 最佳值 |
|---|---|---|---|---|
聚类内部指标 | 轮廓系数 | 样本与簇内相似度减去与最近簇相似度,除以最大值 | [-1, 1] | 接近1 |
聚类内部指标 | Calinski-Harabasz指数 | 簇间方差与簇内方差的比值 | [0, ∞) | 越大越好 |
聚类内部指标 | Davies-Bouldin指数 | 簇内距离与簇间距离的比值 | [0, ∞) | 越小越好 |
聚类外部指标 | 调整兰德指数 | 考虑随机分配的兰德指数调整 | [-1, 1] | 接近1 |
聚类外部指标 | 归一化互信息 | 考虑熵的互信息调整 | [0, 1] | 接近1 |
异常检测指标 | AUROC | ROC曲线下面积 | [0, 1] | 接近1 |
异常检测指标 | AUPRC | PR曲线下面积 | [0, 1] | 接近1 |
异常检测指标 | 误报率 | 正常样本被误判为异常的比例 | [0, 1] | 接近0 |
异常检测指标 | 漏报率 | 异常样本被误判为正常的比例 | [0, 1] | 接近0 |
# 安装必要的Python库
pip install numpy pandas scikit-learn matplotlib seaborn umap-learn关键词: 无监督学习, 评估指标, 聚类评估, 异常检测评估, 表示学习评估, 安全攻防