【LLM】第三章:BERT讲解+情感分析案例
本篇开讲当年叱诧风云、风光无二的BERT大模型。
说明:本文是假定你已经非常了解Transformer了,也不会重复写和注意力机制、Transformer相关的内容了。想了解详情的自行查阅我的NLP专栏:https://blog.csdn.net/friday1203/category_12833594.html?spm=1001.2014.3001.5482
一、BERT概述
2018年10月,Google发布一篇论文《BERT:Pre-training of Deep Bidirectional Transformers for Language Understanding》,BERT模型横空出世,并横扫NLP领域11项目任务的最佳成绩!在BERT出现之前,NLP领域占据统治地位的是LSTM和GRU模型,但是BERT出世后,基本就是NLP领域的标配了。其强大的自然语言表示能力,被广泛应用于文本分类、序列标注,比如命名实体识别、句子匹配等场景。
BERT论文地址是:https://arxiv.org/pdf/1810.04805.pdf 大家可以自行下载原论文阅读。
在BERT中发挥重要作用的结构就是Transformer,之后又出现XLNET,roBERT等模型击败了BERT,但是它们的核心没有变,仍然是Transformer。
二、BERT模型架构
BERT(Bidirectional Encoder Representation from Transformers),采用的是Transformer的编码器结构 ,而编码器是用来理解语义 的,生成词语的token表示 的。所以BERT其实就是一个词向量化模型 ,只是它因使用了注意力机制,而具有更加强大的自然语言表示能力。
同时BERT也是一个预训练模型 ,也就是谷歌已经用海量语料把模型预训练完毕了,发布了两个版本:BERT-base和BERT-large,具体参数规格如下右图:
我们以BERT-base版本为例来讲解。
1、从HF上下载BERT-base预训练模型到本地
在前一个篇章:【LLM】第二章:HuggingFace入门学习-CSDN博客 如何配置环境变量、下载的文件及其地址都有详细的说明和示例。这里不再重复。我把cache中下载的文件复制粘贴到项目文件夹中,如下图所示:
2、从本地加载预训练模型BERT-base
python
#这是加载本地BERT预训练模型+tokenizer的代码
from transformers import AutoModel, AutoTokenizer
model_name = "bert-base-chinese"
cache_dir = r"D:\2MyWork\MyFile\pytorch-deeplearning\大模型\hf-models\bert-model" #带盘符的本地地址,也是1中的位置
model_bert = AutoModel.from_pretrained(model_name, cache_dir=cache_dir)
model_bert
tokenizer_bert = AutoTokenizer.from_pretrained(model_name, cache_dir=cache_dir)
tokenizer_bert

