当前位置:   article > 正文

基于PaddleNLP的中文对话文本匹配_基于emdeding的文本匹配

基于emdeding的文本匹配

★★★ 本文源自AI Studio社区精品项目,【点击此处】查看更多精品内容 >>>


基于PaddleNLP的中文对话文本匹配

一、赛题解析

1.1 赛题背景

文本匹配任务在自然语言处理中是非常重要的基础任务之一,在问答系统、智能对话等诸多应用场景起到关键性的作用,但中文对话中的文本匹配仍然存在很多难点

1.2 赛题任务

根据问题识别出正确的待匹配文本,给定两个问题Q,判定该问题对语义是否匹配。

1.3 评审规则

赛题数据由训练集和测试集组成,训练集数据集读取代码:

import pandas as pd 
pd.read_csv('train.csv',sep='\t')
  • 1
  • 2

1.3.1 评估指标

本次竞赛的评价标准采用准确率指标,最高分为1。

计算方法参考https://scikit-learn.org/stable/modules/generated/sklearn.metrics.accuracy_score.html

评估代码参考:

from sklearn.metrics import accuracy_score
y_pred = [0, 2, 1, 3]
y_true = [0, 1, 2, 3]
accuracy_score(y_true, y_pred)
  • 1
  • 2
  • 3
  • 4

1.3.2 作品提交要求

文件格式:预测结果文件按照csv格式提交

文件大小:无要求

提交次数限制:每支队伍每天最多3次

预测结果文件详细说明:

  1. 以csv格式提交,编码为UTF-8,第一行为表头;

  2. 标签顺序需要与测试集文本保持一致;

  3. 提交前请确保预测结果的格式与sample_submit.csv中的格式一致。具体格式如下:


0
0
0
  • 1
  • 2
  • 3
  • 4

1.4 比赛传送门

比赛传送门

二、Baseline思路

赛题是一个典型的文本匹配的任务,因此基于 ERNIE-Gram 模型搭建匹配网络,然后快速进行语义匹配模型的训练、评估和预测。
流程大致如下:

  • 对数据进行分析
  • 通过同义词和上下文替换的方式进行数据增强
  • 将输入的两个文本拼接为一个序列
  • 经过多层 transformer 模块编码后,将输出层的字向量取平均位置的特征作为句向量
  • 经 softmax 完整最终分类

2.1 模型介绍

在项目效果上,ERNIE模型 对比Bert 等模型有更好的效果

ERNIE 多粒度预训练语义理解技术
作为自然语言处理的基本语义单元,更充分的语言粒度学习能帮助模型实现更强的语义理解能力:

  • ERNIE-Gram 提出显式完备的 n-gram 多粒度掩码语言模型,同步建模 n-gram 内部和 n-gram 之间的语义关系,实现同时学习**细粒度(fine-grained)和粗粒度(coarse-grained)**语义信息
  • ERNIE-Gram 采用双流结构,在预训练过程中实现了单一位置多语义粒度层次预测,进一步增强了语义知识学习

三、基于PaddleNLP 构造基线

3.1 数据分析

3.1.1 数据读取

#解压数据文件
!unzip  -d  /home/aistudio/data/data177274/data /home/aistudio/data/data177274/中文对话文本匹配挑战赛数据集.zip  
  • 1
  • 2
Archive:  /home/aistudio/data/data177274/中文对话文本匹配挑战赛数据集.zip
   creating: /home/aistudio/data/data177274/data/╓╨╬─╢╘╗░╬─▒╛╞е┼ф╠Ї╒╜╚№╩¤╛▌╝п/
  inflating: /home/aistudio/data/data177274/data/╓╨╬─╢╘╗░╬─▒╛╞е┼ф╠Ї╒╜╚№╩¤╛▌╝п/sample_submit.csv  
  inflating: /home/aistudio/data/data177274/data/╓╨╬─╢╘╗░╬─▒╛╞е┼ф╠Ї╒╜╚№╩¤╛▌╝п/test.csv  
  inflating: /home/aistudio/data/data177274/data/╓╨╬─╢╘╗░╬─▒╛╞е┼ф╠Ї╒╜╚№╩¤╛▌╝п/train.csv  
  • 1
  • 2
  • 3
  • 4
  • 5

3.1.2 安装需要的库

