中文问题相似度挑战赛版本2
admin
2024-05-19 00:00:54
0

都将两个版本做对比,构建自己的相似度打分,

版本一:特征工程(中文特征)+集成模型

版本二:预训练模型。

后续改进

模型融合

赛题介绍

赛题背景

问答系统中包括三个主要的部分:问题理解、信息检索和答案抽取。
而问题理解是问答系统的第一部分也是非常关键的一部分。
问题理解有着非常广泛的应用。,如重复评论识别、相似问题识别等。
重复问题检测是一个常见的文本挖掘任务,在很多实际问题社区都有相似的应用。
重复问题检测可以方便地进行问题的答案聚合,以及问题的答案推荐,自动QA等。
由于中文词语的多样性和灵活性,本赛题需要选手构建一个重复问题识别算法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jWsTogz4-1674799039850)(attachment:image.png)]

赛事任务

本次赛题希望参赛选手对两个问题完成相似度打分
训练集:约5千条问题对和标签。若两个问题是相同的问题,标签为1;否则为0。

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

数据说明

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

  • eg:世界上什么东西最恐怖 世界上最恐怖的东西是什么? 1

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

评估指标

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oYrjZnCT-1674799039852)(attachment:image.png)]

ERNIE-Gram介绍

  • ERNIE模型是多层transformer结构的堆叠,transformer首先对输入的token进行位置编码。
  • 经过注意力机制和残差连接进行归一化,在经过多层的前馈神经网络得到最终的embedding.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xxNDcSEi-1674799039852)(attachment:image.png)]

ERNIE是对BERT的改进模型,ERNIE相比BERT模型最大的区别在于BERT是基于Token做的掩码,而ERNIE是基于词做的掩码,相当于提供了更多的先验知识,做了知识增强

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4vkSRVH7-1674799039853)(attachment:image.png)]

导入库

import time
import os
import numpy as np
import paddle
import paddle.nn.functional as F
from paddlenlp.datasets import load_dataset
from paddlenlp.datasets import MapDataset
import paddlenlp
import pandas as pd
from paddle.io import Dataset
from sklearn.model_selection import train_test_split

定义数据类

由于训练数据较大,通常会将数据分成较小的batch喂给神经网络学习。paddle对应的API分别是
paddle.io.Dataset与paddle.io.DataLoaderpaddle.io.Dataset与paddle.io.DataLoaderpaddle.io.Dataset与paddle.io.DataLoader。

构建自定义数据类步骤

定义类函数: 继承:paddle.io.Dataset类

实现__init__函数

实现__getitem__方法

实现__len__方法

对于自定义的数据集,可以在数据集的构造函数中进行数据增强的方法和定义,之后对 getitem 中返回的数据进行应用,就可以完成自定义数据增强。

构造数据类,配合后面的DataLoader使用

class MyDataset(Dataset):def __init__(self,data,mode = 'train'):super(MyDataset,self).__init__()self.mode = modeself.data = self._load_data(data)#改造数据集,将dataframe数据集改造成格式数据转换为[{},{}]形式数据def _load_data(self,data):data_set = []#返回所有行索引以及所有行的内容for index,row in data.iterrows():data_dict = {}data_dict['query'] = row['query']data_dict['title'] = row['title']if self.mode == 'train':data_dict['label'] = row['label']data_set.append(data_dict)return data_setdef __getitem__(self,idx):return self.data[idx]def __len__(self):return len(self.data)

数据导入和处理

train = pd.read_csv('/home/aistudio/data/data102337/train.csv',sep='\t', names=['query', 'title','label'])
x_train,x_dev = train_test_split(train,test_size=0.1, stratify=train['label'].iloc[:])
x_train.shape
train_ds = MapDataset(MyDataset(x_train)) #定义MapDataset数据集
dev_ds = MapDataset(MyDataset(x_dev))

输出训练集的前3条样本

for idx,example in enumerate(train_ds):if idx <= 3:print(example)

将 1 条明文数据的 query、title 拼接起来,根据预训练模型的 tokenizer 将明文转换为 ID 数据

返回 input_ids 和 token_type_ids

def convert_example(example,tokenizer,max_seq_length = 512,is_test = False):query,title = example['query'],example['title']encoded_inputs = tokenizer(text = query,text_pair = title,max_seq_length = max_seq_length)input_ids = encoded_inputs("input_ids")token_type_ids = encoded_inputs["token_type_ids"]if not is_test:label = np.array([example["label"], dtype="int64"])return input_ids,token_type_ids,label#在预测或评估阶段,不返回label字段else:return input_ids,token_type_ids

