ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

智能需求分类与聚类:从TF-IDF到K-means的产品规划实践

智能需求分类与聚类:从TF-IDF到K-means的产品规划实践 干了这么多年产品规划我越来越觉得有件事特别让人头疼每个迭代周期开始前面对需求池里几百条形态各异、描述天马行空的需求怎么把它们理清楚、找到共性、排出优先级往往要耗掉整整一两天。后来我把分类和聚类思路引入需求分析流程做了一个智能需求分类与聚类的小工具才终于从这种重复性劳动里解脱出来。这篇就聊聊整个事情从零到一的思路、算法选型和落地过程中的那些坑。这套方案的核心目标很简单把散落在需求池、用户反馈、竞品评测中的非结构化文本自动归类到业务模块、需求类型等维度下同时用聚类把相似需求聚成一组让产品经理在做版本规划时能一眼看清“这个季度用户到底在集中反馈什么”。它不是要替代产品经理的判断而是把排序、归纳、整合这些机械工作压缩到分钟级让决策者把时间留给真正需要思考的事情。这篇文章适合谁看呢如果你正负责产品规划、需求分析或者在做用户反馈数据清洗又或者只是想了解一下 NLP 文本聚类在业务场景里怎么落地那接下来的内容应该对你有用。我会把文本向量化、K-means、层次聚类这些名词背后的原理尽量讲明白也会放出直接可跑的 Python 代码最后再聊聊我踩过的坑。1. 需求分类与聚类的整体设计思路1.1 为什么需求分类不能全靠人工产品规划这件事最难的不是想功能而是从一堆原始诉求里识别出真正的模式。打个比方需求池就像一个乱糟糟的衣橱里面有夏季T恤、冬季大衣、运动裤、正装衬衫不分类之前你根本不知道哪个季节的衣服缺了、哪类场景覆盖不足。人工分类的弊端在需求量上来之后非常明显一是标准不统一两个人对“体验优化”和“功能迭代”的理解经常打架二是效率低几百条需求逐条打标签机械又疲惫三是容易漏掉隐藏在字面之下的共性比如十几条不同模块的反馈其实都指向同一个底层问题。智能需求分类和聚类要解决的就是这三件事。分类负责“贴标签”把需求按照预设的维度模块、类型、优先级归好聚类负责“找模式”不预设标签让算法自己把相似需求聚成一簇。两者搭配起来用特别舒服先用分类清理结构再用聚类发现潜在主题最后产品经理只需要在簇级别上来回审视而不是逐条看。1.2 监督分类还是无监督聚类怎么选很多第一次接触这个话题的人会问我分类和聚类到底用哪个好我的判断依据很简单如果你们体系里已经有了成熟的需求标签库历史需求都标好了类别那优先用监督分类比如 TextCNN、FastText 或者 BERT 微调准确率和可控性都在线。但如果你跟我当时的处境一样标签体系混乱、历史数据不干净甚至自己都不知道需求应该分几个维度那就别硬套监督分类了先上无监督聚类把数据结构摸清楚再回来定义标签体系会顺利得多。聚类还有一个监督分类替代不了的价值它能把语义相近但字面完全不同的需求找出来。比如“转账后没有回执”、“付款成功看不到凭证”、“交易完成后缺少打印小票”这三条如果按关键词规则分类很可能被分到财务、订单、打印三个不同地方去了但聚类会根据语义把它们聚到“交易结果反馈缺失”这一个簇里。这种跨模块找共性的能力恰恰是产品版本规划特别需要的东西。1.3 技术路线总览整个流程我分成了四个环节数据清洗与分词、文本向量化、聚类建模、结果可视化与业务解读。这四个环节环环相扣任何一个环节出了问题后面全都白搭。词向量化尤其关键如果你拿原始文本直接丢给聚类算法得到的往往是一堆噪声。我后面会详细展开每一步先把整条路线的框架摆在这里方便你心里有个数。2. 需求文本向量化聚类效果的分水岭2.1 文本清洗与中文分词需求文本跟新闻语料或者社交文本完全不一样它的特点是短、口语化、夹杂大量产品专有名词和英文缩写。比如“安卓端订单列表下拉加载更多时出现闪退用户反馈较多建议优化”这种句子首先要处理的就是噪声去掉无意义的语气词、特殊符号、URL统一英文大小写和繁简体。我的实践经验是在需求文本场景里不要过度清洗。像“闪退”、“卡顿”、“白屏”这些词本身就是极有判别力的信号千万别因为它们不够“正式”就给过滤掉。分词阶段中文场景我基本只用 jieba 进行精确模式分词配合自定义词典效果会好很多。产品名词、竞品名、功能模块名、技术术语必须加进自定义词典里。比如“知识付费”、“消息通知”、“会员权益”、“push”如果不用自定义词典jieba 很可能把“知识付费”切成一堆碎词语义信息直接报废。我们当时还加了一部分领域停用词像“建议”、“希望”、“能不能”这类没有实际业务含义但频率极高的词在向量化之前就筛掉。import jieba import jieba.posseg as pseg # 加载自定义词典每一行是一个词可附带词频和词性 jieba.load_userdict(product_terms.txt) stopwords set() with open(stopwords.txt, r, encodingutf-8) as f: for line in f: stopwords.add(line.strip()) def clean_and_tokenize(text): text text.lower().replace(\n, ).strip() words jieba.lcut(text) return [w for w in words if w not in stopwords and len(w.strip()) 0] sample 安卓端订单列表下拉加载更多时出现闪退用户反馈较多建议优化 print(clean_and_tokenize(sample)) # 输出类似[安卓端, 订单, 列表, 下拉, 加载, 更多, 出现, 闪退, 用户, 反馈, 较多, 优化]2.2 TF-IDF、Word2Vec 还是 BERT文本向量化这一步的选择直接决定聚类效果的上限。我三种方式都试过给你一个可能不太一样、但是真实的使用结论在需求文本这种专项业务场景里TF-IDF 经常比 Word2Vec 更实用而 BERT 的嵌入向量虽然语义能力强但计算成本和调参成本都不是一般团队愿意投入的。TF-IDF 的核心是“词频”和“逆文档频率”的乘积。一个词在单篇文档里出现次数多同时在整个语料里出现次数少它就重要。放在需求场景里“支付超时”这个词在一两条需求里反复出现但在全量需求里不多见那它的 TF-IDF 权重就很高聚类时很容易把相关需求归在一起。相反“一个”、“问题”、“麻烦”这类出现频率太高的词会被自动压权。Word2Vec 的优势在于能捕捉近义关系和上下文语义比如“卡顿”和“延迟”在向量空间里距离会很近。但问题在于需求文本太短训练语料往往不够Word2Vec 的向量平均化会把大量细节信息稀释掉。我当时的做法是如果要用 Word2Vec一定用预训练好的中文词向量做初始化再拿自己的需求文本增量训练效果才勉强能和 TF-IDF 打平。BERT 嵌入拿来做聚类效果确实是目前最好的尤其是中文长尾语义理解上确实强。但它的成本也摆在明面上GPU 推理时间长、向量维度高768维甚至更高、需要 PCA 降维才能聚类。就产品需求分类这个任务而言性价比不高除非你们的团队已经把 BERT 服务部署得很完善了。我的建议是先用 TF-IDF 跑通全流程模型效果不满意再往上加语义向量不要一上来就用重武器。2.3 向量化的参数细节用 TF-IDF 的时候有几个参数值得反复调。第一个是max_features也就是特征维度上限。需求池文本量一般不大一两千条已经是比较多的了特征维度不设上限的话可能出现几千维的稀疏矩阵又慢又容易过拟合。我一般设 2000 到 5000配合后面的降维效果都不错。另一个是ngram_range我建议设置成(1, 2)也就是把“闪退”这种单个词和“支付超时”这种相邻两个词的组合都当作特征。这样能保留不少短语级的语义信息尤其是需求文档里大量出现的偏正短语。from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer( max_features3000, ngram_range(1, 2), min_df2, max_df0.8, ) # corpus 是所有需求文本清洗并分词后用空格连接成的列表 tfidf_matrix vectorizer.fit_transform(corpus) print(tfidf_matrix.shape)这里min_df2表示至少在两个文档中出现过的词才会被保留低于这个阈值的词多半是偶然出现的噪声max_df0.8表示在80%以上的文档里都出现的词不要因为它们没有区分度。这两个参数的组合能快速把特征维度降下来同时保住判别力。3. 核心聚类算法从 K-means 到层次聚类3.1 看看你的数据长什么样先画出来聚类分析有一个容易被新手忽略的步骤动手算之前先把向量用降维工具投到二维平面里看一遍。T-SNE 或者 PCA 都可以它们能把高维空间中哪些文本距离近、哪些距离远以散点图的形式直观呈现出来。这不是可选的锦上添花而是必要的检查手段。不看一眼数据分布就去调 K 值等于蒙着眼睛猜路。我当时第一次把 500 条需求文本投影到二维时发现数据呈现出非常明显的三四个密集区域然后才知道用三个簇起步比较合理。这比纯靠肘部法猜 K 值要靠谱得多。不过要记住PCA 可以直接从 TF-IDF 矩阵算t-SNE 则建议在 PCA 降维之后再做否则计算量很大而且不稳定每次跑出来的点位置都不一样。from sklearn.decomposition import PCA import matplotlib.pyplot as plt pca PCA(n_components2) pca_result pca.fit_transform(tfidf_matrix.toarray()) plt.figure(figsize(10, 7)) plt.scatter(pca_result[:, 0], pca_result[:, 1], s10, alpha0.6) plt.title(需求向量 PCA 投影) plt.show()3.2 K-means最稳的基线K-means 是这个任务里我最推荐第一个尝试的算法。它的原理可以用一句话讲清楚预先设定 K 个中心点迭代地把每个样本分到离它最近的中心点所属的簇然后更新中心点为簇内所有样本的平均位置直到收敛。它快、可解释性强、调参简单做需求初步分堆完全够用。K 值怎么确定呢我最常用的是“手肘法”加“轮廓系数”双验证。手肘法看的是误差平方和SSE随 K 值增大的下降曲线正常情况下曲线会在某个 K 值出现明显的拐点像手肘一样那个拐点就是比较合理的簇数。轮廓系数则衡量每个样本与自身簇内样本的距离和与最近邻簇样本的距离的差值取值在 -1 到 1 之间越接近 1 说明聚类越紧凑、簇间分离越明显。两个指标配合看基本能锁定 K 的范围。from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score import numpy as np sse [] silhouette_scores [] K_range range(2, 11) for k in K_range: km KMeans(n_clustersk, random_state42, n_init10) labels km.fit_predict(tfidf_matrix) sse.append(km.inertia_) silhouette_scores.append(silhouette_score(tfidf_matrix, labels)) plt.figure(figsize(12, 4)) plt.subplot(1, 2, 1) plt.plot(list(K_range), sse, markero) plt.title(手肘法SSE vs K) plt.subplot(1, 2, 2) plt.plot(list(K_range), silhouette_scores, markero) plt.title(轮廓系数 vs K) plt.show()我在实际需求数据上测试时K4 的时候手肘拐点和轮廓系数峰值往往能对上。但这里有个经验之谈不要机械地追求指标最优K 值的最终选择要结合业务可解释性。有一次我数据上最优 K 是 7分出来的簇里有两簇全是“零散的运营活动需求”合并成一簇反而对版本规划更有意义后来我就手动把 K 调成了 6。聚类是工具业务解读才是目的。K-means 还有个隐患是初始化中心点对结果影响很大所以我在代码里固定了random_state并且设n_init10让算法自动跑 10 次选最优解。这一步不做的话同一份数据两次运行可能得到完全不同的簇划分。3.3 层次聚类树状图讲出了需求结构K-means 能告诉你“有哪些簇”但如果你还想知道“簇和簇之间什么关系”就得用层次聚类。层次聚类有两种策略自底向上的凝聚式层次聚类先把每个样本单独当成一个簇然后每次合并距离最近的两个簇直到最终合成一个大的簇自顶向下则为分裂式思路反过来。实际中用凝聚式更多。它最棒的地方是输出一张树状图你可以从最顶层往下切自由决定要在哪个层次上截断出多少个簇。对于需求分析这种需要反复调整粒度的场景来说非常方便。比如我想得粗一点看需求大方向就在树状图上部切一刀想细一点定位具体问题就在下部切。层次聚类里不同的簇间距离计算方式即链接标准直接影响结果。我试过三种简单说一下单链接是“两个簇中最近样本之间的距离”容易产生长条状的链式簇不推荐全链接是“两个簇中最远样本之间的距离”倾向于生成紧凑的球形簇在需求文本场景里效果比单链接好平均链接则是所有样本对距离的平均值介于两者之间也是我默认用的标准。热词里专门提到了全链接聚类说明这个方法在文本聚类里确实是常用的。from scipy.cluster.hierarchy import dendrogram, linkage, fcluster from sklearn.metrics import silhouette_score import matplotlib.pyplot as plt # 使用 ward 距离属于平均链接的一种变体簇内方差最小化 Z linkage(tfidf_matrix.toarray(), methodward, metriceuclidean) plt.figure(figsize(14, 7)) dendrogram(Z, truncate_modelevel, p5) plt.title(需求层次聚类树状图) plt.xlabel(需求样本) plt.ylabel(距离) plt.show() # 在指定距离处横向切割得到簇标签 labels_hier fcluster(Z, t8, criteriondistance)关于层次聚类的计算复杂度提醒一句它的复杂度通常在 O(n²) 到 O(n³) 之间需求数量超过几千条后计算非常慢。我做过的最极端一次是用一万条用户反馈做层次聚类跑了将近一小时所以后来大规模的都用 K-means 先粗聚再用层次聚类在簇中心上做细分。3.4 进阶方案DBSCAN、子空间聚类与 K 值聚类如果需求数据里噪声特别多比如夹杂着大量杂七杂八的临时反馈K-means 会把所有点都硬塞进某个簇导致簇的中心被拉到很奇怪的位置。这时候可以试试 DBSCAN。它的思路是基于密度聚类在某个半径范围内如果样本数量超过阈值就继续扩展这个区域形成的稠密区域就是一个簇孤立点则被标记为噪声。它的好处是完全不需要预设 K 值坏处是半径参数eps很难调不同数据集差异太大。子空间聚类则是从另一个角度解决高维问题而出现的。需求文本经过 TF-IDF 变成上千维的稀疏向量传统聚类算法在高维空间里容易失效因为“维度灾难”会让距离度量失去意义。子空间聚类尝试在不同特征子集上分别聚类然后综合结果识别簇特别适合高维稀疏数据。我在实际中用的不多因为调起来比较费劲但值得知道这条路存在。热词榜单里反复出现“子空间聚类概述及 python 完整实现”说明关注这块的人还是很多。还有所谓的“K 值聚类”在搜索结果里也常见其实指的多半是 K-means 或者基于 K 中心点的 K-medoids核心思路都是把数据分成 K 簇。K-medoids 和 K-means 的区别在于簇中心不是所有样本的平均值而是簇内最中心的样本点本身。这在需求文本场景里有个额外的好处簇的中心是真实存在的一条需求可以直接展示给业务方看解释性比 K-means 算出来的虚拟中心强不少就是计算量大一些样本量不大时可以优先考虑。4. 完整落地从聚类结果到产品规划决策4.1 给簇打标签结果能不能用关键这一步算法输出的是一堆数字编号的簇比如“簇0”、“簇1”这对于技术人没问题但给产品决策层看就不合格。聚类的最终交付物一定是一份可读的标签而不是数字。我的习惯是每个簇生成后提取该簇内 TF-IDF 权重最高的前 10 个词人工看一眼结合簇内的几条代表性需求拟定一个业务名。这一步虽然叫“人工”但通常几分钟就能完成跟逐条分类完全不是一个量级。举个例子当时算法聚出来一个簇权重最高的词是“回执”、“凭证”、“超时”、“打印”、“详情”簇内需求基本都是“支付成功无回执”、“订单详情查不到凭证”、“对账单打印超时”这类我把它命名为“交易凭证与回执反馈”在版本规划里对应着的就是财务中台“交易确认流程优化”这个项目。簇标签不一定要按模块分。有时候我是按诉求类型分比如“体验流畅度问题”、“新增功能请求”、“流程卡点反馈”、“运营活动建议”有时候按主题分比如“会员体系意见”、“支付链路问题”、“内容推荐反馈”。语义相关就行目的就是为了让下一轮讨论有共同的抓手。4.2 热图、趋势图与富集条目的可视化聚类做完只是第一步怎么把结果呈现给团队才是决定它能否被采纳的关键。我一般做三张图聚类热图、簇规模变化趋势图、富集条目表。热图用来展示不同需求文本之间在向量空间中的相似度颜色越深代表距离越近能直观看到哪些需求形成了紧密的“块”哪些需求是孤立的异常点。趋势图展示同一个簇在不同版本周期里的需求数量变化用来判断某个主题是上升期还是消退期这对版本规划太重要了。富集条目是我从生物学里借来的概念。我这里的做法是把每个簇里的原创短语提取出来按出现频次排序列成一个“簇→关键短语→需求数”的表格。这样开规划会的时候直接把这个表投到屏幕上不需要逐条念需求大家就能理解这个版本的核心议题是什么。import pandas as pd import seaborn as sns # labels 来自上一步聚类结果 data pd.DataFrame({text: corpus, cluster: labels_hier}) # 每个簇的需求样本量 cluster_counts data[cluster].value_counts().sort_index() # 查看每个簇的代表需求取最靠近簇中心的样本 def get_centroid_sample(cluster_id, matrix, labels): idx_list np.where(labels cluster_id)[0] centroid matrix[idx_list].mean(axis0) distances np.linalg.norm(matrix[idx_list] - centroid, axis1) return idx_list[np.argmin(distances)] sample_indices [get_centroid_sample(c, tfidf_matrix.toarray(), labels_hier) for c in cluster_counts.index] for c, idx in zip(cluster_counts.index, sample_indices): print(f簇 {c}{cluster_counts[c]} 条代表需求{corpus[idx]})4.3 从簇到需求优先级需求按簇归好之后优先级判断就有数据支撑了。我当时的做法是给每个簇计算三个指标簇内需求数量、近30天新增趋势、涉及用户影响面来自需求描述中提到的用户标签。然后按“数量×趋势×影响面”做一个简单评估形成簇级优先级得分再在簇内部按原有的紧急程度排序。这样一来版本规划会上的讨论重点就从“每条需求要不要做”变成“这个主题要不要投入”决策层次高了一级。举个例子有一次聚类结果显示“登录注册体验”相关的需求已经连续三个迭代周期排在第一大簇但当时公司的版本计划里并没有登录模块的专项。这个问题在散乱的需求池里很难被看见因为每个需求单独看都像小修小补但聚成一簇之后“群众的呼声”立刻变得醒目起来。下个季度我就把登录优化提成了专项需求上线后用户满意度确实提升了不少。这就是聚类的价值让分散的信号汇聚成明确的方向。5. 常见问题与排查技巧实录5.1 K 值怎么选为什么同一个 K 跑两次结果不一样K 值选择是我被问得最多的问题。最靠谱的方法还是我前面提到的手肘法加轮廓系数交叉验证但一定要在跑数之前先降维可视化用肉眼看一眼数据天然的分布形态再结合业务约定俗成的分类习惯来定。比如需求方内部一直按“功能、优化、缺陷、体验”四个维度管理那可以先试 K4看结果是否符合预期不行再往上调整。同一个 K 值两次运行结果不一样根源基本在 K-means 的随机初始化。解决办法就是固定随机种子、设n_init比较大或者干脆用 K-means 初始化方法在 sklearn 里默认用的就是 K-means这个方法会尽量让初始中心点彼此分散能显著降低随机性带来的偏差。如果你发现结果依然不稳定考虑数据预处理是不是有问题比如文本里混入了太多无意义字符串。5.2 相似度计算全走了样文本向量质量出问题了聚类结果显示乱七八糟、毫无业务意义我排查的顺序是先看分词结果再看停用词再看向量化参数。分词阶段常见的问题是产品名词被错误切开比如“消息中心”被切成“消息”和“中心”语义就弱了。解决方法是不断给自定义词典补充词条。停用词则是太少了导致“建议”、“希望”、“能不能”这类词占据了太高的权重把真正的业务词挤掉了这种情况适当扩充停用词表就好。向量化参数方面最值得调的是ngram_range。如果聚类结果过于碎片化每个簇都只围绕一两个单独的词语试着把ngram_range上限提高到 2 甚至 3让短语进入特征空间。反过来如果聚类结果全部都聚到一两个大簇几乎看不到区分说明特征是够了但判别力不够这时可以适当调高max_features或者降低max_df把更多低频但有语义的词纳入进来。5.3 中文语境下要不要做繁体转简体、拼音归一化需求文本很多时候来自客服记录客服可能混用繁体、简体甚至拼音缩写。我的经验是繁体转简体一定要做不然“訂單”和“订单”会被当成两个特征直接拉低聚类质量。拼音归一化看场景如果你们的系统输入很规范就没必要做如果大量用户反馈有拼音缩写可能需要建立一张缩写映射表比如“tz”转“通知”“wx”转“微信”。这类映射表一开始维护痛点但积累到一定规模后对整个文本分析流程都有帮助。还有一个小坑是数字和字母的处理。需求文本里经常出现“v2.0”、“iOS 15”、“iPhone 12”这种中英文混排内容分词时会被切成“ios”和“15”。如果“15”这种数字进了向量空间容易造成噪声。我的做法是把单独的年份、版本数字保留原样但把“版本号数字”的模式整体保留为一个 token比如“ios_15”。这种细节看似不起眼但在需求文本聚类里经常能改善好几个百分点的效果。5.4 聚类之后依然有一堆“四不像”需求不是所有需求都能被干净地分进某个簇。总会有少量“漏网之鱼”它们要么是描述太短、信息太少要么是跨主题的复合型需求。遇到这种情况不要强行把它们塞进最近的簇那样反而会污染整个簇的纯度。我的建议是单开一个“待人工复核”的桶在版本规划会议上单独过一遍。这是一个完全正常的业务行为聚类帮助我们处理了80%的常规需求剩下的20%异常情况交由人工处理这就是人机协同最合理的分工。5.5 效果评估不只看轮廓系数更要看业务收益轮廓系数、DBI 这些指标可以帮你调参但它们不能回答“这次聚类结果对版本规划到底有什么用”。我的经验是把聚类结果和实际版本规划决策联系起来做验证。比如这个季度做了聚类分析之后是否发现了两个原本以为无关的需求其实指向同一问题是否减少了对低价值需求的讨论时间是否让需求排期更符合用户反馈的集中度这些业务层面的反馈才是指引你持续优化模型的真正罗盘。后来我把这套流程固化成了一个内部小工具每一次需求导入之后自动跑分类与聚类输出簇列表、关键短语、趋势图和待复核清单产品经理只需要每周花半小时开着会过一遍结果就好。相比之前每个月花一整天在需求池里翻找这个效率提升是肉眼可见的。说句实在话智能需求分类与聚类并不是什么高深莫测的新鲜技术做出来后更像是一个把 NLP 工具链和产品规划流程揉到一起的自动化管道。如果你也在需求管理的泥潭里挣扎我建议你先别急着上大模型拿一个小规模的需求数据集从 TF-IDF 加 K-means 开始跑通流程再用层次聚类去理解数据结构最后再考虑用什么可视化手段把它呈现给团队。工具是现成的难的是让数据帮你做决策的思路。希望这篇文章能帮你少踩一些我踩过的坑。
返回列表