# 安装paddlenlp 相关框架,且需先更新pip组件
!pip install --upgrade pip
!pip install paddlenlp==2.4.2 -i https://pypi.tuna.tsinghua.edu.cn/simple
!python -m pip install --upgrade matplotlib

from IPython.display  import clear_output
clear_output()
print("环境安装成功!请重启内核!!")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
环境安装成功!请重启内核!!
  • 1

3.1.3 查看样例数据

#查看样例数据
#查看训练数据
import pandas as pd
path='/home/aistudio/data/data177274/data/╓╨╬─╢╘╗░╬─▒╛╞е┼ф╠Ї╒╜╚№╩¤╛▌╝п/train.csv'

data=pd.read_csv(path,encoding='utf-8-sig',sep='\t',header=None,names=['text1', 'text2','label'])

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
data
  • 1
text1text2label
0藏獒为什么这么贵藏獒见人不咬为什么0
1人生应该怎么才算精彩?人生要怎么过才算精彩啊1
2为什么打牌老是输为什么我枪神纪进不去了0
3现在网上卖什么最赚钱网上卖什么最赚钱1
4如何提高气质怎样提高自身气质?1
............
49995剑灵拳师破力士不屈剑灵力士能否当T0
49996冰雪奇缘在哪看冰雪奇缘在哪里看1
49997高分子材料与材料科学工程那个专业出路好高分子材料科学与工程专业考研0
49998李世民的年号是什么?李世民是什么王?0
49999什么软件可以挂机赚钱的什么挂机软件赚钱1

50000 rows × 3 columns

3.1.4 计算最大文本长度

#计算最大的文本长度
def max_text_len(self):
    for i, col in enumerate(self):
        data[col] = data[col].astype(str)
        n_max = data[col].str.len().max()
        print(i, col, n_max)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
#查看最大的内容和文本长度
print(max_text_len(data.columns.tolist()))
  • 1
  • 2
0 text1 49
1 text2 131
2 label 1
None
  • 1
  • 2
  • 3
  • 4

3.1.5 标签分布探索

data['label'].value_counts(normalize=True)
  • 1
1    0.58078
0    0.41922
Name: label, dtype: float64
  • 1
  • 2
  • 3
#可视化label 为1 和0 的值的分布情况
import matplotlib.pyplot as plt
import numpy as np

label1= data.loc[data['label']=='1']
label0= data.loc[data['label']=='0']
x = np.array(["label-1", "label-0"])
y = np.array([len(label1), len(label0)])
plt.bar(x, y,  color = ["blue","red"],width = 0.3)
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在这里插入图片描述

能够看到在标签分布中58% 都是为1 的标签,42% 的标签为0

3.1.6 数据增强

#计算标签数据间的差额
label_dif=len(label1)-len(label0)
print(label_dif)
  • 1
  • 2
  • 3
8078
  • 1
#数据增强
#随机抽取label为0的数据,进行数据增强
import sklearn
label0_df=sklearn.utils.shuffle(label0) #随机打乱
label0_data = label0_df.sample(n=label_dif, random_state=0, axis=0)
label0_data.head(5)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
text1text2label
19936时空猎人27w战斗力花多少钱时空猎人战斗力最高是多少0
3646欧洲移民哪个国家好欧洲哪个国家好移民0
34892去腿毛的好方法怎样去黑头?鼻贴用了没效果啊。0
14249许地山以?么作自己的笔名用别人的名字买车0
29883你觉得我发的这个动漫头像好看吗?这两个头像分别是哪部动漫里面的?好看吗?0
# 将抽取的数据以文件的方式保存
label0_data.to_csv('label0_data.csv',  index=False,encoding='utf-8',sep ='|',header =['text1', 'text2','label'])
  • 1
  • 2
#构造文件读取函数
def read_file(filename):
    lines = []
    with open(filename, 'r', encoding='utf-8') as f:
        next(f)
        for line in f:
            lines.append({"text1":line.split("|")[0].strip(),"text2":line.split("|")[1].strip()})
    return lines
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
data_list=read_file('label0_data.csv')
  • 1
#通过同义词和上下文替换的方式来进行数据增强
from paddlenlp.dataaug import WordSubstitute
aug = WordSubstitute(['synonym','mlm'], create_n=1, aug_n=1)
Replenish_data=pd.DataFrame()

