向量模型的词序感知缺陷与优化策略

最近,LAION AI 的创始人 Christoph Schuhmann 分享了一个有趣的发现,他指出,文本向量模型似乎存在一个问题:即使句子词序被打乱,模型输出的向量与原句仍然高度相似。

例如,“柏林是德国的首都” 和 “德国柏林是首都的”这两句话,后者虽然读都读不顺,但模型却分不出来。使用 jina-embeddings-v3 计算,它们的余弦相似度竟高达 0.973。

这不是个例。改变词序虽然会改变句子的意思,但向量之间的距离变化很小。“她看完电影后吃了晚饭” 和 “她吃了晚饭后看完电影”,尽管动作顺序完全颠倒,余弦相似度仍然高达 0.985。

更麻烦的是,没经过特殊训练的模型在处理否定词上也表现不太好。“这是一个有用的模型” 和 “这不是一个有用的模型” 在向量空间中的表示非常相近。同义词的替换(例如将“今天”换成“昨天”)或者时态的改变,对向量的影响也远不如我们预期的那么大。

这些问题可能会造成严重的影响。比如,“从柏林飞往阿姆斯特丹的航班” 和 “从阿姆斯特丹飞往柏林的航班” 这两个搜索查询的向量几乎相同,jina-embeddings-v3 给出的相似度高达 0.997。这对出行规划或物流等应用而言,简直是灾难性的。

cdf239a96bdd258ae41ab3ec7ba61477.png

在 jina.ai/embeddings 可直接测试

本文将探讨文本向量模型在词序、方向、时序、因果、比较和否定等语义关系上的理解缺陷,通过实验,我们试图找到问题本质,并提出基于微调的性能优化方案。

为什么词序打乱后相似度还那么高?

一开始,我们认为这可能与模型组合词义的方式有关。模型先为每个词生成一个向量,然后通过平均池化,将这些向量整合成句子的整体向量。这样一来,最终的向量几乎不包含词序的信息了,因为平均值本身就忽略了数值的排列顺序。

但是,就算用 CLS 池化的模型,也没能避免这个问题(CLS 池化通过关注句子的第一个词来理解整体语义,照理说应该更注重词序)。例如,对于 “柏林是德国的首都” 和 “德国柏林是首都的”这两句话,bge-base-zh-v1.5 模型算出来的余弦相似度还是有 0.974。

这表明向量模型的训练范式可能存在局限性。尽管语言模型在预训练阶段习得了句法结构知识,但在对比学习阶段,模型对句法结构的感知能力出现了退化。

实验:文本长度和词序如何影响相似度

模型为什么一开始就搞不定词序呢?我们首先想到的是文本长度,也就是 Token 的数量。当文本被输入编码器后,模型会为每个 Token 生成一个向量,你可以理解为每个词都有一个独特的向量来代表它的意思。然后,模型会对这些向量取个平均值。

为了弄清楚文本长度和词序到底怎么影响向量的相似度,我们特意做了一个实验:构建了一个包含 180 个合成句子的数据集,你可以在这里找到它:https://huggingface.co/datasets/bwang0911/word-orders-triplet-random-shuffle。

这些句子的长度分别有 3、5、10、15、20 和 30 个 token,并且每种长度的句子,我们都准备了对应的乱序版本。

🔗:https://huggingface.co/datasets/bwang0911/word-orders-triplet-random-shuffle

给大家看几个例子:

68349ebc846e8d1b47f356a470b17709.png

我们使用 jina-embeddings-v3bge-base-en-v1.5 这两个模型对数据集进行编码,然后算了一下原始句子和乱序句子之间的余弦相似度。

现在,我们可以画一个箱线图,这样就能更清楚地看到余弦相似度是怎么变化的了。

4f4333ad5a0a09ea4cb5e9dc66879ea7.png
图 1:jina-embeddings-v3 和 bge-base-en-1.5 模型(均未经过微调)在面对词序混乱的句子时,根据句子长度分组的相似度分布情况。

结果正如我们看到的,向量的平均余弦相似度呈现出明显的线性关系:文本越长,原始句子和打乱后句子的平均余弦相似度就越低。

这很可能和“词位移”有关,也就是词在被打乱后偏离原来位置的距离。你想啊,在短句子中,词能被打乱到的位置本来就少,所以词移不了太远;而长句子就有更多排列组合的可能性,词自然就能移得更远。

10fd0e49a15935bb1cf2616888947a9b.png
图 2:按 Token 数划分的句子组合

💡 这里我们就不继续列表格了,因为组合数量是词数的阶乘。当句子有 30 个词的时候,排列组合数就达到了 265 非亿(2.652528598 E+32)种,太大了放不下。

看下面这张图(余弦相似度与平均词位移关系图),你会发现文本越长,词位移就越大:

045b36f02e0aa7db34613c8a2188e92d.png
图 3:在打乱句子数据集上,余弦相似度与平均词位移之间的关系,且平均词位移与余弦不相似度存在相关性。

Token 向量依赖于它的局部上下文,也就是离它最近的那些词。在短文本里,即使重新排列词,上下文也不会发生太大的变化。

但是,在长文本里,一个词可能被挪到离它原始上下文很远的地方,这就会显著改变它的 Token 向量。所以说,打乱长文本中的词,会产生比短文本更远的向量距离。

上面的图也说明了,无论是用均值池化的 jina-embeddings-v3,还是用 CLS 池化的 bge-base-en-v1.5,都遵循同样的规律:打乱较长的文本并使词位移更远,会导致更低的相似度分数。

更大的模型就能解决问题吗?

通常来说,遇到这类问题,我们第一反应就是用更大的模型。按照文本向量模型的 Scaling Law,通常来说,模型越大,性能就越好:

ad4593facbbdd89ab0b0fb59e325b6c5.png
图 4:向量模型的 Scaling Law

不过,更大的模型真的就能更好地理解词序吗?

为了验证这一点,我们测试了 BGE 模型的三个版本:bge-small-en-v1.5bge-base-en-v1.5bge-large-en-v1.5,它们的参数量分别是 3300 万、1.1 亿和 3.35 亿。

我们还是用之前那 180 个句子,这次就不考虑长度了。我们用这三个不同大小的模型对原始句子和打乱后的句子进行编码,然后画出平均余弦相似度:

c070a6e4ab841d41575ec6e91ae47644.png
图 5:使用打乱句子数据集,对比三个不同模型大小 bge-small-en-v1.5、bge-base-en-v1.5 和 bge-large-en-v1.5 对词序变化的敏感程度。

结果表明,更大的模型确实对词序变化更敏感一些,但效果提升非常有限。

即使是最大的 bge-large-en-v1.5,在区分打乱和未打乱的句子方面,也仅仅只是好了一点点而已。看来,除了模型大小,还有其他因素在影响着模型对词序的敏感度,特别是训练方法的差异。

此外,余弦相似度本身也只是衡量模型区分能力的一种比较粗糙的工具。不过,至少我们可以看到,模型的大小并不是决定性的因素。简单地增大模型规模并不能解决这个问题。

词序和词的选择同样重要!

在文章前面的实验部分,我们用的是 jina-embeddings-v2,而不是最新的 jina-embeddings-v3。主要是因为 v2 模型小得多,在本地 GPU 上跑实验更快,v2 的参数量是 1.37 亿,而 v3 达到了 5.8 亿。

就像我们在开头说的那样,词序并不是向量模型唯一要面对的难题。在实际应用中,更棘手的是词的选择。有很多方法可以改变句子里的词,而这些改变往往不能在向量中得到很好的体现。

比如,我们可以把 “她从巴黎飞到东京” 改成 “她从东京开车到巴黎”,但向量的表示仍然会非常相似。我们把这些情况大致分成了几类:

ed50b08e705df6d566c73db304686d38.png

注意,这只是我们在工作中观察到的一些常见情况,并不一定能涵盖所有情况。

上面的表格展示了一些文本向量模型在捕捉细微词语变化时遇到的失败案例。这其实也符合我们的预期:现在的文本向量模型还缺乏推理能力。

比如说,模型似乎不理解 “from” 和 “to” 之间的关系。文本向量模型做的其实是语义匹配,而语义通常是在 token 的层面被捕获的,然后在池化后压缩成单个密集的向量。相比之下,在万亿词元规模的数据集上训练的大语言模型(LLM),已经开始展现出一些推理的能力了。

这让我们开始思考,我们能不能通过三元组的对比学习来微调向量模型,让查询语句和正样本的向量更接近,同时拉开查询语句和负样本的距离呢?

9e1e5c98b9c15e0243e17e3bb6e96789.png
图 6:对比学习:使查询和正样本更接近,负样本更远离。

比如说,“从阿姆斯特丹飞往柏林” 就可以看作是 “从柏林飞往阿姆斯特丹” 的负例。事实上,在 jina-embeddings-v1 的技术报告中,我们已经初步尝试解决过这个问题:我们用一个由大型语言模型生成的、包含 1 万个否定示例的数据集微调了 jina-embeddings-v1 模型。

🔗:https://huggingface.co/datasets/jinaai/negation-dataset

上述报告的结果很让人振奋:

🚀 我们观察到,在所有模型尺寸中,使用三元组数据(包括我们的否定训练数据集)进行微调,都显著提升了性能,尤其是在 HardNegation 任务上。

