深度学习--Bert
·
使用Bert模型进行文字分类。
一、数据处理
读取数据,得到训练集和验证集,并获得对应的加载器。
1、read_file函数,读文件,得到真实标签和数据内容
def read_file(path):
data = []#列表
label = []
with open(path, "r", encoding="utf-8") as f:
for i, line in enumerate(f):#行号、行内数据
if i == 0:#第一行数据无用
continue
if i > 200 and i< 11000:#只取1-200和7500-7767行数据(包含序号为0/1,表示错误或正确)
continue
line = line.strip("\n")#移除字符串末尾的换行符
line = line.split(",", 1) #把这句话,按照,分割, 1表示分割次数
data.append(line[1])#将符号后面的部分加到line列表第二个元素(数据内容)
label.append(line[0])#将符号前面部分加到line列表第一个元素(序号)
print("读了%d的数据"%len(data))
return data, label
除去文件第一行的字段名,从纯数据中读文件,文件内容太多。将标签为0//1的数据都取一部分。
并由line.split()函数根据逗号实现分割,将标签和数据分开存放。
2、数据集
class wmDataset(Dataset):
def __init__(self, data, label):
self.X = data
self.Y = torch.LongTensor([int(i) for i in label])
def __getitem__(self, item):
return self.X[item], self.Y[item]
def __len__(self):
return len(self.Y)
将read_file函数得到的数据和标签在实例化的时候,自动调用初始化函数,保存为实例属性,在之后训练测试时使用。
__getitem__()函数可以通过dataset[i]获得第i个文本
__len__()得到标签长度
3、加载器
def get_data_loader(path, batchsize, val_size=0.2): #读入数据,分割数据。
data, label = read_file(path)#数据和序号
train_x, val_x, train_y, val_y = train_test_split(data, label, test_size=val_size, shuffle=True, stratify=label)
train_set = wmDataset(train_x, train_y)#训练集
val_set = wmDataset(val_x, val_y)#测试集
train_loader = DataLoader(train_set, batchsize, shuffle=True)#训练加载器
val_loader = DataLoader(val_set, batchsize, shuffle=True)#测试加载器
return train_loader, val_loader
二、模型
class myBertModel(nn.Module):
def __init__(self, bert_path, num_class, device):
super(myBertModel, self).__init__()
self.bert = BertModel.from_pretrained(bert_path)#加载模型和预训练好的参数
# config = BertConfig.from_pretrained(bert_path)#仅加载模型,不加载参数
# self.bert = BertModel(config)#加载自己的参数
self.device = device#将设备信息保存为当前类实例化属性
self.cls_head = nn.Linear(768, num_class)#分类头
self.tokenizer = BertTokenizer.from_pretrained(bert_path)#加载与Bert模型相匹配的分词器
def forward(self, text):
input = self.tokenizer(text, return_tensors="pt", truncation=True, padding="max_length", max_length=128)
#对输入文本进行处理,将其转化为模型要求的输入格式
#return_tensors="pt"要求返回值为张量、truncation=True自动截断超过max_length的文本(有分类头和逗号),padding若不够则输出张量自动填充至max_length
#分词后会化成input_ids、token_type_ids、attention_mask三个字段
#多头自注意力机制(Multi - HeadSelf - Attention)
#残差连接 + LayerNorm
#前馈神经网络(Feed - ForwardNetwork)(FFN)
#残差连接 + LayerNorm
input_ids = input["input_ids"].to(self.device)
token_type_ids = input['token_type_ids'].to(self.device)
attention_mask = input['attention_mask'].to(self.device)
sequence_out, pooler_out = self.bert(input_ids=input_ids,#序列输出(自注意力和前馈神经网络的最后一次输出)和池化输出
token_type_ids=token_type_ids,
attention_mask=attention_mask,
return_dict=False) #返回元组,便于解包
#position_ids(隐式生成),没传,bert也会自动生成
#input_ids、token_type_ids、position_ids为batchsize*128通过词嵌入表、段嵌入表、位置嵌入表(Embedding)后相加变为16*128*768
pred = self.cls_head(pooler_out)#16*768->16*2
return pred
三、训练
import torch
import time
import matplotlib.pyplot as plt
import numpy as np
from tqdm import tqdm
import matplotlib
matplotlib.use('TkAgg')
def train_val(para):
########################################################
model = para['model']
train_loader =para['train_loader']
val_loader = para['val_loader']
scheduler = para['scheduler']
optimizer = para['optimizer']
loss = para['loss']
epoch = para['epoch']
device = para['device']
save_path = para['save_path']
max_acc = para['max_acc']
val_epoch = para['val_epoch']
#################################################
plt_train_loss = []
plt_train_acc = []
plt_val_loss = []
plt_val_acc = []
val_rel = []
for i in range(epoch):
start_time = time.time()
model.train()
train_loss = 0.0
train_acc = 0.0
val_acc = 0.0
val_loss = 0.0
for batch in tqdm(train_loader):#batch为train_loader中的dataset对象,一次batchsize个
optimizer.zero_grad()#梯度清0,是防止梯度累加,毕竟是相乘,如果下一次的梯度还带有上一次会越来越大,例如每次称重前归0
text, labels = batch[0], batch[1].to(device)
pred = model(text)#得到预测值
bat_loss = loss(pred, labels)
bat_loss.backward()#反向回传
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)#梯度裁切,梯度太大变为1,给称重设最大限度
optimizer.step()#更新参数
scheduler.step() #scheduler 调整学习率
train_loss += bat_loss.item() #.detach 表示去掉梯度
train_acc += np.sum(np.argmax(pred.cpu().data.numpy(),axis=1)== labels.cpu().numpy())#得到预测的标签总值
plt_train_loss . append(train_loss/train_loader.dataset.__len__())
plt_train_acc.append(train_acc/train_loader.dataset.__len__())#得到准确率
if i % val_epoch == 0:#每val_epoch验证一次
model.eval()
with torch.no_grad():
for batch in tqdm(val_loader):#batch为val_loader中的dataset对象
val_text, val_labels = batch[0], batch[1].to(device)
val_pred = model(val_text)
val_bat_loss = loss(val_pred, val_labels)
val_loss += val_bat_loss.cpu().item()
val_acc += np.sum(np.argmax(val_pred.cpu().data.numpy(), axis=1) == val_labels.cpu().numpy())
val_rel.append(val_pred)
if val_acc > max_acc:#保存最佳模型
torch.save(model, save_path+str(epoch)+"ckpt")
max_acc = val_acc
plt_val_loss.append(val_loss/val_loader.dataset.__len__())
plt_val_acc.append(val_acc/val_loader.dataset.__len__())
print('[%03d/%03d] %2.2f sec(s) TrainAcc : %3.6f TrainLoss : %3.6f | valAcc: %3.6f valLoss: %3.6f ' % \
(i, epoch, time.time()-start_time, plt_train_acc[-1], plt_train_loss[-1], plt_val_acc[-1], plt_val_loss[-1])
)
if i % 50 == 0:
torch.save(model, save_path+'-epoch:'+str(i)+ '-%.2f'%plt_val_acc[-1])
else:
plt_val_loss.append(plt_val_loss[-1])
plt_val_acc.append(plt_val_acc[-1])
print('[%03d/%03d] %2.2f sec(s) TrainAcc : %3.6f TrainLoss : %3.6f ' % \
(i, epoch, time.time()-start_time, plt_train_acc[-1], plt_train_loss[-1])
)
plt.plot(plt_train_loss)
plt.plot(plt_val_loss)
plt.title('loss')
plt.legend(['train', 'val'])
plt.show()
plt.plot(plt_train_acc)
plt.plot(plt_val_acc)
plt.title('Accuracy')
plt.legend(['train', 'val'])
plt.savefig('acc.png')
plt.show()
更多推荐
所有评论(0)