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

news2024/9/29 6:32:17

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

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

1. 文本嵌入的演变

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

from nltk.stem import SnowballStemmer
from nltk.tokenize import word_tokenize

text = ' 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].embedding

get_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.5

cs_py=dot_product/norm_vector1/norm_vector2
print(cs_py)

from sklearn.metrics.pairwise import cosine_similarity

cs_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 PCA

embeddings_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 tqdm

silhouette_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.coloradmin.cn/o/2176071.html

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

相关文章

win10系统K8S安装教程

准备工作 电脑硬件&#xff1a;支持虚拟化的CPU&#xff0c;内存最好在32G以上&#xff0c;16G也可以操作系统&#xff1a;window10 专业版 1 开启虚拟化 1.1 BIOS 由于主板和CPU的品牌不太一样&#xff0c;这里的操作仅供参考&#xff0c;以Intel的平台为例&#xff1a; …

【刷点笔试面试题试试水】有符号变量与无符号变量的值的转换

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: 注意无符号类型与有符合类型参与计算会做类型提升,有符合的变为无符号…

加法器以及标志位

加法器的结构&#xff1a; OF&#xff08;溢出标志位&#xff09;&#xff0c;SF&#xff08;符号标志位&#xff09;&#xff0c;ZF&#xff08;0标志位&#xff09;&#xff0c;ZF&#xff08;进位/借位标志位&#xff09; 有符号数看标志位&#xff1a;OF&#xff0c;SF 无符…

ubuntu 不用每次输入sudo的四种方式

在Ubuntu系统中&#xff0c;如果不希望每次执行需要管理员权限的命令时都输入sudo&#xff0c;有几种方法可以实现这一目标。以下是一些详细的方法&#xff1a; 第一种方式: 切换root用户 (如果你有足够的权限) # 修改root密码命令(没有设置的用户需要设置一下) consolaadmin…

面试中顺序表常考的十大题目解析

在数据结构与算法的面试中&#xff0c;顺序表是一个常见的考点。它作为一种基础的数据结构&#xff0c;涵盖了多种操作和概念&#xff0c;以下将详细介绍面试中关于顺序表常考的十大题目。 &#x1f49d;&#x1f49d;&#x1f49d;如果你对顺序表的概念与理解还存在疑惑&#…

【Threejs进阶教程-着色器篇】8. Shadertoy如何使用到Threejs-基础版

【Threejs进阶教程-着色器篇】8. Shadertoy如何使用到Threejs - 基础版 前七篇地址,建议按顺序学习致谢带我入门的[X01动力装甲]大佬本文适用范围怎么样在Shadertoy中画出正圆形shadertoy中的坐标系比例转换理解Shadertoy的fragCoord理解Shadertoy中的iResolution 转移Shaderto…

SigmaStudio淡入淡出增益控件(Single SW slew vol(adjustable))延时分析

斜率范围1~23&#xff0c;参考12khz正弦波&#xff08;-17.99db,调减15.2db&#xff09;作为分析依据 一、淡入时间与斜率关系 斜率1-----淡入延时时间大概0.08毫秒 斜率2—淡入延时时间大概0.2毫秒 斜率3–淡入延时时间按大概0.5毫秒 斜率4–淡入延时时间大概1毫秒 斜率5–淡…

C++学习笔记之结构体

C学习笔记之结构体 https://www.runoob.com/cplusplus/cpp-struct.html 结构体是C中一种由用户自定义的数据类型&#xff0c;允许存储不同类型的数据项 1、定义结构体 使用struct语句定义结构体 结构体与C中的类看起来结构相似&#xff0c;同样是可以在其中定义成员变量和成员…

picgo + typora + gitee图床

Picgo打造个人图床&#xff0c;稳定又安全 解决Typora笔记上传到CSDN图片无法显示的问题 typora中

完全二叉树的节点个数 C++ 简单问题

完全二叉树 的定义如下&#xff1a;在完全二叉树中&#xff0c;除了最底层节点可能没填满外&#xff0c;其余每层节点数都达到最大值&#xff0c;并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层&#xff0c;则该层包含 1~ 2h 个节点。 示例 1&#xff…

蓝桥杯—STM32G431RBT6(RTC时钟获取时间和日期)

一、RTC是什么&#xff0c;有什么用&#xff1f; 在 STM32 中&#xff0c;RTC&#xff08;Real-Time Clock&#xff0c;实时时钟&#xff09;主要有以下作用&#xff1a; 时间保持&#xff1a;即使在系统断电情况下&#xff0c;也能持续记录时间。&#xff08;需要纽扣电池供电…

解决银河麒麟V10密码过期无法登录的问题

解决银河麒麟V10密码过期无法登录的问题 1、问题描述2、 解决方法步骤一&#xff1a;更改密码步骤二&#xff1a;调整密码策略&#xff08;可选&#xff09; 3、总结 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在使用银河麒麟桌面操作系…

Java:选择排序

目录 直接选择排序 堆排序 基本思想&#xff1a; 每一次从待排序的数据元素中选出最小(或最大)的一个元素&#xff0c;存放在序列的起始位置&#xff0c;直到全部待排序的数据元素排完。 直接选择排序 思路1&#xff1a; 在元素集合array[i]--array[n-1]中选择关键码最大(小…

【论文阅读】视觉里程计攻击

Adversary is on the Road: Attacks on Visual SLAM using Unnoticeable Adversarial Patch 一、视觉SLAM的不安全因素 根据论文的分析&#xff0c;视觉SLAM由于完全依赖于特征&#xff0c;缺少验证机制导致算法不安全。前端在受到干扰的情况下&#xff0c;会导致误匹配增加&…

算法工程师重生之第十八天(修剪二叉搜索树 将有序数组转换为二叉搜索树 把二叉搜索树转换为累加树 总结篇 )

参考文献 代码随想录 一、修剪二叉搜索树 给你二叉搜索树的根节点 root &#xff0c;同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树&#xff0c;使得所有节点的值在[low, high]中。修剪树 不应该 改变保留在树中的元素的相对结构 (即&#xff0c;如果没有被移除…

【Sentinel-2简介】

Sentinel-2简介 Sentinel-2是欧洲空间局&#xff08;European Space Agency, ESA&#xff09;全球环境和安全监视&#xff08;即哥白尼计划&#xff09;系列卫星的重要组成部分&#xff0c;由Sentinel-2A和Sentinel-2B两颗卫星组成。以下是关于Sentinel-2的详细介绍&#xff1…

信息安全工程师(27)环境安全分析与防护

前言 环境安全分析与防护是一个综合性的议题&#xff0c;涉及多个方面&#xff0c;包括环境安全的概念、分析方法、存在的安全隐患以及相应的防护措施。 一、环境安全的概念 环境安全是指人类赖以生存发展的环境&#xff0c;处于一种不受污染和破坏的安全状态&#xff0c;或者说…

828华为云征文 | 华为云X实例CPU性能测试详解与优化策略

目录 引言 1. 测试环境搭建 1.1 测试实例的选择 1.2 CPU性能测试工具介绍 1.3 安装和配置Sysbench 2. CPU性能测试方法 2.1 测试场景设定 2.2 Sysbench单线程CPU性能测试 2.3 Sysbench多线程CPU性能测试&#xff08;4线程&#xff09; 2.4 高强度多线程CPU性能测试&a…

【QT 开发日志】QT 基础控件详解:按钮、文本框与标签的使用

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 博主简介 博主致力于嵌入式、Python、人工智能、C/C领域和各种前沿技术的优质博客分享&#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.下载附件努…