AI无监督学习折腾手记

AI无监督学习上手并不难,难的是稳定跑起来。

下面只记真正影响结果的部分。

背景和需求

我手头的数据是这样的:

  • 用户最近30天的行为日志(浏览、点击、购买、搜索等)
  • 大概50万用户
  • 每个用户有200多个特征维度

业务需求:把用户分成几类,方便后续做个性化推荐和精细化运营。

现实限制

  1. 没有历史标签数据
  2. 数据质量问题不少(缺失值、异常值)
  3. 计算资源有限(单机16核32G)
  4. 业务方希望下周就要结果

实现过程

第一步:数据清洗和特征工程

先看一眼数据情况,这里用 Python 处理:

import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.impute import SimpleImputer

# 读取数据
df = pd.read_parquet('user_behavior.parquet')

# 先看数据概况
print(f"原始数据形状: {df.shape}")
print(f"缺失值占比:\n{df.isnull().sum() / len(df)}")

# 处理缺失值
# 对于数值型特征,用中位数填充
numeric_features = df.select_dtypes(include=[np.number]).columns
imputer = SimpleImputer(strategy='median')
df[numeric_features] = imputer.fit_transform(df[numeric_features])

# 处理异常值
def cap_outliers(df, column, lower=0.01, upper=0.99):
    lower_bound = df[column].quantile(lower)
    upper_bound = df[column].quantile(upper)
    df[column] = df[column].clip(lower_bound, upper_bound)
    return df

for col in ['daily_clicks', 'daily_views', 'total_spend']:
    df = cap_outliers(df, col)

# 特征标准化(重要!)
scaler = StandardScaler()
scaled_features = scaler.fit_transform(df[numeric_features])

踩坑点:刚开始没做标准化,聚类结果完全不对。因为不同特征的尺度差异太大(比如购买金额是几千,点击次数是几十),距离计算完全被大数值特征主导。

第二步:降维处理

200多个维度太多了,容易维度灾难。先做降维:

from sklearn.decomposition import PCA
import matplotlib.pyplot as plt

# PCA降维
pca = PCA()
pca.fit(scaled_features)

# 看看降维效果
plt.figure(figsize=(10, 6))
plt.plot(np.cumsum(pca.explained_variance_ratio_))
plt.xlabel('Number of Components')
plt.ylabel('Cumulative Explained Variance')
plt.axhline(y=0.9, color='r', linestyle='--')
plt.show()

# 选择保留90%方差的主成分数量
n_components_90 = np.argmax(np.cumsum(pca.explained_variance_ratio_) >= 0.9) + 1
print(f"保留90%方差需要 {n_components_90} 个主成分")

# 实际降维
pca_final = PCA(n_components=n_components_90)
reduced_features = pca_final.fit_transform(scaled_features)

PCA降维效果

踩坑点:PCA对异常值敏感。所以在做PCA之前,一定要先处理异常值。我第一次没处理好,降维效果很差,后来才发现有几个用户的异常行为数据把整个PCA都带偏了。

第三步:聚类分析

用 K-means 做聚类,但首先得确定 K 值:

from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score

# 用肘部法则和轮廓系数找最佳K
inertias = []
silhouette_scores = []
K_range = range(2, 15)

for k in K_range:
    kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
    kmeans.fit(reduced_features)
    inertias.append(kmeans.inertia_)
    silhouette_scores.append(silhouette_score(reduced_features, kmeans.labels_))

# 画肘部图
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(K_range, inertias, 'bo-')
plt.xlabel('K')
plt.ylabel('Inertia')
plt.title('Elbow Method')

# 画轮廓系数
plt.subplot(1, 2, 2)
plt.plot(K_range, silhouette_scores, 'go-')
plt.xlabel('K')
plt.ylabel('Silhouette Score')
plt.title('Silhouette Score')
plt.tight_layout()
plt.show()

K值选择

根据结果,我选择了 K=5。

# 最终聚类
kmeans = KMeans(n_clusters=5, random_state=42, n_init=10)
clusters = kmeans.fit_predict(reduced_features)

# 添加聚类标签
df['cluster'] = clusters

第四步:结果分析和可视化

看看聚出来的用户长什么样:

# 分析各簇的特征
cluster_stats = df.groupby('cluster')[numeric_features].agg(['mean', 'std'])
print(cluster_stats)

# 可视化(用前两个主成分)
plt.figure(figsize=(10, 8))
scatter = plt.scatter(reduced_features[:, 0], reduced_features[:, 1],
                     c=clusters, cmap='viridis', alpha=0.6)
plt.colorbar(scatter)
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.title('User Clusters (PCA Reduced)')
plt.show()

用户聚类可视化

业务解释

  • Cluster 0:高价值用户(购买金额大,频率高)
  • Cluster 1:潜在用户(浏览多但购买少)
  • Cluster 2:流失风险用户(近期活跃度下降)
  • Cluster 3:价格敏感型(只买打折商品)
  • Cluster 4:新用户(注册时间短,行为模式不明显)

踩坑总结

1. 数据质量问题

问题:原始数据里有很多缺失值和异常值,直接用起来效果很差。

解决

  • 缺失值用中位数填充(比均值稳健)
  • 异常值用分位数裁剪
  • 特征标准化必须做

2. 聚类数选择困难

问题:肘部法则的"肘部"不明显,轮廓系数最大值对应的 K 太大(>10),业务不适用。

解决

  • 结合业务理解,K 不能太多(5-8个比较合理)
  • 轮廓系数 >0.3 就算可接受
  • 让业务同事看结果,共同决策

3. 计算性能问题

问题:50万用户 + 200维度,单机跑 K-means 要几个小时。

解决

  • 先 PCA 降维到 30 维左右
  • 用 MiniBatchKMeans 替代普通 K-means
  • 分批处理,每批 5万用户
from sklearn.cluster import MiniBatchKMeans

mbk = MiniBatchKMeans(n_clusters=5, batch_size=50000, random_state=42)
clusters = mbk.fit_predict(reduced_features)

4. 可解释性问题

问题:算法给的结果,怎么向业务解释?

解决

  • 给每个簇做个"画像"(主要特征)
  • 找代表性用户给业务看
  • 做一个简单的用户分布图
# 簇的"画像"
for cluster_id in range(5):
    cluster_users = df[df['cluster'] == cluster_id]
    print(f"\n=== Cluster {cluster_id} ===")
    print(f"用户数量: {len(cluster_users)}")
    print(f"平均购买金额: {cluster_users['total_spend'].mean():.2f}")
    print(f"平均点击次数: {cluster_users['daily_clicks'].mean():.2f}")

最终结果

两周后,业务方给反馈:

  1. 用户转化率提升 8%:针对"潜在用户"和"价格敏感型用户"做了差异化推送
  2. 流失率降低 5%:提前识别了"流失风险用户",做了挽回
  3. 运营效率提升:不再是"广撒网",而是精准触达

当然,效果不是完全归功于聚类,还有后续的营销策略配合。但至少,聚类给出了一个靠谱的起点。

关键流程总结

整个无监督学习流程可以用这个图表示:

graph TD A[原始用户行为数据] --> B[数据清洗] B --> C[特征标准化] C --> D[PCA降维] D --> E[确定K值] E --> F[K-means聚类] F --> G[结果分析] G --> H[业务应用] H --> I[效果评估] I -->|需要优化| B

写在最后

无监督学习不是万能的,但在"有数据没标签"的场景下,它给了我们一个起点。

通过这个项目,我意识到:

  1. 预处理比算法本身更重要:垃圾进,垃圾出
  2. 可解释性很关键:算法结果要能翻译成业务语言
  3. 要和业务方合作:不是一个人埋头苦干就能解决的

如果你也在做类似的事情,希望这些经验能帮到你。有问题欢迎交流。


参考资料

可用性说明:本文发布于 2020 年 5 月,距今已超过五年。文中涉及的软件版本、接口、下载地址、命令参数和操作界面可能已经发生变化,部分方案在当前环境下可能失效。请结合官方最新文档核对后再操作,生产环境使用前务必先行验证。

版权声明: 本文首发于 指尖魔法屋-AI无监督学习折腾手记https://blog.thinkmoon.cn/post/311-ai-unsupervised-learning-label-pattern-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!