3、从2中看BERT的整体架构
可见,bert架构和transformer的编码器相差不大,只要对transformer非常熟悉,上面的架构就没啥新鲜的。
4、澄清:什么是"双向自注意力结构"
网上很多资料强调BERT的创新在于它是一个双向自注意力结构 :在训练每个token表示时,BERT是整合了左右两个方向上的上下文信息,从而获得更准确、更丰富的语义表示:
了解注意力机制的同学就会秒懂,这本身就是注意力机制的固有特点,因为计算注意力分值时,计算的就是整个序列中样本与样本之间的分值。也所以它具有长期记忆能力!所以这个创新根本不是啥创新,没啥稀奇的。上图是BERT论文中的图,个人感觉有些不对! 下篇我们学的GPT,GPT是由12个transformer中的解码器串联而成!不是上面右图那样的神经元连接的结构!同理,BERT也是编码器串联而成的!不是像神经元链接的这种方式!如果是这种方式,那得要多少个编码器?!此外,一个TM就可以输出被喂入的整个序列中的所有token的结果!不是上图那种的一个TM就输出序列中对应的token的结果!
5、BERT的输入部分
BERT的每个输入token表示由三部分组成:
Token Embedding(词嵌入):词本身的embedding表示;
Segment Embedding (段落编码):用于区分句子对任务 中的两个句子,分别用一个可学习的向量表示。
Position Embedding(位置编码):每个词的位置编码;
(1)加入了段落编码(Segment Embedding),就是给序列也进行了位置编码。
(2)位置编码用的是可训练的位置编码(Trainable Positional Embedding)。
(3)BERT输入中一般会包含两个特殊标识:
CLS: 句首标识,一个序列的起始标志。而且这个标识的输出一般被视为其所在序列的整体输出 ,所以这个标识的输出经常被用于下游的文本分类任务。因为BERT是双向自注意力,所以将第一个token当成整个序列的输出也是可以的 ,也就是它可以被看作是专门用于汇总整个序列的语义信息的。
SEP: 句间分隔符,出现在每个句子的末尾。因为有这个标识,所以BERT既可以处理单句任务,也可以处理多句任务,非常灵活。
(4)BERT-base支持的最大序列长度是512。也就是我们一次最多只能喂入模型512个token,再长就不支持了,需要截断,短了没事,加padding直到512即可。
(5)BERT-base的词表是21128个token。
6、BERT的输出部分
BERT的输出有2部分:
一部分是编码层最后的输出层的输出 。是所有输入的tokens(512个)的输出(768维)。
另一部分是pooler层的输出。pooler层是承接CLStoken的,就是处理CLStoken的输出的。
所以,你可以根据你自己的具体下游任务类型,给BERT接入不同的任务输出头:
(1) Token-Level任务,(如命名实体识别、序列标注任务):需要使用每个位置的输出表示,所以每个位置的输出都要接入任务输出头。而任务输出头一般就是一个线性层,该回归该分类视你的具体下游任务而定。
(2) Seqence-Level任务(如文本分类、句子对分类):可以使用特殊tokenCLS的输出表示,来接入任务输出头。此时CLS就被视作是汇总整个序列语义信息的token。
小结:通过BERT预训练模型,我们可以得到每个token的语义表示,微调时,我们可以通过添加简单的任务特定层,适配不同类型的下游任务。
三、BERT的预训练和微调过程
BERT是谷歌的一个开源项目,我们从HF上下载下来的BERT,是人家谷歌,已经使用大量自然语言语料,训练好了的一个自然语言领域的预训练模型。所以,BERT是自然语言领域 的一个通用模型、预训练模型,或者说BERT就是一个自然语言处理的通用解决方案、通用的解决框架,因为它可以解决大部分的NLP任务。当你拿着BERT让它学比如法律、金融等子领域的专业知识,你只需整理好你的法律或者金融领域的特定语料,然后训练很少的参数,就是fine-tune(微调)一下,这个模型就是一个专业领域的专业语言模型了。
所以下面我们详细看看Bert是如何进行预训练的。当我们拿到预训练的bert后又是如何进行微调的。
(一)BERT是如何进行预训练的
1、BERT预训练阶段有两个任务 :
任务1:掩码语言模型 (MLM, Masked Language Modeling):就是把训练句子随机遮住 15%的token,然后让模型根据上下文去预测这15%的token。这个任务是为了让模型学习词级语义。
说明:被遮盖的这15%的token是:
80%的被替换成特殊tokenMASK;
10%被替换成随机词;
10%保持原词不变。
为什么不全部替换成MASK?这么设计是为了增强模型的泛化性,就是今后模型遇到的不是MASK这个特殊token,即使是其他随机词token,模型依然可以预测出这个位置的词。
任务2:下一句预测 (Next Sentence Prediction,NSP)
为了提升模型理解句子和句子之间的逻辑关系 ,BERT在预训练时,模型被喂入的是两个句子,让模型判断第二句是否是第一句的真实后续句子。其中:
50%的训练样本是上下文中真实相邻的句子(正例); 50%是从语料中随机采样的非相邻句子(反例)。