为了后续方便使用,我们使用python函数,partial)给 convert_example 赋予一些默认参数

from functools import partial #传参#训练集和验证集的样本转换函数,后续直接使用map生成每一行的input_id和token_type_id
trans_func = partial(convert_example,tokenizer = tokenzier,max_seq_length = 512
)
我们的训练数据会返回input_ids,token_type_ids,labels三个字段,对数据pad组合
# 需要针对这三个字段分别定义三组batch操作
from paddlenlp.data import Stack, Pad, Tuple
batchify_fn = lambda samples, fn=Tuple(Pad(axis=0, pad_val=tokenizer.pad_token_id),  # input_idsPad(axis=0, pad_val=tokenizer.pad_token_type_id),  # token_type_idsStack(dtype="int64")  # label
): [data for data in fn(samples)]

定义分布式的sampler,自动对训练数据进行切分,支持多卡并行训练

batch_sampler = paddle.io.DistributedBatchSampler(train_ds, batch_size=32, shuffle=True)# 基于train_ds定义train_data_loader
# 因为我们使用了分布式的 DistributedBatchSampler, train_data_loader 会自动对训练数据进行切分
train_data_loader = paddle.io.DataLoader(dataset = train_ds.map(trans_func),batch_sampler = batch_sampler,collate_fn=batchify_fn,return_list = True
)#针对验证数据集加载,我们使用单卡进行评估,所以采用 paddle.io.BatchSampler 即可
# 定义dev_data_loader
batch_sampler = paddle.io.BatchSampler(dev_ds, batch_size=32, shuffle=False)
dev_data_loader = paddle.io.DataLoader(dataset=dev_ds.map(trans_func),batch_sampler=batch_sampler,collate_fn=batchify_fn,return_list=True)

加载预训练模型tokenizer

因为是基于预训练模型 ERNIE-Gram 来进行,所以需要首先加载 ERNIE-Gram 的 tokenizer,其他预训练模型可以参考https://github.com/PaddlePaddle/PaddleNLP/blob/develop/docs/model_zoo/transformers.rst

后续样本转换函数基于 tokenizer 对文本进行切分

tokenizer = paddlenlp.transformers.ErnieGramTokenizer.from_pretrained('ernie-gram-zh')

定义模型类

import paddle.nn as nn# 我们基于 ERNIE-Gram 模型结构搭建 Point-wise 语义匹配网络
# 所以此处先定义 ERNIE-Gram 的 pretrained_model
pretrained_model = paddlenlp.transformers.ErnieGramModel.from_pretrained('ernie-gram-zh')
#pretrained_model = paddlenlp.transformers.ErnieModel.from_pretrained('ernie-1.0')class PointwiseMatching(nn.Layer):# 此处的 pretained_model 在本例中会被 ERNIE-Gram 预训练模型初始化def __init__(self, pretrained_model, dropout=None):super().__init__()self.ptm = pretrained_modelself.dropout = nn.Dropout(dropout if dropout is not None else 0.1)# 语义匹配任务: 相似、不相似 2 分类任务self.classifier = nn.Linear(self.ptm.config["hidden_size"], 2)def forward(self,input_ids,token_type_ids=None,position_ids=None,attention_mask=None):# 此处的 Input_ids 由两条文本的 token ids 拼接而成# token_type_ids 表示两段文本的类型编码# 返回的 cls_embedding 就表示这两段文本经过模型的计算之后而得到的语义表示向量_, cls_embedding = self.ptm(input_ids, token_type_ids, position_ids,attention_mask)cls_embedding = self.dropout(cls_embedding)# 基于文本对的语义表示向量进行 2 分类任务logits = self.classifier(cls_embedding)probs = F.softmax(logits)return probs

定义 Point-wise 语义匹配网络

model = PointwiseMatching(pretrained_model)

定义优化参数与评价函数

from paddlenlp.transformers import LinearDecayWithWarmupepochs = 30
num_training_steps = len(train_data_loader) * epochs# 定义 learning_rate_scheduler,负责在训练过程中对 lr 进行调度
lr_scheduler = LinearDecayWithWarmup(5E-5, num_training_steps, 0.0)# Generate parameter names needed to perform weight decay.
# All bias and LayerNorm parameters are excluded.
decay_params = [p.name for n, p in model.named_parameters()if not any(nd in n for nd in ["bias", "norm"])
]# 定义 Optimizer
optimizer = paddle.optimizer.AdamW(learning_rate=lr_scheduler,parameters=model.parameters(),weight_decay=0.0,apply_decay_param_fun=lambda x: x in decay_params)# 采用交叉熵 损失函数
criterion = paddle.nn.loss.CrossEntropyLoss()# 评估的时候采用准确率指标
metric = paddle.metric.Accuracy()

