bert文本分类任务单卡训练VS使用deepspeed框架进行多卡训练(自用、未完结)
·
一:使用单卡进行bert文本分类的代码
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
from transformers import BertModel, BertTokenizer
#读数据
def read_data(file_name, num=180000):
with open(file_name, encoding="utf-8") as f:
all_data = f.read().split("\n")[:-1][:num]
all_text = []
all_label = []
for text in all_data:
text_, label_ = text.split("\t")
all_text.append(text_)
all_label.append(int(label_))
return all_text, all_label
#定义数据集
class MyDataset(Dataset):
def __init__(self, all_text, all_label):
super().__init__()
self.all_text = all_text
self.all_label = all_label
def __getitem__(self, index):
text = self.all_text[index]
label = self.all_label[index]
text_index = tokenizer.encode(text, truncation=True, padding="max_length", max_length=32)
return torch.tensor(text_index), torch.tensor(label)
def __len__(self):
return len(self.all_label)
# 模型类
class BertClassifier(nn.Module):
def __init__(self, class_num):
super().__init__()
# 加载预训练的bert模型
self.bert = BertModel.from_pretrained("/23085404007/model/bert_base_chinese")
# 冻结bert中的参数,不参与训练(可选)
# for name, param in self.bert.named_parameters():
# param.requires_grad = False
self.classifier = nn.Linear(768, class_num)
self.loss_fun = nn.CrossEntropyLoss()
def forward(self, batch_text, batch_label=None):
# attention_mask=batch_text>0 表示不关注padding的部分,因为padding为0
bert_out = self.bert.forward(batch_text, attention_mask=batch_text>0)
# bert[0]是文本的字向量信息,bert[1]是文本的句向量信息
# bert[0]维度:(句子数/batch_size, 字数, 维度数768)
# bert[1]维度:(句子数/batch_size, 维度数768)
bert_out1, bert_out2 = bert_out[0], bert_out[1]
pre = self.classifier(bert_out2)
if batch_label is not None:
loss = self.loss_fun(pre, batch_label)
return loss
else:
return torch.argmax(pre, dim=-1)
if __name__ == "__main__":
"""
这一块是处理数据的
"""
# 读数据
train_text, train_label = read_data("../data/train.txt")
dev_text, dev_label = read_data("../data/dev.txt")
# 加载预训练的bert分词器,用来给MyDataset类中的getitem函数进行文本的编码
tokenizer = BertTokenizer.from_pretrained("/23085404007/model/bert_base_chinese")
#定义超参数
batch_size = 1000
class_num = len(set(train_label))
epoch = 20
lr = 0.0001
device = "cuda" if torch.cuda.is_available() else "cpu"
# 制作训练集、验证集的数据集和数据加载器
train_dataset = MyDataset(train_text, train_label)
train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=False)
dev_dataset = MyDataset(dev_text, dev_label)
dev_dataloader = DataLoader(dev_dataset, batch_size=10, shuffle=False)
"""
这一块是定义模型、优化器
"""
# 定义模型
model = BertClassifier(class_num).to(device)
# 定义优化器
optim = torch.optim.AdamW(model.parameters(), lr)
"""
这一块是训练的过程
"""
for e in range(epoch):
model.train()
for batch_i, (batch_text, batch_label) in enumerate(train_dataloader):
batch_text = batch_text.to(device)
batch_label = batch_label.to(device)
loss = model.forward(batch_text, batch_label)
loss.backward()
optim.step()
optim.zero_grad()
if batch_i % 30 == 0:
print(f'Loss:{loss:.2f}')
# 每一个epoch进行验证集上的准确率验证
right = 0
model.eval()
for batch_text, batch_label in dev_dataloader:
batch_text = batch_text.to(device)
batch_label = batch_label.to(device)
pre = model.forward(batch_text)
right += int(sum(pre == batch_label))
print(f"acc={right / len(dev_text) * 100} %")
二:使用deepspeed进行单机多卡训练
2.1 deepspeed的使用demo:deepspeed的使用demo(自用、更新)-CSDN博客
2.2 需要准备的三部分文件:1.项目文件(例如:main.py)2.deepspeed的配置文件(deepspeed.json)3.运行deepspeed的脚本文件
2.3 使用deepspeed框架进行bert文本分类项目的代码:
import os
import torch
from torch.utils.data import DataLoader, Dataset
from transformers import BertTokenizer, BertModel
import torch.nn as nn
import deepspeed
import argparse
def read_data(file):
with open(file, 'r', encoding='utf-8') as f:
texts = []
labels = []
all_data = f.read().split('\n')
for data in all_data:
if len(data.split('\t')) != 2:
continue
text, label = data.split('\t')
texts.append(text)
labels.append(int(label))
return texts, labels
class MyDataset(Dataset):
def __init__(self, texts, labels):
super().__init__()
self.texts = texts
self.labels = labels
def __getitem__(self, index):
text = self.texts[index]
label = self.labels[index]
text_idx = tokenizer.encode(text, truncation=True, padding='max_length', max_length=32)
return torch.tensor(text_idx), torch.tensor(label)
def __len__(self):
return len(self.texts)
class BertClassifier(nn.Module):
def __init__(self, class_num):
super().__init__()
self.bert = BertModel.from_pretrained('/root/autodl-tmp/model/bert_base_chinese')
self.classifier = nn.Linear(768, class_num)
self.loss_fun = nn.CrossEntropyLoss()
def forward(self, batch_texts, batch_labels=None):
bert_out = self.bert.forward(batch_texts, attention_mask=batch_texts > 0)
bert_out1, bert_out2 = bert_out[0], bert_out[1]
pre = self.classifier(bert_out2)
if batch_labels is not None:
loss = self.loss_fun(pre, batch_labels)
return loss
else:
return torch.argmax(pre)
def parse_arg():
# 定义一个参数解析器
parser = argparse.ArgumentParser(description="deepspeed training argparser")
# 添加参数
parser.add_argument("--local_rank", type=int, help="use GPU num", default=-1)
# 将参数解析器交给deepspeed进行管理
parser = deepspeed.add_config_arguments(parser=parser)
# 解析命令行参数
args = parser.parse_args()
return args
if __name__ == "__main__":
args = parse_arg()
train_text, train_label = read_data(os.path.join("..", "bert_deepspeed", "data", "train.txt"))
dev_text, dev_label = read_data(os.path.join("..", "bert_deepspeed", "data", "dev.txt"))
tokenizer = BertTokenizer.from_pretrained('/root/autodl-tmp/model/bert_base_chinese')
train_dataset = MyDataset(train_text, train_label)
dev_dataset = MyDataset(dev_text, dev_label)
epoch = 100
batch_size = 1000
lr = 0.001
class_num = len(set(train_label))
# device = "cuda" if torch.cuda.is_available() else "cpu"
# train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
dev_dataloader = DataLoader(dev_dataset, batch_size=batch_size, shuffle=False)
model = BertClassifier(class_num)
# opt = torch.optim.AdamW(model.parameters(), lr=lr)
# 初始化deepspeed,将解析器参数、模型、模型参数、训练集交给deepspeed管理
new_model, optimizer, train_dataloader, lr_scheduler = deepspeed.initialize(args=args, model=model, model_parameters=model.parameters(), training_data=train_dataset)
for e in range(epoch):
new_model.train()
for batch_i, (batch_data, batch_label) in enumerate(train_dataloader):
batch_data = batch_data.to(new_model.device)
batch_label = batch_label.to(new_model.device)
loss = new_model.forward(batch_data, batch_label)
# loss.backward()
# opt.step()
# opt.zero_grad()
new_model.backward(loss)
new_model.step()
if batch_i % 30 == 0:
print(f'Loss:{loss:.2f}')
new_model.eval()
right = 0
for batch_text, batch_label in dev_dataloader:
batch_text = batch_text.to(new_model.device)
batch_label = batch_label.to(new_model.device)
pre = new_model.forward(batch_text)
right += int(sum(pre == batch_label))
print(f"acc:{right / len(dev_text) * 100} %")
2.3.1 使用deepspeed前后,项目文件中部分代码的区别
- 使用deepspeed时,要初始化deepspeed,将解释器参数、模型、模型参数、训练集交给deepspeed进行管理。调用初始化方法后,会返回新的模型、优化器、训练集的数据加载器、学习率策略。
# 初始化deepspeed,将解析器参数、模型、模型参数、训练集交给deepspeed管理 new_model, optimizer, train_dataloader, lr_scheduler = deepspeed.initialize(args=args, model=model, model_parameters=model.parameters(), training_data=train_dataset) - 因为初始化deepspeed之后模型会返回新的模型、优化器、数据加载器和学习策略,所以在此之前就不用定义优化器、数据加载器和学习率了。(模型还是需要定义的,因为deepspeed初始化时,需要传入模型及模型参数,但是device可以不用定义了,因为后面会使用到返回的新模型的new_model.device)下面这些代码就可以注释掉了。
# device = "cuda" if torch.cuda.is_available() else "cpu" # lr = 0.001 # train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True) # opt = torch.optim.AdamW(model.parameters(), lr=lr) - 因为deepspeed会返回新的模型new_model,并且新的模型中有使用的设备信息,所以之前代码中的model将换成new_model。之前的device将换成new_model.device.
# 更换前使用模型 model.train() model.eval() loss = model.forward(batch_data, batch_label) # 更换后使用模型 new_model.train() new_model.eval() loss = new_model.forward(batch_data, batch_label) # 更换前使用设备 model = BertClassifier(class_num).to(device) batch_data = batch_data.to(device) batch_label = batch_label.to(device) # 更换后使用设备 model = BertClassifier(class_num) # 这里模型不用指定设备,传给deepspeed初始化后,返回的新模型会带有设备的信息 batch_data = batch_data.to(new_model.device) batch_label = batch_label.to(new_model.device) - 使用deepspeed前后的loss反向传播和梯度更新都有区别
# 使用deepspeed前 loss = model.forward(batch_data, batch_label) loss.backward() opt.step() opt.zero_grad() # 使用deepapeed后 loss = new_model.forward(batch_data, batch_label) # loss.backward() # opt.step() # opt.zero_grad() new_model.backward(loss) # 反向传播时,使用new_model.backward()函数,然后将loss传入 new_model.step() # 更新梯度时,使用new_model.step()
2.4 脚本文件的编写
export CUDA_VISIBLE_DEVICES=0,1 # 想要使用的显卡id
deepspeed bert_文本分类_deepspeed --deepspeed --deepspeed_config deepspeed.json
2.5 配置文件(deepspeed.json)的编写
三:使用deepspeed进行多机多卡的配置
在上面三个文件的基础上,再加上一个多机器的配置文件(假设文件名为abc),文件内写上机器的ip地址,以及可用的显卡数量。如果ip访问不了,需要将要访问的ip公钥告诉服务器(上网查找方法)。
10.153.205.160 slots=2
其次,还要修改脚本文件(run.sh)中的内容,让它识别到多机器的配置文件。
# 单机多卡配置
deepspeed bert_文本分类_deepspeed.py --deepspeed --deepspeed_config deepspeed.json
# 多机多卡配置
deepspeed --hostfile abc bert_文本分类_deepspeed.py --deepspeed --deepspeed_config deepspeed.json
更多推荐


所有评论(0)