Bert系列:基于Huggingface预训练模型微调,实现中文实体链接分类

内容摘要

  • 中文实体链接问题定义
  • 开发环境准备,预训练模型模型下载
  • transformers中文编码预处理
  • PyTorch模型搭建
  • 微调前后模型效果对比

中文实体链接问题定义

本节的中文实体链接针对的是一个中文实体的全称和简称的链接,即输入一对候选的中文名称预测他们是否是一对全简称,比如**“奥林匹克运动会”和“奥运会”是正样本**,而**“奥林匹克运动会”和“亚运会”则是负样本。该任务本质上是文本二分类,输入是一对中文文本,而Bert的输入也是[CLS]...[SEP]...[SEP]**形式的一对文本,因此引入Bert预训练模型再结合本任务特定的全简称正负样本进行微调。预训练模型类似一个人已经学习了语言的基础语法和语义,微调相当于在这个基础上再单独学习全称简称命名这一领域的语言知识。


开发环境准备,预训练模型模型下载

本节基于Huggingface的transformers和Pytorch进行开发,依赖包版本如下

transformers       4.24.0
torch              1.12.1+cu113

transformers框架提供了基于预训练模型进行算法开发的标准流程范式,提供了统一的API,包括调用各种预训练模型,文本编码,数据转换抽取,模型搭建,训练测试评价等,使得代码开发更加高效和标准化。

预训练模型下载

分别下载五个文件,每个文件各自的作用如下

  • config.json:Bert模型内部结构的配置信息,包括隐藏层大小,注意力头数,encoder层数,dropout比率等,transformers中BertModel需要该文件来倒入预训练模型,BertConfig需要该文件来倒入预训练模型的配置字典
  • pytorch_model.bin:PyTorch框架中用于保存模型权重的二进制文件,Bert预训练的参数结果保存在该文件中,transformers中BertModel需要该文件来倒入预训练模型
  • tokenizer.json:记录了分词器属性信息,包括了truncation, padding,added_tokens,normalizer,pre_tokenizer,post_processor,decoder等属性
  • tokenizer_config.json:记录了分词器的配置信息
  • vocab.txt:词汇表,该预训练模型涉及到的所有字,transformers中BertTokenizer需要该文件来倒入分词词表

下载之后放到一个文件目录下比如./model/bert_base_chinese,工程目录如下

.
├── bin
├── data
├── etc
│   ├── config.yml
├── logs
├── model
│   ├── bert_base_chinese
│   │   ├── config.json
│   │   ├── pytorch_model.bin
│   │   ├── tokenizer_config.json
│   │   ├── tokenizer.json
│   │   └── vocab.txt
├── README.md
├── requirements.txt
├── src
│   ├── main
│   │   ├── model_pth_hugging_face.py
│   └── utils


transformers中文编码预处理

首先定义数据,继承PyTorch的Dataset类,从而方便完成后续的数据tensor转换,数据迭代器生成等操作。

class TrainData(Dataset):
    def __init__(self, path, upper_sample=False):
        self.data = pickle.load(open(os.path.join(ROOT_PATH, path), "rb"))
        if upper_sample:
            self.data = self.sample()

    def sample(self):  # 上采样
        positive = [x for x in self.data if x.label[1] == 1]
        negative = [x for x in self.data if x.label[1] == 0]
        if len(positive) > len(negative):
            total_num = len(positive) * 2
            upper = negative
        else:
            total_num = len(negative) * 2
            upper = positive
        while len(self.data) < total_num:
            self.data.extend(upper)
        self.data = self.data[:total_num]
        return self.data

    def __len__(self):
        return len(self.data)

    def __getitem__(self, item):
        return self.data[item].fullname, self.data[item].shortname, self.data[item].co_vector, self.data[item].label

TrainData类在实例化之后通过索引输出一对全简称,统计特征和label样本,例如

train_data = TrainData("./data/train.pkl", upper_sample=True)
>>> train_data[0]
('奥林匹克运动会', '奥运会', [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 1])

下一步定义数据整理函数,目的是将Dataset的一行特征转化为每个列的特征组,以及完成Bert的分词编码输出PyTorch的tensor格式。

from transformers import BertModel, BertTokenizer
TOKENIZER = BertTokenizer.from_pretrained(os.path.join(ROOT_PATH, "./model/bert_base_chinese"))