训练过程中要同时在验证集中进行评估,我们需要同时定义评估函数

# 因为训练过程中同时要在验证集进行模型评估,因此我们先定义评估函数@paddle.no_grad()
def evaluate(model, criterion, metric, data_loader, phase="dev"):model.eval()metric.reset()losses = []for batch in data_loader:input_ids, token_type_ids, labels = batchprobs = model(input_ids=input_ids, token_type_ids=token_type_ids)loss = criterion(probs, labels)losses.append(loss.numpy())correct = metric.compute(probs, labels)metric.update(correct)accu = metric.accumulate()print("eval {} loss: {:.5}, accu: {:.5}".format(phase,np.mean(losses), accu))model.train()metric.reset()

开始正式训练

# 接下来,开始正式训练模型,训练时间较长,可注释掉这部分global_step = 0
tic_train = time.time()for epoch in range(1, epochs + 1):for step, batch in enumerate(train_data_loader, start=1):input_ids, token_type_ids, labels = batchprobs = model(input_ids=input_ids, token_type_ids=token_type_ids)loss = criterion(probs, labels)correct = metric.compute(probs, labels)metric.update(correct)acc = metric.accumulate()global_step += 1# 每间隔 10 step 输出训练指标if global_step % 10 == 0:print("global step %d, epoch: %d, batch: %d, loss: %.5f, accu: %.5f, speed: %.2f step/s"% (global_step, epoch, step, loss, acc,10 / (time.time() - tic_train)))tic_train = time.time()loss.backward()optimizer.step()lr_scheduler.step()optimizer.clear_grad()# 每间隔 100 step 在验证集和测试集上进行评估if global_step % 100 == 0:evaluate(model, criterion, metric, dev_data_loader, "dev")# 训练结束后,存储模型参数
save_dir = os.path.join("checkpoint", "model_%d" % global_step)
os.makedirs(save_dir)save_param_path = os.path.join(save_dir, 'model_state.pdparams')
paddle.save(model.state_dict(), save_param_path)
tokenizer.save_pretrained(save_dir)

定义预测函数

def predict(model,data_loader):batch_probs = []#预测阶段打开eval模式,模型中的dropout操作会关掉model.eval()with paddle.no_grad():for batch_data in data_loader:input_ids,token_type_ids = batch_datainput_ids = paddle.to_tensor(input_ids)token_type_ids = paddle.to_tensor(token_type_ids)#获取每个样本的预测概率,[batch_size, 2] 的矩阵batch_prob = model(input_ids=input_ids, token_type_ids=token_type_ids).numpy()batch_probs.append(batch_prob)batch_probs = np.concatenate(batch_probs, axis=0)return batch_probs
## 定义数据dataloader
# 预测数据的转换函数
# predict 数据没有 label, 因此 convert_exmaple 的 is_test 参数设为 True
trans_func = partial(convert_example,tokenizer=tokenizer,max_seq_length=512,is_test=True)# 预测数据的组 batch 操作
# predict 数据只返回 input_ids 和 token_type_ids,因此只需要 2 个 Pad 对象作为 batchify_fn
batchify_fn = lambda samples, fn=Tuple(Pad(axis=0, pad_val=tokenizer.pad_token_id),  # input_idsPad(axis=0, pad_val=tokenizer.pad_token_type_id),  # segment_ids
): [data for data in fn(samples)]# 加载预测数据
test = pd.read_csv('/home/aistudio/data/data102337/test.csv',sep='\t', names=['query', 'title'])
test_ds = MapDataset(MyDataset(test,mode='test'))batch_sampler = paddle.io.BatchSampler(test_ds, batch_size=32, shuffle=False)# 生成预测数据 data_loader
predict_data_loader =paddle.io.DataLoader(dataset=test_ds.map(trans_func),batch_sampler=batch_sampler,collate_fn=batchify_fn,return_list=True)
save_param_path

保存参数加载模型并预测

#定义模型
pretrained_model = paddlenlp.transformers.ErnieGramModel.from_pretrained('ernie-gram-zh')model = PointwiseMatching(pretrained_model)state_dict = paddle.load(save_param_path)
model.set_dict(state_dict)
# 执行预测函数
y_probs = predict(model, predict_data_loader)# 根据预测概率获取预测 label
y_preds = np.argmax(y_probs, axis=1)
y_preds
# 保存结果文件
res = pd.DataFrame({'label':y_preds})
dle.load(save_param_path)
model.set_dict(state_dict)
# 执行预测函数
y_probs = predict(model, predict_data_loader)# 根据预测概率获取预测 label
y_preds = np.argmax(y_probs, axis=1)
# 保存结果文件
res = pd.DataFrame({'label':y_preds})
res.to_csv('./baseline.csv',index=False)

