迁移学习文本分类的案例

中文文本分类的案例

实现流程

properties 复制代码
1.获取数据集
2.数据预处理: 实例化dataset,dataloader,注意这里面在dataloder里用了自定义函数进行文本张量化处理
3.搭建模型: 注意,我们这里用bert预训练模型来得到文本的特征表示,然后在经过自定义网络实现分类
4.模型训练: 注意,不训练bert模型的参数,只更新自己定义的网络参数
5.模型测试: 注意,如果在GPU上训练的模型,想在CPU上使用, model.load_state_dict(torch.load(path, map_location="cpu")) #将模型放到cpu上

1 数据预处理

1.1 得到Dataset对象

properties 复制代码
我们这里是直接应用的datasets第三方库里面的load_dataset的方法,可以直接返回DataSet数据源对象

代码实现

python 复制代码
def  dm_file2dataset():
    # 1.加载训练数据集
    train_dataset = load_dataset('csv', data_files="./data/train.csv", split="train")
    # 1.1第二种加载方式
    # train_dataset = load_dataset(path='./data', data_files="train.csv", split="train")
    print(f'训练数--》{train_dataset[0]}')
    # print(f'训练数据取出前三行数据---》{train_dataset[:3]}')
    # 2.加载测试数据集
    test_dataset = load_dataset('csv', data_files="./data/test.csv", split="train")
    # print(f'test_dataset--》{test_dataset}')
    # print(f'测试数据取出前三行数据---》{test_dataset[:3]}')
    # 3.加载验证数据集
    valid_dataset = load_dataset('csv', data_files="./data/validation.csv", split="train")
    # print(f'valid_dataset--》{valid_dataset}')
    # print(f'验证数据取出前三行数据--》{valid_dataset[:3]}')
    return train_dataset, test_dataset, valid_dataset

1.2 实现自定义函数

properties 复制代码
在Dataloader里面自动调用,目的是处理dataset里面的数据,进行张量化处理

代码实现

python 复制代码
def collate_fn1(data):
    '''
    data:是从dataset里面获取的数据.类型为list,[第一个样本,第二样本,。。。]
# data传过来的数据是list eg: 批次数8,8个字典
    # [{'text':'xxxx','label':0} , {'text':'xxxx','label':1}, ...]
    '''
    # print(f'data-->{len(data)}')
    # print(f'data[0]-->{data[0]}')
    sents = [item["text"] for item in data]
    labels = [item["label"] for item in data]
    inputs = my_pre_tokenizer.batch_encode_plus(sents, truncation=True,
                                            max_length=300, padding="max_length",
                                            return_tensors='pt',
                                            return_length=True)
    # print(f'inputs-------》{inputs}')
    # print(f'inputs["input_ids"]-------》{inputs["input_ids"].shape}')
    inputs_ids = inputs["input_ids"]
    token_type_ids = inputs["token_type_ids"]
    attention_mask = inputs["attention_mask"]
    labels = torch.LongTensor(labels)
    return inputs_ids, token_type_ids, attention_mask, labels

1.3 得到Dataloader

代码实现

python 复制代码
def get_dataloader():
    # 1.获得dataset的数据源对象
    train_dataset = load_dataset('csv', data_files="./data/train.csv", split="train")
    # 2.实例化dataloader
    my_dataloader = DataLoader(dataset=train_dataset, batch_size=8, shuffle=True,
                                collate_fn=collate_fn1, drop_last=True)
    # # print(f'my_dataloader-->{len(my_dataloader)}')
    # for inputs_ids, token_type_ids, attention_mask, labels in my_dataloader:
    #     print(f'inputs_ids--》{inputs_ids.shape} ')
    #     print(f'token_type_ids--》{token_type_ids.shape} ')
    #     print(f'attention_mask--》{attention_mask.shape} ')
    #     print(f'labels--》{labels.shape} ')
    #     break
    return my_dataloader

2 搭建模型

properties 复制代码
应用迁移学习的思路:bert预训练模型特征处理+自定义模型(分类)

代码实现

python 复制代码
# 自定义下游任务模型
class AiModel(nn.Module):
    def __init__(self):
        super().__init__()
        # 定义输出层
        # 768代表bert模型的特征表示,2代表二分类
        self.linear = nn.Linear(768, 2)

    def forward(self, input_ids, token_type_ids, attention_mask):
        # 不对预训练模型的参数更新
        # my_pre_model代表bert预训练模型的对象
        with torch.no_grad():
            bert_output = my_pre_model(input_ids=input_ids, attention_mask=attention_mask,token_type_ids=token_type_ids)
        # print(f'bert模型的输出结果-->{bert_output}')
        # print(f'bert模型的last_hidden_state-->{bert_output.last_hidden_state.shape}')
        # print(f'bert模型的pooler_output-->{bert_output.pooler_output.shape}')
        # bert模型的输出结果包括:last_hidden_state,pooler_output
        # last_hidden_state(最后一层)--》[8,500,768]-->8个样本,每个样本500个单词,每个单词用768维度的向量表示
        # pooler_output--》[8,768]-->这个代表8个样本,每个样本都用768维度的向量表示。
        # bert模型输出有一个特殊的字符CLS,pooler_output的结果是每一个样本得到CLS这个token对应的向量表示,
        # 原始论文中说明:当利用bert模型去做分类任务的时候,一般直接取CLS这个token对应的向量表示当前这个样本的特征,进行实现分类
        output = self.linear(bert_output.pooler_output) # output-->[8, 2]
        return output