def collate_fn(data):
    name_pairs = []
    co_vectors = []
    labels = []
    for d in data:
        name_pairs.append((d[0], d[1]))
        co_vectors.append(d[2])
        labels.append(0 if d[3][0] == 1 else 1)
    data = TOKENIZER.batch_encode_plus(batch_text_or_text_pairs=name_pairs, truncation=True, padding="max_length",
                                       max_length=50, return_tensors="pt")
    input_ids = data["input_ids"].to(DEVICE)
    attention_mask = data["attention_mask"].to(DEVICE)
    token_type_ids = data["token_type_ids"].to(DEVICE)
    co_vectors = torch.tensor(co_vectors).to(DEVICE)
    labels = torch.LongTensor(labels).to(DEVICE)

    return input_ids, attention_mask, token_type_ids, co_vectors, labels

在整理函数collate_fn中使用transformers导入预训练模型将样本分词编码的方法和预训练Bert一样,在batch_encode_plus中输入**[(A, B),(A, B)…]**的一对对样本数据,自定义句子的最大长度截取逻辑,以tensor的形式进行输出,例如将奥林匹克运动会,奥运会这一对进行编码如下

>>> tmp = TOKENIZER.batch_encode_plus(batch_text_or_text_pairs=[("奥林匹克运动会", "奥运会")], truncation=True, padding="max_length", max_length=50, return_tensors="pt")
{'input_ids': tensor([[ 101, 1952, 3360, 1276, 1046, 6817, 1220,  833,  102, 1952, 6817,  833,
          102,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
            0,    0]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
         0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
         0, 0]])}

分词器返回编码后的input_ids,token_type_ids,attention_mask,可以对input_ids进行反编码查看编码过程,编码器会自动将一对文本添加特殊字符处理成Bert的输入格式,然后返回每个字符在vocab.txt中的id位置

>>> TOKENIZER.decode(tmp['input_ids'][0])
'[CLS] 奥 林 匹 克 运 动 会 [SEP] 奥 运 会 [SEP] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] ...

下一步将数据和整理函数打包为一个PyTorch的DataLoader,定义batch_size和shuffle直接喂给模型训练。

train_loader = DataLoader(train_data, shuffle=True, batch_size=512, drop_last=False, collate_fn=collate_fn)

查看单个批次的输出数据如下,原始的字符串已经转化为分词id,一个批次为512个样本,每个输入最长50个字符。

for i, (input_ids, attention_mask, token_type_ids, co_vectors, labels) in enumerate(train_loader):
    break

>>> input_ids
tensor([[ 101, 2813,  704,  ...,    0,    0,    0],
        [ 101, 4403, 3862,  ...,    0,    0,    0],
        [ 101,  704, 1744,  ...,    0,    0,    0],
        ...,
        [ 101,  704, 1744,  ...,    0,    0,    0],
        [ 101, 1744, 6084,  ...,    0,    0,    0],
        [ 101, 7270, 4510,  ...,    0,    0,    0]], device='cuda:1')

>>> input_ids.shape
torch.Size([512, 50])


PyTorch模型搭建

使用PyTorch搭建模型继承nn.Module,在网络层引入本地下载好的预训练BertModel,同时在微调中加入其他的实体统计特征co_vectors和Bert的输出进行拼接,一起输入nn.ModuleList定义多层全连接层得到最终的模型输出

from transformers import BertModel, BertTokenizer

PRE_TRAIN = BertModel.from_pretrained(os.path.join(ROOT_PATH, "./model/bert_base_chinese"))

class Model(nn.Module):
    def __init__(self, bert_hidden_size=768, co_vector_size=55, fc_hidden_size="256,64", dropout_rate=0.1):
        super(Model, self).__init__()
        self.pre_train = PRE_TRAIN
        self.fc_hidden_size = [bert_hidden_size + co_vector_size] + list(map(int, fc_hidden_size.split(",")))
        self.fc_layers = nn.ModuleList([nn.Linear(self.fc_hidden_size[i], self.fc_hidden_size[i + 1]) for i in
                                        range(len(self.fc_hidden_size) - 1)])
        self.linear = nn.Linear(self.fc_hidden_size[-1], 2)
        self.relu = nn.ReLU()
        self.drop = nn.Dropout(p=dropout_rate)

    def forward(self, input_ids, attention_mask, token_type_ids, co_vectors):
        # [None, 768]
        pre_train_out = self.pre_train(input_ids=input_ids, attention_mask=attention_mask,
                                       token_type_ids=token_type_ids).pooler_output
        # [None, 768 + 55]
        concat = torch.concat([pre_train_out, co_vectors], dim=1)
        # fc => [None, 64]
        fc_out = concat
        for i in range(len(self.fc_layers)):
            fc_out = self.fc_layers[i](fc_out)
            fc_out = self.relu(fc_out)
            fc_out = self.drop(fc_out)
        out = self.linear(fc_out)
        prob = nn.Softmax(dim=1)(out)  # [None, 2]
        return out, prob