paddle.io.DistributedBatchSampler

  • 分布式批采样器,加载数据的一个子集,每个进程可以传递给DataLoader一个DistributedBatchSampler的实例
  • 每个进程加载原始数据的一个子集。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ig87Wlac-1674799039854)(attachment:image.png)]

MapDataset函数解析

自定义数据集一般有MapDataset 和 IterDataset 两种数据集。MapDataset一边可以满足绝大部分数据的要求。

后续实战应用的话,将支持飞浆的paddlenlp框架学习一波。

主要精力用在调模型上,其他的东西根据精力进行更新模板都行啦的样子与打算。

将相同比赛的两个版本记录,其他的模型会用下一个版本,构建自己的模型思路。


相关内容

热门资讯

猾开头的成语有哪些 猾开头的成语有哪些没有猾开头的成语,含猾的成语如下:  齿牙为猾 指谗言造成灾祸。  齿牙之猾 ...
2016~2017年度十大最具... 2016~2017年度十大最具科学早教气质的推荐视听节目(排名不分先后)都有哪些?《动物好伙伴》、《...
对于逆反期的孩子应该怎样教育? 对于逆反期的孩子应该怎样教育?孩子到了逆反期感觉非常不听话该怎么办?所谓的逆反也就是叛逆,人生是有3...
火锅蘸料绝密配方 火锅蘸料绝密配方火锅蘸料绝密配方芝麻酱、红油辣酱、生抽王、葱、姜、糖、醋、酱油、花椒粉、味精。吃火锅...
完美国际剑灵和魅灵哪个好?好在... 完美国际剑灵和魅灵哪个好?好在哪?看你个人喜欢,剑灵群怪爽魅灵是护士
泰戈尔的每部诗集比较权威的翻译... 泰戈尔的每部诗集比较权威的翻译是谁呀将诗集一部部列出来 再分别标注每一部最好的翻译者谢谢我也认为吴岩...
假海龟的故事的读后感? 假海龟的故事的读后感?读了这篇文章使我受益匪浅,假装成别人终会有被揭穿的一天。就像故事局启迟中的主人...
双耳蓝牙耳机为什么只能连接一个 双耳蓝牙耳机为什么只能连接一个无线双耳喊燃连接郑岩虚只有一边有声音,需要恢复两边都有声音,那就恢复出...
天源长寿村的传奇故事 天源长寿村的传奇故事 广州市有个长寿村,村里的人平均寿命达到世界先进国家水平,千百年来长寿村的老寿星...
什么是感情? 什么是感情?对于感情而言,就是,当你面对两个人的时候:一个是你不爱的人,但是他有雄厚的经济基础,可以...
《机器之血》这部剧还是很不错的... 《机器之血》这部剧还是很不错的,其中几位主演的演技如何?演员演技表情很不错都挺到位的演技很好,剧情也...
清凉游、非遗赛事……多彩假日解... 央视网消息:天气炎热,很多人选择戏水消解暑意。为了满足游客需求,黑龙江省集贤县将冬季玩雪胜地改造成森...
安全教育的意义是什么? 安全教育的意义是什么?安全教育能够适应复杂的社会治安形势,提高人们的安全意识,从根本上控制和预防安全...
第一次去张家界五日游攻略,张家... 第一天:初探天门奇观 清晨从长沙乘坐高铁抵达张家界西站,一出站就被四周环绕的群山震撼。我提前联系了当...
重庆荔枝打卡地图来了!可去这些... 2025重庆荔枝打卡地图 重庆市气象服务中心供图 在重庆也能享受边摘边吃荔枝的快乐?近日,重庆市气象...
天涯海角石头的来历 天涯海角石头的来历
手机被偷,第一件事情应该干什么... 手机被偷,第一件事情应该干什么?首先打开电脑并登陆手机的账户,对手机实行定位,之后远程打开挂失模式,...
餐厅服务员,遇到退菜该怎么办? 餐厅服务员,遇到退菜该怎么办?餐厅服务员碰到客人退菜,首先要问清楚退菜的原因,自己能解释清楚就自行处...
刘家成委员称,影视剧选角不能唯... 刘家成委员称,影视剧选角不能唯流量论,你觉得演技重要还是流量重要?其实我们都知道很多时候有一些明星是...
作文灰太狼回乡记童话故事 作文灰太狼回乡记童话故事这是我本人原创,而且是第一次创作童话,想必有不妥之处,请君参考! ...