使用Hugging Face中的BERT进行标题分类

  • 前言
  • 相关介绍
  • 出处
  • 基本原理
  • 优点
  • 缺点
  • 前提条件
  • 实验环境
  • BERT进行标题分类
  • 准备数据集
  • 读取数据集
  • 划分数据集
  • 设置相关参数
  • 创建自己DataSet对象
  • 计算准确率
  • 定义预训练模型
  • 定义优化器
  • 训练模型
  • 保存模型
  • 测试模型
  • 参考文献


使用Hugging Face中的BERT进行标题分类_数据集


使用Hugging Face中的BERT进行标题分类_bert_02


使用Hugging Face中的BERT进行标题分类_bert_03


使用Hugging Face中的BERT进行标题分类_人工智能_04

前言

  • 由于本人水平有限,难免出现错漏,敬请批评改正。

相关介绍

BERT(Bidirectional Encoder Representations from Transformers)是Google在2018年提出的一种基于Transformer架构的预训练语言模型。

出处

BERT由Google AI研究院提出,《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》,旨在通过大规模无标注数据的预训练,学习丰富的语义表示,进而在各种自然语言处理(NLP)任务中进行微调,以取得优异的表现。BERT的提出标志着NLP领域的一个重要里程碑,刷新了多个NLP任务的记录。

基本原理

BERT的基本原理是基于Transformer架构的双向编码器,通过在大规模文本数据上的预训练来捕捉语言的深层双向表征。其训练过程分为两个阶段:预训练和微调。

  1. 预训练阶段
  • 任务:BERT的预训练任务主要包括两个,即遮蔽语言模型(Masked Language Model, MLM)和下一句预测(Next Sentence Prediction, NSP)。
  • MLM:类似于完形填空,模型被训练来预测输入句子中被遮蔽的词。这种任务促使模型学习上下文中的词汇关系。
  • NSP:判断两个句子是否是连续的文本序列。这有助于模型理解句子间的关联。
  • 模型结构:BERT模型由多层的Transformer编码器堆叠而成,每一层都包含自注意力机制(Self-Attention)和前馈神经网络。这种深层结构使得BERT能够捕捉从浅层语法特征到深层语义特征的不同级别的语言信息。
  1. 微调阶段
  • 在预训练完成后,BERT模型可以通过添加任务特定的输出层来进行微调,以适应不同的NLP任务,如情感分析、问答、命名实体识别等。
  • 微调过程利用了预训练阶段学到的语言表征,使得模型能够快速适应新的任务并取得优异的性能。

使用Hugging Face中的BERT进行标题分类_数据集

优点

  1. 上下文相关性:BERT采用双向Transformer编码器,可以有效地捕捉上下文之间的依赖关系,从而提高了NLP任务的准确性。这种双向编码的能力使得BERT在理解文本时能够更全面地考虑上下文信息。
  2. 预训练和微调:BERT通过在大规模无标注数据上进行预训练,学习到了通用的语言知识。这使得BERT可以在各种具体的NLP任务上进行微调,从而大大减少训练时间和数据需求。
  3. 多任务支持:BERT可以支持多种NLP任务,如文本分类、命名实体识别、问答系统、文本相似度计算等。这种多任务支持的能力使得BERT具有广泛的应用前景。
  4. 可迁移性:BERT具有很高的可迁移性,可以通过微调适应不同的NLP任务。这意味着在训练一个新的NLP任务时,可以利用BERT已经学到的通用语言知识,从而加速训练过程并提高性能。
  5. 处理速度和精度:BERT使用了Transformer结构,可以并行计算,速度较快。同时,BERT在处理各种NLP任务时都表现出了较高的精度。

缺点

  1. 计算资源需求高:BERT需要大量的计算资源和存储空间进行训练和部署。这使得在一些资源受限的环境下使用BERT变得困难。
  2. 训练数据需求大:BERT需要大量的训练数据才能发挥其优势。对于一些小型数据集,BERT的表现可能不如其他模型。这限制了BERT在一些特定领域的应用。
  3. 长文本处理效果有限:BERT对于长文本的处理效果不如一些传统模型(如CNN和RNN)。这可能是因为BERT采用的是固定长度的输入序列,对于过长的文本可能无法有效地捕捉全部信息。
  4. 微调成本:虽然BERT可以通过微调适应不同的NLP任务,但这需要一定的人工标注数据和时间成本。这增加了使用BERT的复杂性。
  5. 特定领域适应性:对于一些特定领域的文本,BERT可能需要进行特定的训练才能达到更好的效果。这限制了BERT在某些专业领域的应用。