a7fa955cc16632b8b52880319598f3ab.png
图 7:不同的 jina-embeddings 模型在配对训练和组合三元组/配对训练下的 EasyNegation 和 HardNegation 得分对比
a8ef9eda324a8e0e13c58dcfcff9340b.png
图 8:不同模型大小的 jina-embeddings 训练策略的性能比较

用精选数据集微调文本向量模型

在前面,我们讨论了一些关于文本向量的关键发现:

  1. 短文本更容易在理解词序上出错。

  2. 单纯增加文本向量模型的规模,不一定能提升词序理解能力。

  3. 对比学习或许能为解决这些问题提供一些思路。

基于这些认识,我们用一个包含了否定和词序的数据集(总共有大约 11000 个训练样本)微调了 jina-embeddings-v2-base-enbge-base-en-1.5 这两个模型。

🔗:https://huggingface.co/bwang0911/word-order-jina

🔗:https://huggingface.co/bwang0911/word-order-bge

为了更好地评估微调的效果,我们又生成了一个包含 1000 个三元组的数据集,每个三元组都包含一个查询(query)、一个正例(positive, pos)和一个负例(negative, neg)。

🔗:https://huggingface.co/datasets/bwang0911/word-orders-triplet

这里给大家看一个例子:

79d59fba4f756f5e1e345810c12e73ed.png

这些三元组的设计初衷,是为了覆盖各种容易翻车的情况,包括因为词序变化而导致的方向、时间、因果关系等方面的语义变化。

现在,我们可以在三个不同的评估集上测试模型:

  • 前面提到的那 180 个合成句子集的随机打乱版本

  • 前面我们手动检查的那 5 个例子

  • 我们新生成的三元组数据集里选出来的 94 个比较有代表性的例子

下面是微调前后,模型在打乱句子上的表现对比:e398d964ddd56c2991c0f98127d99cd4.png

结果相当明显:尽管微调只花了五分钟,我们在随机打乱句子的数据集上看到了显著的性能提升!

54155280b05f712d13e6bcfccfe7f95b.png

在方向性、时序、因果和比较的案例上,我们也看到了性能的提升。模型整体性能的提高,可以从平均余弦相似度的下降上看出来。其中,提升最明显的是否定案例,这是因为我们的微调数据集里包含了 10,000 个否定训练的样本。

结论

在这篇文章里,我们深入研究了文本向量模型面临的挑战,特别是它们在处理词序时的各种问题。具体来说,我们确认了五种主要的问题类型:方向性、时序性、因果性、比较性和否定。这些类型的查询对词序的敏感性要求很高,如果你的应用场景涉及到这些,了解模型的局限性就非常重要。

我们还做了一个快速但成功的实验,把原来只关注否定情况的数据集扩展到了涵盖所有这五种类型。实验结果挺让人兴奋的:通过仔细挑选的难负例进行微调,模型在区分语义相似和不相似的条目上,都有了明显的改进!

当然,这方面还有很多可以深入研究的地方。未来,我们会继续探索数据集的大小和质量如何影响模型性能,期待取得更多突破。

b521abc594a1ffaf4aa861a3693a65e0.png