for line in data_list:
    text1=aug.augment(line['text1'])
    text2=aug.augment(line['text2'])
    result={"text1":text1,"text2":text2}
    if len(text1)!=0 and len(text2)!=0:
        text1=text1[0]
        text2=text2[0]
    #对于增强失败的数据,直接调换text1和text2的位置
    elif len(text1)!=0 and len(text2)==0:
        text1=text1[0]
        text2=line['text1']
    elif len(text1)!=0 and len(text2)==0:
        text2=text2[0]
        text1=line['text2']
    else:
        text1=line['text2']
        text2=line['text1']
    
    result={"text1":text1,"text2":text2}
    Replenish_data=Replenish_data.append(result,ignore_index=True)

 
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
#查看数据增强后的数据
Replenish_data
  • 1
  • 2
text1text2
0日子猎人27w战斗力花多少钱时空猎人27w战斗力花多少钱
1欧洲哪个国家好移民欧洲移民哪个国家好
2怎样去黑头?鼻贴用了没效果啊。去腿毛的好方法
3用别人的名字买车许地山以?么作自己的笔名
4你觉得我发的这个动漫半身像好看吗?这两块头像分别是哪部动漫里面的?好看吗?
.........
8073牛的母爱读书答案牛的母爱阅读答案
8074吃湿核桃藉多了会发胖么?请问吃核桃能润肠通便吗?天长日久吃它会发胖吗?
8075昵图网如何查素材被谁下载了求怎样用手机看种子
8076花灰色裤子配什么颜色的袄?土灰色的裤子配什么颜色的短装呀?
8077女孩子说我宣你是什么意思?女孩子说我是你的汤是什么意思

8078 rows × 2 columns

#进行数据集合并
result = pd.merge(data, Replenish_data, how='outer', on=['text1', 'text2'])
result.fillna('0',inplace=True)
  • 1
  • 2
  • 3
result.loc[(result['label']!='1') &  (result['label']!='0')]


  • 1
  • 2
  • 3
text1text2label

3.2 构造训练集和测试集

# 构造训练集和测试集
import sklearn
df=sklearn.utils.shuffle(result) #随机打乱
train_data = df.sample(frac=0.8, random_state=0, axis=0)
dev_data = df[~df.index.isin(train_data.index)]
  • 1
  • 2
  • 3
  • 4
  • 5
# input_ids:字的编码
# token_type_ids:标识是第一个句子还是第二个句子
# attention_mask:标识是不是填充
  • 1
  • 2
  • 3
#生成训练集和测试集的数据文件
train_data.to_csv('train_data.csv',  index=False,encoding='utf-8',sep ='♬',header =['text1', 'text2','label'])
dev_data.to_csv('dev_data.csv',  index=False,encoding='utf-8',sep ='♬',header =['text1', 'text2','label'])
  • 1
  • 2
  • 3
from paddlenlp.datasets import load_dataset

def read(data_path):
    with open(data_path, 'r', encoding='utf-8') as f:
    # 跳过列名
        next(f)
        for line in f:
            text1, text2,label = line.strip('\t').split("♬")[0].strip(),line.strip('\t').split("♬")[1].strip(),line.strip('\t').split("♬")[2].strip()
            text1 = line.strip('\t').split("♬")[0].strip()
            text2 = line.strip('\t').split("♬")[1].strip()
            label=line.strip('\t').split("♬")[2].strip()
            yield ({'text1': text1, 'text2': text2,'label':label })


# data_path为read()方法的参数
train_dataset  = load_dataset(read, data_path='train_data.csv',lazy=False, split="train")
dev_dataset  = load_dataset(read, data_path='dev_data.csv',lazy=False, split="dev")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
#查看训练集和测试集的划分情况
print(len(train_dataset))
print(len(dev_dataset))
  • 1
  • 2
  • 3
46462
11616
  • 1
  • 2
# 输出训练集的前 3 条样本
for idx, example in enumerate(train_dataset):
    if idx <= 3:
        print(example)
  • 1
  • 2
  • 3
  • 4
{'text1': '孕妇可以吃草莓吗?', 'text2': '孕妇能吃草莓么', 'label': '1'}
{'text1': '这句日语是什么意思啊?', 'text2': '日语这句话什么意思', 'label': '1'}
{'text1': '求杉杉来吃', 'text2': '武汉话吃饼子?', 'label': '0'}
{'text1': '有哪些好听的经典钢琴曲', 'text2': '好听的伤感钢琴曲有哪些', 'label': '1'}
  • 1
  • 2
  • 3
  • 4