transformers通过一行代码引入Bert模型,将input_ids,attention_mask,token_type_ids直接输入,即可一行代码得到Bert的CLS池化输出,相比于上一节从Bert的源码进行微调方便的太多,可以将更多的精力聚焦在微调的网络结构本身还不是去探究Bert环节的实现。
下一步进行模型训练,设置最大样本复制15轮,在验证集10次不出现AUC上升则早停,通过transformers的get_scheduler设置学习衰减和学习率预热

model = Model().to(DEVICE)
    epochs = 15
    optimizer = Adam(model.parameters(), lr=0.0001, weight_decay=0.0)
    scheduler = get_scheduler("linear", num_warmup_steps=10, num_training_steps=epochs * len(train_loader),
                              optimizer=optimizer)
    criterion = nn.CrossEntropyLoss(reduction="mean")
    step = 0
    val_auc_list = []
    early_stop_flag = False
    for epoch in range(epochs):
        for i, (input_ids, attention_mask, token_type_ids, co_vectors, labels) in enumerate(train_loader):
            model.train()
            optimizer.zero_grad()
            output, prob = model(input_ids, attention_mask, token_type_ids, co_vectors)
            loss = criterion(output, labels)
            loss.backward()
            optimizer.step()
            scheduler.step()
            step += 1
            if step % 1 == 0:
                auc = roc_auc_score(labels.cpu().detach().numpy(), output.cpu().detach().numpy()[:, 1])
                print("epoch: {} step: {} loss: {} auc: {}".format(epoch + 1, step, loss.item(), auc))
            if step % 10 == 0:
                auc, good_recall, bad_recall, acc, loss = model_metrics(val_loader, model)
                print("[evaluation] loss: {} 正确全简称的召回率: {} 错误全简称的召回率: {} 正确率: {} AUC: {}"
                      .format(loss, good_recall, bad_recall, acc, auc))
                diff_auc = (auc - max(val_auc_list)) if len(val_auc_list) else 0
                val_auc_list.append(auc)
                print("本轮auc比之前最大auc{}:{}, 当前最大auc: {}\n".format("上升" if diff_auc > 0 else "下降", abs(diff_auc),
                                                                 max(val_auc_list)))
                if diff_auc > 0:
                    torch.save(model.state_dict(), os.path.join(ROOT_PATH, "./model/pth_model/model.pth"))
                    print("[save checkpoint]")
                if early_stop_auc(val_auc_list, windows=10):
                    print("{:-^30}".format("early stop!"))
                    early_stop_flag = True
                    break
        if early_stop_flag:
            break

训练早停日志如下

epoch: 3 step: 246 loss: 0.330115407705307 auc: 0.9397805484762006
epoch: 3 step: 247 loss: 0.2885383367538452 auc: 0.9506531204644413
epoch: 3 step: 248 loss: 0.2339172214269638 auc: 0.9690625620352131
epoch: 3 step: 249 loss: 0.22491329908370972 auc: 0.9756562881562881
epoch: 3 step: 250 loss: 0.2437824159860611 auc: 0.9659361819182102
[evaluation] loss: 0.5039 正确全简称的召回率: 0.8096 错误全简称的召回率: 0.8154 正确率: 0.8125 AUC: 0.8902
本轮auc比之前最大auc下降:0.00990000000000002, 当前最大auc: 0.9001
---------early stop!----------


微调前后模型效果对比

