使用bart生成中文摘要时遇到的一个问题(已解决)

最近在使用复旦大学预训练好的中文bart模型来生成摘要(参考BART中文摘要生成,(nplcc与LCSTS数据集)_nlpcc2017数据集_道天翁的博客-CSDN博客),期间在复现的时候遇到了几个很有意思的问题,记录一下。

代码流程

将训练代码封装在类中,初始化的时候加载预训练模型

class BlogSumBartClass:
    def __init__(self,config,options):
        self.config = config
        self.options = options
        self.data_path = ""
        bart_path = "./data/models/bart/bart-base-chinese"
        self.tokenizer = AutoTokenizer.from_pretrained(bart_path)
        self.model_config = BertConfig.from_pretrained(bart_path)
        self.model = AutoModelForSeq2SeqLM.from_pretrained(bart_path)
        self.max_input_length = 512
        self.max_target_length = 256
        self.output_dir = ""
        self.logs_dir = ""
        if not os.path.exists(self.output_dir):
            os.makedirs(self.output_dir)
        if not os.path.exists(self.logs_dir):
            os.makedirs(self.logs_dir)
        self.batch_size = 16
        self.num_epochs = 10

加载数据的时候使用load_dataset函数报错,可能是数据集格式有问题,只能手动从jsonl文件中逐行读取数据

def load_data(self):
        list_all = []
        count = 0
        with open(self.data_path) as file:
            for it in jsonlines.Reader(file):
                list_data = []
                body = it["body"]
                summary = it["summary"]
                if len(body)>510:
                    body = body[:255] +  body[255:]
                list_data.append(body.strip())
                if len(summary)>510:
                    summary = summary[:510]
                list_data.append(summary.strip())
                list_all.append(list_data)
                count += 1
                
        df = pd.DataFrame(list_all,columns=['body','summary'])
        dataset = Dataset.from_pandas(df)
        return dataset

由于预训练模型最大输入长度为512,当一篇文章过长时为选择取其头尾各255个字符读取。

再对数据进行分词和处理

def preprocess_function(self,examples):
        inputs = [doc for doc in examples["document"]]
        model_inputs = self.tokenizer(inputs,max_length=self.max_input_length,truncation=True)

        labels = self.tokenizer(examples["summary"],max_length=self.max_target_length,truncation=True)
        
        model_inputs["labels"] = labels["input_ids"]
        return model_inputs

然后是计算评估指标rouge的代码

def compute_metrics(self,eval_pred):
        predictions,labels = eval_pred
        decoded_preds = self.tokenizer.batch_decode(predictions,skip_special_tokens=True)
        labels = np.where(labels != -100,labels,self.tokenizer.pad_token_id)
        decoded_labels = self.tokenizer.batch_decode(labels,skip_special_tokens=True)
        decoded_preds = ["".join(pred.replace(" ","")) for pred in decoded_preds]
        decoded_labels = ["".join(label.replace(" ","")) for label in decoded_labels]
        labels_lens = [np.count_nonzero(pred != self.tokenizer.pad_token_id) for pred in labels]
        rouge = lawrouge.Rouge()
        result = rouge.get_scores(decoded_preds,decoded_labels,avg=True)
        print(result)
        result = {'rouge-1': result['rouge-1']['f'], 'rouge-2': result['rouge-2']['f'], 'rouge-l': result['rouge-l']['f']}
        result = {key:value * 100 for key,value in result.items()}
        return result

以下是处理数据和训练模型的总流程代码

    def train(self):
        dataset = self.load_data()
        dataset = dataset.map(self.flatten,remove_columns=["body","summary"])
        train_data_txt,validation_data_txt = dataset.train_test_split(test_size=0.1,shuffle=True,seed=42).values()
        train_data_txt,test_data_txt = train_data_txt.train_test_split(test_size=0.1,shuffle=True,seed=42).values()
        dd = datasets.DatasetDict({"train":train_data_txt,"validation":validation_data_txt,"test":test_data_txt})
        raw_datasets = dd
        print(raw_datasets)
        tokenized_datasets = raw_datasets.map(self.preprocess_function,batched=True)
        args = Seq2SeqTrainingArguments(
            output_dir=self.output_dir,
            num_train_epochs=self.num_epochs,
            do_train=True,
            do_eval=True,
            per_device_train_batch_size=self.batch_size,
            per_device_eval_batch_size=self.batch_size,
            learning_rate=1e-04,
            weight_decay=0.001,
            label_smoothing_factor=0.1,
            predict_with_generate=True,
            logging_dir=self.logs_dir,
            logging_steps=200,
            evaluation_strategy="epoch",
            save_total_limit=3,
            generation_max_length=256,
            generation_num_beams=1,
        )
        print(self.tokenizer.pad_token_id)
        data_colltaot = DataCollatorForSeq2Seq(self.tokenizer,model=self.model)
        trainer = Seq2SeqTrainer(
            model=self.model,
            args=args,
            train_dataset=tokenized_datasets["train"],
            eval_dataset=tokenized_datasets["validation"],
            data_collator=data_colltaot,
            tokenizer=self.tokenizer,
            compute_metrics=self.compute_metrics
        )
        train_result = trainer.train()
        trainer.save_model()
        metrics = train_result.metrics
        trainer.log_metrics("train",metrics)
        trainer.save_metrics("train",metrics)
        trainer.save_state()

数据文件格式是一个jsonl文件,内部格式如下:

{"body":"文章一","summary":"摘要一"}
{"body":"文章二","summary":"摘要二"}
{"body":"文章三","summary":"摘要三"}

一切准备就绪了,允许代码,不出意外就要出意外了。

报错

在compute_metrics()中报错了,

在decoded_preds = self.tokenizer.batch_decode(predictions,skip_special_tokens=True)这行报错:OverflowError: out of range integral type conversion attempted。

这行代码的作用是在验证过程中将模型生成的摘要ids通过字典解码成单词,报错的意思是解码时整形范围溢出了。

在网上大量查阅资料和翻看源代码,终于找到了原因了。

原因和解决方法

先说解决方法,很简单,只要在decoded_preds = self.tokenizer.batch_decode(predictions,skip_special_tokens=True)前加入一行

predictions = np.where(predictions != -100,predictions,self.tokenizer.pad_token_id)
self.tokenizer.batch_decode(predictions,skip_special_tokens=True)

就可以了,可以参考[run_translation.py] out of range integral type conversion attempted · Issue #22634 · huggingface/transformers · GitHub的讨论

至于原因,是生成的ids出现了-100,而字典里index没有-100,自然就溢出了。比如生成的摘要ids可能为

[127,155,164,135,199,6642.....,0,0,0,0]
[127,165,1354,4521,242,652.....,0,-100,-100,-100]

可以在compute_metrics()中添加

def compute_metrics(self,eval_pred):
        predictions,labels = eval_pred
        for i in predictions:
            print(i)
        decoded_preds = self.tokenizer.batch_decode(predictions,skip_special_tokens=True)
        print(decoded_preds)

来查看验证过程中的生成情况

为什么会出现这样的情况呢?

我们在data_colltaot = DataCollatorForSeq2Seq(self.tokenizer,model=self.model)这行代码中将数据集分词处理并转化为data_collator格式,后面长度未到最大长度就会补齐,在DataCollatorForSeq2Seq()函数中如果没有规定label_pad_token_id的值那么默认补齐的值为-100.所以说-100这个值很特殊,很有可能是补齐的值,但是这里的-100是原数据集中评估集的ids值,并不是模型生成的,并且-100这个值会在模型训练中被忽略掉,所以似乎不是这里导致的报错。

但是我们知道了-100极有可能是补齐的值,而且-100的位置出现都是值尾部而且长度不一,甚至有时候并没有出现-100,而且很多时候都是用0在补齐。这就很疑惑了,为什么会有两个补齐值。

通过翻看Trainer的源代码,发现问题出在generation_max_length这个参数上面。

这个参数的意思是生成的摘要最大长度为这个,当超过时会截断,不足时会补齐,补齐就是用的-100.

当评估数据和生成数据长度不一样时,Trainer会自动补齐长度,使用-100来补齐,这就导致了预测生成的摘要尾部可能会出现-100导致整形溢出。所以只要使用numpy的where将-100改成模型的padding值就行了,这个模型的padding值为0。

其他遇到的一些问题 

如果transformer版本较高的话,就不能使用with tokenizer.as_target_tokenizer()来同步分词了,这个函数在高版本被移除了。

测试的时候使用Trainer.get_eval-dataloader()来加载测试集和生成集,然后在使用np.where()时报错无法加载GPU张量,在使用前

outputs = outputs.to('cuda')
outputs = outputs.cpu().numpy()

将GPU张量改为CPU张量就可以了

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: LCSTS数据集共分为三个部分:训练集、验证集和测试集。 训练集:训练集是指用于训练模型的数据集,LCSTS训练集包含了来自新浪新闻网2013年的短文本和点击量最高的评论,共计2,400,000个样本。这些样本已经通过了数据清洗和去重处理,并且已经按照一定的比例随机划分成多个小批次,方便模型的训练。 验证集:验证集是指用于验证模型性能和调整参数的数据集,LCSTS验证集共包含1,200个样本,其中50%为含有讽刺和批评的样本,另外50%为不含有讽刺和批评的样本。这些样本可以帮助我们评估模型的性能和确定模型的最佳参数。 测试集:测试集是指用于测试模型在未知数据上的性能的数据集,LCSTS测试集共包含1,500个样本,其中50%为含有讽刺和批评的样本,另外50%为不含有讽刺和批评的样本。这些样本可以用来评估模型在真实场景下的性能和泛化能力。 ### 回答2: lcsts数据集由三部分组成:原始数据集、分词后数据集和摘要数据集。 原始数据集包含了70万对中英文短文本,其中中文短文本长度在10个字至100个字之间,英文短文本长度在5个字至50个字之间。这部分数据集对于机器翻译、文本匹配和摘要生成领域的研究有很大的参考价值。 为了方便处理和使用,lcsts数据集还提供了分词后的数据集。根据中文文本特性,对中文短文本进行分词后,可以得到更加清晰、规范的中文单词序列。这使得NLP领域的处理和分析更为方便和准确。 摘要数据集是原始数据集的精华。通过对原始数据集中英文短文本进行人工筛选、编辑和生成,得到了5.5万对中文摘要和英文摘要。这些中英文摘要是对原始短文本的高度概括和提炼,是进行文本摘要和文本生成研究的重要数据来源。 综上所述,lcsts数据集的三部分都对于自然语言处理领域的研究有着重要的意义和价值,在文本翻译、匹配、摘要生成等方面都有广泛的应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值