3.3 加载预训练模型

# 模型加载
import paddlenlp
MODEL_NAME = "ernie-gram-zh"
tokenizer = paddlenlp.transformers.ErnieGramTokenizer.from_pretrained(MODEL_NAME)
  • 1
  • 2
  • 3
  • 4
[2022-12-29 00:51:20,152] [    INFO] - Already cached /home/aistudio/.paddlenlp/models/ernie-gram-zh/vocab.txt
[2022-12-29 00:51:20,165] [    INFO] - tokenizer config file saved in /home/aistudio/.paddlenlp/models/ernie-gram-zh/tokenizer_config.json
[2022-12-29 00:51:20,167] [    INFO] - Special tokens file saved in /home/aistudio/.paddlenlp/models/ernie-gram-zh/special_tokens_map.json
  • 1
  • 2
  • 3

3.4 数据格式转换

3.4.1 定义转换器

def convert_example(example, tokenizer, max_seq_length=256, is_test=False):

    query, title = example["text1"], example["text2"]

    encoded_inputs = tokenizer(
        text=query, text_pair=title, max_seq_len=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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
# 对训练集的第 1 条数据进行转换
input_ids, token_type_ids, label = convert_example(train_dataset[0], tokenizer)
  • 1
  • 2
#查看转换成的结果
print(input_ids)
print(token_type_ids)
print(label)
  • 1
  • 2
  • 3
  • 4
[1, 1883, 1176, 48, 22, 943, 688, 3496, 1114, 12045, 2, 1883, 1176, 52, 943, 688, 3496, 356, 2]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]
[1]
  • 1
  • 2
  • 3

3.4.2 构建Dataloader

# 为了后续方便使用,我们使用python偏函数(partial)给 convert_example 赋予一些默认参数
# 预训练模型的最大文本长度为512,当前最大文本长度为131
from functools import partial

# 训练集和验证集的样本转换函数
trans_func = partial(
    convert_example,
    tokenizer=tokenizer,
    max_seq_length=256)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
# 我们的训练数据会返回 input_ids, token_type_ids, labels 3 个字段
# 因此针对这 3 个字段需要分别定义 3 个组 batch 操作
from paddlenlp.data import Stack, Pad, Tuple
import paddle
import paddlenlp
import paddle.nn as nn

batchify_fn = lambda samples, fn=Tuple(
    Pad(axis=0, pad_val=tokenizer.pad_token_id),  # input_ids
    Pad(axis=0, pad_val=tokenizer.pad_token_type_id),  # token_type_ids
    Stack(dtype="int64")  # label
): [data for data in fn(samples)]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
# # 定义分布式 Sampler: 自动对训练数据进行切分,支持多卡并行训练
# batch_sampler = paddle.io.DistributedBatchSampler(train_dataset, batch_size=32, shuffle=True)

# 使用单卡进行评估,所以采用 paddle.io.BatchSampler 即可
batch_sampler = paddle.io.BatchSampler(dev_dataset, batch_size=32, shuffle=False)


# 基于 train_dataset 定义 train_data_loader
#  train_data_loader 会自动对训练数据进行切分
train_data_loader = paddle.io.DataLoader(
        dataset=train_dataset.map(trans_func),
        batch_sampler=batch_sampler,
        collate_fn=batchify_fn,
        return_list=True)

# 定义 dev_data_loader
dev_data_loader = paddle.io.DataLoader(
        dataset=dev_dataset.map(trans_func),
        batch_sampler=batch_sampler,
        collate_fn=batchify_fn,
        return_list=True)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

3.5 搭建语义匹配网络

# 我们基于 ERNIE-Gram 模型结构搭建 Point-wise 语义匹配网络
# 所以此处先定义 ERNIE-Gram 的 pretrained_model
pretrained_model = paddlenlp.transformers.ErnieGramModel.from_pretrained(MODEL_NAME)


class PointwiseMatching(nn.Layer):
   
    # 此处的 pretained_model 在本例中会被 ERNIE-Gram 预训练模型初始化
    def __init__(self, pretrained_model, dropout=None):
        super().__init__()
        self.ptm = pretrained_model
        self.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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