2、在预训练时,BERT是同时优化MLM和 NSP两个目标 ,具体操作如下:
其实,NSP其实后面接的就是pooler层,pooler层是一个线性层+tanh激活层,用来承接CLStoken的输出的,而CLStoken的输出可以看作是对所有输入的总结信息。所以可以用这个头来承接Seqence-Level任务。
重点:损失函数 是上图的两个任务头的损失加和,通过优化加和损失来训练模型的。
(二)BERT是如何进行微调的
在模型预训练完成后,BERT就可以通过微调来适配多种下游任务了,比如文本分类、句子匹配、问答系统、序列标注等。
老套路,微调时,模型主体结构保持不变,仅在顶部添加一个特定任务的输出头 ,使用少量的、带标注的、专业领域的任务数据,对整个模型进行微调训练 。不同下游任务的差异主要体现在输出层的设计 、以及从模型输出中提取哪些信息来标识预测结果。
此外,微调时的格式依旧要和BERT的要求保持一致,比如也是要以CLS为序列头标识,以SEP为句子的结尾标识。这都是基本常识。
1、句子对儿分类任务
比如判断两个句子之间是否存在重复、蕴含、矛盾等关系。
要实现上面的任务,我们可以到GLUE网站上下载对应的数据集,可以让你的模型在这些数据集上跑跑效果。
2、单句分类任务 
3、问答任务
上图是抽取式问答,还有生成式问答,后面讲T5是生成式问答。我们平时和AI助手聊天的应用,就是生成式问答。
4、序列标注任务
比如命名实体识别任务:
总之:bert模型本质上是一个分类任务的模型,然而由于各种花式分类,让bert不仅可以分类,还可以填空。
四、示例:下载bert到本地-->加载bert-->实现分类示例
由于我们想实现分类任务,而且不想写模型架构了,所以这里展示一下如何直接调用带有任务头的bert-base模型:
python
#这是下载的带任务头的BERT-base预训练模型+tokenizer的代码
from transformers import BertTokenizer, BertForSequenceClassification
model_name='bert-base-chinese'
model = BertForSequenceClassification.from_pretrained(model_name)
tokenizer = BertTokenizer.from_pretrained(model_name)
model
tokenizer
其实不用再下载一遍,其实这里下载的文件和地址都和上面的一样,都在cache路径下,都是一样的文件,只是BertForSequenceClassification这个类会帮我们自动添加一个二分类的任务头:
python
#把从本地加载BertForSequenceClassification
from transformers import BertTokenizer, BertForSequenceClassification
model_name = 'bert-base-chinese'
model_dir = r"D:\2MyWork\MyFile\pytorch-deeplearning\大模型\hf-models\bert-model\models--bert-base-chinese\snapshots\8f23c25b06e129b6c986331a13d8d025a92cf0ea"
model = BertForSequenceClassification.from_pretrained(model_dir)
tokenizer = BertTokenizer.from_pretrained(model_dir)
model
tokenizer

意思就是预训练模型:bert主体架构,是已经被训练好了的模型,但是最后的线性层(任务头)是没有被训练的,需要训练才能推理。
python
#pipeline是在线访问,我们用pipoline推理一下试试,访问成功与否取决于你的网络状况
from transformers import pipeline
classifier = pipeline('text-classification', model=model, tokenizer=tokenizer)
result = classifier('这是一首歌')
result
只是一个简单的在线实现的任务,其实很多时候我们是需要本地部署、本地推理预测的,此时我们不仅得把模型下载下来,还得用我们自己的数据集对模型进行训练,才能完成我们自己设置的任务,比如写诗啥的。下面是用我们自己数据训练的情感分类任务:
五、实战:基于本地的bert、本地的数据,实现评论的情感分析任务
说明:本例用的数据是 【NLP】第八章:项目实操案例:文本情感分析-CSDN博客 这个例子中的数据。
(一)只要BERT主体架构,我们自己手动添加适配具体任务的线性层
1、代码架构是:

2、各个模块的代码及运行结果
python
#config.py模块的代码
from pathlib import Path
ROOT_DIR = Path(__file__).parent #d:\2MyWork\MyFile\pytorch-deeplearning\大模型\BERT实战-情感分析
RAW_DATA = ROOT_DIR/'data'/'raw'
PROCESSED_DATA = ROOT_DIR/'data'/'processed'
PRETRAINED_DIR = ROOT_DIR/'pretrained'
MODELS_DIR = ROOT_DIR/'models'
SEQ_LEN = 128
BATCH_SIZE=16
LEARNING_RATE=1e-6 #预训练模型的微调,由于只调最后一个层,所以尽量把学习率设置小一点,避免梯度不稳定。
EPOCHS=1
python
#process.py模块的代码
from datasets import load_dataset, ClassLabel
from transformers import AutoTokenizer
import config
from tqdm import tqdm
def process():
print('开始处理数据。。。。')
#读取原始数据文件
raw_data = load_dataset('csv', data_files=str(config.RAW_DATA/'online_shopping_10_cats.csv'))
dataset1 = raw_data['train'] #raw_data是个字典,所以要用key把文件索引出来
#数据清洗
dataset2 = dataset1.remove_columns('cat') #删除列
dataset3 = dataset2.filter(lambda x:x['review'] is not None) #删除空行
#划分数据集
dataset4 = dataset3.cast_column('label', ClassLabel(names=['negative','positive'])) #转字段类型
dataset_train_test = dataset4.train_test_split(test_size=0.2, stratify_by_column='label')
print(dataset_train_test)
#创建Tokenizer
tokenizer = AutoTokenizer.from_pretrained(config.PRETRAINED_DIR)
#构建可以直接喂入模型的训练集和测试集
def batch_encode(batch):
inputs = tokenizer(batch['review'], padding='max_length', truncation=True,max_length=config.SEQ_LEN)
inputs['labels'] = batch['label']
return inputs
dataset_ids = dataset_train_test.map(batch_encode, batched=True, remove_columns=['review', 'label'])
#保存训练集和测试集
dataset_ids.save_to_disk(config.PROCESSED_DATA)
print('数据处理完成。。。。')
if __name__ == '__main__':
process()
python
#dataset.py模块的代码
import config
from datasets import load_from_disk
from torch.utils.data import Dataset, DataLoader
def get_dataloader(train=True):
path = str(config.PROCESSED_DATA/('train' if train else 'test'))
dataset = load_from_disk(path)
dataset.set_format(type='torch')
return DataLoader(dataset, batch_size=config.BATCH_SIZE, shuffle=True)
python
#model.py模块的代码
import config
from transformers import AutoModel
import torch
class Bert_Main(torch.nn.Module): #只用主模型,下游任务的线性层自己写
def __init__(self, ):
super().__init__()
self.bert = AutoModel.from_pretrained(config.PRETRAINED_DIR)
self.linear = torch.nn.Linear(self.bert.config.hidden_size, 1)
def forward(self, input_ids, attention_mask, token_type_ids):
output = self.bert(input_ids, attention_mask, token_type_ids)
last_hidden_state = output.last_hidden_state
cls_hidden_state = last_hidden_state[:,0,:]
yhat = self.linear(cls_hidden_state).squeeze(-1)
return yhat
python
#train.py模块的代码
import torch
from transformers import AutoTokenizer
import config, model, dataset
from tqdm import tqdm
def train():
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') #设备
train_data = dataset.get_dataloader() #数据
bert_main_model = model.Bert_Main().to(device) #模型
criterion = torch.nn.BCEWithLogitsLoss() #损失函数
optimizer = torch.optim.Adam(bert_main_model.parameters(), lr=config.LEARNING_RATE) #优化器
#开始训练
bert_main_model.train()
epoch_loss = []
best_loss = float('inf')
for epoch in range(config.EPOCHS):
print('='*10, f'Epoch: {epoch+1}', '='*10)
batch_loss=0
for batch in tqdm(train_data, desc='模型训练中。。。'):
inputs = {k : v.to(device) for k,v in batch.items()} #把张量都迁移到GPU上
labels = inputs.pop('labels').to(dtype=torch.float) #把标签拿出来
outputs = bert_main_model(**inputs) #正向传播 把除标签外的数据,以解引用的方式传给预训练模型
loss = criterion(outputs, labels) #求损失
loss.backward() #反向传播
optimizer.step() #更新网络参数
optimizer.zero_grad() #情况梯度
batch_loss += loss.item()
epoch_loss_mean = batch_loss/len(train_data)
print(f'本轮epoch的平均损失是: {epoch_loss_mean}')
epoch_loss.append(epoch_loss_mean)
if epoch_loss_mean < best_loss:
best_loss = epoch_loss_mean
torch.save(bert_main_model.state_dict(),config.MODELS_DIR/'best_model.pt')
print('模型保存成功。。。')
if __name__=='__main__':
train()

python
#predict.py模块的代码
import torch
from transformers import AutoTokenizer
import config, model
def predict(text, tokenizer, Model, device):
tokens = tokenizer(text,padding='max_length',truncation=True,max_length=config.SEQ_LEN,return_tensors='pt')
inputs = {k : v.to(device) for k,v in tokens.items()} #把张量都迁移到GPU上
Model.eval()
with torch.no_grad():
output = Model(**inputs)
yhat = torch.sigmoid(output)
predict_re = f'正向评价(置信度{yhat})' if yhat>=0.5 else f'负向评价(置信度{1-yhat})'
return predict_re
def run_predict():
print('欢迎使用BERT模型进行情感分析(输入q或者quit退出)')
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') #设备
my_tokenizer = AutoTokenizer.from_pretrained(config.PRETRAINED_DIR) #tokenizer
bert_main_model = model.Bert_Main().to(device) #加载模型
bert_main_model.load_state_dict(torch.load(config.MODELS_DIR/'best_model.pt')) #加载参数
while True:
user_input= input('请输入>>> ')
if user_input in ['q', 'quit']:
print('欢迎下次再来!')
break
if user_input.strip() == '':
print('请输入内容')
continue
predict_re = predict(user_input, my_tokenizer, bert_main_model, device)
print(predict_re)
if __name__=='__main__':
run_predict()

