科大讯飞2021中文问题相似度挑战赛暨词向量模型构建

合集:AI案例-NLP-传媒业
赛题:中文问题相似度挑战赛
主办方:科大讯飞xDatawhale
主页:http://challenge.xfyun.cn/topic/info?type=chinese-question-similarity&ch=dw-sq-1
AI问题:语义相似度识别
数据集:约5千条问题对和标签。若两个问题是相同的问题,标签为1,否则为0。
数据集价值:构建一个重复问题识别算法。
解决方案:使用gensim构建词向量模型

一、赛题描述

背景

问答系统中包括三个主要的部分:问题理解,信息检索和答案抽取。而问题理解是问答系统的第一部分也是非常关键的一部分。问题理解有非常广泛的应用,如重复评论识别、相似问题识别等。

重复问题检测是一个常见的文本挖掘任务,在很多实际问答社区都有相应的应用。重复问题检测可以方便进行问题的答案聚合,以及问题答案推荐,自动QA等。由于中文词语的多样性和灵活性,本赛题需要选手构建一个重复问题识别算法。

任务

本次赛题希望参赛选手对两个问题完成相似度打分。

训练集:约5千条问题对和标签。若两个问题是相同的问题,标签为1;否则为0。

测试集:约5千条问题对,需要选手预测标签。

二、数据集描述

数据说明

训练集给定问题对和标签,使用\t进行分隔。测试集给定问题对,使用\t进行分隔。

E.g.:世界上什么东西最恐怖 世界上最恐怖的东西是什么? 1

解析:“世界上什么东西最恐怖”与”世界上最恐怖的东西是什么“问题相同,故是重复问题相似度高,标签为1。否则标签为0。

数据样例:train.csv

"有哪些女明星被潜规则啦    哪些女明星被潜规则了  1"          
"怎么支付宝绑定银行卡? 银行卡怎么绑定支付宝 1"
"请问这部电视剧叫什么名字 请问谁知道这部电视剧叫什么名字 1"
"泰囧完整版下载 エウテルペ完整版下载 0"
"在沧州市区哪家卖的盐焗鸡好吃? 沧州饭店哪家便宜又好吃又实惠 0"
"男生说女生很像他自己什么意思 女生和男生说我想咬你是什么意思 0"
"影音先锋下载怎么那么慢? 影音先锋怎么下载 0"
"胆怯反义词是什么 胆怯的反义词是什么啊 1"
"“必须”和“必需”有什么区别? 必须和必需的区别是什么? 1"
"怎么在手机上找回支付宝支付密码 手机怎么找回支付宝的支付密码 1"

数据集版权许可协议

BY-NC-SA 4.0
https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh-hans

三、解决方案样例

工作原理介绍

中文词向量是通过训练(如 Word2Vec、BERT、FastText 等模型)将词语映射到高维空间中的稠密向量(例如 300 维)。每个向量隐含了词语的 ​语义 和 ​语法 特征,例如:

  • 语义"猫" 和 "狗" 的向量方向接近(同属动物)。
  • 语法"跑步" 和 "游泳" 的向量方向接近(同属动词)。

中文词向量相似度是 将词语语义映射到数学空间的量化表达,核心在于通过向量计算捕捉词语间的语义关联。实际应用中需结合具体任务选择模型、优化分词,并理解其局限性(如多义词、领域偏差)。Word2Vec 是 Google 于 2013 年提出的浅层神经网络词嵌入模型,旨在将文本中的单词映射为稠密向量(词向量),通过向量空间距离捕捉语义和语法关系。

词向量的意义

  • 语义相似性:向量空间中距离相近的词具有相似含义(如 国王 - 男女王 - 女)。
  • 上下文关联:同一上下文中出现的词向量方向相近(如 咖啡 - 因特网茶 - 微信)。
  • 数学运算:向量可进行加减运算推断关系(如 巴黎 - 法国 + 北京 - 中国东京 - 日本)。

运行环境

python3.12.3
sklearn-compat0.1.3
gensim4.3.3
python-Levenshtein0.27.1
lightgbm3.3.0

python-Levenshtein 包的功能是:专注于高效计算字符串之间的 Levenshtein 距离(莱文斯坦编辑距离) 和相似度,底层用 C 语言优化,速度极快。莱文斯坦编辑距离定义为:将一个字符串转换成另一个字符串所需的最少单字符编辑操作次数。适用拼写纠错、模糊搜索、DNA序列比对等需要快速计算字符串差异的场景。

distance 包的功能是:提供多种字符串/序列距离算法的通用库,包括但不限于 Levenshtein 距离。

导入系统库

import pandas as pd
import distance  
import Levenshtein
from tqdm import tqdm
import numpy as np
import pandas as pd
import os
import jieba
from gensim.models import word2vec
import lightgbm as lgb
from sklearn.model_selection import KFold
from sklearn.metrics import accuracy_score
from scipy.spatial.distance import cosine, cityblock, canberra, euclidean, minkowski, braycurtis, \
   correlation, chebyshev, jensenshannon, mahalanobis, seuclidean, sqeuclidean    