<think>嗯,用户现在想知道如何将词向量转化为句向量。首先,我需要回忆一下相关的知识,可能涉及到自然语言处理中的常见方法。比如,最简单的可能是对词向量进行平均或者加权平均。还有可能用更复杂的方法,比如使用深度学习模型如RNN、LSTM或者Transformer来生成句向量。 根据引用里的内容,用户提到了Word2Vec和BERT,这些是生成词向量的工具。那如何从这些词向量得到句向量呢?比如,对于Word2Vec生成的词向量,可以取每个词的向量然后求平均,或者用TF-IDF权重加权平均。而像BERT这样的模型,本身在输出的时候已经考虑了上下文,可以直接取特定层的输出作为句向量,比如CLS标签或者最后一层的平均。 另外,引用3提到了分布式表示,说明词向量的语义信息可以通过向量运算来捕捉。这可能意味着句向量的构建也需要考虑词之间的语义组合。例如,使用词向量的简单组合或者更复杂的神经网络结构。 用户提供的引用4中有使用TfidfVectorizer的例子,这可能涉及到将词频或TF-IDF权重向量结合,构建句子的向量表示。比如,将每个词的TF-IDF值作为权重,对词向量进行加权求和,得到句向量。 还要考虑是否有现成的工具或库可以直接生成句向量,比如Sentence-BERT、GloVe的句向量处理方法,或者使用预训练模型如BERT的句子编码器。这些方法可能更高效或效果更好。 需要分步骤解释不同的方法,从简单到复杂。首先介绍基础方法如平均法,然后是加权平均,接着是深度学习模型,最后是预训练模型的方法。同时,可能需要给出代码示例,比如使用Gensim的Word2Vec模型计算平均句向量,或者用Hugging Face的BERT模型获取句向量。 还需要注意用户提到的机器学习自然语言处理的上下文,所以应该强调这些方法在实际应用中的使用场景和选择依据。比如,简单的平均法适用于快速原型开发,而预训练模型适合需要高精度的任务。 最后,检查是否有遗漏的方法,比如使用Doc2Vec直接生成句向量,或者使用注意力机制来加权词向量。确保覆盖主流方法,并引用相关的文献或工具库,方便用户进一步查阅。</think>### 如何将词向量转换为句向量? 在自然语言处理中,将词向量转换为句向量(即句子表示)的核心思路是通过组合词向量来捕捉句子的整体语义。以下是几种主流方法及实现工具: --- #### 一、基础方法 1. **简单平均法** 直接对句子中所有词的词向量取算术平均值: $$ \text{句向量} = \frac{1}{n} \sum_{i=1}^{n} \mathbf{v}_i $$ 其中 $\mathbf{v}_i$ 是第 $i$ 个词的词向量,$n$ 为句子长度。 **适用场景**:快速实现,但对词序和语义权重不敏感[^3]。 **示例代码(基于Word2Vec)**: ```python from gensim.models import Word2Vec sentences = [["我", "喜欢", "自然语言处理"], ["机器学习", "很", "有趣"]] model = Word2Vec(sentences, vector_size=100, window=5, min_count=1) def sentence_vector(sentence): vectors = [model.wv[word] for word in sentence if word in model.wv] return sum(vectors) / len(vectors) if vectors else None ``` 2. **加权平均法** 引入权重(如TF-IDF)对词向量加权求和: $$ \text{句向量} = \frac{1}{\sum w_i} \sum_{i=1}^{n} w_i \mathbf{v}_i $$ 通过 `TfidfVectorizer` 计算词权重,提升关键词的重要性[^4]。 **示例代码**: ```python from sklearn.feature_extraction.text import TfidfVectorizer import numpy as np corpus = ["我喜欢自然语言处理", "机器学习很有趣"] tfidf = TfidfVectorizer() tfidf_matrix = tfidf.fit_transform(corpus) # 假设已预训练Word2Vec模型 def weighted_sentence_vector(sentence, model, tfidf_weights): words = sentence.split() vectors = [model.wv[word] for word in words if word in model.wv] weights = [tfidf_weights.get(word, 0) for word in words] return np.average(vectors, axis=0, weights=weights) ``` --- #### 二、深度学习模型 1. **RNN/LSTM/GRU** 使用循环神经网络编码句子,最终隐藏状态作为句向量: $$ \mathbf{h}_t = \text{RNN}(\mathbf{v}_t, \mathbf{h}_{t-1}) $$ 最后一时刻的 $\mathbf{h}_n$ 即为句向量。 **优点**:捕捉词序和长距离依赖[^5]。 2. **Transformer/预训练模型** - **BERT**:直接使用 `[CLS]` 标记的向量或各词向量的平均值作为句向量[^2]。 ```python from transformers import BertTokenizer, BertModel tokenizer = BertTokenizer.from_pretrained("bert-base-chinese") model = BertModel.from_pretrained("bert-base-chinese") inputs = tokenizer("这是一个示例句子", return_tensors="pt") outputs = model(**inputs) sentence_embedding = outputs.last_hidden_state.mean(dim=1) # 平均池化 ``` - **Sentence-BERT**:对BERT输出进行池化(如均值、最大值)优化句子相似度任务[^2]。 --- #### 三、专用工具库 1. **Doc2Vec** 直接生成句子向量,无需组合词向量。通过 `gensim` 实现: ```python from gensim.models import Doc2Vec from gensim.models.doc2vec import TaggedDocument documents = [TaggedDocument(words=["我", "喜欢", "编程"], tags=[0]), TaggedDocument(words=["机器学习", "改变", "世界"], tags=[1])] model = Doc2Vec(documents, vector_size=100, window=2, min_count=1) sentence_vec = model.dv[0] # 获取标签为0的句向量 ``` 2. **Sentence-Transformers** 专为句向量设计的库,支持预训练模型: ```python from sentence_transformers import SentenceTransformer model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2") sentence_embeddings = model.encode(["这是一个示例句子"]) ``` --- #### 四、方法对比选择建议 | 方法 | 优点 | 缺点 | 适用场景 | |----------------|-------------------------------|-------------------------------|-----------------------| | 简单平均 | 实现简单,计算快 | 忽略词序和语义权重 | 快速原型开发 | | TF-IDF加权平均 | 突出关键词作用 | 依赖外部权重计算 | 信息检索、文本分类 | | RNN/LSTM | 捕捉词序信息 | 训练成本高,长文本效果受限 | 序列标注、生成任务 | | BERT/Sentence-BERT | 上下文感知,精度高 | 计算资源消耗大 | 语义匹配、复杂NLP任务 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值