python
#evaluate.py模块的代码
import torch
import config, model, dataset
from tqdm import tqdm
def run_evaluate():
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') #设备
Model = model.Bert_Main().to(device) #加载模型
Model.load_state_dict(torch.load(config.MODELS_DIR/'best_model.pt')) #加载参数
print('模型加载成功!!!')
test_dataloader = dataset.get_dataloader(train=False) #测试数据
total_sample=0
predict_correct = 0
Model.eval()
with torch.no_grad():
for batch in tqdm(test_dataloader, desc='模型测试中。。。'):
inputs = {k : v.to(device) for k,v in batch.items()} #把张量都迁移到GPU上
labels = inputs.pop("labels").tolist()
output = Model(**inputs) #正向传播
output = torch.sigmoid(output)
output = output.tolist()
result = [0 if i<0.5 else 1 for i in output]
total_sample += len(output)
for yhat, y_true in zip(result, labels):
if yhat == y_true:
predict_correct += 1
print(f'模型在测试集的效果:准确率{predict_correct/total_sample:.4f}')
if __name__ == '__main__':
run_evaluate()

(二)直接调用带任务头的BERT
1、项目架构:
2、各个模块的代码及运行结果展示
python
#config.py模块的代码
from pathlib import Path
ROOT_DIR = Path(__file__).parent #d:\2MyWork\MyFile\pytorch-deeplearning\大模型\BERT实战-情感分析
RAW_DATA = ROOT_DIR/'data'/'raw'
PROCESSED_DATA = ROOT_DIR/'data'/'processed'
PRETRAINED_DIR = ROOT_DIR/'pretrained'
MODELS_DIR = ROOT_DIR/'models'
SEQ_LEN = 128
BATCH_SIZE=16
LEARNING_RATE=1e-6 #预训练模型的微调,由于只调最后一个层,所以尽量把学习率设置小一点,避免梯度不稳定。
EPOCHS=1
python
#process.py模块的代码
from datasets import load_dataset, ClassLabel
from transformers import AutoTokenizer
import config
from tqdm import tqdm
def process():
print('开始处理数据。。。。')
#读取原始数据文件
raw_data = load_dataset('csv', data_files=str(config.RAW_DATA/'online_shopping_10_cats.csv'))
dataset1 = raw_data['train'] #raw_data是个字典,所以要用key把文件索引出来
#数据清洗
dataset2 = dataset1.remove_columns('cat') #删除列
dataset3 = dataset2.filter(lambda x:x['review'] is not None) #删除空行
#划分数据集
dataset4 = dataset3.cast_column('label', ClassLabel(names=['negative','positive'])) #转字段类型
dataset_train_test = dataset4.train_test_split(test_size=0.2, stratify_by_column='label')
print(dataset_train_test)
#创建Tokenizer
tokenizer = AutoTokenizer.from_pretrained(config.PRETRAINED_DIR)
#构建可以直接喂入模型的训练集和测试集
def batch_encode(batch):
inputs = tokenizer(batch['review'], padding='max_length', truncation=True,max_length=config.SEQ_LEN)
inputs['labels'] = batch['label']
return inputs
dataset_ids = dataset_train_test.map(batch_encode, batched=True, remove_columns=['review', 'label'])
#保存训练集和测试集
dataset_ids.save_to_disk(config.PROCESSED_DATA)
print('数据处理完成。。。。')
if __name__ == '__main__':
process()
python
#dataset.py模块的代码
import config
from datasets import load_from_disk
from torch.utils.data import Dataset, DataLoader
def get_dataloader(train=True):
path = str(config.PROCESSED_DATA/('train' if train else 'test'))
dataset = load_from_disk(path)
dataset.set_format(type='torch')
return DataLoader(dataset, batch_size=config.BATCH_SIZE, shuffle=True)
python
#model.py模块的代码---只要加载一下预训练模型即可
from transformers import AutoModelForSequenceClassification
import config
import torch
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') #设备
model_full = AutoModelForSequenceClassification.from_pretrained(config.PRETRAINED_DIR).to(device)
print(model_full)
python
#train.py模块的代码
import torch
from transformers import AutoModelForSequenceClassification
import config, dataset
from tqdm import tqdm
def train():
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') #设备
train_data = dataset.get_dataloader() #数据
model_full = AutoModelForSequenceClassification.from_pretrained(config.PRETRAINED_DIR).to(device) #模型
optimizer = torch.optim.Adam(model_full.parameters(), lr=config.LEARNING_RATE) #优化器
#开始训练
model_full.train()
epoch_loss = []
best_loss = float('inf')
for epoch in range(config.EPOCHS):
print('='*10, f'Epoch: {epoch+1}', '='*10)
batch_loss=0
for batch in tqdm(train_data, desc='模型训练中。。。'):
inputs = {k : v.to(device) for k,v in batch.items()} #把张量都迁移到GPU上
outputs = model_full(**inputs) #正向传播 把除标签外的数据,以解引用的方式传给预训练模型
loss = outputs.loss
loss.backward() #反向传播
optimizer.step() #更新网络参数
optimizer.zero_grad() #情况梯度
batch_loss += outputs.loss.item()
epoch_loss_mean = batch_loss/len(train_data)
print(f'本轮epoch的平均损失是: {epoch_loss_mean}')
epoch_loss.append(epoch_loss_mean)
if epoch_loss_mean < best_loss:
best_loss = epoch_loss_mean
model_full.save_pretrained(config.MODELS_DIR) #####保存模型的api#####
print('模型保存成功。。。')
if __name__=='__main__':
train()


