最近在学习pytorch和Bert,所以做了一个这样完全新手向的入门项目来练习。
由于之前在网上学习发现现存的教程比较少,所以记录一下自己的学习过程,加深印象,也希望能帮到别的学习者吧,能涨粉丝就更好了。
项目模块化之类的做的并不好,感觉机器学习项目那么小没啥分包的必要,分了一堆包只会降低代码的可读性,反而不利于新手学习(其实是懒得分)
数据集就是谷歌官方在bert教程中提供得一个GLUE数据集,名字叫MRPC
下载地址:https://github.com/google-research/bert
任务目标就是判断两句输入是否是同义句子。
很简单的结构,下面会尽量分模块详细说明。
如果不想看模块,最后会直接把三个文件的内容都发出来供参考。
from torch.utils.data import Dataset
from itertools import islice
class MRPCDataset(Dataset):
def __init__(self):
file = open('data/msr_paraphrase_train.txt', 'r', encoding="utf-8")
data = []
for line in islice(file, 1, None):
content = line.split(sep='\t')
data.append(content)
self.data = data
def __getitem__(self, index):
#把标签从char转成int,不然做损失的时候会报错
if self.data[index][0] == '0':
label = 0
else:
label = 1
#这里要手动拼接句子,中间加一个拼接token
sentences = self.data[index][3] + '[SEP]' + self.data[index][4]
return sentences, label
def __len__(self):
return len(self.data)
Dataset就是pytorch封装的用来加载数据的接口,将其继承后并重写__getitem__方法就可以实现一个自定义的读取器。
似乎不用dataset类也能自己实现读取器,但是感觉官方的应该会有加载速度的优化,所以还是选了官方的。
from transformers import BertTokenizer, BertModel
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
bert_model = BertModel.from_pretrained("bert-base-uncased")
bert_model.to(device)
因为谷歌官方用的是tensorflow的模型,这里使用的是hugging face提供的接口进行的加载的(民间SOTA)。
还挺好用的,两行就把预训练的模型加载进来了。
import torch
class FCModel(torch.nn.Module):
def __init__(self):
super(FCModel, self).__init__()
self.fc = torch.nn.Linear(in_features=768, out_features=1)
def forward(self, input):
score = self.fc(input)
result = torch.sigmoid(score)
return result
这里是把模型封装成类并继承了Module,这样比较方便后面的换GPU、传参数等操作。其实模型很简单,就是一个全连接+sigmoid
#定义优化器&损失函数
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
bert_optimizer = torch.optim.Adam(bert_model.parameters(), lr=0.00001)
crit = torch.nn.BCELoss()
这里就是定义优化器和损失函数,但是有一个巨大的坑。
全连接层和bert模型的学习率是不一样的,因为bert只需要微调,所以学习率应该很低才对。这也是为什么要把全连接层独立封装出来。
开始不懂这一点,调了一天也没收敛,后来多亏大佬指点。
#定义训练方法
def train():
#记录统计信息
epoch_loss, epoch_acc = 0., 0.
total_len = 0
#分batch进行训练
for i, data in enumerate(train_loader):
bert_model.train()
model.train()
#获取训练数据
sentence, label = data
label = label.cuda()
#数据喂给模型,并把模型拼起来
encoding = tokenizer(sentence, return_tensors='pt', padding=True, truncation=True)
bert_output = bert_model(**encoding.to(device))
pooler_output = bert_output.pooler_output
predict = model(pooler_output).squeeze()
#计算损失和准确度
loss = crit(predict, label.float())
acc = binary_accuracy(predict, label) #自定义方法
#gd
optimizer.zero_grad() #把梯度重置为零
bert_optimizer.zero_grad()
loss.backward() #求导
optimizer.step() #更新模型
bert_optimizer.step()
epoch_loss += loss * len(label)
epoch_acc += acc * len(label)
total_len += len(label)
print("batch %d loss:%f accuracy:%f" % (i, loss, acc))
return epoch_loss/total_len, epoch_acc/total_len
这里就都是一些常规的操作:喂数据、记录训练信息、求导、更新…
因为只是截取部分源码,所以会出现一些没声明过的变量,比如model啥的,最好配合完整代码看。
#开始训练
Num_Epoch = 3
index = 0
for epoch in range(Num_Epoch):
epoch_loss, epoch_acc = train()
index += 1
print("EPOCH %d loss:%f accuracy:%f" % (index, epoch_loss, epoch_acc))
这里也没什么好说的,就是定一下训练轮次。
和训练模块大同小异
因为在图书馆,跑代码机器会发出很大的噪音影响别的同学学习,所以就不跑一遍再截图了。(其实也是懒得跑)
我之前自己跑3轮训练,准确率能到达88%左右,提供参考。
(谷歌官方教程里也说这个数据集差不多也就跑90%,不用太较真)
import torch
from torch.utils.data import DataLoader
from FCModel import FCModel
from MRPCDataset import MRPCDataset
from transformers import BertTokenizer, BertModel
#载入数据预处理模块
mrpcDataset = MRPCDataset()
train_loader = DataLoader(dataset=mrpcDataset, batch_size=32, shuffle=True)
print("数据载入完成")
#设置运行设备
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print("设备配置完成")
#加载bert模型
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
bert_model = BertModel.from_pretrained("bert-base-uncased")
bert_model.to(device)
print("bert层模型创建完成")
#创建模型对象
model = FCModel()
model = model.to(device)
print("全连接层模型创建完成")
#定义优化器&损失函数
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
bert_optimizer = torch.optim.Adam(bert_model.parameters(), lr=0.001)
crit = torch.nn.BCELoss()
#计算准确率的公式
def binary_accuracy(predict, label):
rounded_predict = torch.round(predict)
correct = (rounded_predict == label).float()
accuracy = correct.sum() / len(correct)
return accuracy
#定义训练方法
def train():
#记录统计信息
epoch_loss, epoch_acc = 0., 0.
total_len = 0
#分batch进行训练
for i, data in enumerate(train_loader):
bert_model.train()
model.train()
sentence, label = data
label = label.cuda()
encoding = tokenizer(sentence, return_tensors='pt', padding=True, truncation=True)
bert_output = bert_model(**encoding.to(device))
pooler_output = bert_output.pooler_output
predict = model(pooler_output).squeeze()
loss = crit(predict, label.float())
acc = binary_accuracy(predict, label)
#gd
optimizer.zero_grad() #把梯度重置为零
bert_optimizer.zero_grad()
loss.backward() #求导
optimizer.step() #更新模型
bert_optimizer.step()
epoch_loss += loss * len(label)
epoch_acc += acc * len(label)
total_len += len(label)
print("batch %d loss:%f accuracy:%f" % (i, loss, acc))
return epoch_loss/total_len, epoch_acc/total_len
#开始训练
Num_Epoch = 3
index = 0
for epoch in range(Num_Epoch):
epoch_loss, epoch_acc = train()
index += 1
print("EPOCH %d loss:%f accuracy:%f" % (index, epoch_loss, epoch_acc))
from torch.utils.data import Dataset
from itertools import islice
class MRPCDataset(Dataset):
def __init__(self):
file = open('data/msr_paraphrase_train.txt', 'r', encoding="utf-8")
data = []
for line in islice(file, 1, None):
content = line.split(sep='\t')
data.append(content)
self.data = data
def __getitem__(self, index):
if self.data[index][0] == '0':
label = 0
else:
label = 1
sentences = self.data[index][3] + '[SEP]' + self.data[index][4]
return sentences, label
def __len__(self):
return len(self.data)
import torch
class FCModel(torch.nn.Module):
def __init__(self):
super(FCModel, self).__init__()
self.fc = torch.nn.Linear(in_features=768, out_features=1)
def forward(self, input):
score = self.fc(input)
result = torch.sigmoid(score)
return result