[2022-12-29 00:51:41,914] [    INFO] - Already cached /home/aistudio/.paddlenlp/models/ernie-gram-zh/ernie_gram_zh.pdparams
W1229 00:51:41.920701 26894 gpu_resources.cc:61] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 11.2, Runtime API Version: 11.2
W1229 00:51:41.924329 26894 gpu_resources.cc:91] device: 0, cuDNN Version: 8.2.
  • 1
  • 2
  • 3

e: 0, cuDNN Version: 8.2.

3.6 模型训练和评估

3.6.1 定义训练配置

from paddlenlp.transformers import LinearDecayWithWarmup
import paddle.nn.functional as F
import time
from visualdl import LogWriter
import os 

#定义模型训练相关配置
#训练轮数
epochs = 20
#学习率
learning_rate=2e-5
# 每间隔 1000 step 在验证集和测试集上进行评估
eval_steps=1000  
# 每间隔 10 step 输出训练指标
log_steps =100
# 训练模型保存路径
output_dir = 'checkpoints'
#训练日志保存路径
log_writer = LogWriter('visualdl_log_dir')
#学习率预热比例
warmup=0.01
#总训练步数
num_training_steps = len(train_data_loader) * epochs
# AdamW优化器参数weight_decay
weight_decay=0.01


# 定义 learning_rate_scheduler,负责在训练过程中对 lr 进行调度
lr_scheduler = LinearDecayWithWarmup(learning_rate, num_training_steps, warmup)

# # LayerNorm参数不参与weight_decay
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=weight_decay,
    apply_decay_param_fun=lambda x: x in decay_params)

# 采用交叉熵 损失函数
criterion = paddle.nn.loss.CrossEntropyLoss()

# 评估的时候采用准确率指标
metric = paddle.metric.Accuracy()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

3.6.2 构造训练函数

#训练函数
def train(model, train_data_loader):
    global_step = 0
    tic_train = time.time()
    for epoch in range(epochs):
        for step, batch in enumerate(train_data_loader):
            #正向传播
            optimizer.clear_grad() 
            global_step += 1
            input_ids, token_type_ids, label = batch
            outputs=model(input_ids=input_ids,token_type_ids=token_type_ids)
            loss = criterion(outputs, label)
            correct = metric.compute(outputs, label)
            metric.update(correct)
            acc = metric.accumulate()
           
  
        ## 每间隔 100 step 输出训练指标
            if global_step % log_steps == 0:
                print(
                "global step %d, epoch: %d, batch: %d, loss: %.5f, accu: %.5f, speed: %.2f "
                % (global_step, epoch, step, loss, acc,
                    log_steps / (time.time() - tic_train)))
                log_writer.add_scalar("train_loss", loss, global_step)
                tic_train = time.time()

            ## 反向梯度回传,更新参数        
            loss.backward()
            optimizer.step()
            lr_scheduler.step()
            optimizer.clear_grad()        
            
            # 每间隔 1000 step 在验证集和测试集上进行评估
            if global_step % eval_steps  == 0:
                evaluate(model, criterion, metric, dev_data_loader, "dev")

   

    # 训练结束后,存储模型参数
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)
    save_dir = os.path.join(output_dir)
    # 保存模型参数
    save_param_path = os.path.join(save_dir, 'model_state.pdparams')
    paddle.save(model.state_dict(), save_param_path)
    # 保存tokenizer的词表等
    tokenizer.save_pretrained(output_dir)
  

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

3.6.3 构造评估函数

# 因为训练过程中同时要在验证集进行模型评估,因此我们先定义评估函数

@paddle.no_grad()
def evaluate(model, criterion, metric, data_loader, phase="dev"):
    model.eval()
    total_eval_accuracy = 0
    total_eval_loss = 0
    losses = []
    for batch in data_loader:
        #正常传播
        input_ids, token_type_ids, label = batch
        outputs=model(input_ids=input_ids,token_type_ids=token_type_ids)
        loss = criterion(outputs, label)
        losses.append(loss.numpy())
        correct = metric.compute(outputs, label)
        metric.update(correct)
        accu = metric.accumulate()
    print("eval {} loss: {:.5}, accu: {:.5}".format(phase,
                                                    np.mean(losses), accu))
    model.train()
    metric.reset()
    
 

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