python
#predict.py模块的代码
import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer
import config
def predict(text, tokenizer, Model, device):
tokens = tokenizer(text,padding='max_length',truncation=True,max_length=config.SEQ_LEN,return_tensors='pt')
inputs = {k : v.to(device) for k,v in tokens.items()} #把张量都迁移到GPU上
Model.eval()
with torch.no_grad():
output = Model(**inputs)
yhat = torch.sigmoid(output.logits) ####用.logits取出结果
predict_re = f'负向评价(置信度{yhat[0][0]})' if yhat[0][0]>yhat[0][1] else f'正向评价(置信度{yhat[0][1]})'
return predict_re
def run_predict():
print('欢迎使用BERT模型进行情感分析(输入q或者quit退出)')
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') #设备
my_tokenizer = AutoTokenizer.from_pretrained(config.PRETRAINED_DIR) #tokenizer
model_full = AutoModelForSequenceClassification.from_pretrained(config.MODELS_DIR).to(device) #加载模型
while True:
user_input= input('请输入>>> ')
if user_input in ['q', 'quit']:
print('欢迎下次再来!')
break
if user_input.strip() == '':
print('请输入内容')
continue
predict_re = predict(user_input, my_tokenizer, model_full, device)
print(predict_re)
if __name__=='__main__':
run_predict()
python
#evaluate.py模块的代码
import torch
import config, dataset
from transformers import AutoModelForSequenceClassification
from tqdm import tqdm
def run_evaluate():
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') #设备
model_full = AutoModelForSequenceClassification.from_pretrained(config.MODELS_DIR).to(device) #加载模型
print('模型加载成功!!!')
test_dataloader = dataset.get_dataloader(train=False) #测试数据
total_sample=0
predict_correct = 0
model_full.eval()
with torch.no_grad():
for batch in tqdm(test_dataloader, desc='模型测试中。。。'):
inputs = {k : v.to(device) for k,v in batch.items()} #把张量都迁移到GPU上
labels = inputs.pop("labels").tolist()
output = model_full(**inputs) #正向传播
output = torch.sigmoid(output.logits)
output = output.tolist()
result = [0 if i[0]>i[1] else 1 for i in output]
total_sample += len(output)
for yhat, y_true in zip(result, labels):
if yhat == y_true:
predict_correct += 1
print(f'模型在测试集的效果:准确率{predict_correct/total_sample:.4f}')
if __name__ == '__main__':
run_evaluate()

这个结果和我们自己手动写最后线性层的结果一模一样。