主题演化关系挖掘:桑基图 + 相似度矩阵,2 张图讲透主题怎么”流” 上篇博客讲了主题强度演化 (某个主题在每个时间窗有多火)。但老板们更关心的是主题之间的关系 ——T1(聊”创新驱动”)是怎么”流”到 T2(聊”数字经济”)的?T3 是从 T1 演化来的还是独立新生?
这就需要主题演化关系挖掘 ——算主题间相似度,画桑基图 展示主题从一个时间窗”流向”另一个时间窗。
我们的 LDA 主题演化分析软件 专门做这件事:相似度矩阵 + 桑基图 + 三元组 三件套输出。
一、核心数据:主题相似度矩阵 主题 A 在 T1 时间窗,主题 B 在 T2 时间窗,它俩有多像 ?用 JS 散度(Jensen-Shannon Divergence) 或 余弦相似度 算:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import numpy as npfrom gensim.matutils import hellingerfrom scipy.spatial.distance import cosinedef topic_similarity (model_a, topic_a_id, model_b, topic_b_id, topn=20 ): """两个主题的词分布相似度(余弦),值越大越相似""" vec_a = np.array([prob for _, prob in model_a.show_topic(topic_a_id, topn=topn)]) vec_b = np.array([prob for _, prob in model_b.show_topic(topic_b_id, topn=topn)]) words_a = {w: p for w, p in model_a.show_topic(topic_a_id, topn=topn)} words_b = {w: p for w, p in model_b.show_topic(topic_b_id, topn=topn)} all_words = set (words_a.keys()) | set (words_b.keys()) va = np.array([words_a.get(w, 0 ) for w in all_words]) vb = np.array([words_b.get(w, 0 ) for w in all_words]) return 1 - cosine(va, vb) K = 10 time_models = { '2022' : lda_2022, '2023' : lda_2023, '2024' : lda_2024 } similarity = {} for t1, t2 in [('2022' , '2023' ), ('2023' , '2024' )]: sim_matrix = np.zeros((K, K)) for i in range (K): for j in range (K): sim_matrix[i][j] = topic_similarity( time_models[t1], i, time_models[t2], j ) similarity[(t1, t2)] = sim_matrix
二、相似度矩阵热力图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import matplotlib.pyplot as pltimport seaborn as snsfig, axes = plt.subplots(1 , 2 , figsize=(16 , 6 )) for idx, ((t1, t2), mat) in enumerate (similarity.items()): sns.heatmap( mat, annot=True , fmt='.2f' , cmap='YlGnBu' , vmin=0 , vmax=1 , xticklabels=[f'T{j} ' for j in range (K)], yticklabels=[f'T{i} ' for i in range (K)], ax=axes[idx] ) axes[idx].set_title(f'{t1} → {t2} 主题相似度矩阵' , fontsize=13 ) axes[idx].set_xlabel(f'{t2} 主题' ) axes[idx].set_ylabel(f'{t1} 主题' ) plt.tight_layout() plt.savefig('topic_similarity_matrix.png' , dpi=150 )
怎么看 :
矩阵里 (i, j) 位置的深浅 = t1 主题 i 与 t2 主题 j 的相似度
亮 = 演化继承(这俩其实是同一个主题在不同年份)
暗 = 全新主题(这一年新冒出来的)
三、桑基图:主题怎么”流” 桑基图(Sankey diagram)最适合展示流向 ——左边一列节点是 t1 的主题,右边一列是 t2 的主题,连线的粗细 = 主题相似度(继承强度) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import plotly.graph_objects as godef build_sankey_data (similarity_matrix, threshold=0.5 , K=10 ): """把相似度矩阵转成桑基图数据""" sources, targets, values, labels = [], [], [], [] for i in range (K): labels.append(f'{t1} 主题{i} ' ) for j in range (K): labels.append(f'{t2} 主题{j} ' ) for i in range (K): for j in range (K): sim = similarity_matrix[i][j] if sim >= threshold: sources.append(i) targets.append(K + j) values.append(sim) return sources, targets, values, labels sources, targets, values, labels = build_sankey_data( similarity[('2022' , '2023' )], threshold=0.4 ) fig = go.Figure(data=[go.Sankey( node=dict ( pad=15 , thickness=20 , line=dict (color='black' , width=0.5 ), label=labels, color='#6e5cff' ), link=dict ( source=sources, target=targets, value=values, color='rgba(110, 92, 255, 0.4)' ) )]) fig.update_layout( title_text='LDA 主题演化桑基图(2022 → 2023)' , font_size=12 , height=600 ) fig.write_html('topic_sankey.html' ) fig.show()
关键参数 :threshold=0.4 是相似度过滤阈值,低于这个值的连线不画(避免画面太乱)。建议从 0.3 试起,根据画面清晰度调到 0.5-0.7。
四、三元组(source, target, value)数据结构 桑基图底层其实就是三元组列表 ——老板可能想把这份数据喂给 BI 工具画自己风格的图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def to_triples (similarity_matrix, t1, t2, threshold=0.4 , K=10 ): triples = [] for i in range (K): for j in range (K): sim = similarity_matrix[i][j] if sim >= threshold: triples.append({ 'source' : f'{t1} _T{i} ' , 'target' : f'{t2} _T{j} ' , 'value' : round (float (sim), 4 ) }) return triples triples = to_triples(similarity[('2022' , '2023' )], '2022' , '2023' , threshold=0.4 ) pd.DataFrame(triples).to_csv('topic_evolution_triples.csv' , index=False , encoding='utf-8-sig' )
输出格式 :
source
target
value
2022_T0
2023_T1
0.7321
2022_T0
2023_T5
0.4521
…
…
…
喂给 ECharts 桑基图 、Tableau 、Power BI 都能直接画。
五、批量处理:3 年 30 个主题全自动演化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 all_triples = [] for (t1, t2), mat in similarity.items(): triples = to_triples(mat, t1, t2, threshold=0.4 , K=K) all_triples.extend(triples) all_labels = [] for t in time_models.keys(): for i in range (K): all_labels.append(f'{t} _T{i} ' ) sources_all, targets_all, values_all = [], [], [] offset = 0 for (t1, t2), mat in similarity.items(): K_curr = K for i in range (K_curr): for j in range (K_curr): sim = mat[i][j] if sim >= 0.4 : sources_all.append(offset + i) targets_all.append(offset + K_curr + j) values_all.append(sim) offset += K_curr
坑 :拼图时 offset 要对——offset 是当前时间窗在全局节点列表里的起始位置。
六、典型应用案例 案例 1:政策演化 2022-2024 三年的”科技创新”政策文件,桑基图清晰展示:
T0(数字化) → T1(数字化转型) → T2(数字化转型+智能化)(主题合并扩展)
T0(碳中和) → T1(绿色发展) → T2(双碳目标)(主题持续演进)
案例 2:竞品口碑演化 2022-2024 三个双 11 的电商评论,桑基图显示:
T0(物流吐槽) → T1(物流吐槽+客服) → T2(物流吐槽基本消失,客服成主角)(竞品补足了物流短板)
七、踩坑大全 1. 阈值选取 :threshold 太高(>0.7)= 几乎画不出连线(主题间相似度很少到 0.7);太低(<0.3)= 画面太乱。建议从 0.4 起步。
2. 主题编号不一致 :不同时间窗 LDA 给的主题编号是随机的,必须用相似度矩阵做对齐 (前面代码里的贪心匹配)。否则会画出”假演化”——以为 T1 → T2,实际是 T5 → T7。
3. 桑基图性能 :节点数 > 50 时 plotly 会卡。建议分时间窗画,不要拼 1 张大图。
4. 三元组的 value 含义 :value = 相似度(0~1),不是流量。通量图才用通量值。老板常分不清,记得在报告里标注。
5. 节点标签 :一定要带”时间戳前缀”(如 2022_T0),否则读图时不知道节点属于哪一年。
我们的 LDA 主题演化分析软件 一键出三大件:相似度矩阵(Excel + 热力图)+ 桑基图(HTML 交互版)+ 三元组(CSV) 。老板拿到三元组后想用 ECharts、Tableau、Power BI 二次加工都行。