首页 PaddleNLP 帖子详情
为什么paddlenlp使用ernie-tiny进行实体识别效果不如paddehub训练效果
收藏
快速回复
PaddleNLP 文章NLP训练 1643 3
为什么paddlenlp使用ernie-tiny进行实体识别效果不如paddehub训练效果
收藏
快速回复
PaddleNLP 文章NLP训练 1643 3

paddlenlp版本:2.0.5

paddlehub版本:2.1.0

paddlepaddle版本:paddlepaddle-gpu 2.0.2.post110

系统:centos

问题描述:

使用自建命名实体识别数据集训练集,数据量为7万多条,进行NER训练,使用paddlenlp和paddlehub训练出来的指标差别很大,paddehub训练出来的模型在测试集上的的f1有0.95,paddlenlp训练出来的模型在测试集上的f1只有0.7,请问什么原因导致的,之前使用paddlenlp也有过这种情况,希望可以解答一下

首先是paddlenlp使用ernie-tiny进行训练代码如下:训练10个epoch,最终的f1只有0.7

from functools import partial
import paddle
from paddlenlp.datasets import MapDataset
from paddlenlp.data import Stack, Tuple, Pad
from paddlenlp.transformers import ErnieTokenizer, ErnieForTokenClassification, ErnieTinyTokenizer
from paddlenlp.metrics import ChunkEvaluator
from utils import convert_example, evaluate, predict, load_dict, load_dict_intelligence

# 加载自定义数据集
def load_dataset(datafiles):
    """
    加载自定义数据集:将都区后的语料数据加载为MapDataset
    Parameters
    ----------
    datafiles

    Returns
    -------

    """

    def read(data_path):
        """
        读取语料文件,返回迭代器
        Parameters
        ----------
        data_path:文件路径

        Returns:words:word列表;labels:label列表
        -------

        """
        with open(data_path, 'r', encoding='utf-8') as fp:
            # next(fp)  # 跳过第一行
            for line in fp.readlines():
                words, labels = line.strip('\n').split('\t')
                words = words.split('\002')
                if len(words) > 256:
                    words = words[:256]
                labels = labels.split('\002')
                if len(labels) > 256:
                    labels = labels[:256]
                yield words, labels

    if isinstance(datafiles, str):
        return MapDataset(list(read(datafiles)))
    elif isinstance(datafiles, list) or isinstance(datafiles, tuple):
        return [MapDataset(list(read(datafile))) for datafile in datafiles]

train_file = r'./corpus_data/train_intell_info_ner_first.txt'
val_file = r'/./corpus_data/val_intell_info_ner_first.txt'
test_file = r'./corpus_data/test_intell_info_ner_first.txt'


# 标签文件
tag_file = r'./corpus_data/conf/tag.dic'
train_ds, dev_ds, test_ds = load_dataset(datafiles=(train_file, val_file, test_file))

# 数据处理
label_vocab = load_dict_intelligence(tag_file)

tokenizer = ErnieTinyTokenizer.from_pretrained("ernie-tiny")

trans_func = partial(convert_example, tokenizer=tokenizer, label_vocab=label_vocab)

train_ds.map(trans_func)
dev_ds.map(trans_func)
test_ds.map(trans_func)
print(train_ds[0])

# 使用paddle.io.DataLoader接口多线程异步加载数据。
ignore_label = -1
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(),  # seq_len
    Pad(axis=0, pad_val=ignore_label)  # labels
): fn(samples)

train_loader = paddle.io.DataLoader(
    dataset=train_ds,
    batch_size=128,
    return_list=True,
    collate_fn=batchify_fn)
dev_loader = paddle.io.DataLoader(
    dataset=dev_ds,
    batch_size=128,
    return_list=True,
    collate_fn=batchify_fn)
test_loader = paddle.io.DataLoader(
    dataset=test_ds,
    batch_size=128,
    return_list=True,
    collate_fn=batchify_fn)

# 加载模型,ernie和ernie-tiny都使用ErnieForTokenClassification加载模型
model = ErnieForTokenClassification.from_pretrained("ernie-tiny", num_classes=len(label_vocab))

# 设置Fine-Tune优化策略, 模型配置
# 学习率策略为warmup的动态学习率适用于ERNIE/BERT这类Transformer模型的迁移优化
metric = ChunkEvaluator(label_list=label_vocab.keys(), suffix=True)

loss_fn = paddle.nn.loss.CrossEntropyLoss(ignore_index=ignore_label)
optimizer = paddle.optimizer.AdamW(learning_rate=5e-5, parameters=model.parameters())


# 模型训练与评估
step = 0
epochs = 10
eval_freq = 100
best_f1 = 0
for epoch in range(epochs):
    for idx, (input_ids, token_type_ids, length, labels) in enumerate(train_loader):
        logits = model(input_ids, token_type_ids)
        loss = paddle.mean(loss_fn(logits, labels))
        loss.backward()
        optimizer.step()
        optimizer.clear_grad()
        step += 1
        print("epoch:%d - step:%d - loss: %f" % (epoch, step, loss))
        if step % eval_freq == 0:
            precision, recall, f1_score = evaluate(model, metric, dev_loader)