综上所述,BERT作为一种基于Transformer架构的预训练语言模型,在自然语言处理领域具有广泛的应用前景和优势。然而,其计算资源需求高、训练数据需求大、长文本处理效果有限等缺点也需要注意。在实际应用中,需要根据具体任务和资源情况选择合适的模型和方法。

前提条件

实验环境

Package                       Version
----------------------------- ------------
matplotlib                    3.3.4
numpy                         1.19.5
Pillow                        8.4.0
pip                           21.2.2
protobuf                      3.19.6
requests                      2.27.1
scikit-learn                  0.24.2
scipy                         1.5.4
sentencepiece                 0.1.91
setuptools                    58.0.4
threadpoolctl                 3.1.0
thulac                        0.2.2
tokenizers                    0.9.3
torch                         1.9.1+cu111
torchaudio                    0.9.1
torchvision                   0.10.1+cu111
tornado                       6.1
tqdm                          4.64.1
traitlets                     4.3.3
transformers                  3.5.1
urllib3                       1.26.20

BERT进行标题分类

准备数据集

  • 本文数据集下载地址:

使用Hugging Face中的BERT进行标题分类_bert_06

  • academy_titles.txt:该文件是关于学术的标题数据。
  • job_titles.txt:该文件是关于招聘的标题数据。

读取数据集

# 定义两个list分别存放两个板块的帖子数据
academy_titles = []
job_titles = []
with open('academy_titles.txt', encoding='utf8') as f:
    for l in f:  # 按行读取文件
        academy_titles.append(l.strip())  # strip 方法用于去掉行尾空格
with open('job_titles.txt', encoding='utf8') as f:
    for l in f:  # 按行读取文件
        job_titles.append(l.strip())  # strip 方法用于去掉行尾空格

data_list = []
for title in academy_titles:
    data_list.append([title, 0])

for title in job_titles:
    data_list.append([title, 1])

max_length = 0
for case in data_list:
    max_length = max(max_length, len(case[0])+2)
print(max_length)

划分数据集

from sklearn.model_selection import train_test_split
train_list, dev_list = train_test_split(data_list,test_size=0.3,random_state=15,shuffle=True)

设置相关参数

import os
import time
import random
import torch
import torch.nn.functional as F
from torch import nn
from tqdm import tqdm
import random

from transformers import get_linear_schedule_with_warmup, AdamW
# from transformers import BertTokenizer, BertForSequenceClassification
from transformers import BertTokenizer, BertForSequenceClassification, BertConfig, BertModel

if torch.cuda.is_available():
    device = torch.device("cuda")
else:
    device = torch.device("cpu")
max_train_epochs = 10
warmup_proportion = 0.05
gradient_accumulation_steps = 2
train_batch_size = 16
valid_batch_size = train_batch_size
test_batch_size = train_batch_size
# data_workers= 2
data_workers= 0

learning_rate=1e-6
weight_decay=0.01
max_grad_norm=1.0
cur_time = time.strftime("%Y-%m-%d_%H:%M:%S")