处理流程

  1. 数据加载:读取训练集(train.csv)和测试集(test.csv)
  2. 数据合并:将训练集和测试集合并进行统一特征工程处理
  3. 特征工程:构建丰富的文本相似度特征
  4. 模型训练:使用LightGBM进行5折交叉验证
  5. 预测输出:生成测试集的预测结果

1、加载数据集

读取训练集(train.csv)和测试集(test.csv)

train = pd.read_csv('data/train.csv', sep='\t', header=None)
train.columns = ['q1','q2','label']

test = pd.read_csv('data/test.csv', sep='\t', header=None)
test.columns = ['q1','q2']
test['label'] = 1

sample_submit = pd.read_csv('data/sample_submit.csv')
sample_submit['q1'] = test['q1']
sample_submit['q2'] = test['q2']

2、数据合并

将训练集和测试集合并进行统一特征工程处理。

3、特征工程

构建丰富的文本相似度特征。

基础特征

  • 文本长度特征:每个问题的字符长度(q1_len, q2_len)
  • 长度差异特征:长度差、绝对差、比例(q1q2_len_diff, q1q2_len_diff_abs, q1q2_rate)
  • 特殊符号特征:是否以问号结尾(q1_end_special)

共现字特征

  • 共现字符数:两个问题共有的不同字符数量(comm_q1q2char_nums)
  • 字符位置匹配:前8个字符在另一个问题中的匹配位置(q1_pos_1到q1_pos_8)

距离特征:使用多种文本距离算法计算相似度:

  • Jaccard距离
  • Sorensen距离
  • Levenshtein编辑距离
  • Levenshtein比率

词向量特征

  1. 分词处理:使用jieba进行中文分词
  2. Word2Vec训练:基于所有问题文本训练词向量模型
  3. 向量相似度计算:
    • 计算12种不同的向量距离(余弦、欧式、曼哈顿等)
    • 对每个问题的词向量取平均得到句向量
  4. 句向量存储:为每个问题存储100维的归一化句向量
# 共现字位置
def char_match_pos(q1, q2, pos_i):
   q1 = list(q1)
   q2 = list(q2)

   if pos_i < len(q1):
       q2_len = min(len(q2), 25)  # q2_len只匹配前25个字
       for pos_j in range(q2_len):
           if q1[pos_i] == q2[pos_j]:
               q_pos = pos_j + 1  # 如果匹配上了,记录匹配的位置
               break
           elif pos_j == q2_len - 1:
               q_pos = 0  # 如果没有匹配上,赋值为0
   else:
       q_pos = -1  # 如果后续长度不存在,赋值为-1

   return q_pos

for pos_i in range(8):
   data['q1_pos_' + str(pos_i + 1)] = data.apply(
       lambda row: char_match_pos(row['q1'], row['q2'], pos_i), axis=1).astype(np.int8)
# 词向量的相似度特征
data['vec_cosine'] = data.progress_apply(lambda index: get_w2v(index['q1_words_list'], index['q2_words_list'], 1),
                                        axis=1)
data['vec_canberra'] = data.progress_apply(
   lambda index: get_w2v(index['q1_words_list'], index['q2_words_list'], 2), axis=1)
data['vec_cityblock'] = data.progress_apply(
   lambda index: get_w2v(index['q1_words_list'], index['q2_words_list'], 3), axis=1)
data['vec_euclidean'] = data.progress_apply(
   lambda index: get_w2v(index['q1_words_list'], index['q2_words_list'], 4), axis=1)
data['vec_braycurtis'] = data.progress_apply(
   lambda index: get_w2v(index['q1_words_list'], index['q2_words_list'], 5), axis=1)
data['vec_minkowski'] = data.progress_apply(
   lambda index: get_w2v(index['q1_words_list'], index['q2_words_list'], 6), axis=1)
data['vec_correlation'] = data.progress_apply(
   lambda index: get_w2v(index['q1_words_list'], index['q2_words_list'], 7), axis=1)

data['vec_chebyshev'] = data.progress_apply(
   lambda index: get_w2v(index['q1_words_list'], index['q2_words_list'], 8), axis=1)
data['vec_jensenshannon'] = data.progress_apply(
   lambda index: get_w2v(index['q1_words_list'], index['q2_words_list'], 9), axis=1)
data['vec_mahalanobis'] = data.progress_apply(
   lambda index: get_w2v(index['q1_words_list'], index['q2_words_list'], 10), axis=1)
data['vec_seuclidean'] = data.progress_apply(
   lambda index: get_w2v(index['q1_words_list'], index['q2_words_list'], 11), axis=1)
data['vec_sqeuclidean'] = data.progress_apply(
   lambda index: get_w2v(index['q1_words_list'], index['q2_words_list'], 12), axis=1)

4、模型训练