3 模型训练

properties 复制代码
注意: 1,因为使用的预训练模型,所以在训练的时候,对自定义的模型加上,model.train();2.不更新bert预训练模型的参数《requires_grad=False》3.如果要想在GPU上训练:3.1将预训练模型的对象放到GPU上,3.2自定义的模型对象放到GPU上,3.3模型的输入放到GPU上,eg:input_ids = input_ids.to('cuda');model = model.to('cuda')

代码实现

python 复制代码
# 定义训练方法
def train_model():
    # 1.加载训练数据集
    train_dataset = load_dataset('csv', data_files="./data/train.csv", split="train")
    # 2.实例化模型
    my_model = AiModel().to(device)
    # 3. my_pre_model对预训练模型的参数requires_grad设置为False,不计算梯度
    for param in my_pre_model.parameters():
        # print(f'param--》{param.requires_grad}')
        param.requires_grad_(False)
        # print(f'param--》{param.requires_grad}')
    # p.numel()计算每个参数的元素个数
    # total_params = sum(p.numel() for p in my_pre_model.parameters())
    # 4.实例化损失函数对象
    # mean计算一个批次样本的平均损失,sum是损失之和,
    my_crossentropy = nn.CrossEntropyLoss(reduction='mean')
    # 5.实例化优化器
    my_adamw = AdamW(my_model.parameters(), lr=5e-4)
    # 6.设置模型为训练模型
    my_model.train()
    # 7.开始训练
    for epoch_idx in range(3):
        start_time = time.time()
        # 实例化dataloader
        my_dataloader = DataLoader(dataset=train_dataset, batch_size=8,
                                   shuffle=True, collate_fn=collate_fn1,
                                   drop_last=True)
        # 内部数据迭代
        for i, (inputs_ids, token_type_ids, attention_mask, labels) in enumerate(tqdm(my_dataloader), start=1):
            # print(f'labels---》{labels}')
            inputs_ids = inputs_ids.to(device)
            token_type_ids = token_type_ids.to(device)
            attention_mask = attention_mask.to(device)
            labels = labels.to(device)
            output = my_model(inputs_ids, token_type_ids, attention_mask,)
            # print(f'output--》{output.shape}')
            # print(f'output--》{output}')
            # 计算损失
            loss = my_crossentropy(output, labels)
            # print(f'loss--》{loss}')
            # 梯度清零
            my_adamw.zero_grad()
            # 反向传播
            loss.backward()
            # 梯度更新
            my_adamw.step()

            # 打印日志:每隔5步计算平均准确率
            if i % 5 == 0:
                tem = torch.argmax(output, dim=-1)
                # print(f'tem--》{tem}')
                acc = (tem == labels).sum().item() / len(labels)
                use_time = time.time() - start_time
                print("当前训练的轮次%d,迭代的步数%d,当前的损失%.2f, 当前的准确率%.2f, 时间%d"%(epoch_idx+1, i,
                                                                                       loss, acc, use_time))

        # 保存模型
        torch.save(my_model.state_dict(), "./AI19_model/classify_%d.bin"%(epoch_idx+1))

4 模型预测

properties 复制代码
注意: model.eavl()和with torch.no_grad()
#如果在GPU上训练的模型,想在CPU上使用, model.load_state_dict(torch.load(path, map_location="cpu")) #将模型放到cpu上

代码实现

python 复制代码
# 定义模型评估方法
def test_model():
    # 1.加载训练数据集
    test_dataset = load_dataset('csv', data_files="./data/test.csv", split="train")
    # 2.实例化dataloader
    test_dataloader = DataLoader(dataset=test_dataset, batch_size=8, shuffle=True,
                                 collate_fn=collate_fn1, drop_last=True)
    # 3.加载训练好的模型
    path = './AI19_model/classify_3.bin'
    my_model = AiModel()
    my_model.load_state_dict(torch.load(path, map_location="cpu")) #将模型放到cpu上
    # my_model = my_model.to("cpu")
    # 4.定义测试的参数
    correct = 0
    total = 0
    # 5.设定模型为eval
    my_model.eval()
    # 6.迭代数据送入模型
    for i, (inputs_ids, token_type_ids, attention_mask, labels )in enumerate(tqdm(test_dataloader), start=1):
        with torch.no_grad():
            output = my_model(inputs_ids, token_type_ids, attention_mask,)

        # 得到预测的最大概率值的索引
        temp = torch.argmax(output, dim=-1)
        # 得到预测正确的个数
        correct += (temp == labels).sum().item()
        # 当前训练的样本个数
        total += len(labels)
        # 每隔5步打印测试的结果
        if i % 5 == 0:
            print(f'平均准确率-->{correct/total}')
            # 取出一个批次的第一个样本来查验是否预测正确
            text_list = my_pre_tokenizer.decode(inputs_ids[0],skip_special_tokens=True)
            print(f'原始的文本是--》{text_list}', end='    ')
            print(f'模型预测的结果是:{temp[0]}, 真实的结果是:{labels[0]}')