换了paddlehub进行训练,同样加载ernie-tiny,同样的数据,训练10个epoch,f1为0.95,代码:

import paddlehub as hub
import paddle
import os, io, csv
from paddlehub.datasets.base_nlp_dataset import InputExample, TextClassificationDataset


def load_dict(dict_path):
    """加载词汇表"""
    vocab = {}
    for line in open(dict_path, 'r', encoding='utf-8'):
        value, key = line.strip('\n').split('\t')
        vocab[key] = int(value)
    return vocab

label_vocab = load_dict(r'./corpus_data/conf/tag.dic')

label_list = [value for value in label_vocab.keys()]
label_map = {key: value for key, value in enumerate(label_list)}
print(label_map, label_list)

# 选择所需要的模型
model = hub.Module(name='ernie_tiny', version='2.0.1', task='token-cls', label_map=label_map)

from paddlehub.datasets.base_nlp_dataset import SeqLabelingDataset


class ExpressNER(SeqLabelingDataset):
    # 数据集存放目录
    base_path = r'./corpus_data'
    # 数据集的标签列表
    label_list = label_list
    label_map = {idx: label for idx, label in enumerate(label_list)}
    # 数据文件使用的分隔符
    split_char = '\002'

    def __init__(self, tokenizer, max_seq_len: int = 128, mode: str = 'train'):
        if mode == 'train':
            data_file = 'train_intell_info_ner_first.txt'
        elif mode == 'test':
            data_file = 'test_intell_info_ner_first.txt'
        else:
            data_file = 'val_intell_info_ner_first.txt'
        super().__init__(
            base_path=self.base_path,
            tokenizer=tokenizer,
            max_seq_len=max_seq_len,
            mode=mode,
            data_file=data_file,
            label_file=None,
            label_list=self.label_list,
            split_char=self.split_char,
            is_file_with_header=False)

    # 解析文本文件里的样本
    def _read_file(self, input_file, is_file_with_header: bool = False):
        csv.field_size_limit(500 * 1024 * 1024)
        if not os.path.exists(input_file):
            raise RuntimeError("The file {} is not found.".format(input_file))
        else:
            with io.open(input_file, "r", encoding="UTF-8") as f:
                reader = csv.reader(f, delimiter="\t", quotechar=None)
                examples = []
                seq_id = 0
                header = next(reader) if is_file_with_header else None
                for line in reader:
                    example = InputExample(guid=seq_id, text_a=line[0], label=line[1])
                    seq_id += 1
                    examples.append(example)
                return examples


tokenizer = model.get_tokenizer()
# 获取数据集
train_dataset = ExpressNER(tokenizer=tokenizer, max_seq_len=256, mode='train')
dev_dataset = ExpressNER(tokenizer=tokenizer, max_seq_len=256, mode='dev')
test_dataset = ExpressNER(tokenizer=tokenizer, max_seq_len=256, mode='test')

optimizer = paddle.optimizer.Adam(learning_rate=5e-5, parameters=model.parameters())  # 优化器的选择和参数配置
trainer = hub.Trainer(model, optimizer, checkpoint_dir='./ckpt_intell_first', use_gpu=True, use_vdl=True)  # fine-tune任务的执行者
trainer.train(train_dataset, epochs=10, batch_size=128, eval_dataset=dev_dataset, save_interval=1)  # 配置训练参数,启动训练,并指定验证集

# 在测试集上评估当前训练模型
result = trainer.evaluate(test_dataset, batch_size=64)
print('测试集的f1为:', result)

0
收藏
回复
全部评论(3)
时间顺序
应十五
#2 回复于2021-07

首先这是一个不正常的现象,因为使用PaddleHub进行训练时内部也是调用PaddleNLP进行模型的加载。

这里由于看不到完整的代码,建议看一下两者的evaluate函数逻辑是否一致,模型加载时num_classes是否一致。

如果方便的话,贴出完整代码,我们可以具体分析~

0
回复
c
chengcheng
#3 回复于2021-08
首先这是一个不正常的现象,因为使用PaddleHub进行训练时内部也是调用PaddleNLP进行模型的加载。 这里由于看不到完整的代码,建议看一下两者的evaluate函数逻辑是否一致,模型加载时num_classes是否一致。 如果方便的话,贴出完整代码,我们可以具体分析~
展开

谢谢你的回复,这个问题已经解决了,github上面issue链接为:https://github.com/PaddlePaddle/PaddleNLP/issues/809

0
回复
应十五
#4 回复于2021-08

ok

0
回复
需求/bug反馈?一键提issue告诉我们
发现bug?如果您知道修复办法,欢迎提pr直接参与建设飞桨~
在@后输入用户全名并按空格结束,可艾特全站任一用户