AI无监督学习折腾手记
AI无监督学习上手并不难,难的是稳定跑起来。
下面只记真正影响结果的部分。
背景和需求
我手头的数据是这样的:
- 用户最近30天的行为日志(浏览、点击、购买、搜索等)
- 大概50万用户
- 每个用户有200多个特征维度
业务需求:把用户分成几类,方便后续做个性化推荐和精细化运营。
现实限制:
- 没有历史标签数据
- 数据质量问题不少(缺失值、异常值)
- 计算资源有限(单机16核32G)
- 业务方希望下周就要结果
实现过程
第一步:数据清洗和特征工程
先看一眼数据情况,这里用 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都带偏了。
第三步:聚类分析
用 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=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}")
最终结果
两周后,业务方给反馈:
- 用户转化率提升 8%:针对"潜在用户"和"价格敏感型用户"做了差异化推送
- 流失率降低 5%:提前识别了"流失风险用户",做了挽回
- 运营效率提升:不再是"广撒网",而是精准触达
当然,效果不是完全归功于聚类,还有后续的营销策略配合。但至少,聚类给出了一个靠谱的起点。
关键流程总结
整个无监督学习流程可以用这个图表示:
写在最后
无监督学习不是万能的,但在"有数据没标签"的场景下,它给了我们一个起点。
通过这个项目,我意识到:
- 预处理比算法本身更重要:垃圾进,垃圾出
- 可解释性很关键:算法结果要能翻译成业务语言
- 要和业务方合作:不是一个人埋头苦干就能解决的
如果你也在做类似的事情,希望这些经验能帮到你。有问题欢迎交流。
参考资料:
可用性说明:本文发布于 2020 年 5 月,距今已超过五年。文中涉及的软件版本、接口、下载地址、命令参数和操作界面可能已经发生变化,部分方案在当前环境下可能失效。请结合官方最新文档核对后再操作,生产环境使用前务必先行验证。
版权声明: 本文首发于 指尖魔法屋-AI无监督学习折腾手记(https://blog.thinkmoon.cn/post/311-ai-unsupervised-learning-label-pattern-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。