解读文本嵌入:语义表达的练习

【引子】近来在探索并优化AIPC的软件架构,AI产品经理关于语义搜索的讨论给了自己较多的触动,于是重新梳理嵌入与语义的关系,遂成此文。

文本转换成机器可理解格式的最早版本之一是 ASCII码,这种方法有助于渲染和传输文本,但不能编码单词的意义,其标准的搜索技术是关键字搜索,寻找包含特定单词或 N-gram的所有文档。如今,我们可以计算单词、句子甚至图像的嵌入。嵌入也是数字的向量,但它们可以捕捉意义。因此,可以使用它们进行语义搜索,甚至处理不同语言的文档。

1. 文本嵌入的演变

将文本转换为向量的最基本方法是使用词袋模型(bag of words,BoW)。获得一个单词向量的第一步是将文本分割成单词(标记) ,然后将单词减少到它们的基本形式。例如,“ running”将转换为“ run”,这个过程称为词干分析。我们可以使用NLTK 来观察这个过程。

from nltk.stem import SnowballStemmer
from nltk.tokenize import word_tokenizetext = ' target text for Bow model'# tokenization - splitting text into words
words = word_tokenize(text)
print(words)stemmer = SnowballStemmer(language = "english")
stemmed_words = list(map(lambda x: stemmer.stem(x), words))
print(stemmed_words)

现在,有了所有单词的基本形式列表。下一步是计算它们的频率,创建一个向量。

import collections
bag_of_words = collections.Counter(stemmed_words)
print(bag_of_words)

这种方法非常基本,而且没有考虑到词语的语义,略有改进的版本是 TF-IDF ,这是两个度量的乘法。

TF显示文档中单词的频率。最常见的计算方法是将文档中的词汇的原始计数除以文档中的词汇(单词)总数。然而,还有许多其他方法,如原始计数、布尔“频率”和不同的标准化方法。IDF表示单词提供的信息量。例如,单词“ a”或“ that”不会提供关于文档主题的任何其他信息。它被计算为文档总数与包含单词的文档总数之比的对数。IDF 越接近于0ーー这个词越常见,它提供的信息就越少。

最后,将得到常见单词的权重较低的向量,而在文档中多次出现的罕见单词的权重较高。这个策略会给出一个更好的结果,但是它仍然不能捕获语义。

这种方法的一个问题是会产生稀疏向量。由于向量的长度等于语料库的大小,将有巨大的向量。但是,句子一般不会有超过50个独特的单词,向量中大量的值将为0,不编码任何信息。

有名的密集向量表示方法之一是 word2vec,由谷歌于2013年在 Mikolov 等人的论文“Efficient Estimation of Word Representations in Vector Space”中提出。文章中提到了两种不同的 word2vec 方法: “CBoW”和“Skip-gram”。

3421395d43cdd191a6346c01a322534e.jpeg

密集向量表示的核心思想是训练两种模型: 编码器和解码器。例如,在Skip-gram情况下,我们可以将“国庆节”传递给编码器。然后,编码器将产生一个向量,我们传递给解码器期望得到单词“快乐”“祝”“你”。这个模型开始考虑单词的意思,因为它是根据单词的上下文进行训练的。然而,它忽略了词语的表面形式。这个缺点后来在 GloVe 中得到了一定的解决。

word2vec 只能处理单词,但我们希望编码整个句子,于是人们引入了Transformer。在论文“ Attention Is All You Need”中,transformer能够产生信息密集的矢量,并成为现代语言模型的主导技术。

Transformers 允许使用相同的基础模型,并针对不同的用例对其进行微调,而无需重新训练基础模型,这导致了预训练模型的兴起。第一个流行的模型之一是 BERT ,是基于transformer的双向编码器表示。BERT 仍然在类似 word2vec 的token级别上运行,获得句子嵌入的简单方法可能是取所有向量的平均值。不幸的是,这种方法并没有显示出良好的性能。在论文“Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks”中,解决了句子嵌入的计算问题。

然而, 句子的嵌入 或者说句子的语义表达是个大课题, 还需要进一步深入研究。

2.文本嵌入的计算

如今,已经有很多的嵌入模型可以供我们参考和使用,例如 OpenAI 的text-embedding-ada-002和text-embedding-3-large,当然,我们也可以通过Huggingface的嵌入模型排行榜进行选择并探索。

