一:使用单卡进行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

更多推荐