基于孪生神经网络(Siamese Neural Network)的文本语义相似度计算算法详解
我将详细讲解如何利用孪生神经网络判断两段文本的语义相似度。这是一个经典的深度学习方法,广泛应用于问答匹配、重复问题检测、信息检索等场景。
1. 算法核心思想
孖生神经网络的核心是“比较”,而不是“分类”或“生成”。它的设计理念是:
- 目标:学习一个函数,能够将输入文本映射到一个“语义向量空间”中。在这个空间里,语义相似的文本距离很近,语义不同的文本距离很远。
- 核心结构:使用两个(或多个)结构相同、参数共享的子神经网络(通常称为“塔”或“分支”)分别处理两个输入文本。
- 工作流程:两个文本分别通过共享参数的子网络,得到各自的语义向量表示。然后,通过一个可学习的相似度函数(如余弦相似度、欧氏距离等)比较这两个向量,输出一个相似度分数。
为什么参数共享?
- 确保将“苹果公司”和“Apple Inc.”映射到空间中的同一个点,将“苹果”和“水果”映射到不同点。这要求对两个输入使用相同的映射规则(即相同的网络参数)。
- 极大减少了参数量,避免了模型为两个输入学习两套不同的编码规则。
2. 算法关键组件详解
组件一:共享参数的编码器网络
这是孪生网络的“塔”,负责将变长的文本序列编码为固定长度的语义向量。其内部可以是非常灵活的结构:
-
输入表示层:
- 文本首先被分词,并转换为词索引序列。
- 通过一个嵌入层,将每个词索引映射为一个稠密的词向量(可以是随机初始化,也可以加载预训练词向量如Word2Vec、GloVe)。
-
上下文编码器:这是语义提取的关键。常用结构有:
- LSTM/GRU:能够捕获文本的序列信息和长距离依赖。通常取最后一个时间步的隐藏状态,或所有时间步隐藏状态的平均/最大值,作为整个句子的向量。
- CNN:使用多个不同大小的卷积核来捕获文本中的局部N-gram特征,然后通过池化层(如最大池化)聚合得到句子向量。
- Transformer/BERT:使用自注意力机制,能更好地建模词与词之间的全局依赖关系。可以用
[CLS]标记的向量,或所有词向量的平均作为句子向量。
假设编码器函数为 \(f_{\theta}\),对于两个输入文本 \(T_a\) 和 \(T_b\),我们得到:
\[\mathbf{v}_a = f_{\theta}(T_a) \]
\[ \mathbf{v}_b = f_{\theta}(T_b) \]
其中,\(\mathbf{v}_a, \mathbf{v}_b \in \mathbb{R}^d\) 是维度为 \(d\) 的语义向量。
组件二:距离/相似度计算层
得到两个语义向量后,我们需要量化它们的“接近程度”。常见的度量函数有:
- 余弦相似度:
\[ \text{sim}_{\text{cos}}(\mathbf{v}_a, \mathbf{v}_b) = \frac{\mathbf{v}_a \cdot \mathbf{v}_b}{\|\mathbf{v}_a\| \|\mathbf{v}_b\|} \]
输出在 $[-1, 1]$ 之间,值越大越相似。
- 曼哈顿距离/L1距离:
\[ d_{\text{L1}}(\mathbf{v}_a, \mathbf{v}_b) = \|\mathbf{v}_a - \mathbf{v}_b\|_1 \]
距离越小越相似。
- 欧几里得距离/L2距离:
\[ d_{\text{L2}}(\mathbf{v}_a, \mathbf{v}_b) = \|\mathbf{v}_a - \mathbf{v}_b\|_2 \]
距离越小越相似。
- 可学习的相似度函数:
- 将两个向量 \(\mathbf{v}_a, \mathbf{v}_b\) 拼接,或计算它们的绝对差 \(|\mathbf{v}_a - \mathbf{v}_b|\)、点积 \(\mathbf{v}_a \cdot \mathbf{v}_b\) 等。
- 将这个结果输入到一个多层感知机中,输出最终的相似度分数。这比固定的距离度量更灵活。
组件三:损失函数
损失函数的作用是指导模型学习参数,使“相似文本对”的向量距离小,“不相似文本对”的向量距离大。最经典的是对比损失:
\[\mathcal{L} = \frac{1}{2N} \sum_{i=1}^{N} [ y_i \cdot d^2 + (1 - y_i) \cdot \max(\text{margin} - d, 0)^2 ] \]
- \(y_i = 1\) 表示 \(T_a\) 和 \(T_b\) 是相似对(正样本),\(y_i = 0\) 是不相似对(负样本)。
- \(d\) 是 \(\mathbf{v}_a\) 和 \(\mathbf{v}_b\) 的欧氏距离。
- margin 是一个超参数,表示我们希望不相似对之间的“最小距离”。当它们的距离大于这个 margin 时,损失为0,模型不再对其优化。
- 这个损失直观理解是:
- 如果是正样本,我们希望 \(d\) 越小越好,即损失直接是 \(d^2\)。
- 如果是负样本,我们希望 \(d\) 越大越好,但只有当 \(d\) 小于 margin 时才计算损失,鼓励它至少拉开到 margin 的距离。
3. 算法完整训练流程
假设我们有一个标注好的数据集,每条数据包含两个文本和一个标签(1表示相似,0表示不相似)。
- 输入:一对文本 \((T_a, T_b)\) 和标签 \(y\)。
- 前向传播:
- 文本 \(T_a\) 经过嵌入层和共享参数的编码器网络 \(f_{\theta}\),得到向量 \(\mathbf{v}_a\)。
- 文本 \(T_b\) 经过同一个编码器网络 \(f_{\theta}\),得到向量 \(\mathbf{v}_b\)。
- 相似度计算:
- 计算 \(\mathbf{v}_a\) 和 \(\mathbf{v}_b\) 的距离 \(d\) 或相似度得分。
- 计算损失:
- 使用对比损失(或三元组损失、交叉熵损失等)比较模型预测的相似度与真实标签 \(y\) 的差异,得到损失值 \(\mathcal{L}\)。
- 反向传播与更新:
- 通过反向传播算法,计算损失 \(\mathcal{L}\) 对网络所有参数 \(\theta\) 的梯度。
- 使用优化器(如Adam)根据梯度更新参数 \(\theta\),目的是降低损失。
- 重复:在整个训练集上重复步骤1-5,直到模型收敛。
4. 推理与应用
模型训练好后,用于预测新文本对的相似度:
- 将待判断的两个新文本 \(T_a^{'}\) 和 \(T_b^{'}\) 输入到已训练好的孪生网络中。
- 网络会输出一个相似度分数(如果是距离,可以取负或通过函数映射为相似度)。
- 如果任务是二分类(相似/不相似),可以设置一个阈值(如0.5),分数高于阈值则判断为语义相似。
典型应用场景:
- 重复问题检测:在问答社区,判断用户的新问题与历史问题是否相似,从而直接推荐已有答案。
- 语义检索:将查询句与文档库中的句子进行相似度匹配,返回最相关的结果。
- 文本蕴含/对矛盾识别:判断两句话是蕴含、矛盾还是中性关系。
5. 算法的优势与挑战
优势:
- 端到端学习:自动从数据中学习适合任务的语义表示和相似度度量,无需复杂的人工特征工程。
- 共享参数:模型更精简,更易于训练,泛化能力更强。
- 灵活性:编码器网络(塔)可以根据任务复杂度灵活选择(从简单的CNN到强大的BERT)。
挑战:
- 数据需求:需要大量标注的相似/不相似文本对,数据标注成本高。
- 负样本构造:构造有信息量的“困难负样本”(与正样本相似但实际不同的样本)对模型性能至关重要。
- 对称性假设:标准的孪生网络假设任务是对称的,即 \(sim(A, B) = sim(B, A)\)。对于非对称任务(如问答,问题与答案的角色不同),可能不是最优结构(此时可考虑非对称的孪生网络或相关架构)。
通过以上五个步骤,我们从思想、组件、流程、应用到优缺点,完整地剖析了基于孪生神经网络的文本语义相似度计算算法。其“共享参数、比较差异”的核心思想,使其成为NLP中衡量语义相似度的强大而经典的工具。