from openai import OpenAI
client = OpenAI()def get_embedding(text, model="text-embedding-3-small"):text = text.replace("\n", " ")return client.embeddings.create(input = [text], model=model)\.data[0].embeddingget_embedding("Here is TEXT what we want ..... ")

结果,我们得到了一个1536维的浮点数向量, 然后,我们可以为所有的数据计算向量,并展开分析,一个主要的目标是了解句子之间的意义有多接近。我们可以计算向量之间的距离,较小的距离相当于较近的意义。

假设有两个文本的嵌入是vector1 和vector2, 可以使用不同的度量标准来衡量两个向量之间的距离:

  • 欧式距离

  • 曼哈顿距离

  • 向量点积

  • 余弦距离

2.1 欧式距离

定义两点(或向量)之间距离的直观方法是欧式距离,或者叫 L2范数。我们可以直接使用python或者利用 numpy 函数来计算这个度量。

import numpy as np
L2_py = sum(list(map(lambda x, y: (x - y) ** 2, vector1, vector2))) ** 0.5
L2_np = np.linalg.norm((np.array(vector1) - np.array(vector2)), ord = 2)

2.2 曼哈顿距离

另一个常用的距离是 L1标准距离或曼哈顿距离,是以纽约曼哈顿岛来命名的。这个岛上的街道有网格布局,曼哈顿两点之间的最短路线是跟着网格走的 L1距离。我们同样可以使用python或者利用 numpy 函数来计算这个度量。

L1_py = sum(list(map(lambda x, y: abs(x - y), vector1, vector2)))
L1_np = np.linalg.norm((np.array(vector1) - np.array(vector2)), ord = 1)

2.3 向量点积

观察向量间距离的另一种方法是计算点积。

sum(list(map(lambda x, y: x*y, vector1, vector2)))
np.dot(vector1, vector2)

点积需要从几何上进行理解。一方面,它显示向量是否指向一个方向。另一方面,结果高度依赖于矢量的大小。例如,计算两对(1,1)向量之间的点积为2, 计算两对(10,10)向量之间的点积为20,在这两种情况下,向量是共线的,但是点积在第二种情况下要大十倍。

2.4 余弦距离

余弦距离是由向量的大小(或范数)归一化的点积。我们可以用前面的方法计算余弦距离,还可以利用Sklearn。

dot_product = sum(list(map(lambda x, y: x*y, vector1, vector2)))
norm_vector1 = sum(list(map(lambda x: x ** 2, vector1))) ** 0.5
norm_vector2 = sum(list(map(lambda x: x ** 2, vector2))) ** 0.5cs_py=dot_product/norm_vector1/norm_vector2
print(cs_py)from sklearn.metrics.pairwise import cosine_similaritycs_sk = cosine_similarity(np.array(vector1).reshape(1, -1), np.array(vector2).reshape(1, -1))[0][0]
print(cs_sk)

cosine_similarity 函数需要2D 数组,所以需要将向量转化为数组的形式。余弦距离等于两个向量之间的余弦。向量越接近,度量值就越高。

我们可以使用任何距离来比较所有的文本嵌入。然而,对于自然语言处理的任务,一般的做法通常是使用余弦距离,因为:

  • 余弦距离在 -1和1之间,而 L1和 L2是无界的,所以更容易解释。

  • 从实际角度来看,计算欧几里得度量点积比计算平方根更有效。

  • 余弦距离受维数灾难的影响较小。

其中,“维数灾难”是指维度越高,矢量之间的距离分布越窄。

3. 文本嵌入的可视化

理解数据的最好方法就是将它们可视化。不幸的是,如果文本嵌入有1536个维度,理解数据会非常困难。然而,我们可以使用降维技术在二维空间中做向量投影。

最基本的降维技术是 PCA (主成分分析) ,我们将嵌入转换成一个2D numpy 数组,然后将其传递给 sklearn。

import numpy as np
from sklearn.decomposition import PCAembeddings_array = np.array(df.embedding.values.tolist())
print(embeddings_array.shape)pca_model = PCA(n_components = 2)
pca_model.fit(embeddings_array)pca_embeddings_values = pca_model.transform(embeddings_array)
print(pca_embeddings_values.shape)