这些参数通常用于配置深度学习模型的训练过程,特别是在使用PyTorch这样的深度学习框架时。下面是对每个参数的解释:

  1. batch_size = 128:
  • 批大小(Batch Size)是指在模型训练过程中,一次迭代(iteration)所使用的数据样本数量。这里设置为128,意味着每次更新模型参数前,会使用128个样本来计算损失和梯度。较大的批大小可以加速训练,但也可能增加内存消耗并影响模型的泛化能力。
  1. data_workers = 0:
  • 数据加载工作线程数(Data Workers)是指用于并行加载数据的线程数量。设置为0意味着数据加载将在主线程上同步进行,这可能会降低数据加载的速度。通常,增加工作线程数可以加速数据加载过程,但过多的线程可能会增加系统开销。
  1. learning_rate = 0.0001:
  • 学习率(Learning Rate)是控制模型参数更新幅度的超参数。较小的学习率意味着参数更新的步长较小,训练过程可能更稳定但收敛速度较慢;较大的学习率可能导致训练过程不稳定甚至发散。这里设置为0.0001是一个相对较小的值,适用于一些精细调整的场景。
  1. gradient_accumulation_steps = 1:
  • 梯度累积步数(Gradient Accumulation Steps)是指在更新模型参数前,累积梯量的次数。设置为1意味着每次迭代都会立即更新模型参数。在内存有限但希望使用较大批大小进行训练时,可以通过增加梯度累积步数来模拟较大的批大小。
  1. max_train_epochs = 30:
  • 最大训练轮数(Max Training Epochs)是指整个训练数据集被遍历的次数。一个epoch等于整个数据集通过模型一次。这里设置为30,意味着整个数据集将被遍历30次。
  1. warmup_proportion = 0.05:
  • 预热比例(Warmup Proportion)是指在训练初期,学习率逐渐增加所占整个训练过程的比例。预热可以帮助模型在训练初期更稳定地更新参数,避免由于初始学习率过高而导致的训练不稳定。这里设置为0.05,意味着在前5%的训练轮数中,学习率会逐渐增加。
  1. weight_decay = 0.01:
  • 权重衰减(Weight Decay)是一种正则化技术,用于防止模型过拟合。它通过向损失函数添加一个与模型参数平方成正比的项来实现,鼓励模型参数保持较小值。这里设置为0.01。
  1. max_grad_norm = 1.0:
  • 最大梯度范数(Max Grad Norm)是梯度裁剪(Gradient Clipping)的一种形式,用于控制梯度的最大值。如果梯度的范数超过这个值,梯度将被缩放以确保其范数不超过这个值。这有助于防止梯度爆炸问题。这里设置为1.0。
  1. cur_time = time.strftime(“%Y-%m-%d_%H:%M:%S”):
  • 这行代码用于获取当前时间,并将其格式化为字符串,通常用于生成具有时间戳的文件名或日志,以便记录训练过程。
  1. device = torch.device(‘cuda’):
  • 这行代码指定了模型和数据应该在哪种设备上运行。'cuda'表示使用NVIDIA的CUDA技术来加速计算,通常是在具有NVIDIA GPU的计算机上。如果系统中没有可用的CUDA设备,PyTorch将回退到CPU。

这些参数共同决定了模型训练的具体配置,包括训练速度、模型性能以及训练过程中的稳定性等。

创建自己DataSet对象

# tokenizer = BertTokenizer.from_pretrained('bert-base-chinese') # 在线读取,可能会报网络错误
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese/') # 注意此处为本地文件夹
class MyDataSet(torch.utils.data.Dataset):
    def __init__(self, examples):
        self.examples = examples

    def __len__(self):
        return len(self.examples)

    def __getitem__(self, index):
        example = self.examples[index]
        title = example[0]
        label = example[1]
        r = tokenizer.encode_plus(title, max_length=max_length, padding="max_length")
        return title, label, index#, r['token_type_ids'], label, index

def the_collate_fn(batch):
    r = tokenizer([b[0] for b in batch], padding=True)
    input_ids = torch.LongTensor(r['input_ids'])
    attention_mask = torch.LongTensor(r['attention_mask'])
    label = torch.LongTensor([b[1] for b in batch])
    indexs = [b[2] for b in batch]
    return input_ids, attention_mask, label, indexs #, token_type_ids

train_dataset = MyDataSet(train_list)
train_data_loader = torch.utils.data.DataLoader(
    train_dataset,
    batch_size=train_batch_size,
    shuffle = True,
    num_workers=data_workers,
    collate_fn=the_collate_fn,
)

dev_dataset = MyDataSet(dev_list)
dev_data_loader = torch.utils.data.DataLoader(
    dev_dataset,
    batch_size=train_batch_size,
    shuffle = False,
    num_workers=data_workers,
    collate_fn=the_collate_fn,
)

计算准确率

