sklearn中KMeans与FeatureUnion结合的fit_predict及聚类结果异常求助
咱们逐个拆解你遇到的两个问题,给出原因分析和可落地的解决办法:
问题1:聚类输出为浮点型距离而非整数标签
原因
KMeans默认的transform()方法返回的是每个样本到所有聚类中心的欧氏距离矩阵(形状是(样本数, 聚类数)的浮点数组),而不是我们需要的「样本所属聚类的整数标签」。你的Pipeline里直接用了KMeans()作为最后一步,FeatureUnion在执行fit_transform()时会触发这个transform()方法,所以得到的是距离值而非聚类标签。
解决办法
自定义一个包装器Transformer,让它调用KMeans的predict()方法输出聚类标签,替代默认的transform()行为:
from sklearn.base import BaseEstimator, TransformerMixin from sklearn.cluster import KMeans class KMeansPredictor(BaseEstimator, TransformerMixin): def __init__(self, n_clusters=8, random_state=42, **kwargs): # 初始化KMeans实例,保留原有的参数配置 self.kmeans = KMeans(n_clusters=n_clusters, random_state=random_state, **kwargs) def fit(self, X, y=None): # 拟合KMeans模型 self.kmeans.fit(X, y) return self def transform(self, X): # 返回聚类标签,并且转成列向量(方便和其他特征合并) return self.kmeans.predict(X).reshape(-1, 1)
然后修改你的聚类Pipeline:
Clustering = Pipeline([ ('SelectPosition', SelectPosition), # 替换成自定义的KMeansPredictor,指定聚类数等参数 ('Clustering', KMeansPredictor(n_clusters=3, random_state=42)) ])
现在再运行FinalPipeline.fit_transform(data),聚类部分就会输出整数类型的聚类标签,和area列合并成你需要的特征矩阵。
问题2:FeatureUnion对象无fit_predict属性
原因
FeatureUnion的设计定位是合并多个特征提取器的输出,它只实现了fit()、transform()、fit_transform()这几个特征处理相关的方法,本身并不具备fit_predict()——这个方法是给分类、聚类这类预测型模型用的,FeatureUnion不属于这类模型。
解决办法
根据你的需求,有两种可行方案:
方案1:手动拆分fit和预测流程
如果只是想先拟合整个FeatureUnion,再获取合并后的特征(原类别+聚类标签),可以手动分步操作:
# 先拟合整个FeatureUnion FinalPipeline.fit(data) # 分别提取两部分的结果 area_features = FinalPipeline.transformer_list[0][1].transform(data) # 调用自定义KMeansPredictor的predict方法获取标签 cluster_labels = FinalPipeline.transformer_list[1][1].predict(data) # 合并成DataFrame方便查看 final_result = pd.concat([ area_features, pd.DataFrame(cluster_labels, columns=['cluster_label']) ], axis=1)
方案2:自定义包装类实现fit_predict
如果希望像调用普通模型一样直接用fit_predict(),可以写一个简单的包装类,给FeatureUnion补上这个方法:
class FeatureUnionWithPredict(BaseEstimator, TransformerMixin): def __init__(self, transformer_list): self.feature_union = FeatureUnion(transformer_list) def fit(self, X, y=None): self.feature_union.fit(X, y) return self def transform(self, X): return self.feature_union.transform(X) def fit_predict(self, X, y=None): # 先拟合再转换,模拟fit_predict的逻辑 self.fit(X, y) return self.transform(X)
然后用这个类替换原来的FeatureUnion:
FinalPipeline = FeatureUnionWithPredict([ ('cat', SelecCategoricalVariable), ('cluster', Clustering) ]) # 现在就可以正常调用fit_predict了 result = FinalPipeline.fit_predict(data)
完整可运行测试代码(结合你的示例数据)
最后给你一个完整的测试版本,用你提供的示例数据验证:
import pandas as pd from sklearn.base import BaseEstimator, TransformerMixin from sklearn.cluster import KMeans from sklearn.pipeline import Pipeline, FeatureUnion # 自定义列选择器(保留你原来的代码) class SelectColumns(BaseEstimator, TransformerMixin): def __init__(self, columns): self.columns = columns def transform(self, data_train): return data_train[self.columns] def fit(self, *_): return self # 自定义KMeans预测器 class KMeansPredictor(BaseEstimator, TransformerMixin): def __init__(self, n_clusters=8, random_state=42, **kwargs): self.kmeans = KMeans(n_clusters=n_clusters, random_state=random_state, **kwargs) def fit(self, X, y=None): self.kmeans.fit(X, y) return self def transform(self, X): return self.kmeans.predict(X).reshape(-1, 1) # 自定义带fit_predict的FeatureUnion包装类 class FeatureUnionWithPredict(BaseEstimator, TransformerMixin): def __init__(self, transformer_list): self.feature_union = FeatureUnion(transformer_list) def fit(self, X, y=None): self.feature_union.fit(X, y) return self def transform(self, X): return self.feature_union.transform(X) def fit_predict(self, X, y=None): self.fit(X, y) return self.transform(X) # 你的示例数据 data = pd.DataFrame({ 'air': ['Dining bar', 'Dining bar', 'Dining bar', 'Bar/Cocktail', 'Japanese food', 'Izakaya', 'Izakaya'], 'latitude': [35.641463, 35.662665, 35.693840, 35.670651, 35.712607, 35.743575, 35.693840], 'longitude': [139.698171, 139.668268, 139.703549, 139.771861, 139.779996, 139.847180, 139.703549] }) # 构建Pipeline(注意你示例里的列名是air,不是area,这里做了对应) SelectPosition = SelectColumns(columns=['latitude', 'longitude']) Clustering = Pipeline([('SelectPosition', SelectPosition), ('Clustering', KMeansPredictor(n_clusters=2, random_state=42))]) SelecCategoricalVariable = SelectColumns(columns=['air']) FinalPipeline = FeatureUnionWithPredict([('cat', SelecCategoricalVariable), ('cluster', Clustering)]) # 测试fit_predict result = FinalPipeline.fit_predict(data) print(pd.DataFrame(result, columns=['air', 'cluster_label']))
运行后会输出包含原类别和聚类标签的结果,完全符合预期。
内容的提问来源于stack exchange,提问作者Robin Nicole