因此得到了一个矩阵,可以很容易地把它做成在一个散点图。

fig = px.scatter(x = pca_embeddings_values[:,0], y = pca_embeddings_values[:,1],color = df.topic.values,hover_name = df.full_text.values,title = 'PCA embeddings', width = 800, height = 600,color_discrete_sequence = plotly.colors.qualitative.Alphabet_r
)fig.update_layout(xaxis_title = 'first component', yaxis_title = 'second component')
fig.show()

PCA是一种线性算法,而现实生活中大多数关系是非线性的。因此,由于非线性的原因,可以尝试使用一个非线性算法 t-SNE。

from sklearn.manifold import TSNE
tsne_model = TSNE(n_components=2, random_state=42)
tsne_embeddings_values = tsne_model.fit_transform(embeddings_array)fig = px.scatter(x = tsne_embeddings_values[:,0], y = tsne_embeddings_values[:,1],color = df.topic.values,hover_name = df.full_text.values,title = 't-SNE embeddings', width = 800, height = 600,color_discrete_sequence = plotly.colors.qualitative.Alphabet_r
)fig.update_layout(xaxis_title = 'first component', yaxis_title = 'second component')
fig.show()

此外,还可以制作三维空间的投影,并将其可视化。

4. 文本嵌入的应用示例

文本嵌入的主要目的不是将文本编码为数字向量,或者仅仅为了将其可视化。我们可以从捕捉文本含义的能力中受益匪浅。

4.1 聚类

聚类是一种非监督式学习的技术,它允许将数据分成不带任何初始标签的组,可以帮助理解数据中的内部结构模式。最基本的聚类算法是K-Means,应用时需要指定聚类的数目,可以使用轮廓得分来定义最佳的聚类。例如,尝试 聚类数量k 介于2和50之间,对于每个 k,训练一个模型并计算轮廓分数。轮廓得分越高,聚类效果越好。

from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
import tqdmsilhouette_scores = []
for k in tqdm.tqdm(range(2, 51)):kmeans = KMeans(n_clusters=k, random_state=42, n_init = 'auto').fit(embeddings_array)kmeans_labels = kmeans.labels_silhouette_scores.append({'k': k,'silhouette_score': silhouette_score(embeddings_array, kmeans_labels, metric = 'cosine')})fig = px.line(pd.DataFrame(silhouette_scores).set_index('k'),title = '<b>Silhouette scores </b>',labels = {'value': 'silhoutte score'}, color_discrete_sequence = plotly.colors.qualitative.Alphabet)
fig.update_layout(showlegend = False)

如果有实际文本的主题标签,我们可以用它来评估聚类结果的好坏。

4.2 分类

同样,文本嵌入可以用于分类或回归任务。例如,预测客户评论的情绪(分类)或 NPS 评分(回归)。分类和回归是监督式学习,所以需要有数据标签。为了正确评估分类模型的性能,我们将数据集划分为训练集和测试集(80% 比20%)。然后,在一个训练集上训练模型,并在一个测试集上测量质量。

以随机森林分类器为例:

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
class_model = RandomForestClassifier(max_depth = 5)# defining features and target
X = embeddings_array
y = df.topic# splitting data into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state = 49, test_size=0.2, stratify=y)# fit & predict 
class_model.fit(X_train, y_train)
y_pred = class_model.predict(X_test)

然后,我们计算一个混淆矩阵,理想的情况下所有非对角线的元素应该是0。

from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, y_pred)fig = px.imshow(cm, x = class_model.classes_,y = class_model.classes_, text_auto='d', aspect="auto", labels=dict(x="predicted label", y="true label", color="cases"), color_continuous_scale='pubugn',title = '<b>Confusion matrix</b>', height = 550)fig.show()

我们还可以使用嵌入来发现数据中的异常。例如,在可视化的图像上,看到一些问题与它们的聚类相去甚远,那些就可能是异常的数据。

4.3 RAG

随着 LLM 最近越来越流行,文本嵌入在 RAG 用例中得到了广泛的应用。当有很多文档需要检索增强生成时,而我们却不能将它们全部传递给 LLM,因为:

  • LLM 对上下文大小有限制(例如,GPT-4 Turbo 的上下文大小是128K)。

  • 由于需要为token付费,所以传递所有信息的成本更高。

  • 在更大的上下文中,LLM 显示出的性能较差。

