视频链接:HuggingFace简明教程,BERT中文模型实战示例.NLP预训练模型,Transformers类库,datasets类库快速入门._哔哩哔哩_bilibili
import torch
from datasets import load_from_disk
from transformers import BertTokenizer, BertModel
from torch.utils.data import Dataset, DataLoader
from torch.optim import AdamW
import os
from transformers import logging
# 设置transformers模块的日志等级,减少不必要的警告,对训练过程无影响,请忽略
logging.set_verbosity_error()
# 从本地加载数据集
dataset = load_from_disk('../datasets/ChnSentiCorp/data')
#定义数据集
class Dataset(Dataset):
def __init__(self, dataset):
self.dataset = dataset
def __len__(self):
return len(self.dataset)
def __getitem__(self, i):
text = self.dataset[i]['text']
label = self.dataset[i]['label']
return text, label
train = Dataset(dataset['train'])
validation = Dataset(dataset['validation']) # 不能叫test,会与test()重名,报错
#加载字典和分词工具
token = BertTokenizer.from_pretrained('bert-base-chinese')
# 定义批处理函数
def collate_fn(data):
sents = [i[0] for i in data]
labels = [i[1] for i in data]
#编码
data = token.batch_encode_plus(batch_text_or_text_pairs=sents,
truncation=True,
padding='max_length',
max_length=500,
return_tensors='pt',
return_length=True)
#input_ids:编码之后的数字
#attention_mask:是补零的位置是0,其他位置是1
input_ids = data['input_ids']
attention_mask = data['attention_mask']
token_type_ids = data['token_type_ids']
labels = torch.LongTensor(labels)
#print(data['length'], data['length'].max())
return input_ids, attention_mask, token_type_ids, labels
#数据加载器
loader = DataLoader(dataset=train,
batch_size=16,
collate_fn=collate_fn,
shuffle=True,
drop_last=True)
for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader):
break
# print(len(loader)) # 600
# print(input_ids.shape, attention_mask.shape, token_type_ids.shape, labels) # torch.Size([16, 500]) torch.Size([16, 500]) torch.Size([16, 500]) tensor([0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1])
#加载预训练模型
pretrained = BertModel.from_pretrained('bert-base-chinese')
#不训练,不需要计算梯度
for param in pretrained.parameters():
param.requires_grad_(False)
#模型试算
# out = pretrained(input_ids=input_ids,attention_mask=attention_mask, token_type_ids=token_type_ids)
# print(out.last_hidden_state.shape) # torch.Size([16, 500, 768])
#定义下游任务模型
class Model(torch.nn.Module):
def __init__(self):
super().__init__()
self.fc = torch.nn.Linear(768, 2) # 预训练模型的隐层输出维度是768
def forward(self, input_ids, attention_mask, token_type_ids):
with torch.no_grad(): # 不改变预训练模型参数
out = pretrained(input_ids=input_ids,
attention_mask=attention_mask,
token_type_ids=token_type_ids)
out = self.fc(out.last_hidden_state[:, 0]) # 解决分类问题只需要使用到[CLS],即第一个字符
out = out.softmax(dim=1)
return out
model = Model()
#训练
optimizer = torch.optim.AdamW(model.parameters(), lr=5e-4)
criterion = torch.nn.CrossEntropyLoss()
model.train()
for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader):
out = model(input_ids=input_ids,
attention_mask=attention_mask,
token_type_ids=token_type_ids)
loss = criterion(out, labels)
loss.backward()
optimizer.step()
optimizer.zero_grad()
if i % 5 == 0:
out = out.argmax(dim=1)
accuracy = (out == labels).sum().item() / len(labels)
print(i, loss.item(), accuracy)
if i == 300: # 只训练300个batch ,300*16 = 4800条数据,即一半数据
break
# 保存模型
def save_pretrained(model, path):
# 保存模型,先利用os模块创建文件夹,后利用torch.save()写入模型文件
os.makedirs(path, exist_ok=True)
torch.save(model, os.path.join(path, 'model.pth'))
save_pretrained(model, './model/classifier2/')
#测试
def test():
model.eval()
correct = 0
total = 0
loader_test = DataLoader(dataset=validation,
batch_size=32,
collate_fn=collate_fn,
shuffle=True,
drop_last=True)
for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader_test):
with torch.no_grad(): # 测试不更改模型参数
out = model(input_ids=input_ids,
attention_mask=attention_mask,
token_type_ids=token_type_ids)
out = out.argmax(dim=1)
correct += (out == labels).sum().item()
total += len(labels)
print(correct / total) # 0.8631756756756757
test()
import torch
import torch.nn as nn
from datasets import load_from_disk
from torch.utils.data import Dataset, DataLoader
from transformers import BertTokenizer, BertModel
from transformers import logging
from torch.optim import AdamW
import os
# 设置transformers模块的日志等级,减少不必要的警告,对训练过程无影响,请忽略
logging.set_verbosity_error()
# 从本地加载数据集
dataset = load_from_disk('../datasets/ChnSentiCorp/data')
dataset_train = dataset['train']
dataset_validation = dataset['validation']
dataset_test = dataset['test']
# 定义数据集
class Dataset(Dataset): # 固定写法,至少保留下述3个函数,否则无法初始化
def __init__(self, dataset):
def f(data): # 过滤数据,只保留text长度大于30的数据
return len(data['text']) > 30
self.dataset = dataset.filter(f) # filter()过滤函数,保留符合f条件的数据
def __len__(self):
return len(self.dataset)
def __getitem__(self, i): # 该数据集中只保留text,做填空任务不需要label
text = self.dataset[i]['text']
return text
dataset = Dataset(dataset_train)
dataset_test = Dataset(dataset_test)
# print(len(dataset), dataset[0])
# 9192
# 选择珠江花园的原因就是方便,有电动扶梯直接到达海边,周围餐馆、食廊、商场、超市、摊位一应俱全。酒店装修一般,但还算整洁。 泳池在大堂的屋顶,因此很小,不过女儿倒是喜欢。 包的早餐是西式的,还算丰富。 服务吗,一般
# 加载字典和分词工具
token = BertTokenizer.from_pretrained('bert-base-chinese')
# print(token)
# PreTrainedTokenizer(name_or_path='bert-base-chinese', vocab_size=21128, model_max_len=512, is_fast=False, padding_side='right', truncation_side='right', special_tokens={'unk_token': '[UNK]', 'sep_token': '[SEP]', 'pad_token': '[PAD]', 'cls_token': '[CLS]', 'mask_token': '[MASK]'})
# 定义批处理函数
def collate_fn(data):
# 对数据集中的文本进行编码
data = token.batch_encode_plus(
batch_text_or_text_pairs=data,
truncation=True,
padding='max_length',
max_length=30,
return_tensors='pt',
return_length=True
)
# input_ids:编码之后的数据
# attention_mask:padding位置是0,其余位置是1
# token_type_ids:第一句话和特殊符号位置是0,第二句话位置是1
input_ids = data['input_ids']
attention_mask = data['attention_mask']
token_type_ids = data['token_type_ids']
# 固定把第16个词替换为mask
labels = input_ids[:, 15].reshape(-1).clone() # 获取当前batch的第16个单词作为label,并转换为行向量
input_ids[:, 15] = token.get_vocab()[token.mask_token] # 将当前batch的第16个单词替换为[MASK]
return input_ids, attention_mask, token_type_ids, labels
# 数据加载器
loader = DataLoader(
dataset=dataset,
batch_size=16,
collate_fn=collate_fn,
shuffle=True,
drop_last=True
)
# for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader): # 取出第一个batch中的数据,进行试算
# break
# 查看数据样例
# print(len(loader)) #574
# print(token.decode(input_ids[0])) #[CLS] 我 一 般 都 不 愿 意 写 点 评 的 , 但 这 [MASK] 的 是 我 入 住 过 的 五 星 级 酒 店 中 [SEP]
# print(token.decode(labels[0])) #真
# print(input_ids.shape, attention_mask.shape, token_type_ids.shape, labels.shape) #torch.Size([16, 30]) torch.Size([16, 30]) torch.Size([16, 30]) torch.Size([16])
# 加载预训练模型
pretrained = BertModel.from_pretrained('bert-base-chinese')
# 不训练,不需要计算梯度
# for param in pretrained.parameters():
# param.requires_grad_(False)
# 模型试算
# out = pretrained(
# input_ids=input_ids,
# attention_mask=attention_mask,
# token_type_ids=token_type_ids
# )
# print(out.last_hidden_state.shape) # torch.Size([16, 30, 768]) 每一个batch有16条数据,每一条数据有30个单词,每个单词的维度是768
# 定义下游任务模型
class Model(nn.Module): # 固定写法,至少需要下述2个函数,否则会报错
def __init__(self):
super().__init__()
self.decoder = nn.Linear(768, token.vocab_size, bias=False) # 因为要预测是哪个单词,所以输出维度为字典长度
self.bias = nn.Parameter(torch.zeros(token.vocab_size)) # 初始化bias为0
self.decoder.bias = self.bias
def forward(self, input_ids, attention_mask, token_type_ids):
with torch.no_grad(): # 不改变预训练模型参数,所以不需要计算梯度
out = pretrained(
input_ids=input_ids,
attention_mask=attention_mask,
token_type_ids=token_type_ids
)
out = self.decoder(out.last_hidden_state[:, 15]) # 向解码器中输入第16个字符,即要预测的字符
return out
model = Model()
# print(model(input_ids,attention_mask,token_type_ids).shape) # torch.Size([16, 21128]) 一个batch中有16条数据,每条数据预测结果的输出维度为21128,即字典长度
# 训练下游任务模型
optimizer = AdamW(model.parameters(), lr=5e-4)
criterion = nn.CrossEntropyLoss()
model.train() # 固定写法,与训练代码之间没有空行
for epoch in range(5): # 训练5个轮次
for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader):
out = model(
input_ids=input_ids,
attention_mask=attention_mask,
token_type_ids=token_type_ids
)
loss = criterion(out, labels)
loss.backward()
optimizer.step()
optimizer.zero_grad()
if i % 50 == 0: # 每50个batch输出一次结果
out = out.argmax(dim=1) # argmax()获取最大值的索引
accuracy = (out == labels).sum().item() / len(labels)
print(epoch, i, loss.item(), accuracy) # 输出训练轮次,第几个batch,loss值,训练集上的准确率
# 保存模型
def save_pretrained(model, path):
# 保存模型,先利用os模块创建文件夹,后利用torch.save()写入模型文件
os.makedirs(path, exist_ok=True)
torch.save(model, os.path.join(path, 'model.pth'))
save_pretrained(model, './model/fillBlanks/')
# 测试
def test():
model.eval() # 固定写法,与测试代码之间没有空行
correct = 0
total = 0
loader_test = DataLoader(
dataset=dataset_test,
batch_size=32,
collate_fn=collate_fn,
shuffle=True,
drop_last=True
)
for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader_test):
with torch.no_grad():
out = model(
input_ids=input_ids,
attention_mask=attention_mask,
token_type_ids=token_type_ids
)
out = out.argmax(dim=1) # 维度为batch_size
correct += (out == labels).sum().item() # sum()获取相等的个数
total += len(labels)
# 输出当前batch的第一条数据的预测结果
print(token.decode(input_ids[0]))
print(token.decode(labels[0]), token.decode(out[0])) # 真实标签 预测结果
print(correct / total) # 0.6839285714285714
test()
import torch
import torch.nn as nn
from datasets import load_from_disk
from torch.utils.data import Dataset, DataLoader
from transformers import BertTokenizer, BertModel
from transformers import logging
from torch.optim import AdamW
import os
import random
# 设置transformers模块的日志等级,减少不必要的警告,对训练过程无影响,请忽略
logging.set_verbosity_error()
# 从本地加载数据集
dataset = load_from_disk('../datasets/ChnSentiCorp/data')
dataset_train = dataset['train']
dataset_validation = dataset['validation']
dataset_test = dataset['test']
# 定义数据集
class Dataset(Dataset):
def __init__(self, dataset):
def f(data):
return len(data['text']) > 40
self.dataset = dataset.filter(f) # 只保留text长度大于40的文本数据
def __len__(self):
return len(self.dataset)
def __getitem__(self, i):
text = self.dataset[i]['text']
# 将一句话切分为前半句和后半句
sentence1 = text[:20]
sentence2 = text[20:40]
label = 0 # 两个句子相邻
# 有一半概率将后半句话替换为一句无关的话
if random.randint(0, 1) == 0:
j = random.randint(0, len(self.dataset)-1) # 随机获取数据集中数据的索引
sentence2 = self.dataset[j]['text'][20:40] # 将后半句话替换为随机句子的后半句
label = 1 # 两个句子不相邻
return sentence1, sentence2, label
# 查看数据样例
dataset = Dataset(dataset_train)
dataset_test = Dataset(dataset_test)
# print(len(dataset)) # 8001
# print(dataset[0]) # ('选择珠江花园的原因就是方便,有电动扶梯直', '接到达海边,周围餐馆、食廊、商场、超市、', 0)
# 加载字典和分词工具
token = BertTokenizer.from_pretrained('bert-base-chinese')
# 定义批处理函数
def collate_fn(data):
sents = [i[:2] for i in data]
labels = [i[2] for i in data]
# 对文本数据进行编码
data = token.batch_encode_plus(
batch_text_or_text_pairs=sents, # 编码一对一对的句子
truncation=True,
padding='max_length',
max_length=45,
return_tensors='pt',
return_length=True,
add_special_tokens=True
)
# input_ids:编码之后的数据
# attention_mask:padding位置是0,其余位置是1
# token_type_ids:第一句话和特殊符号位置是0,第二句话位置是1
input_ids = data['input_ids']
attention_mask = data['attention_mask']
token_type_ids = data['token_type_ids']
labels = torch.LongTensor(labels)
return input_ids, attention_mask, token_type_ids, labels
# 数据加载器
loader = DataLoader(
dataset=dataset,
batch_size=8,
collate_fn=collate_fn,
shuffle=True,
drop_last=True
)
# for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader):
# break
# 查看数据样例
# print(len(loader)) # 1000
# print(token.decode(input_ids[0])) # [CLS] 这 本 书 的 版 本 一 般 , 不 敢 贸 然 说 是 盗 版 的 , 但 [SEP] 设 施 比 较 老 旧 , 服 务 不 好 , 我 两 次 将 洗 发 液 忘 [SEP] [PAD] [PAD]
# print(input_ids.shape, attention_mask.shape, token_type_ids.shape, labels.shape) # torch.Size([8, 45]) torch.Size([8, 45]) torch.Size([8, 45]) torch.Size([8])
# 加载预训练模型
pretrained = BertModel.from_pretrained('bert-base-chinese')
# 定义下游任务模型
class Model(nn.Module):
def __init__(self):
super().__init__()
self.fc = nn.Linear(768, 2)
def forward(self, input_ids, attention_mask, token_type_ids):
with torch.no_grad():
out = pretrained(
input_ids=input_ids,
attention_mask=attention_mask,
token_type_ids=token_type_ids
)
out = self.fc(out.last_hidden_state[:, 0])
out = out.softmax(dim=1)
return out
model = Model()
# 训练下游任务模型
optimizer = AdamW(model.parameters(), lr=5e-4)
criterion = nn.CrossEntropyLoss()
model.train()
for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader):
out = model(
input_ids=input_ids,
attention_mask=attention_mask,
token_type_ids=token_type_ids
)
loss = criterion(out, labels)
loss.backward()
optimizer.step()
optimizer.zero_grad()
if i % 5 == 0: # 每5个batch输出一次结果
out = out.argmax(dim=1)
accuracy = (out == labels).sum().item() / len(labels)
print(i, loss.item(), accuracy)
if i == 300: # 训练300个batch
break
# 保存模型
def save_pretrained(model, path):
# 保存模型,先利用os模块创建文件夹,后利用torch.save()写入模型文件
os.makedirs(path, exist_ok=True)
torch.save(model, os.path.join(path, 'model.pth'))
save_pretrained(model, './model/isNext/')
# 测试
def test():
model.eval()
correct = 0
total = 0
loader_test = DataLoader(
dataset=dataset_test,
batch_size=32,
collate_fn=collate_fn,
shuffle=True,
drop_last=True
)
for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(loader_test):
with torch.no_grad():
out = model(
input_ids=input_ids,
attention_mask=attention_mask,
token_type_ids=token_type_ids
)
pred = out.argmax(dim=1)
correct += (pred == labels).sum().item()
total += len(labels)
print(correct / total) # 0.8583333333333333
test()