NLP实战:基于Siamese Network的问题相似性判定

一、技术背景与问题定义

在智能客服、问答系统等场景中,判断两个问题是否语义相似是核心需求。传统方法依赖关键词匹配或词向量平均,难以捕捉深层语义关联。例如”如何重置密码”与”密码忘了怎么改”虽表述不同但意图相同,传统方法易误判。

Siamese Network(孪生网络)通过共享权重的双塔结构,将两个句子映射到低维空间,通过距离度量判断相似性。其优势在于:

  • 参数共享减少计算量
  • 适用于小样本场景
  • 可解释性强(距离直观反映相似度)

典型应用场景包括:

  1. 智能客服中的问题归类
  2. 搜索引擎的查询重写
  3. 论坛重复问题检测
  4. 学术文献的相似性分析

二、网络架构设计

1. 基础双塔结构

  1. class SiameseNetwork(tf.keras.Model):
  2. def __init__(self, embedding_dim=128):
  3. super(SiameseNetwork, self).__init__()
  4. self.embedding = tf.keras.layers.Embedding(vocab_size, 300)
  5. self.lstm = tf.keras.layers.LSTM(embedding_dim)
  6. def call(self, inputs):
  7. # inputs为形状(2, batch_size, seq_len)的张量
  8. # 分别处理两个句子
  9. sent1 = self.lstm(self.embedding(inputs[0]))
  10. sent2 = self.lstm(self.embedding(inputs[1]))
  11. return sent1, sent2

双塔结构包含:

  • 共享的词嵌入层(预训练或随机初始化)
  • 共享的编码器(LSTM/GRU/Transformer)
  • 独立的句子表示输出

2. 编码器选择对比

编码器类型 优点 缺点 适用场景
LSTM 捕捉长程依赖 训练速度慢 序列数据
CNN 并行计算高效 局部特征捕捉 短文本
Transformer 长距离建模强 计算量大 复杂语义

建议:中等长度文本优先选择BiLSTM,超长文本考虑Transformer的变体。

3. 相似度计算方法

常见距离度量:

  • 欧氏距离:distance = tf.norm(sent1 - sent2, axis=1)
  • 余弦相似度:similarity = tf.reduce_sum(sent1*sent2, axis=1)
  • 曼哈顿距离:distance = tf.reduce_sum(tf.abs(sent1 - sent2), axis=1)

实验表明,余弦相似度在文本场景中表现更稳定,因其关注方向而非绝对距离。

三、损失函数设计

1. 对比损失(Contrastive Loss)

  1. def contrastive_loss(y_true, y_pred, margin=1.0):
  2. # y_true: 标签(1=相似,0=不相似)
  3. # y_pred: 预测距离
  4. square_pred = tf.square(y_pred)
  5. margin_square = tf.square(tf.maximum(margin - y_pred, 0.0))
  6. return tf.reduce_mean(y_true * square_pred +
  7. (1 - y_true) * margin_square)

关键参数:

  • margin:决定不相似样本的分离阈值,典型值0.8-1.2
  • 优势:直接优化距离度量,收敛速度快

2. 三元组损失(Triplet Loss)

  1. def triplet_loss(anchor, positive, negative, margin=1.0):
  2. pos_dist = tf.reduce_sum(tf.square(anchor - positive), axis=1)
  3. neg_dist = tf.reduce_sum(tf.square(anchor - negative), axis=1)
  4. basic_loss = pos_dist - neg_dist + margin
  5. return tf.reduce_mean(tf.maximum(basic_loss, 0.0))

采样策略要点:

  • Hard Negative Mining:选择最难区分的负样本
  • Semi-Hard采样:避免过难的负样本导致梯度消失
  • 批处理时保证每个anchor有足够正负样本

3. 损失函数选择建议

  • 数据量小:对比损失(收敛快)
  • 类别不平衡:改进的三元组损失(加权版本)
  • 复杂语义:结合交叉熵的混合损失

四、实现步骤与优化技巧

1. 数据准备要点

  • 负样本构造:
    • 同主题不同问法
    • 不同主题相似表述
    • 随机采样(比例不超过30%)
  • 数据增强:
    • 同义词替换(使用WordNet或预训练词向量)
    • 回译生成(英译中再译回)
    • 句子结构变换(主动被动转换)