为了能够使用广泛的知识库,我们可以利用 RAG 方法:

  • 计算所有文档的嵌入,并将它们存储在向量存储器中。

  • 当得到一个用户请求时,可以计算它的嵌入并从存储中检索该请求的相关文档。

  • 只将相关文档传递给 LLM 以获得最终答案。

5. 一句话小结

文本处理方法的演变导致了文本嵌入的出现,从词汇的语义表达到句子嵌入,多种距离度量方法可以帮助理解文本是否具有相似的意义,文本嵌入的可视化可以帮助我们了解数据的内在模式,常见的应用示例包括聚类、分类以及基于大模型的RAG等。

ps. 对文本嵌入感兴趣的朋友可以参考老码农的一本译作《基于混合方法的自然语言处理》。

d0f5b259d3eb9204d48a80b098a23559.jpeg

【关联阅读】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1549826.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构_2.2、顺序表插入删除查找

1、线性表的顺序存储表示定义&#xff1a; 线性表&#xff1a;是具有相同数据类型的n &#xff08;n≥0&#xff09;个数据元素的有限序列 顺序表&#xff1a;用顺序存储的方式实现线性表 顺序存储&#xff1a;把逻辑上相邻的元素存储在物理 位置上也相邻的存储单元中&#…

深度学习框架的选择:深入比较PyTorch与TensorFlow

深度学习框架的选择&#xff1a;深入比较PyTorch与TensorFlow 前言深度学习框架的起源与发展**PyTorch****TensorFlow** 框架的进化**TensorFlow****PyTorch** 数据对比结论结语 前言 在人工智能的浪潮中&#xff0c;深度学习技术已成为推动行业变革的核心力量。随着技术的不断…

C语言 | Leetcode C语言题解之第443题压缩字符串

题目&#xff1a; 题解&#xff1a; void swap(char *a, char *b) {char t *a;*a *b, *b t; }void reverse(char *a, char *b) {while (a < b) {swap(a, --b);} }int compress(char *chars, int charsSize) {int write 0, left 0;for (int read 0; read < charsSi…

leetcode_55:跳跃游戏

给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输…

Java基于easyExcel的自定义表格格式

这里用的到easyExcel版本为3.3.4 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.3.4</version></dependency> 效果 代码部分 package com.tianyu.test;import com.alibaba.exc…

单调递增/递减栈

单调栈 单调栈分为单调递增栈和单调递减栈 单调递增栈&#xff1a;栈中元素从栈底到栈顶是递增的 单调递减栈&#xff1a;栈中元素从栈底到栈顶是递减的 应用&#xff1a;求解下一个大于x元素或者是小于x的元素的位置 给一个数组&#xff0c;返回一个大小相同的数组&#x…

C语言课程设计题目七:学生成绩管理系统设计

题目七&#xff1a;学生成绩管理系统设计 学生成绩信息包括&#xff1a;学期&#xff0c;学号&#xff0c;班别&#xff0c;姓名&#xff0c;四门课程成绩(语文、数学、英语和计算机)等。 主要功能&#xff1a; 能按学期、按班级完成对学生成绩的录入、修改。能按班级统计学生…

Element-Plus中上传文件upload取消提示按钮与文字

去除提示按钮与文字 添加样式&#xff0c;让这个div进行隐藏 .el-upload__input {display: none !important; }

WEB 编程:富文本编辑器 Quill 配合 Pico.css 样式被影响的问题之还是 iframe

这个系列已经写了 3 篇了。这篇写如何使用 iframe 解决标题里面提到的问题。 前情提要 请看上一篇博文&#xff1a; WEB 编程&#xff1a;富文本编辑器 Quill 配合 Pico.css 样式被影响的问题之Shadow DOM WEB 编程&#xff1a;富文本编辑器 Quill 配合 Pico.css 样式被影响…

深度学习反向传播-过程举例

深度学习中&#xff0c;一般的参数更新方式都是梯度下降法&#xff0c;在使用梯度下降法时&#xff0c;涉及到梯度反向传播的过程&#xff0c;那么在反向传播过程中梯度到底是怎么传递的&#xff1f;结合自己最近的一点理解&#xff0c;下面举个例子简单说明&#xff01; 一、…

