1. 层次聚类(HC)基础与原理剖析
层次聚类(Hierarchical Clustering)是一种经典的聚类分析方法,它通过构建数据的层次分解来形成聚类结构。这种方法特别适合探索性数据分析,因为它不需要预先指定聚类数量,而是通过树状图(dendrogram)直观展示数据点之间的层次关系。
1.1 层次聚类的基本思想
层次聚类的核心思想可以用一个简单的比喻来理解:想象你有一堆不同颜色的积木,首先你会把颜色完全相同的积木放在一起,然后把颜色相近的组再合并,最终形成一个从最细粒度到最粗粒度的层次结构。在数学上,这个过程就是通过计算数据点之间的相似度或距离,逐步合并最相似的簇(cluster)。
层次聚类主要分为两种类型:
- 凝聚式(Agglomerative):自底向上,每个数据点初始为一个簇,然后逐步合并
- 分裂式(Divisive):自顶向下,所有数据点初始为一个簇,然后逐步分裂
在实际应用中,凝聚式层次聚类更为常见,也是MATLAB中linkage函数默认采用的方法。
1.2 距离度量与连接准则
层次聚类的质量很大程度上取决于两个关键选择:
- 距离度量:如何计算两个数据点之间的距离
- 连接准则:如何计算两个簇之间的距离
常见的距离度量包括:
- 欧几里得距离(Euclidean):
sqrt(sum((x-y).^2)) - 曼哈顿距离(Manhattan):
sum(abs(x-y)) - 余弦相似度(Cosine):
1 - dot(x,y)/(norm(x)*norm(y))
对于连接准则,MATLAB的linkage函数支持以下几种:
- 单连接(Single):两个簇中最近点之间的距离
- 全连接(Complete):两个簇中最远点之间的距离
- 平均连接(Average):两个簇中所有点对距离的平均值
- 质心连接(Centroid):两个簇质心之间的距离
- Ward方法:合并后簇内方差增加最小的方式
提示:对于大多数应用场景,平均连接('average')是一个稳健的选择,它平衡了单连接的"链式效应"和全连接的"紧凑性倾向"。
2. MATLAB实现层次聚类的完整流程
2.1 数据准备与预处理
在应用层次聚类前,数据预处理是至关重要的一步。对于数值型数据,常见的预处理步骤包括:
缺失值处理:
data(isnan(data)) = mean(data,'omitnan'); % 用均值填充缺失值数据标准化:
data = zscore(data); % 使每个特征均值为0,标准差为1异常值处理:
[tf,~] = isoutlier(data,'gesd'); data(tf) = median(data); % 用中位数替换异常值
对于分类数据,需要先将其转换为数值表示。MATLAB提供了dummyvar函数可以创建虚拟变量:
categories = {'red','blue','green','red'}; [~,~,cat_num] = unique(categories); dummy_matrix = dummyvar(cat_num);2.2 距离矩阵计算
MATLAB中计算距离矩阵的主要函数是pdist,它支持多种距离度量:
distanceMatrix = pdist(data, 'euclidean'); % 欧几里得距离(默认) %distanceMatrix = pdist(data, 'cityblock'); % 曼哈顿距离 %distanceMatrix = pdist(data, 'cosine'); % 余弦距离 %distanceMatrix = pdist(data, 'hamming'); % 分类数据距离对于大型数据集,pdist可能会消耗大量内存,因为一个n×n的距离矩阵需要存储n(n-1)/2个元素。当n>10000时,建议考虑以下优化:
- 使用稀疏矩阵表示
- 采样部分数据进行初步分析
- 使用更高效的距离计算函数如
pdist2
2.3 层次聚类执行
linkage函数将距离矩阵转换为层次聚类树:
linkageMatrix = linkage(distanceMatrix, 'average');linkageMatrix是一个(n-1)×3的矩阵,其中每行表示一次合并:
- 第一列和第二列是被合并的两个簇的索引
- 第三列是这两个簇之间的距离
- 当簇索引≤n时,表示原始数据点;>n时,表示中间簇
我们可以通过cophenet函数评估聚类质量:
c = cophenet(linkageMatrix, distanceMatrix); % c值越接近1,表示聚类结构越好2.4 聚类结果可视化
树状图是层次聚类最直观的可视化方式:
figure; dendrogram(linkageMatrix, 'ColorThreshold', 'default'); title('层次聚类树状图'); xlabel('数据点索引/簇'); ylabel('距离');ColorThreshold参数控制着色阈值,可以设置为:
- 'default':使用70%的最大距离作为阈值
- 具体数值:如0.7*max(linkageMatrix(:,3))
- 'cutoff':交互式选择切割点
对于高维数据,可以结合主成分分析(PCA)进行降维可视化:
[coeff,score] = pca(data); figure; scatter(score(:,1), score(:,2), 30, clusterID, 'filled'); colorbar; title('PCA降维后的聚类结果');3. 层次聚类的实战技巧与优化
3.1 确定最佳聚类数量
虽然层次聚类不需要预先指定簇数,但实际应用中常需要确定切割点。以下是几种常用方法:
不一致性系数法:
inconsistency = inconsistent(linkageMatrix); cutoff = mean(inconsistent(end-10:end,4)) + std(inconsistent(end-10:end,4)); clusterID = cluster(linkageMatrix, 'cutoff', cutoff);肘部法则(Elbow Method):
last_merge = linkageMatrix(end:-1:1,3); diff_merge = diff(last_merge); [~,k] = max(diff_merge); clusterID = cluster(linkageMatrix, 'maxclust', k+1);轮廓系数法:
silhouette_vals = zeros(10,1); for k = 2:10 clusterID = cluster(linkageMatrix, 'maxclust', k); silhouette_vals(k) = mean(silhouette(data, clusterID)); end [~,optimal_k] = max(silhouette_vals);
3.2 处理大规模数据的技巧
当数据量较大时(n>10000),标准层次聚类可能效率低下。可以考虑以下优化:
使用快速近似算法:
% 先进行k-means预聚类 [~,C] = kmeans(data, 1000); distanceMatrix = pdist(C); linkageMatrix = linkage(distanceMatrix, 'average');并行计算:
pool = parpool; % 启动并行池 distanceMatrix = pdist(data, 'UseParallel', true); delete(pool);使用GPU加速:
if gpuDeviceCount > 0 dataGPU = gpuArray(data); distanceMatrix = pdist(dataGPU); distanceMatrix = gather(distanceMatrix); end
3.3 分类数据的特殊处理
对于混合类型数据(数值+分类),需要特殊处理:
Gower距离:
% 需要自定义实现或使用Statistics and Machine Learning Toolbox distanceMatrix = pdist(data, @gower_distance);虚拟变量转换:
categorical_cols = [2,5]; % 假设第2和第5列是分类变量 numeric_cols = setdiff(1:size(data,2), categorical_cols); dummy_data = []; for col = categorical_cols [~,~,cat_num] = unique(data(:,col)); dummy_data = [dummy_data dummyvar(cat_num)]; end processed_data = [data(:,numeric_cols) dummy_data];
4. 常见问题与解决方案
4.1 内存不足错误
问题:当数据量较大时,pdist可能报错"Out of memory"。
解决方案:
- 使用稀疏矩阵:
distanceMatrix = sparse(squareform(pdist(data))); - 分批计算:
n = size(data,1); distanceMatrix = zeros(n*(n-1)/2,1); idx = 1; for i = 1:n-1 temp = pdist2(data(i,:), data(i+1:end,:)); distanceMatrix(idx:idx+size(temp,2)-1) = temp; idx = idx + size(temp,2); end
4.2 树状图过于密集
问题:数据点太多时,树状图难以辨认。
解决方案:
- 限制显示叶子数:
dendrogram(linkageMatrix, 50); % 只显示50个叶子节点 - 使用交互式缩放:
f = figure; h = dendrogram(linkageMatrix); set(h, 'ButtonDownFcn', @zoomCallback);
4.3 聚类结果不稳定
问题:相同数据多次运行结果不一致。
原因与解决:
- 数据顺序敏感:使用
sortrows先对数据排序data = sortrows(data); - 随机初始化:设置随机种子
rng(42); % 固定随机种子
4.4 分类变量处理不当
问题:直接对分类变量使用欧氏距离导致错误聚类。
解决方案:
- 使用合适的距离度量:
distanceMatrix = pdist(data, 'hamming'); % 对于二元分类 - 先进行独热编码:
categorical_cols = [2,5]; numeric_cols = setdiff(1:size(data,2), categorical_cols); dummy_data = []; for col = categorical_cols [~,~,cat_num] = unique(data(:,col)); dummy_data = [dummy_data dummyvar(cat_num)]; end processed_data = [data(:,numeric_cols) dummy_data];
5. 高级应用与扩展
5.1 动态可视化与交互
MATLAB支持创建交互式树状图:
f = figure; h = dendrogram(linkageMatrix, 'Labels', cellstr(num2str((1:size(data,1))'))); set(h, 'ButtonDownFcn', @(src,evt) disp(get(src,'Label')));结合uitree创建更丰富的交互界面:
tree = uitree(f, 'Position', [20 20 200 400]); rootNode = uitreenode(tree, 'Text','Root'); for i = 1:size(linkageMatrix,1) node = uitreenode(rootNode, 'Text',sprintf('Merge %d',i)); end5.2 与其他聚类方法结合
层次聚类可以与k-means等算法结合:
% 先用层次聚类确定初始中心 clusterID = cluster(linkageMatrix, 'maxclust', k); initial_centers = zeros(k, size(data,2)); for i = 1:k initial_centers(i,:) = mean(data(clusterID==i,:)); end % 再用k-means优化 [finalID, C] = kmeans(data, k, 'Start', initial_centers);5.3 时间序列数据的特殊处理
对于时间序列数据,可以使用动态时间规整(DTW)距离:
distanceMatrix = zeros(n*(n-1)/2,1); idx = 1; for i = 1:n-1 for j = i+1:n distanceMatrix(idx) = dtw(data(i,:), data(j,:)); idx = idx + 1; end end linkageMatrix = linkage(distanceMatrix, 'average');在实际项目中,我发现层次聚类特别适合探索性数据分析阶段。它不需要预先假设簇的数量,通过树状图可以直观地观察数据的内在结构。对于中小规模数据集(n<10000),MATLAB的实现已经足够高效。当处理更大数据时,合理的预处理和算法选择是关键。