StructBERT中文相似度模型保姆级教学:如何用TSNE可视化高维句向量空间分布
StructBERT中文相似度模型保姆级教学如何用TSNE可视化高维句向量空间分布1. 引言为什么需要可视化句向量当你使用StructBERT这样的模型计算句子相似度时你得到的只是一个0到1之间的数字。这个数字告诉你两个句子“有多像”但它无法告诉你为什么像也无法展示整个句子集合的语义结构。想象一下你有一堆中文句子有的是关于科技产品的有的是关于美食的有的是关于旅行的。StructBERT可以帮你计算任意两个句子之间的相似度但如果你想知道所有科技相关的句子是不是都聚在一起“手机电池耐用”和“续航能力强”这两个句子在向量空间里到底有多近不同主题的句子之间有没有清晰的边界这时候光看相似度分数就不够了。你需要一种方法把几百甚至几千维的句向量“压缩”到我们能理解的二维或三维空间里然后直观地看到它们的分布。这就是TSNE可视化要做的事情。学习目标 通过这篇教程你将学会用StructBERT模型提取中文句子的特征向量使用TSNE算法将高维向量降到二维用Matplotlib绘制出漂亮的语义空间分布图从可视化结果中发现有趣的语义规律前置知识基本的Python编程会写函数、会用列表知道怎么安装Python包pip install对自然语言处理有初步了解知道什么是词向量、句向量即使你是NLP新手也没关系我会用最直白的方式讲解每个步骤。2. 环境准备与模型加载2.1 安装必要的库首先确保你的Python环境已经准备好。打开终端或命令行执行以下命令pip install torch transformers streamlit matplotlib scikit-learn pandas numpy这些包的作用分别是torch: PyTorch深度学习框架transformers: Hugging Face的Transformer模型库streamlit: 用于构建交互式Web应用我们用来展示结果matplotlib: 绘图库用来画图scikit-learn: 机器学习工具包包含TSNE算法pandas和numpy: 数据处理的基础库如果你有NVIDIA显卡并且想用GPU加速建议安装CUDA版本的PyTorch。不过没有GPU也没关系CPU也能跑只是慢一点。2.2 准备StructBERT模型StructBERT是阿里达摩院开源的优秀中文预训练模型。我们需要先下载模型权重。如果你已经按照项目说明把模型放在了指定路径可以直接使用。如果没有可以通过以下代码自动下载from transformers import AutoTokenizer, AutoModel import torch # 模型名称 model_name alibaba-pai/pai-structbert-zh-base-sentence-similarity # 加载tokenizer和模型 print(正在加载tokenizer...) tokenizer AutoTokenizer.from_pretrained(model_name) print(正在加载模型...) model AutoModel.from_pretrained(model_name) # 如果有GPU就把模型放到GPU上 device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) print(f模型已加载到: {device})重要提示 第一次运行时会下载模型文件大小约400MB需要一些时间。下载完成后模型会缓存在本地下次就不需要再下载了。2.3 准备测试句子为了演示效果我准备了一些中文句子涵盖了不同主题sentences [ # 科技产品类 这款手机的电池非常耐用, 续航能力强是这款手机的主要卖点, 手机充电速度很快, 相机拍照效果很清晰, # 美食类 这家餐厅的火锅味道很正宗, 麻辣火锅是我的最爱, 四川火锅以麻辣鲜香著称, 清汤火锅适合不吃辣的人, # 旅游类 三亚的海滩非常漂亮, 海边的日落景色很美, 海风轻轻吹拂很舒服, 在海边散步是一种享受, # 一些相似的句子对 电池续航时间长, # 与科技类相关 火锅底料很香, # 与美食类相关 海边的风景如画, # 与旅游类相关 ]这些句子有明确的主题分类待会儿我们可以看到TSNE能不能把它们正确地分开。3. 提取句向量从文字到数字3.1 理解句向量的提取过程StructBERT模型处理句子的过程可以分为三步分词把中文句子拆分成模型能理解的“词片”Token编码模型把词片转换成高维向量表示池化把所有的词向量合并成一个句向量这里的关键是均值池化Mean Pooling。简单来说就是取句子中所有词向量的平均值。为什么用平均值而不是其他方法因为平均值能更好地保留整个句子的综合信息。3.2 实现向量提取函数下面这个函数就是完成上述三步的核心代码def get_sentence_embedding(sentence, tokenizer, model, device): 提取单个句子的向量表示 参数: sentence: 中文句子字符串 tokenizer: 分词器 model: StructBERT模型 device: 计算设备CPU或GPU 返回: embedding: 句子的768维向量 # 1. 分词并添加特殊标记 inputs tokenizer( sentence, return_tensorspt, # 返回PyTorch张量 paddingTrue, truncationTrue, max_length128 ) # 把输入数据移到正确的设备上 inputs {key: value.to(device) for key, value in inputs.items()} # 2. 模型推理不计算梯度节省内存 with torch.no_grad(): outputs model(**inputs) # 3. 获取最后一层的隐藏状态每个词片的向量 last_hidden_state outputs.last_hidden_state # 形状: [1, 词片数, 768] # 4. 均值池化 # 创建注意力掩码0表示padding1表示真实词片 attention_mask inputs[attention_mask] # 形状: [1, 词片数] # 扩展掩码的维度用于后续计算 mask_expanded attention_mask.unsqueeze(-1).expand(last_hidden_state.size()).float() # 用掩码把padding位置的向量置零 masked_embeddings last_hidden_state * mask_expanded # 对非padding位置求和 sum_embeddings torch.sum(masked_embeddings, dim1) # 计算非padding位置的数量 sum_mask torch.clamp(mask_expanded.sum(dim1), min1e-9) # 计算平均值得到句向量 sentence_embedding sum_embeddings / sum_mask # 从GPU移到CPU并转换成numpy数组 return sentence_embedding.squeeze().cpu().numpy()3.3 批量提取所有句子的向量有了单个句子的提取函数我们可以批量处理所有句子def extract_all_embeddings(sentences, tokenizer, model, device): 批量提取所有句子的向量 参数: sentences: 句子列表 tokenizer: 分词器 model: StructBERT模型 device: 计算设备 返回: embeddings: 形状为 [句子数, 768] 的numpy数组 print(f开始提取 {len(sentences)} 个句子的向量...) embeddings [] for i, sentence in enumerate(sentences): # 显示进度 if (i 1) % 5 0 or (i 1) len(sentences): print(f 正在处理第 {i 1}/{len(sentences)} 个句子) # 提取向量 embedding get_sentence_embedding(sentence, tokenizer, model, device) embeddings.append(embedding) # 转换成numpy数组 embeddings_array np.array(embeddings) print(f向量提取完成形状: {embeddings_array.shape}) return embeddings_array现在运行这个函数import numpy as np # 提取所有句子的向量 embeddings extract_all_embeddings(sentences, tokenizer, model, device)你会看到类似这样的输出开始提取 15 个句子的向量... 正在处理第 5/15 个句子 正在处理第 10/15 个句子 正在处理第 15/15 个句子 向量提取完成形状: (15, 768)这意味着我们得到了15个句子每个句子用768维的向量表示。这就是StructBERT理解中文句子的方式——把文字转换成768个数字。4. TSNE降维从768维到2维4.1 什么是TSNETSNEt-Distributed Stochastic Neighbor Embedding是一种专门用于可视化的降维算法。它的核心思想是在高维空间中距离近的点在低维空间中也应该近在高维空间中距离远的点在低维空间中也应该远但这里有个问题768维的空间我们人类无法理解。TSNE帮我们把这些点“投影”到二维平面上让我们能用眼睛看到它们之间的关系。4.2 TSNE的工作原理简单版你可以把TSNE想象成一个“智能投影仪”它先计算768维空间中每两个点之间的距离然后尝试在二维平面上摆放这些点摆放的原则是让二维空间中的距离关系尽量接近768维空间中的距离关系经过多次调整找到最好的二维布局4.3 实现TSNE降维from sklearn.manifold import TSNE import matplotlib.pyplot as plt def reduce_dimension_with_tsne(embeddings, n_components2, perplexity5, random_state42): 使用TSNE将高维向量降到二维 参数: embeddings: 高维向量数组形状 [n_samples, n_features] n_components: 降维后的维度通常是2或3 perplexity: TSNE的参数控制局部和全局结构的平衡 对于小数据集建议用较小的值如3-10 random_state: 随机种子保证每次结果一样 返回: reduced_embeddings: 降维后的二维坐标 print(开始TSNE降维...) print(f 输入形状: {embeddings.shape}) print(f 参数: perplexity{perplexity}, n_components{n_components}) # 创建TSNE模型 tsne TSNE( n_componentsn_components, perplexityperplexity, random_staterandom_state, initpca, # 用PCA初始化效果更稳定 learning_rate200 ) # 执行降维 reduced_embeddings tsne.fit_transform(embeddings) print(f 输出形状: {reduced_embeddings.shape}) print(TSNE降维完成) return reduced_embeddings # 执行降维 tsne_results reduce_dimension_with_tsne(embeddings, perplexity5)参数解释perplexity这个参数可以理解为“每个点考虑多少个邻居”。值太小只关注局部结构值太大关注全局结构。对于15个句子的小数据集我用5是比较合适的。random_state设为固定值如42可以保证每次运行结果一样方便调试。initpca用PCA算法初始化TSNE通常比随机初始化效果更好。4.4 理解TSNE的输出运行上面的代码后tsne_results是一个形状为(15, 2)的数组。比如[[ 12.345, -5.678], [ 8.901, 2.345], [ -3.456, 7.890], ...]每一行对应一个句子第一列是X坐标第二列是Y坐标。这些坐标没有实际单位只是相对位置。重要的是点与点之间的相对距离。5. 可视化把数字变成图形5.1 基础散点图现在有了二维坐标我们可以用Matplotlib画图了。先画一个最简单的散点图def plot_basic_scatter(tsne_results, sentences): 绘制基础的散点图 参数: tsne_results: TSNE降维后的二维坐标 sentences: 对应的句子列表 plt.figure(figsize(10, 8)) # 提取X和Y坐标 x tsne_results[:, 0] y tsne_results[:, 1] # 绘制散点 plt.scatter(x, y, s100, alpha0.6, edgecolorsblack, linewidth1) # 添加句子标签 for i, sentence in enumerate(sentences): # 只显示前10个字符避免标签重叠 short_sentence sentence[:10] ... if len(sentence) 10 else sentence plt.annotate( short_sentence, (x[i], y[i]), fontsize9, alpha0.7, xytext(5, 5), textcoordsoffset points ) # 设置标题和标签 plt.title(StructBERT句向量TSNE可视化, fontsize16, fontweightbold) plt.xlabel(TSNE维度1, fontsize12) plt.ylabel(TSNE维度2, fontsize12) # 添加网格 plt.grid(True, alpha0.3) # 显示图形 plt.tight_layout() plt.show() # 绘制基础散点图 plot_basic_scatter(tsne_results, sentences)这个图已经能显示句子的分布了但所有点都是同一种颜色看不出类别信息。5.2 按主题着色的高级可视化让我们用颜色来区分不同的主题def plot_colored_by_category(tsne_results, sentences): 按主题类别着色的散点图 # 定义类别根据我们之前准备的句子 categories [] colors [] # 科技类 - 蓝色 tech_sentences [这款手机的电池非常耐用, 续航能力强是这款手机的主要卖点, 手机充电速度很快, 相机拍照效果很清晰, 电池续航时间长] # 美食类 - 红色 food_sentences [这家餐厅的火锅味道很正宗, 麻辣火锅是我的最爱, 四川火锅以麻辣鲜香著称, 清汤火锅适合不吃辣的人, 火锅底料很香] # 旅游类 - 绿色 travel_sentences [三亚的海滩非常漂亮, 海边的日落景色很美, 海风轻轻吹拂很舒服, 在海边散步是一种享受, 海边的风景如画] # 为每个句子分配类别和颜色 category_colors { 科技: blue, 美食: red, 旅游: green } for sentence in sentences: if sentence in tech_sentences: categories.append(科技) colors.append(category_colors[科技]) elif sentence in food_sentences: categories.append(美食) colors.append(category_colors[美食]) elif sentence in travel_sentences: categories.append(旅游) colors.append(category_colors[旅游]) else: categories.append(其他) colors.append(gray) # 创建图形 plt.figure(figsize(12, 10)) # 按类别分组绘制 unique_categories list(set(categories)) for category in unique_categories: # 找出属于该类别的点的索引 indices [i for i, cat in enumerate(categories) if cat category] if indices: # 如果有属于该类别的点 # 提取这些点的坐标 x tsne_results[indices, 0] y tsne_results[indices, 1] # 获取颜色 color category_colors.get(category, gray) # 绘制散点 plt.scatter( x, y, s120, alpha0.7, edgecolorsblack, linewidth1.5, labelcategory, colorcolor ) # 添加标签只显示部分句子的完整标签避免重叠 for i, idx in enumerate(indices): if i % 2 0: # 每隔一个点添加标签 short_sentence sentences[idx][:8] ... if len(sentences[idx]) 8 else sentences[idx] plt.annotate( short_sentence, (x[i], y[i]), fontsize9, alpha0.8, xytext(7, 7), textcoordsoffset points, bboxdict(boxstyleround,pad0.3, facecolorwhite, alpha0.7) ) # 添加图例 plt.legend(title句子类别, fontsize11, title_fontsize12) # 设置标题和标签 plt.title(StructBERT句向量空间分布按主题着色, fontsize18, fontweightbold, pad20) plt.xlabel(TSNE维度1, fontsize14) plt.ylabel(TSNE维度2, fontsize14) # 添加说明文字 plt.figtext(0.5, 0.01, 相同颜色的点表示相同主题的句子距离越近表示语义越相似, hacenter, fontsize11, styleitalic, alpha0.8) # 添加网格 plt.grid(True, alpha0.2, linestyle--) # 调整布局并显示 plt.tight_layout(rect[0, 0.05, 1, 0.98]) plt.show() return categories # 绘制彩色散点图 categories plot_colored_by_category(tsne_results, sentences)5.3 添加相似度连线的增强可视化我们还可以把语义相似的句子用线连起来更直观地展示它们的关系def plot_with_similarity_lines(tsne_results, sentences, categories, similarity_threshold0.7): 在散点图上添加相似度连线 参数: similarity_threshold: 相似度阈值高于这个值的句子对会被连线 # 先计算所有句子对之间的余弦相似度 from sklearn.metrics.pairwise import cosine_similarity print(计算句子间相似度...) similarity_matrix cosine_similarity(embeddings) # 创建图形 plt.figure(figsize(14, 12)) # 绘制散点按类别 unique_categories list(set(categories)) category_colors {科技: blue, 美食: red, 旅游: green, 其他: gray} for category in unique_categories: indices [i for i, cat in enumerate(categories) if cat category] if indices: x tsne_results[indices, 0] y tsne_results[indices, 1] color category_colors.get(category, gray) plt.scatter( x, y, s150, alpha0.8, edgecolorsblack, linewidth2, labelcategory, colorcolor, zorder5 # 确保散点在线条上面 ) # 添加相似度连线 line_count 0 for i in range(len(sentences)): for j in range(i 1, len(sentences)): similarity similarity_matrix[i, j] # 如果相似度高于阈值且不是同一个类别为了展示跨类别相似 if similarity similarity_threshold: x1, y1 tsne_results[i, 0], tsne_results[i, 1] x2, y2 tsne_results[j, 0], tsne_results[j, 1] # 根据相似度设置线条样式 line_width similarity * 3 # 相似度越高线越粗 line_alpha similarity * 0.7 # 相似度越高线越不透明 # 绘制连线 plt.plot( [x1, x2], [y1, y2], colorpurple, linewidthline_width, alphaline_alpha, zorder1 # 线条在散点下面 ) line_count 1 print(f添加了 {line_count} 条相似度连线阈值 {similarity_threshold}) # 添加几个高相似度句子对的标注 high_similarity_pairs [] for i in range(len(sentences)): for j in range(i 1, len(sentences)): if similarity_matrix[i, j] 0.85: # 非常相似的句子对 high_similarity_pairs.append((i, j, similarity_matrix[i, j])) # 取相似度最高的3对 high_similarity_pairs.sort(keylambda x: x[2], reverseTrue) for idx, (i, j, sim) in enumerate(high_similarity_pairs[:3]): x1, y1 tsne_results[i, 0], tsne_results[i, 1] x2, y2 tsne_results[j, 0], tsne_results[j, 1] # 连线中点 mid_x, mid_y (x1 x2) / 2, (y1 y2) / 2 # 添加标注 plt.annotate( f相似度: {sim:.3f}, (mid_x, mid_y), fontsize10, fontweightbold, colordarkred, xytext(0, 15), textcoordsoffset points, hacenter, bboxdict(boxstyleround,pad0.3, facecoloryellow, alpha0.7) ) # 设置图形属性 plt.legend(title句子类别, fontsize12, title_fontsize13, locupper right) plt.title(StructBERT句向量分布与相似度关系, fontsize20, fontweightbold, pad25) plt.xlabel(TSNE维度1, fontsize15) plt.ylabel(TSNE维度2, fontsize15) # 添加说明 plt.figtext(0.5, 0.02, f紫色连线表示相似度 {similarity_threshold} 的句子对线越粗表示越相似, hacenter, fontsize12, styleitalic) plt.grid(True, alpha0.15, linestyle:) plt.tight_layout(rect[0, 0.04, 1, 0.98]) plt.show() # 绘制带连线的图 plot_with_similarity_lines(tsne_results, sentences, categories, similarity_threshold0.7)6. 交互式可视化应用静态图片虽然直观但交互式应用能让我们更好地探索数据。下面我用Streamlit创建一个简单的Web应用import streamlit as st import pandas as pd def create_interactive_app(): 创建交互式Streamlit应用 st.set_page_config(page_titleStructBERT句向量可视化, layoutwide) st.title( StructBERT中文句向量TSNE可视化工具) st.markdown(---) # 侧边栏参数控制 with st.sidebar: st.header(⚙️ 参数设置) perplexity st.slider( TSNE Perplexity, min_value2, max_value20, value5, help控制TSNE算法的复杂度小数据集用较小值 ) similarity_threshold st.slider( 相似度连线阈值, min_value0.1, max_value0.95, value0.7, step0.05, help相似度高于此值的句子对会被连线 ) point_size st.slider( 散点大小, min_value50, max_value300, value150, step10 ) show_labels st.checkbox(显示句子标签, valueTrue) show_grid st.checkbox(显示网格, valueTrue) if st.button( 重新生成可视化): st.rerun() # 主区域显示可视化 col1, col2 st.columns([2, 1]) with col1: st.subheader(句向量空间分布) # 这里可以嵌入我们之前创建的图形 # 在实际应用中你需要把matplotlib图形转换成Streamlit能显示的格式 # 为了简化这里用文字描述 st.info( **可视化说明** - 蓝色点科技/手机相关句子 - 红色点美食/火锅相关句子 - 绿色点旅游/海边相关句子 - 点之间的距离越近表示语义越相似 - 紫色连线表示高度相似的句子对 ) # 显示相似度矩阵 if st.checkbox(显示相似度矩阵): from sklearn.metrics.pairwise import cosine_similarity similarity_matrix cosine_similarity(embeddings) # 创建DataFrame df_similarity pd.DataFrame( similarity_matrix, index[s[:15] ... for s in sentences], columns[s[:15] ... for s in sentences] ) st.dataframe( df_similarity.style.background_gradient(cmapBlues, axisNone), use_container_widthTrue ) with col2: st.subheader(句子列表) # 显示所有句子及其类别 for i, (sentence, category) in enumerate(zip(sentences, categories)): color_map {科技: blue, 美食: red, 旅游: green} color color_map.get(category, gray) st.markdown( fdiv styleborder-left: 5px solid {color}; padding: 10px; margin: 5px 0; fb句子 {i1}/b ({category})br f{sentence}/div, unsafe_allow_htmlTrue ) st.markdown(---) st.caption(f共 {len(sentences)} 个句子{len(set(categories))} 个类别) # 注意Streamlit应用需要单独运行 # 在实际使用中你需要创建一个app.py文件把上面的代码放进去 # 然后在命令行运行streamlit run app.py7. 结果分析与解读运行完上面的代码你会得到几张可视化图形。让我们来解读一下这些结果7.1 你可能会观察到的现象同类句子聚在一起所有关于“手机/电池”的蓝色点应该聚在一团所有关于“火锅”的红色点聚在另一团所有关于“海边”的绿色点聚在第三团语义相似的句子距离很近“电池耐用”和“续航能力强”这两个点应该几乎挨在一起“麻辣火锅”和“四川火锅”也应该很接近不同类别之间有清晰边界科技、美食、旅游这三个主题的点应该形成三个相对独立的簇簇与簇之间有一定的距离跨类别相似性如果“电池续航时间长”这个句子离科技类点很近说明模型正确理解了它的语义你可能还会发现一些有趣的跨类别关系7.2 从可视化中学到什么通过这个可视化练习你可以验证模型效果如果同类句子确实聚在一起说明StructBERT的语义理解能力很强发现语义关系可以看到哪些句子在语义空间中是“邻居”调试模型如果某个句子的位置不对劲可能需要检查数据或模型解释相似度分数不仅知道两个句子相似度是0.8还能看到它们在语义空间中的相对位置7.3 实际应用场景这种可视化方法在实际项目中很有用文本聚类分析快速查看文本数据是否有自然的类别结构异常检测找到那些“不合群”的句子离所有簇都很远的点模型比较比较不同模型生成的句向量分布数据质量检查发现标注不一致的问题8. 总结通过这篇保姆级教程我们完成了从理论到实践的完整流程8.1 关键步骤回顾环境准备安装了必要的Python库加载了StructBERT模型数据准备准备了一批有明确类别的中文句子向量提取用StructBERT模型把句子转换成768维的向量降维处理用TSNE算法把768维向量压缩到2维可视化用Matplotlib绘制了多种类型的图形交互探索用Streamlit创建了交互式应用8.2 核心收获理解了句向量的概念句子不再是文字而是高维空间中的点掌握了TSNE的使用知道了如何把高维数据可视化学会了结果解读能从图形中读出语义关系获得了实用工具有了可以直接复用的代码8.3 下一步建议如果你想继续深入学习可以尝试处理更大规模的数据试试1000个句子看看TSNE还能不能很好地工作尝试其他降维方法除了TSNE还可以试试PCA、UMAP等方法比较不同模型用同样的句子试试BERT、RoBERTa、ERNIE等模型比较它们的向量分布应用到实际项目用这个方法分析你的业务数据比如用户评论、产品描述等最重要的是现在你有了一个强大的工具可以把抽象的“语义相似度”变成直观的图形。下次当你需要向别人解释为什么两个句子相似时不用再说“因为相似度是0.85”而是可以直接展示这张图“你看它们在这个语义空间里是邻居”获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2472269.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!