2. 训练优化实践

  1. # 典型训练配置
  2. model.compile(
  3. optimizer=tf.keras.optimizers.Adam(0.001),
  4. loss=contrastive_loss,
  5. metrics=['accuracy']
  6. )
  7. # 回调函数配置
  8. callbacks = [
  9. tf.keras.callbacks.EarlyStopping(patience=5),
  10. tf.keras.callbacks.ReduceLROnPlateau(factor=0.5, patience=3),
  11. tf.keras.callbacks.ModelCheckpoint('best_model.h5')
  12. ]

关键参数设置:

  • 批量大小:64-256(根据GPU内存调整)
  • 学习率:初始0.001,衰减策略采用余弦退火
  • 训练轮次:50-100轮,监控验证集损失

3. 部署优化方向

  • 模型压缩:
    • 知识蒸馏(用大模型指导小模型)
    • 量化处理(FP16或INT8)
  • 加速策略:
    • ONNX运行时转换
    • TensorRT加速
  • 服务化部署:
    • REST API封装
    • 批处理请求优化

五、性能评估与改进

1. 评估指标体系

指标类型 计算方法 适用场景
准确率 (TP+TN)/总样本 类别平衡时
F1值 2(PR)/(P+R) 类别不平衡
AUC-ROC ROC曲线面积 排序任务
平均精度 AP@K 检索场景

2. 常见问题诊断

  • 过拟合
    • 现象:训练集准确率>95%,验证集<70%
    • 解决方案:增加Dropout层(率0.3-0.5),使用L2正则化
  • 欠拟合
    • 现象:训练验证准确率均低
    • 解决方案:增加模型容量,减少正则化
  • 长尾问题
    • 现象:少数类准确率极低
    • 解决方案:重采样或损失加权

3. 最新改进方向

  • 预训练模型融合:
    1. # 使用BERT生成句子嵌入
    2. bert_model = TFBertModel.from_pretrained('bert-base-chinese')
    3. def bert_encoder(inputs):
    4. outputs = bert_model(inputs)
    5. return outputs.last_hidden_state[:,0,:] # 取[CLS]标记
  • 多模态扩展:
    • 结合语音特征进行跨模态相似度计算
    • 使用视觉信息辅助文本理解
  • 动态权重调整:
    • 根据句子长度动态调整注意力权重
    • 引入领域知识图谱增强语义表示

六、完整代码示例

  1. import tensorflow as tf
  2. from tensorflow.keras.layers import Input, LSTM, Dense, Lambda
  3. from tensorflow.keras.models import Model
  4. from tensorflow.keras import backend as K
  5. def euclidean_distance(vects):
  6. x, y = vects
  7. sum_square = K.sum(K.square(x - y), axis=1, keepdims=True)
  8. return K.sqrt(K.maximum(sum_square, K.epsilon()))
  9. def eucl_dist_output_shape(shapes):
  10. shape1, _ = shapes
  11. return (shape1[0], 1)
  12. def contrastive_loss(y_true, y_pred):
  13. margin = 1
  14. square_pred = K.square(y_pred)
  15. margin_square = K.square(K.maximum(margin - y_pred, 0))
  16. return K.mean(y_true * square_pred + (1 - y_true) * margin_square)
  17. # 定义输入层
  18. input_a = Input(shape=(None,))
  19. input_b = Input(shape=(None,))
  20. # 共享权重层
  21. embedding = tf.keras.layers.Embedding(10000, 128)
  22. encoded_a = LSTM(64)(embedding(input_a))
  23. encoded_b = LSTM(64)(embedding(input_b))
  24. # 距离计算
  25. distance = Lambda(euclidean_distance,
  26. output_shape=eucl_dist_output_shape)([encoded_a, encoded_b])
  27. # 模型构建
  28. model = Model([input_a, input_b], distance)
  29. model.compile(loss=contrastive_loss, optimizer='adam')
  30. # 示例训练
  31. # 假设x_train_a, x_train_b是句子ID序列,y_train是标签
  32. # model.fit([x_train_a, x_train_b], y_train,
  33. # batch_size=128, epochs=50)

七、总结与展望

Siamese Network为文本相似性计算提供了高效的解决方案,其核心价值在于:

  1. 参数共享机制降低计算成本
  2. 距离度量直观可解释
  3. 易于集成预训练模型

未来发展趋势包括:

  • 与图神经网络结合捕捉结构信息
  • 动态注意力机制改进长文本处理
  • 轻量化设计适配边缘计算场景

实际应用中,建议根据具体场景选择合适的编码器和损失函数,并通过持续的数据迭代优化模型性能。对于资源有限的团队,可考虑使用行业常见技术方案提供的预训练模型作为基础,进行微调适配特定业务需求。