def get_score():
    y_true = []
    y_pred = []
    for step, batch in enumerate(tqdm(dev_data_loader)):
        model.eval()
        with torch.no_grad():
            input_ids, attention_mask = (b.to(device) for b in batch[:2])
        y_true += batch[2].numpy().tolist()
        logist = model(input_ids, attention_mask)[0]
        result = torch.argmax(logist, 1).cpu().numpy().tolist()
        y_pred += result
    correct = 0
    for i in range(len(y_true)):
        if y_true[i] == y_pred[i]:
            correct += 1
    accuracy = correct / len(y_pred)
    
    return accuracy

定义预训练模型

# model = BertForSequenceClassification.from_pretrained('bert-base-chinese') # 在线读取预训练模型
# 本地读取预训练模型
config = BertConfig.from_json_file("bert-base-chinese/config.json")
model = BertForSequenceClassification.from_pretrained("bert-base-chinese/pytorch_model.bin", config=config)

model.to(device)

定义优化器

from transformers import AdamW, get_linear_schedule_with_warmup

t_total = len(train_data_loader) // gradient_accumulation_steps * max_train_epochs + 1
num_warmup_steps = int(warmup_proportion * t_total)
print('warmup steps : %d' % num_warmup_steps)
no_decay = ['bias', 'LayerNorm.weight'] # no_decay = ['bias', 'LayerNorm.bias', 'LayerNorm.weight']
param_optimizer = list(model.named_parameters())
optimizer_grouped_parameters = [
    {'params':[p for n, p in param_optimizer if not any(nd in n for nd in no_decay)],'weight_decay': weight_decay},
    {'params':[p for n, p in param_optimizer if any(nd in n for nd in no_decay)],'weight_decay': 0.0}
]
optimizer = AdamW(optimizer_grouped_parameters, lr=learning_rate, correct_bias=False)
scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=num_warmup_steps, num_training_steps=t_total)

训练模型

def print_test(title):
    r = tokenizer([title])
    input_ids = torch.LongTensor(r['input_ids']).to(device)
    attention_mask = torch.LongTensor(r['attention_mask']).to(device)
    logist = model(input_ids, attention_mask)[0]
    result = torch.argmax(logist, 1).cpu().numpy().tolist()[0]
    result = ['考研考博', '招聘信息'][result]
    print(title, result)
def print_cases():
    print_test('考研心得')
    print_test('北大实验室博士')
    print_test('考外校博士')
    print_test('北大实验室招博士')
    print_test('工作or考研?')
    print_test('急求自然语言处理工程师')
    print_test('校招offer比较')

for epoch in range(max_train_epochs):
    b_time = time.time()
    model.train()
    for step, batch in enumerate(tqdm(train_data_loader)):
        input_ids, attention_mask, label = (b.to(device) for b in batch[:-1])
        loss = model(input_ids, attention_mask, labels=label)
        loss = loss[0]
        loss.backward()
        if (step + 1) % gradient_accumulation_steps == 0:
            optimizer.step()
            scheduler.step() 
            optimizer.zero_grad()
    print('Epoch = %d Epoch Mean Loss %.4f Time %.2f min' % (epoch+1, loss.item(), (time.time() - b_time)/60))
    print(get_score())
    print_cases()