对了三种模型策略,分别是

  • 统计特征的Xgboost分类:通过手动构造实体间的相关统计指标来量化相似性,对实体是否有链接进行预测
  • 统计特征和Bert结构不使用预训练模型:在统计特征的基础上,使用Bert的网络结构学习文本表征,随机初始化所有字的embedding,不依赖外部预训练模型,只通过该案例的样本进行训练
  • 统计特征和Bert结构且使用bert-base-chinese预训练模型:使用外部大数据训练得到的预训练模型,在本样本上进行微调

三种算法策略的测试集模型指标如下

策略正确链接识别率错误连接识别率AUC
统计特征Xgboost0.7740.5600.757
统计特征 + 2层8头Bert0.7510.8380.886
统计特征 + 12层12头Bert + 预训练模型0.8990.7330.903

仅通过统计相似度特征进行预测能达到一定的分类水平AUC为0.757,而加入Bert表征的字符特征后AUC上升13个点说明全简称有语义规律,再加入预训练模型AUC提升到0.903说明在外部大样本上预训练在特征任务上微调策略的有效性。

如何系统的去学习大模型LLM ?

作为一名热心肠的互联网老兵,我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。

但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的 AI大模型资料 包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来

😝有需要的小伙伴,可以V扫描下方二维码免费领取🆓

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

img

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

img

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

img

在这里插入图片描述

四、AI大模型商业化落地方案

img

阶段1:AI大模型时代的基础理解

  • 目标:了解AI大模型的基本概念、发展历程和核心原理。
  • 内容
    • L1.1 人工智能简述与大模型起源
    • L1.2 大模型与通用人工智能
    • L1.3 GPT模型的发展历程
    • L1.4 模型工程
    • L1.4.1 知识大模型
    • L1.4.2 生产大模型
    • L1.4.3 模型工程方法论
    • L1.4.4 模型工程实践
    • L1.5 GPT应用案例

阶段2:AI大模型API应用开发工程

  • 目标:掌握AI大模型API的使用和开发,以及相关的编程技能。
  • 内容
    • L2.1 API接口
    • L2.1.1 OpenAI API接口
    • L2.1.2 Python接口接入
    • L2.1.3 BOT工具类框架
    • L2.1.4 代码示例
    • L2.2 Prompt框架
    • L2.2.1 什么是Prompt
    • L2.2.2 Prompt框架应用现状
    • L2.2.3 基于GPTAS的Prompt框架
    • L2.2.4 Prompt框架与Thought
    • L2.2.5 Prompt框架与提示词
    • L2.3 流水线工程
    • L2.3.1 流水线工程的概念
    • L2.3.2 流水线工程的优点
    • L2.3.3 流水线工程的应用
    • L2.4 总结与展望

阶段3:AI大模型应用架构实践

  • 目标:深入理解AI大模型的应用架构,并能够进行私有化部署。
  • 内容
    • L3.1 Agent模型框架
    • L3.1.1 Agent模型框架的设计理念
    • L3.1.2 Agent模型框架的核心组件
    • L3.1.3 Agent模型框架的实现细节
    • L3.2 MetaGPT
    • L3.2.1 MetaGPT的基本概念
    • L3.2.2 MetaGPT的工作原理
    • L3.2.3 MetaGPT的应用场景
    • L3.3 ChatGLM
    • L3.3.1 ChatGLM的特点
    • L3.3.2 ChatGLM的开发环境
    • L3.3.3 ChatGLM的使用示例
    • L3.4 LLAMA
    • L3.4.1 LLAMA的特点
    • L3.4.2 LLAMA的开发环境
    • L3.4.3 LLAMA的使用示例
    • L3.5 其他大模型介绍

阶段4:AI大模型私有化部署

  • 目标:掌握多种AI大模型的私有化部署,包括多模态和特定领域模型。
  • 内容
    • L4.1 模型私有化部署概述
    • L4.2 模型私有化部署的关键技术
    • L4.3 模型私有化部署的实施步骤
    • L4.4 模型私有化部署的应用场景

学习计划:

  • 阶段1:1-2个月,建立AI大模型的基础知识体系。
  • 阶段2:2-3个月,专注于API应用开发能力的提升。
  • 阶段3:3-4个月,深入实践AI大模型的应用架构和私有化部署。
  • 阶段4:4-5个月,专注于高级模型的应用和部署。
这份完整版的大模型 LLM 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

😝有需要的小伙伴,可以Vx扫描下方二维码免费领取🆓

  • 20
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值