3.7 模型训练

3.7.1 进行训练

# 调用模型训练
train(model, train_data_loader)
  • 1
  • 2

3.7.2 可视化训练过程

3.8 模型预测

3.8.1 查看测评集的数据

path='/home/aistudio/data/data177274/data/╓╨╬─╢╘╗░╬─▒╛╞е┼ф╠Ї╒╜╚№╩¤╛▌╝п/test.csv'
#validation_data=pd.read_csv(path,encoding='utf-8-sig',sep='\s+',header=None,names=['text1', 'text2'])
validation_data=pd.read_csv(path,encoding='utf-8-sig',sep='\t',header=None,names=['text1', 'text2'])
validation_data.head(5)


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
text1text2
0成语中的历史人物成语有关的历史人物
1黄财神怎样供奉怎样供奉财神
2进门是餐厅好吗进门见餐厅好吗
3怎么提高理解力?怎样提高理解力
4仓鼠用什么磨牙都可以吗用什么可以代替仓鼠的磨牙棒

3.8.2 定义预测的data_loader

#定义预测的data_loader
# 预测数据的转换函数
# 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_ids
    Pad(axis=0, pad_val=tokenizer.pad_token_type_id),  # segment_ids
): [data for data in fn(samples)]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
# 因为测评集的数据没用label,因此需要做特殊调整
def read_file(data_path):
    with open(data_path, 'r', encoding='utf-8') as f:
    # 跳过列名
        next(f)
        for line in f:
            text1, text2 = line.strip('\t').split("♬")[0].strip(),line.strip('\t').split("♬")[1].strip()
            text1 = line.strip('\t').split("♬")[0].strip()
            text2 = line.strip('\t').split("♬")[1].strip()
            yield ({'text1': text1, 'text2': text2})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
#将测评集的数据进行格式转换
validation_data.to_csv('validation_data.csv',  index=False,encoding='utf-8',sep ='♬',header =['text1', 'text2'])
#加载测评集数据
validation_dataset  = load_dataset(read_file, data_path='validation_data.csv',lazy=False, split="val")

batch_sampler = paddle.io.BatchSampler(validation_dataset, batch_size=32, shuffle=False)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
# 生成预测数据 data_loader
validation_data_loader =paddle.io.DataLoader(
        dataset=validation_dataset.map(trans_func),
        batch_sampler=batch_sampler,
        collate_fn=batchify_fn,
        return_list=True)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3.8.3 加载训练好的模型

# 加载训练好的模型
state_dict = paddle.load("./checkpoints/model_state.pdparams")
model.set_dict(state_dict)
  • 1
  • 2
  • 3

3.8.4 构造预测函数

#预测函数
import paddle.nn.functional as F

def prediciton(model, data_loader):
    model.eval()
    prediction_list = []
    label_map = {0: '0', 1: '1'}
    with paddle.no_grad():
        for batch in data_loader:
            input_ids, token_type_ids = batch
            input_ids = paddle.to_tensor(input_ids)
            token_type_ids = paddle.to_tensor(token_type_ids)
            # 获取每个样本的预测概率: [batch_size, 2] 的矩阵
            outputs=model(input_ids=input_ids,token_type_ids=token_type_ids)
            probs = F.softmax(outputs, axis=-1)
            idx = paddle.argmax(probs, axis=1).numpy()
            idx = idx.tolist()
            labels = [label_map[i] for i in idx]
            prediction_list.extend(labels)
        
        return  prediction_list
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

3.8.5 进行预测

# 执行预测函数
test_result=prediciton(model,validation_data_loader)
  • 1
  • 2

3.9 生成预测结果文件

pd.DataFrame(test_result).to_csv('submit.csv', header=None, index=None)
  • 1

四、项目总结

4.1 优化思路

这个项目还有很多值得优化的地方,如:

  • 多尝试不同的模型,以及调参;
  • 多折交叉验证;
  • 融合或者 stacking

4.2 作者介绍

本人是AI达人特训营第二期项目中的一名学员,非常有幸能与大家分享自己的所思所想。

作者:范远展 指导导师:黄灿桦


  • 1
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/415571
推荐阅读
相关标签
  

闽ICP备14008679号