0.9896858884200657
考研心得 考研考博
北大实验室博士 考研考博
考外校博士 考研考博
北大实验室招博士 招聘信息
工作or考研? 考研考博
急求自然语言处理工程师 招聘信息
校招offer比较 招聘信息
100%|██████████| 311/311 [00:20<00:00, 14.83it/s]
Epoch = 2 Epoch Mean Loss 0.0172 Time 0.35 min
100%|██████████| 134/134 [00:02<00:00, 55.21it/s]
0.9967182372245663
考研心得 考研考博
北大实验室博士 考研考博
考外校博士 考研考博
北大实验室招博士 招聘信息
工作or考研? 考研考博
急求自然语言处理工程师 招聘信息
校招offer比较 招聘信息
100%|██████████| 311/311 [00:19<00:00, 15.56it/s]
Epoch = 3 Epoch Mean Loss 0.0123 Time 0.33 min
100%|██████████| 134/134 [00:04<00:00, 31.16it/s]
0.9995311767463666
考研心得 考研考博
北大实验室博士 考研考博
考外校博士 考研考博
北大实验室招博士 招聘信息
工作or考研? 考研考博
急求自然语言处理工程师 招聘信息
校招offer比较 招聘信息
100%|██████████| 311/311 [00:22<00:00, 14.12it/s]
Epoch = 4 Epoch Mean Loss 0.0053 Time 0.37 min
100%|██████████| 134/134 [00:04<00:00, 31.56it/s]
0.9995311767463666
考研心得 考研考博
北大实验室博士 考研考博
考外校博士 考研考博
北大实验室招博士 招聘信息
工作or考研? 考研考博
急求自然语言处理工程师 招聘信息
校招offer比较 招聘信息
100%|██████████| 311/311 [00:21<00:00, 14.58it/s]
Epoch = 5 Epoch Mean Loss 0.0052 Time 0.36 min
100%|██████████| 134/134 [00:03<00:00, 33.80it/s]
0.9995311767463666
考研心得 考研考博
北大实验室博士 考研考博
考外校博士 考研考博
北大实验室招博士 招聘信息
工作or考研? 考研考博
急求自然语言处理工程师 招聘信息
校招offer比较 招聘信息
100%|██████████| 311/311 [00:20<00:00, 14.85it/s]
Epoch = 6 Epoch Mean Loss 0.0030 Time 0.35 min
100%|██████████| 134/134 [00:04<00:00, 33.33it/s]
0.9995311767463666
考研心得 考研考博
北大实验室博士 考研考博
考外校博士 考研考博
北大实验室招博士 招聘信息
工作or考研? 考研考博
急求自然语言处理工程师 招聘信息
校招offer比较 招聘信息
100%|██████████| 311/311 [00:21<00:00, 14.79it/s]
Epoch = 7 Epoch Mean Loss 0.0022 Time 0.35 min
100%|██████████| 134/134 [00:04<00:00, 32.78it/s]
0.9995311767463666
考研心得 考研考博
北大实验室博士 考研考博
考外校博士 考研考博
北大实验室招博士 招聘信息
工作or考研? 考研考博
急求自然语言处理工程师 招聘信息
校招offer比较 招聘信息
100%|██████████| 311/311 [00:21<00:00, 14.65it/s]
Epoch = 8 Epoch Mean Loss 0.0053 Time 0.35 min
100%|██████████| 134/134 [00:04<00:00, 33.41it/s]
0.9995311767463666
考研心得 考研考博
北大实验室博士 考研考博
考外校博士 考研考博
北大实验室招博士 招聘信息
工作or考研? 考研考博
急求自然语言处理工程师 招聘信息
校招offer比较 招聘信息
100%|██████████| 311/311 [00:21<00:00, 14.64it/s]
Epoch = 9 Epoch Mean Loss 0.0052 Time 0.35 min
100%|██████████| 134/134 [00:03<00:00, 33.91it/s]
0.9995311767463666
考研心得 考研考博
北大实验室博士 考研考博
考外校博士 考研考博
北大实验室招博士 招聘信息
工作or考研? 考研考博
急求自然语言处理工程师 招聘信息
校招offer比较 招聘信息
100%|██████████| 311/311 [00:20<00:00, 14.84it/s]
Epoch = 10 Epoch Mean Loss 0.0021 Time 0.35 min
100%|██████████| 134/134 [00:04<00:00, 32.88it/s]0.9995311767463666
考研心得 考研考博
北大实验室博士 考研考博
考外校博士 考研考博
北大实验室招博士 招聘信息
工作or考研? 考研考博
急求自然语言处理工程师 招聘信息
校招offer比较 招聘信息

保存模型

torch.save(model.state_dict(), 'bert_model_parameter.pkl')
torch.save(model, 'bert_model.pkl')

测试模型

model = torch.load('bert_model.pkl')
model.load_state_dict(torch.load('bert_model_parameter.pkl'))

print_cases()
考研心得 考研考博
北大实验室博士 考研考博
考外校博士 考研考博
北大实验室招博士 招聘信息
工作or考研? 考研考博
急求自然语言处理工程师 招聘信息
校招offer比较 招聘信息

参考文献

[1] 论文地址:https://arxiv.org/abs/1810.04805
[2] 开源代码:https://paperswithcode.com/method/bert
[3] https://huggingface.co/docs/transformers/model_doc/bert