使用LightGBM进行5折交叉验证。

  • 模型选择:LightGBM梯度提升决策树
  • 参数设置:
    • 控制模型复杂度的参数:num_leaves=5, max_depth=6
    • 正则化参数:lambda_l1=1, lambda_l2=0.001
    • 采样参数:feature_fraction=0.9, bagging_fraction=0.95
    • 学习率:0.1
  • 训练方式:5折交叉验证
  • 评估指标:二分类对数损失(binary_logloss)
no_feas = ['q1','q2','label','q1_words_list','q2_words_list']
features = [col for col in data.columns if col not in no_feas]

train, test = data[:train_size], data[train_size:]
len(features)

X = train[features]  # 训练集输入
y = train['label']  # 训练集标签
X_test = test[features]  # 测试集输入

n_fold = 5
folds = KFold(n_splits=n_fold, shuffle=True, random_state=1314)

params = {
   'boosting_type': 'gbdt',  # 决定集成学习的方式,gbdt: 梯度提升决策树
   'objective': 'binary',  # 定义优化目标(损失函数),binary: 二分类任务,使用对数损失函数 (Log Loss)
   'num_leaves': 5,  # 每棵树的叶子节点数量,叶子节点越少,模型越简单(偏向欠拟合);叶子节点越多,模型越复杂(容易过拟合)
   'max_depth': 6,  # 单棵树的最大深度,树越深,模型越复杂(容易过拟合);树越浅,模型越简单(偏向欠拟合
   'min_data_in_leaf': 450,  # 确保每个叶子节点至少包含指定数量的样本
   'learning_rate': 0.1,  # 学习率,控制每一步迭代的权重更新幅度
   'feature_fraction': 0.9,  # 每次迭代仅使用部分特征进行训练
   'bagging_fraction': 0.95,  # 每次迭代仅使用部分样本进行训练
   'bagging_freq': 5,  # 每隔多少棵树应用一次 Bagging(特征/样本采样)
   'lambda_l1': 1,  # •L1 正则化 (lambda_l1): 产生稀疏权重,适合特征选择
   'lambda_l2': 0.001,  # •L2 正则化 (lambda_l2): 防止权重过大,提升泛化性
   'min_gain_to_split': 0.2,  # 分裂节点所需的最小增益阈值
}

oof = np.zeros(len(X))
prediction = np.zeros(len(X_test))

for fold_n, (train_index, valid_index) in enumerate(folds.split(X)):
   X_train, X_valid = X[features].iloc[train_index], X[features].iloc[valid_index]
   y_train, y_valid = y[train_index], y[valid_index]
   # model = lgb.LGBMRegressor(**params, n_estimators=50000, n_jobs=-1)
   model = lgb.LGBMRegressor(**params, n_estimators=50000, n_jobs=-1, verbose=50, early_stopping_rounds=200)  # 移到这里
   # 在较新版本的 LightGBM 中,verbose 参数已经被移动到构造函数 (LGBMRegressor 初始化时) 而不是 fit 方法中。
   model.fit(X_train, y_train,
             eval_set=[(X_train, y_train), (X_valid, y_valid)],
             eval_metric='binary_logloss')
           #   verbose=50,
           #   early_stopping_rounds=200)
   y_pred_valid = model.predict(X_valid)
   y_pred = model.predict(X_test, num_iteration=model.best_iteration_)
   oof[valid_index] = y_pred_valid.reshape(-1,)
   prediction += y_pred

prediction /= n_fold

5、预测输出

生成测试集的预测结果。

#  from sklearn.metrics import accuracy_score
y_pred = (oof > 0.5)
score = accuracy_score(y_pred ,train['label'].values)
score

sub_pred = (prediction > 0.5).astype(int)
sample_submit['label'] = sub_pred

sample_submit.to_csv('lgb_submit.csv',index=None)
sample_submit['label'].value_counts()

运行结果

输出:预测准确率为0.84。

输出文件:lgb_submit.csv。数据样例如下。其中label为1代表相似、0代表不相似。

labelq1q2
1玩梦幻西游能赚钱吗梦幻西游2不花钱能玩吗
1夏天去什么地方旅游好夏季去什么地方旅游最好(国内的地方)
0为什么梦幻西游网站打不开为什么下载梦幻西游游戏补丁网页打不开
0这对双胞胎像不像这样的可爱卡通图片要男的谢了
1免费网络游戏都有哪些?有哪些免费的网络游戏
1什么牌子的沐浴露比较香什么牌子沐浴露比较润
0您好,这边是查询不到的,麻烦您联系银行核实一下的,辛苦您了如果您无法通过网银自助查询,建议您联系银行为您核实查询一下。
1英雄联盟的游戏名仿英雄联盟的游戏
1您好,手机端操作的呢,还是电脑上操作的呢亲,您是手机端操作的还是电脑端操作的呢?
0房地产咨询公司面试求知名房地产咨询公司
1挂学籍是什么意思学籍是什么意思啊
。。。

源码开源协议

GPL-v3

四、获取案例套装

需要登录后才允许下载文件包。登录

发表评论