锐捷 NBR 1300G路由器 越权CLI命令执行漏洞

漏洞描述 锐捷NBR 1300G路由器 越权CLI命令执行漏洞&#xff0c;guest账户可以越权获取管理员账号密码 漏洞复现 FOFA title"锐捷网络 --NBR路由器--登录界面" 请求包 POST /WEB_VMS/LEVEL15/ HTTP/1.1 Host: Connection: keep-alive Content-Length: 73 Autho…

网络编程(12)——完善粘包处理操作(id字段)

十二、day12 之前的粘包处理是基于消息头包含的消息体长度进行对应的切包操作&#xff0c;但并不完整。一般来说&#xff0c;消息头仅包含数据域的长度&#xff0c;但是如果要进行逻辑处理&#xff0c;就需要传递一个id字段表示要处理的消息id&#xff0c;当然可以不在包头传i…

naocs注册中心,配置管理,openfeign在idea中实现模块间的调用,getway的使用

一 naocs注册中心步骤 1 nacos下载安装 解压安装包&#xff0c;直接运行bin目录下的startup.cmd 这里双击运行出现问题的情况下 &#xff08;版本低的naocs&#xff09; 在bin目录下 打开cmd 运行以下命令 startup.cmd -m standalone 访问地址&#xff1a; http://localh…

一文了解:最新版本 Llama 3.2

Meta AI最近发布了 Llama 3.2。这是他们第一次推出可以同时处理文字和图片的多模态模型。这个版本主要关注两个方面&#xff1a; 视觉功能&#xff1a;他们现在有了能处理图片的模型&#xff0c;参数量从11亿到90亿不等。 轻量级模型&#xff1a;这些模型参数量在1亿到3亿之间…

基于SSM+小程序的高质量阅读微信管理系统(阅读5)(源码+sql脚本+视频导入教程+文档)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 1、其管理员管理文章&#xff0c;留言板&#xff0c;交流论坛以及用户信息。 2、用户收藏并评论文章&#xff0c;查看和评论论坛交流信息&#xff0c;管理自己发布的帖子&#xff0c;管理…

数据结构与算法笔记7:最小生成树-Prim和Kruskal算法

常用的最小生成树的算法主要有两种&#xff0c;一种是Prim算法&#xff0c;一种是Kruskal算法。题目链接&#xff1a;KamaCoder 53. 寻宝&#xff08;第七期模拟笔试&#xff09; 这里假设有V个节点&#xff0c;因为我们的节点的标号是1~V&#xff0c;这样我们直接使用标号作…

队列及笔试题

队列 先进先出 使用单链表进行队尾插入 队头删除 其中带头结点直接尾插&#xff0c;不带头结点第一次操作要判断一下 但是带头结点需要malloc和free 函数传需要修改的参数方法 1、二级指针 2、带哨兵位的头结点 3、返回值 4、如果有多个值&#xff0c;用结构体封装起来…

努比亚 Z17 NX563J Root 教程三方REC刷写工具教程

教程&#xff1a;1&#xff0c;自用成功 正常链接列表 adb devices 检查fastboot链接列表 fastboot devices 解锁设备fastboot oem nubia_unlock NUBIA_NX563J 我用的解锁设备是&#xff1a;fastboot flashing unlock 1.打开开发者选项。将OEM解锁的按钮打开 2.下载附件努…

甄选范文“论企业应用系统的数据持久层架构设计”,软考高级论文,系统架构设计师论文

论文真题 数据持久层(Data Persistence Layer)通常位于企业应用系统的业务逻辑层和数据源层之间,为整个项目提供一个高层、统一、安全、并发的数据持久机制,完成对各种数据进行持久化的编程工作,并为系统业务逻辑层提供服务。它能够使程序员避免手工编写访问数据源的方法…

MQ基础:RabbitMQ真面目

同步调用方式&#xff0c;指的是发送方直接发送给接收方的形式。而这种方式在某些情况下可能出现问题&#xff0c;比如当业务逻辑变得复杂&#xff0c;同步的方式需要等待上一条指令被接收后才会继续&#xff0c;对性能的影响很大。 异步的方式&#xff0c;增加了一个消息代理…